diff --git a/.gitignore b/.gitignore index 4acafde18..dd6b6c05c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ # Created by https://www.gitignore.io/api/visualstudiocode,linux,latex,python # Edit at https://www.gitignore.io/?templates=visualstudiocode,linux,latex,python +# +*.mips +pyrightconfig.json ### LaTeX ### ## Core latex/pdflatex auxiliary files: @@ -408,3 +411,34 @@ dmypy.json # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) +src/__pycache__ +src/cool_example.cl +src/testing.py +src/devdeb.py +src/zTests/Misc/00HelloRodro.cl +src/zTests/Misc/01Simple.cl +src/zTests/Misc/02Simple.cl +src/zTests/Misc/03Simple.cl +src/zTests/Misc/04SallySilly.cl +src/zTests/Misc/05Ackerman.cl +src/zTests/Misc/06FooBarRaz.cl +src/zTests/Misc/07MultipleClass.cl +src/zTests/Misc/08Cellullar.cl +src/zTests/Misc/09GameOfLife.cl +src/zTests/Misc/10BiG.cl +.gitignore +./gitignore +.gitignore +src/type_logger.py +src/zTests/Auto/00Simple.cl +src/zTests/Auto/01Assign.cl +src/semantics/notes.md +src/test.cl +src/test.cl +src/class1_error.txt +src/class1.cl +src/test.cl +src/dispatch6.cl +src/arithmetic8_error.txt +src/arithmetic8.cl +src/dispatch6_error.txt diff --git a/doc/report/img/class.png b/doc/report/img/class.png new file mode 100644 index 000000000..e26e025e4 Binary files /dev/null and b/doc/report/img/class.png differ diff --git a/doc/report/img/object.png b/doc/report/img/object.png new file mode 100644 index 000000000..51db37f36 Binary files /dev/null and b/doc/report/img/object.png differ diff --git a/doc/report/img/pipeline.png b/doc/report/img/pipeline.png new file mode 100644 index 000000000..24a80bf1a Binary files /dev/null and b/doc/report/img/pipeline.png differ diff --git a/doc/report/report.md b/doc/report/report.md new file mode 100644 index 000000000..2046206a1 --- /dev/null +++ b/doc/report/report.md @@ -0,0 +1,764 @@ +# Informe de Compilación + +*Rodrigo D. Pino Trueba C412* +*Adrián Rodríguez Portales C412* + +# CLI + +Para ejecutar el compilador es necesario tener instalado `typer` y `ply`. + +```bash +python3 compiler +``` + +El compilador también cuenta con otras opciones a las cuales se pueden acceder si pasamos como parámetro `--help` + +```bash +❯ python3 compiler --help +Usage: compiler [OPTIONS] INPUT_FILE + + Welcome to CoolCows Compiler! + +Arguments: + INPUT_FILE [required] + +Options: + --ccil / --no-ccil Create .ccil file + corresponding to the ccil code + generated during compilation + [default: False] + + --cool-ast / --no-cool-ast Print COOL AST + [default: False] + + --install-completion Install completion for the current + shell. + + --show-completion Show completion for the current + shell, to copy it or customize the + installation. + + --help Show this message and exit. +``` + +# Arquitectura + +El compilador se diseño siguiendo una ariquitectura modular y funcional. Cada compoenente del mismo es totalmente sustituible sin afectar el funcionamiento de los demás. La imagen siguiente muestra el p*pipeline* del compilador, desde que recibe el programa de COOL hasta que genera la salida del programa correspondiente en MIPS. + +![pipeline](./img/pipeline.png) + +La distribución del código se puede apreciar a continuación: + +``` +├── asts +│   ├── ccil_ast.py +│   ├── inferencer_ast.py +│   ├── __init__.py +│   ├── mips_ast.py +│   ├── parser_ast.py +│   └── types_ast.py +├── lexing +│   ├── errors.py +│   ├── __init__.py +│   └── lexer.py +├── __main__.py +├── parsing +│   ├── errors.py +│   ├── __init__.py +│   ├── parser.py +│   └── parsetab.py +└── visitors + ├── ast_print + │   ├── __init__.py + │   └── type_logger.py + ├── code_gen + │   ├── ccil_gen.py + │   ├── ccil_mips_gen.py + │   ├── constants.py + │   ├── __init__.py + │   ├── mips_gen.py + │   └── tools.py + ├── semantics + │   ├── inference + │   ├── __init__.py + │   ├── tools + │   ├── type_builder.py + │   └── type_collector.py + └── utils + ├── __init__.py + └── visitor.py +``` +En `asts` se encuentran cada uno de los AST que se utilizan a lo largo del proyecto. En `lexing` y `parsing` están la implementación del parser y lexer utilizados. En la carpeta `visitors` se ubican las clases que utilizan el patrón de mismo nombre para cada etapa del compilador divididos en subcarpetas. + +# Lexing + +Para el proceso de *lexing* y *parsing* se usó la biblioteca de *Python* **PLY**. Esta es una implementación de las herramientas *lex* y *yacc* completamente escritas en *Python*. Para el caso particular de la tokenización, *lex* fue la herramienta utilizada. + +La implementación se encuetra en el módulo `lexer.py`. La misma cuenta con una clase `Lexer` donde se definen las expresiones regulares para cada uno de los tokens. En el caso de los *string* y comentarios multilínea se usaron dos estados distintos para cada una, debido a que estos enguajes no son regulares y era necesario usar algoritmos más potentes. + +# Parsing + +Como se menciona en la sección anterior, para el proceso de *parsing* se utilizó *ply.yacc*. Esta herramienta cuenta con la misma técnica de parsing LALR que el *yacc* original. + +La gramática utilizada se encuentra definida en `parser.py`. En este módulo se encuentra la clase `Parser`, la cual cual cuenta con un conjunto de métodos que representan cada una de las producciones de la gramática. Como se puede apreciar, en el caso de las operaciones aritméticas y de comparación las producciones son ambiguas, sin embargo *ply.yacc* permite desambiguar definiendo la precedencia y la asociatividad de los operadores como se muestra a continuación: + +```python +self.precedence = ( + ("right", "ASSIGN"), + ("right", "NOT"), + ("nonassoc", "LESSEQ", "<", "="), + ("left", "+", "-"), + ("left", "*", "/"), + ("right", "ISVOID"), + ("left", "~"), + ("left", "@"), + ("left", "."), +) +``` + +Esto permite usar una gramática ambigua y evita tener que definir más producciones para poder parsear. + +# Semántica e Inferencia + +El análisis de la semántica se divide en 3 componentes: + +* `Colección de tipos`: Reconocer todos los tipos definidos por el usuario. +* `Construcción de tipos` +* `Chequeo semántico` + +## Colección de tipos + +Utilizando el patrón visitor se recorren todas los tipos definidos, se hayan sus padres y se verifica que no hayan herencias ilegales (heredar de `Int`, `String` o `Bool`) ni herencias circulares. Se ordenan las clases en orden descendente partiendo de la clase más general. + +## Construcción de tipos + +Se visitan todos los objetos y se almacena la interfaz de cada uno. Sus métodos y atributos. Los tipos que heredan de otros también se les define los atributos de sus padres. + +## Chequeo semántico + +Nuestro proyecto hace uso de `AUTO_TYPE` con la que incorpora inferencia de tipos al lenguage Cool. La inferencia se realiza varias veces en distintos vistores. En nuestra implementación debido a que le inferencia se apoya fuertemente sobre el reglas semánticas, el chequeo semántico se realiza a la par que la inferencia, y dividido de igual manera por los visitores. + +La idea principal para realizar la inferencia es considerar todo declaración `AUTO_TYPE` no como un tipo específico sino como un conjunto de tipos, donde inicialmente ese conjunto esta compuesto por todos los tipos definidos en un programa Cool. Los tipos declarados específicamente como `Int` o `String` se consideran conjuntos con un solo elemento. + +En Cool muchas veces las expresiones se ven obligadas a conformarse a un tipo definido por el usuario. Deben corresponder con el tipo definido de una variable, argumento o retorno de una función. También deben obedecer las reglas semánticas, cuando están presente frente a una operación aritmética, o en una posición donde se espera que el resultado sea un tipo pre-definido (e.g `Bool`). Para reducir el conjuntos de tipos de una expresión se realizan las siguientes operaciones: + +1. Cuando el tipo declarado de una variable esta bien definido (diferente de `AUTO_TYPE`) , se eliminan del conjunto de tipos inferidos de la expresión los elementos que no conforman a dicho tipo bien definido. + +2. Cuando el tipo declarado de una variable es `AUTO_TYPE`, esta se puede reducir analizando que valores debe tener para que los tipos de la expresión inferida se conformen con el. + +3. Cuando ambos tipos, tanto el definido como el inferido son `AUTO_TYPES` se busca que valores puede tener el segundo para conformarse al primero, y que valores el primero para que el segundo se conforme. + +Para tratar cada caso el inferenciador se divide en cuatro partes: + +1. **soft inferencer** que aplica la primera regla y tercera regla. Se le llama **soft** porque perdona y no realiza ningún tipo de chequeo semántico y permite cosas como que un conjunto tengo dentro de si dos tipos sin un ancestro común. +2. **hard inferencer ** aplica la primera y la tercera regla, y fuerza el chequeo semántico sobre todas las expresiones. No permite bolsas de tipos sin ancestros comunes dentro de un mismo conjunto. +3. **back inferencer** aplica la segunda y tercera regla. Dada las expresiones trata de reducir los conjuntos de los tipos definidos como `AUTO_TYPE` (e.g. parámetros de una función, retorno de una función, o declaración de variables) +4. **types inferencer** reduce todos los conjuntos de tipos de cada nodo al mayor ancestro en todos los casos, excepto cuando se trata del valor de retorno de una función, en el que reduce al ancestro común más cercano de los tipos del conjunto. + +Cada inferenciador se ejecuta secuencialmente, una sola vez, exceptuando por el **back inferencer** que puede ejecutarse tantas veces como sea necesario. + +### Soft Inferencer + +El **soft inferencer** es permisivo pues como es el primero en leer el programa, puede que un conjunto de tipos inválidos en la línea 5 se vuelva válido más adelante en el código. + +En este ejemplo no funcional (Lanza `RuntimeError` debido a que `a` es `Void`) donde el tipo de `a` puede ser cualquiera, al leer `a.f()`, se reducen los tipos de a de {`Object`, `String`, `IO`, `Int`, `Bool`, `Main`, `A`, `B`} a tan solo {`A`, `B`}. No obstante `A` y `B` no tienen un ancestro común dentro del conjunto, luego `a` posee un tipo invalido. Lanzar una excepción en este momento puede crear un falso positivo pues se puede corregir más adelante ya que el programa no se ha leído completamente. + +Luego cuando se lee `a.g()` el conjunto de tipos se reduce a solo {`A`}. Estos tipos inferidos se guardan para los próximos visitores del chequeo semántico. + +```c# +class Main { + a : AUTO_TYPE; + method main():AUTO_TYPE { + { + a.f(); // Boom si no es el soft inferencer + a.g(); // Solucionado + } + } +} +class A { + method f():Int{ + 3 + 3 + } + metod g():String{ + "g" + } + +}; +class B { + method f():String{ + "f" + } +} +``` + +### Herramientas para la Inferencia + +#### Join + +La operación `join` cuando se busca encontrar el tipo mas general entre dos bolsas de tipos, es buscar el ancestro común más cercano al que todos los tipos de ambas bolsas se conformaran. Si ese tipo se encuentra dentro de alguna de las dos bolsas se utiliza, si no, se añade. + +Esta operación se utiliza para analizar los resultados de las distintas ramas de las expresiones `if` y `case of`. + +#### Conform + +`conform` se utiliza para saber si una bolsa de tipos `a` conforma con otra `b`, se analizan todos sus subtipos de `a` y se desechan los que no conformen con ningún subtipo de `b`. + +Se ejecuta durante el chequeo del **soft inferencer** y el **hard inferencer**, para reducir el tipo de las expresiones a base de un tipo declarado o una regla semántica. + +#### Unify + +Con `unify` se halla la intersección entre dos bolsas de tipos. Se ejecuta en el **back inferencer** para reducir las tipos declarados a partir de las expresión inferida. + +### Case Of + +Durante el útlimo visitor de la semántica (**Types Inferencer**) se organizan las ramas de la expresión `case of` en orden de especifidad. Las que tienen un tipo más específico primero. + +### SELF_TYPE + +Los `AUTO_TYPE` no incluyen dentro de su bolsa de tipo al tipo especial `SELF_TYPE`. + +La combinación de `SELF_TYPE` con `AUTO_TYPE` trajo sus problemas, sobre todo porque funciona como un comodín que puede tener un tipo dependiendo de las circunstancia. Logramos una mejor integración entre estos fue posible intercambiando el `SELF_TYPE` por el tipo según donde se estuviera analizando el código. Después se intercambiaba para atrás, en caso de que no se pueda, significa que no cumple las reglas de la semántica y se lanza como error después de finalizada el chequeo semántico. + +# Generación de Código Intermedio + +Para producir código CIL se toma como principal el guía el capitulo 7 del libro de `Cool Compilers`. + +El programa original se divide en tres secciones: + +* En **types** se guarda la signatura de los tipos. Nombre de atributos y funciones. +* **data** almacena todos los `String` definidos en tiempo de compilación por el usarion así como `Strings` definidos durante la propia generación de código. +* En **code** se encuentra el equivalente en CIL de las funciones definidas en Cool. Cada función en vez de tener expresiones y sub-expresiones complejas tienen una secuencia de operaciones más sencillas que producen un resultado equivalente. + +## Types + +Contiene solo el nombre de la clase, los métodos y su identificador único para buscarlo cuando se necesite llamar a un método y los atributos de la misma. Los tipos también contienen dentro de ellos los atributos de las clases que heredan al igual que sus funciones. + +Para todas las clases se les genera una función `init` donde se inicializan los valores iniciales de todos sus atributos. Si el atributo no esta inicializado, se inicializa por defecto apuntando a la cadena vacía en caso de ser de tipo `String` o con valor 0 en otro caso. + +En caso de que el atributo se inicialice con una expresión, se transforma a operaciones en CIL y se le asigna el resultado final al atributo correspondiente. + +La función `init` se ejecuta siempre que se instancie una clase. + +## Data + +Se almacenan todos las cadenas definidos por el usuario en el programa Cool. Ademas se tiene también la cadena vacía a la cual apuntan las variables `String` sin inicializar. Durante la generación de código se almacena aquí además los mensajes de errores en ejecución. + +## Code + +Cada expresión de Cool se representa como una secuencia de operaciones en CIL. Se asegura siempre que dentro de esa secuencia haya una instrucción que guarde en una variable local el resultado final de dicha expresión. + +La secuencia de instrucciones de una expresión varían de acuerdo a las secuencias de instrucciones de sus sub-expresiones respectivas. Para producir código CIL a partir de una expresión: + +1. Produce las operaciones de todas sus sub-expresiones +2. Produce las operaciones propias. Cuando se necesite el valor de una sub-expresión se busca la variable local que almacena el resultado final de dicha sub-expresión. +3. Organiza las operaciones propias y de sus sub-expresiones de una manera que tenga sentido +4. Finalmente crea una variable local donde se almacene el valor final propio. + +En CIL se trata de desglosar las expresiones en las instrucciones con el nivel más bajo posible. Existen casos para lo cual lo anterior no es posible como la comparación de `String` y las operaciones `built-in` que se realizan sobre estos. + +Se añaden `warnings` al compilador cuando el programador tiene una expresión permitida, pero sin sentido, como `isVoid 3`, o algún otro tipo que no pueda ser `Void`. + +Durante la generación de código se genera también las excepciones que pueden ser lanzadas durante la ejecución: + ++ División por cero ++ El despacho ocurre desde un tipo sin inicializar (`Void`) ++ El rango del substring no es válido ++ La expresión del `case of` es `Void` ++ Ninguna rama de algún `case of` es igual al tipo de la expresión + +Es posible para el usuario definir variables con mismos nombres con distintos contextos, para tratar con esto se reutilizan una versión simplificada del `Scope` de la semántica, donde se almacenan según el contexto la variable definida por el usuario y su traducción a Cool. Esto permite que en el ejemplo siguiente se conozca siempre a que variable `x` se refiere el programa: + +```assembly +# COOL +let x:int = 3 + in (let x:int = 4 in x) + x +# CIL +local let_x_0 +local let_x_1 + +let_x_0 = 3 +let_x_1 = 4 +... +``` + +### Transformaciones + +Ejemplos de traducción de Cool a CIL + +#### Declaración de Clase + + **Cool Input** + +```haskell +class C { + -- Initialized attributes + a1: <- ; + a2: <- ; + ... + am: <- ; + + -- Functions + f1() { } + f2() { } + ... + fn() { } + +} +``` + +**CCIL Output** + +```assembly +type C { + attribute a1; + ... + attribute am; + + method f1 : ; + ... + method fn : ; +} +``` + +#### Herencia de Clases + + **Cool Input** + +```haskell +class A { + a1: + f1():{...} +} + +class B inherits A { + b1: + g1():{...} +} +``` + +**CCIL Output** + +```assembly +type A { + attr a1; + method f1 : f_f1_A +} + +type B { + attr a1; + attr b1; + method f1: f_f1_A + method g1: f_g1_B +} +``` + +#### While Loop + +**Cool Input** + +```assembly +while () loop pool +``` + +**CCIL Output** + +```assembly +label while_init +x = +ifFalse x goto while_end + + + +goto while_init +label while_end +``` + +#### If Then Else + +**Cool Input** + +``` +if then else fi +``` + +**CCIL Output** + +```assembly + # Produce todas las operaciones de la expr de la cond. inicial +x = # Guarda ese valor +ifFalse x goto else_expr +# x = 1 + +f = # El resultado final de la expresion if +goto endif + +# x = 0 +label else_expr + +f = # El resultado final de la expresion if + +label endif +``` + +#### Let In + +**Cool Input** + +``` +let :, ... : in +``` + +**CCIL Output** + +```assembly +# Inicializa todas las variables let, tengan expresión o no + + +... + +# traduce la expresion en operacions + +f = # Almacena el resultado final de la expresion let +``` + +#### Case Of + +Para los `case of` las ramas desde la semántica son organizadas para que los tipos más general se quede siempre al fondo. + +Durante la generación de código de la expresión `case of`, cuando se busca si conforman con el tipo`A` se compara también con todos sus subtipos. Se le añade la optimización que si ya existe ese subtipo de `A` declarado en el `case of` entonces no se añade nuevamente. + +Para el caso de `Object` específico no se compara con ningún subtipo, sencillamente se pone la operación `goto` + +**Cool Input** + +``` +case of + : => + : => + ... + : => +esac +``` + +**CCIL Output** + +```assembly + + +... + + + +x = +t = typeof x + +# Analiznado rama 1 +t1 = typeof +b1 = t1 == t # Comparando tipos +if b1 goto branch1: # En caso de exito ve a la rama + +# Analizando rama 2 +t2 = typeof +b2 = t2 == t +if b2 goto branch2 + +... + +# Analizando rama n +tn = typeof +bn = tn == t +if bn goto brannch + + # Lanza una excepcion en ejcucion si no se ejecuta ninguna rama + + +# Realizando logica the rama1 +label branch1 + +goto end_case + +# Realizando logica the rama2 +label branch2 + +goto end_case + +... + +# Realizando logica the raman +label branchn + +goto end_case + +label end_case +``` + +#### Despacho Dinámico 1 + +**Cool Input** + +``` +(, , ..., ); +``` + +**CCIL Output** + +```assembly + # EL primer argumento es el objeto desde el que se llama + + +... + +r = vcall +``` + +#### Despacho Dinámico 2 + +**Cool Input** + +``` +.(, , ..., ); +``` + +**CCIL Output** + +```assembly +# Se realiza la expresion + + + + # El primer argumento es una variable local con el tipo de la expresión + + +... + +r = vcall +``` + +#### Despacho Estático + +**Cool Input** + +``` +@.(, , ..., ); +``` + +**CCIL Output** + +```assembly +# Se realiza la expresion + + + +t = allocate # Crea el tipo donde se busca la funcion por su nombre único + + # El primer argumento es una variable local con el tipo de la expresión + + +... + +r = ccall t n +``` + +#### Declaración de un método + +**Cool Input** + +``` +(:, ..., :) : +{ + +} +``` + +**CCIL Output** + +```assembly +function { + param + param + ... + param + local + local + ... + local + + r = + return r +} +``` + +#### Expresión de Bloque + +**Cool Input** + +``` +{ + ; + ; + ... + ; +} +``` + +**CCIL Output** + +``` + + +... + + + + +... + +``` + +#### Expresiones Aritméticas + +**Cool Input** + +```c# +3 + 5 +``` + +**CCIL Output** + +``` +t = 3 + 5 +``` + +--- + +###### Varios operadores + +**Cool Input** + +``` +3 + 5 + 7 +``` + +**CCIL Output** + +```assembly +t1 = 5 + 7 +t2 = 3 + t1 +``` + +--- + +###### En operaciones no conmutativas + +```python +3 - 5 - 7 +# -2 -7 +# -9 +``` + +```assembly +t1 = 3 - 5 +t2 = t - 7 +``` + +--- + +**Cool Input** + +``` +100 / 20 / 5 / 2 +``` + +**CCIL Output** + +``` +t1 = 100 / 20 +t2 = t1 / 5 +t3 = t2 / 2 +``` + + + +## Lenguaje CCIL + +Definición del lenguaje CCIL(Cool Cows Intermediate Language). Tomamos como No Terminales sólo las palabras que empiecen con mayúsculas. El resto de palabras y símbolos se consideran como Terminales. + +$$ +\begin{array}{rcl} +\text{Program} &\rarr& \text{.type TypeList .code CodeList}\\ +\text{TypeList} &\rarr& \text{Type } | \text{ Type TypeList}\\ +\text{Type} &\rarr& \text{FeatureList}\\ +\text{FeatureList} &\rarr& \text{Attribute } | \text{ Function } | \text{ FeatureList } | \space\epsilon\\ +&|& \text{ Attribute; FeatureList } | \text{ Function; FeatureList}\\ +\\ +\text{CodeList} &\rarr& \text{ FuncCode CodeList }| \space\epsilon \\ +\text{AttrCode} &\rarr& \text{id }\{ \text{LocalList OperationList} \}\\ +\text{FuncCode} &\rarr& \text{id }\{\\ +&&\text{ParamList}\\ +&&\text{LocalList}\\ +&&\text{OperationList} \text{\}}\\ +\\ +\text{OperationList} &\rarr& \text{Operation; OperationList } | \space\epsilon \\ +\text{Operation} &\rarr& \text{id = ReturnOp}\\ +&|& \text{goto id}\\ +&|& \text{label id}\\ +&|& \text{return Atom}\\ +&|& \text{setattr id id Atom}\\ +&|& \text{if Atom goto id}\\ +&|& \text{ifFalse Atom goto id}\\ +&|& \text{arg id}\\ + +\text{ReturnOp} &\rarr& \text{Atom + Atom}\\ +&|& \text{Atom - Atom}\\ +&|& \text{Atom * Atom}\\ +&|& \text{Atom / Atom}\\ +&|& \text{not Atom}\\ +&|& \text{neg Atom}\\ +&|& \text{call id}\\ +&|& \text{vcall typeId id}\\ +&|& \text{typeof id}\\ +&|& \text{getatrr id id}\\ +&|& \text{allocate typeId}\\ +&|& \text{Atom < Atom}\\ +&|& \text{Atom <= Atom}\\ +&|& \text{Atom = Atom}\\ +&|& \text{allocate typeId}\\ +&|& \text{getattr id id}\\ +&|& \text{Atom}\\ +\text{Atom} &\rarr& \text{Constant } | \text{ id}\\ +\text{Constant} &\rarr& \text{ integer } | \text{ string } +\end{array} +$$ + +# Generación de código MIPS + +Una vez definido un lenguaje intermedio, se pasa a MIPS. En este punto del proceso de compilación se tuvieron que resolver dos problemas fundamentales: la semántica de tipos y el manejo de la memoria. + +## Representación de los tipos + +En *MIPS* las instancias de clases se representan a través de información estática y dinámica. La parte estática se define en la sección *.data* y contiene la información que se comparte entre cada uno de los objetos del mismo tipo. + +![class](./img/class.png) + +La sección `type` contiene la dirección hacia esa sección de código. En `init` se encuentra la dirección de una función especial que inicializa una instancia cualquiera de la clase correspondiente. Esta función se genera en ccil. Luego, en la sección `name` se encuentra la dirección del *string* que corresponde al nombre del tipo. En `attr count` se encuentra un valor que representa la cantidad de atributos que tiene la clase. Después de esta porción de memoria se encuentran los métodos, es decir, las direcciones de las funciones que implementan cada uno de los métodos en una clase. + +Es importante señalar que los métodos de las clases están declarados en el mismo orden. Si una clase `B` hereda de otra clase `A`, los métodos de `A` que `B` hereda están en la misma posición que en `A`. De este modo los llamados a métodos virtuales se pueden hacer conociendo solamente el tipo estático del objeto y el *offset* del método que se está invocando. + +En el caso de las instancias de los tipos, su representación en memoria es como se muestra a continuación: + +![object](./img/object.png) + +En la sección `type` se encuentra la dirección hacia la información estática del tipo que se mencionó anteriormente. Luego le siguen cada uno de los atributos. Estos, al igual que en el caso de los métodos, se encuentran en el mismo orden para todas las instancias de una clase y de sus subclases. De este modo la lectura y escritura de los atributo se reduce a saber su tipo estático y el *offset* del atributo. + +## Llamados a función + +Para los llamados a función en MIPS se tomó como convenio pasar primeramente los argumentos a través de la pila ( el primer argumento siempre es la instnacia del objeto que realiza el llamado ) para luego saltar al *label* donde se encuentra la definición de la función. A continuación se guardan en la pila el *return address* y el *frame pointer*. Una vez hecho esto se ajusta el nuevo valor del *frame pointer* y se reserva el espacio necesario para las variables locales en la pila. Cuando se realizan cada una de las opraciones correspondientes a la función invocada se sacan de la pila los valores anteriores del *return address* y *frame pointer*, y se libera el espacio correspondiente a las variables locales y los argumentos de la función. Los valores de retorno de las funciones se guardan siempre en el registro `v0`. + +## new SELF_TYPE + +Como se había mencionado anteriormente, cuando se instancia un nuevo objeto, sabiendo el tipo podemos llamar directamente a la función `init` correspondiente y reservar la cantidad de memoria necesaria. Sin embargo, en el caso del `SELF_TYPE`, no se puede proceder como en los demás casos, porque el tipo del objeto que se va a instanciar solo se conoce en tiempo de ejecución. Por tanto, para resolver este problema, lo que se hace es buscar el tipo dinámico del objeto donde se encuentra la invocación del método actual que porsiempre se encuentra en el primer argumento del llamado a una función en MIPS, y a través del cual se puede obtener el tipo del objeto (identificador realmente ) y así acceder a la información estática de dicho tipo, donde se puede saber la cantidad memoria necesaria a reservar según la cantidad de atributos y a la función `init` que le corresponde. + + +### Funciones Built-in + +Las funciones *built-in* se implementaron directamente en el AST de MIPS. diff --git a/doc/report/report.pdf b/doc/report/report.pdf new file mode 100644 index 000000000..4587fcb0b Binary files /dev/null and b/doc/report/report.pdf differ diff --git a/doc/team.yml b/doc/team.yml index c16162532..c62382945 100644 --- a/doc/team.yml +++ b/doc/team.yml @@ -1,10 +1,7 @@ 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: Adrián Rodríguez Portales + github: adrian13579 + group: C412 + - name: Rdrigo Pino + github: rodrigo-pino + group: C412 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..9112c6679 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,203 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "click" +version = "8.0.4" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.6" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "typer" +version = "0.4.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +click = ">=7.1.1,<9.0.0" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] +doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] +test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.10" +content-hash = "3d6cb1e0090fa8104508e4a6a73dae119039d4b01752e7564067fe3f9baf925d" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +click = [ + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +ply = [ + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyparsing = [ + {file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"}, + {file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +typer = [ + {file = "typer-0.4.0-py3-none-any.whl", hash = "sha256:d81169725140423d072df464cad1ff25ee154ef381aaf5b8225352ea187ca338"}, + {file = "typer-0.4.0.tar.gz", hash = "sha256:63c3aeab0549750ffe40da79a1b524f60e08a2cbc3126c520ebf2eeaf507f5dd"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..f62f5f931 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "cool-compiler-2021" +version = "0.1.0" +description = "COOL language compiler" +authors = ["rodrigo-pino "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.10" +pytest = "^6.2.5" +ply = "^3.11" +typer = "^0.4.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 9eb0cad1a..6da230a91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ pytest pytest-ordering +ply +typer diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..9221b85c5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +ignore = F811 diff --git a/src/compiler/__main__.py b/src/compiler/__main__.py new file mode 100644 index 000000000..b67c54322 --- /dev/null +++ b/src/compiler/__main__.py @@ -0,0 +1,133 @@ +from visitors.code_gen.ccil_gen import CCILGenerator +from visitors.code_gen.ccil_mips_gen import CCILToMIPSGenerator +from visitors.code_gen.mips_gen import MIPSGenerator +from visitors.ast_print.type_logger import TypeLogger +import sys +import typer + +from lexing import Lexer +from parsing import Parser +from visitors.semantics import TypeBuilder, TypeCollector +from visitors.semantics.inference import ( + SoftInferencer, + HardInferencer, + BackInferencer, + TypesInferencer, +) + + +def format_errors(errors, s=""): + for error in errors: + s += error[1] + "\n" + return s[:] + + +def lexing_pipeline(program): + lexer = Lexer() + tokens = list(lexer.tokenize(program)) + if lexer.errors: + for error in lexer.errors: + print(error) + exit(1) + return tokens + + +def parsing_pipeline(program): + parser = Parser(Lexer()) + ast = parser.parse(program) + if parser.errors: + for error in parser.errors: + print(error) + exit(1) + return ast + + +def semantics_pipeline(program_ast, cool_ast): + + collector = TypeCollector() + collector.visit(program_ast) + context = collector.context + errors = collector.errors + + builder = TypeBuilder(context) + builder.visit(program_ast) + errors += builder.errors + + soft = SoftInferencer(context) + soft_ast = soft.visit(program_ast) + errors += soft.errors + + hard = HardInferencer(context) + hard_ast = hard.visit(soft_ast) + errors += hard.errors + + if len(errors) > 0: + s = format_errors(errors) + print(s) + exit(1) + + change = True + back = BackInferencer(context) + + back_ast, change = back.visit(hard_ast) + while change: + back_ast, change = back.visit(back_ast) + + types = TypesInferencer(context) + types_ast = types.visit(back_ast) + errors += types.errors + + if cool_ast: + logger = TypeLogger(context) + log = logger.visit(back_ast, back_ast.scope) + print(log) + + if len(errors) > 0: + s = format_errors(errors) + print(s) + exit(1) + + return types_ast + + +def main( + input_file: str, + ccil: bool = typer.Option( + False, + help="Create a .ccil file corresponding to the ccil code generated during compilation ", + ), + cool_ast: bool = typer.Option(False, help="Print COOL AST"), +): + """ + Welcome to CoolCows Compiler! + """ + program_file = open(input_file) + program = program_file.read() + program_file.close() + out_file = input_file.split(".")[0] + + tokens = lexing_pipeline(program) + ast = parsing_pipeline(program) + type_ast = semantics_pipeline(ast, cool_ast) + + ccil_gen = CCILGenerator() + ccil_ast = ccil_gen.visit(type_ast) + + if ccil: + path_to_file = f"{out_file}.ccil" + with open(path_to_file, "w") as f: + f.write(str(ccil_ast)) + + ccil_mips_gen = CCILToMIPSGenerator() + mips_ast = ccil_mips_gen.visit(ccil_ast) + + mips_gen = MIPSGenerator() + mips_code = mips_gen.visit(mips_ast) + + path_to_file = f"{out_file}.mips" + with open(path_to_file, "w") as f: + f.write(mips_code) + + +if __name__ == "__main__": + typer.run(main) diff --git a/src/compiler/asts/__init__.py b/src/compiler/asts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/compiler/asts/ccil_ast.py b/src/compiler/asts/ccil_ast.py new file mode 100644 index 000000000..8a697107b --- /dev/null +++ b/src/compiler/asts/ccil_ast.py @@ -0,0 +1,508 @@ +from __future__ import annotations +from dataclasses import dataclass +from typing import Dict, List, Tuple + +from visitors.semantics.tools.type import Type + + +@dataclass(frozen=True) +class CCILProgram: + """Top level class that represents a CCIL program""" + + entry_func: FunctionNode + types_section: List[Class] + code_section: List[FunctionNode] + data_section: List[Data] + + def __str__(self, all=True) -> str: + types_section = self.types_section + code_section = self.code_section + if not all: + types_section = self.types_section[5:] + code_section = self.code_section[10:] + + ident = "\t" + types = "\n".join(ident + str(type) for type in types_section) + data = "\n".join(ident + str(data) for data in self.data_section) + code = "\n".join(str(func) for func in code_section) + return f"TYPES:\n{types}\nDATA:\n{data}\nCODE:\n{code} " + + +@dataclass(frozen=False) +class Class: + """ + This item represent the .type section in ccil + """ + + id: str + attributes: List[Attribute] + methods: List[Method] + init_operations: FunctionNode + + def __str__(self, all=True) -> str: + ident = "\t\t" + attributes = "\n".join(ident + str(a) for a in self.attributes) + methods = "\n".join(ident + str(m) for m in self.methods) + init_function = "\n" + str(self.init_operations) + "\n" if all else "" + return f"type {self.id} {{\n {attributes} \n {methods} \n {init_function}\t}}" + + +@dataclass(frozen=True) +class BaseVar: + """ + This item represents the pair common in attributes, parameters and local vars + """ + + id: str + type: str + + def __str__(self) -> str: + return f"{self.id} : {self.type}" + + +@dataclass(frozen=True) +class Attribute(BaseVar): + cool_id: str + + def __str__(self) -> str: + return "attr " + super().__str__() + + +class Parameter(BaseVar): + def __str__(self) -> str: + return "param " + super().__str__() + + +class Local(BaseVar): + def __str__(self) -> str: + return "local " + super().__str__() + + +@dataclass(frozen=True) +class Data: + """ + This class hold constant values + """ + + id: str + value: str + + def __str__(self) -> str: + return f"{self.id} : '{self.value}'" + + +@dataclass(frozen=True) +class Method: + """ + This item represent the method of every class + """ + + id: str + function: FunctionNode + + def __str__(self) -> str: + return f"method {self.id} : {self.function.id}" + + +class Node: + pass + + +class FunctionNode(Node): + """ + This class represents funtions in the .code section. Most of a Cool program is split along this nodes. + """ + + def __init__( + self, + idx: str, + params: List[Parameter], + locals: List[Local], + operations: List[OperationNode], + ret: str, + ) -> None: + super().__init__() + # Function identifier, different than Method identifier + self.id = idx + # Variable that holds the return value + self.ret = ret + # Function operations + self.params = params + self.locals = locals + self.operations = operations + + def __str__(self) -> str: + indent = "\t\t" + params = "\n".join(indent + str(p) for p in self.params) + locals = "\n".join(indent + str(l) for l in self.locals) + ops = "\n".join(indent + str(o) for o in self.operations) + + return f"\tfunc {self.id}:\n {params}\n {locals} \n {ops} \n {indent}return {self.ret}" + + +class OperationNode(Node): + """ + Base Class for all operation Nodes + """ + + def __init__(self) -> None: + super().__init__() + + +class StorageNode(OperationNode): + def __init__(self, idx: str, operation: ReturnOpNode) -> None: + """ + Node used to store the value of operations done. + Parameters: + node <- Node to maintain knowledge of colummn and position in the original code. + idx <- Id of this node. + opeartion <- The operation this node is storing. + """ + super().__init__() + self.id = idx + self.operation = operation + + def __str__(self) -> str: + return f"{self.id} = {str(self.operation)}" + + +class SetAttrOpNode(OperationNode): + def __init__( + self, instance_id: str, attr_id: str, new_value: AtomOpNode, instance_type: str + ) -> None: + self.instance_type = instance_type + self.new_value = new_value + self.attr = attr_id + self.instance = instance_id + + def __str__(self) -> str: + return f"set_attr {self.instance}({self.instance_type}) {self.attr} {self.new_value}" + + +class Abort(OperationNode): + def __str__(self) -> str: + return "abort" + + +class PrintStrNode(OperationNode): + def __init__(self, idx: str) -> None: + super().__init__() + self.id = idx + + def __str__(self) -> str: + return f"print_str {self.id}" + + +class PrintIntNode(PrintStrNode): + def __str__(self) -> str: + return f"print_int {self.id}" + + +class ReturnOpNode(OperationNode): + def __init__( + self, + ) -> None: + super().__init__() + + +class ReadStrNode(ReturnOpNode): + """ + This nodes reads a string from the standard input + """ + + def __str__(self) -> str: + return "read_str" + + +class ReadIntNode(ReturnOpNode): + """ + This nodes reads an int from the standard input + """ + + def __str__(self) -> str: + return "read_int" + + +class GetAttrOpNode(ReturnOpNode): + def __init__(self, instance_type_id: str, instance_id: str, attr_id: str) -> None: + super().__init__() + self.instance_type = instance_type_id + self.instance = instance_id + self.attr = attr_id + + def __str__(self) -> str: + return f"get_attr {self.instance}({self.instance_type}) {self.attr}" + + +class CallOpNode(ReturnOpNode): + def __init__(self, idx: str, type_idx: str, args: List[IdNode]) -> None: + super().__init__() + self.id = idx + self.type = type_idx + self.args = args + + def __str__(self) -> str: + args = ", ".join(f"{a.value}" for a in self.args) + return f"call {self.id} : {self.type} (args: {args})" + + +class VCallOpNode(CallOpNode): + def __init__(self, idx: str, type_idx: str, args: List[IdNode]) -> None: + super().__init__(idx, type_idx, args) + + def __str__(self) -> str: + return "v" + super().__str__() + + +class VoidNode(ReturnOpNode): + """Operation that indicate that the Storage Node is not initialized""" + + def __str__(self) -> str: + return "" + + +class NewOpNode(ReturnOpNode): + def __init__(self, type_idx: str) -> None: + super().__init__() + self.type: str = type_idx + + def __str__(self) -> str: + return f"new {self.type}" + + +class BinaryOpNode(ReturnOpNode): + def __init__(self, left: AtomOpNode, right: AtomOpNode) -> None: + """ + Node that represents all binary operation + Parameters: + left <- Left atomic node. + right <- Right atomic node. + """ + super().__init__() + self.left = left + self.right = right + + def __str__(self) -> str: + return f"{self.left.value} op {self.right.value}" + + +class ArithmeticOpNode(BinaryOpNode): + pass + + +class SumOpNode(ArithmeticOpNode): + def __str__(self) -> str: + return f"{self.left.value} + {self.right.value}" + + +class MinusOpNode(ArithmeticOpNode): + def __str__(self) -> str: + return f"{self.left.value} - {self.right.value}" + + +class MultOpNode(ArithmeticOpNode): + def __str__(self) -> str: + return f"{self.left.value} * {self.right.value}" + + +class DivOpNode(ArithmeticOpNode): + def __str__(self) -> str: + return f"{self.left.value} / {self.right.value}" + + +class ComparisonOpNode(BinaryOpNode): + pass + + +class EqualIntNode(ComparisonOpNode): + def __str__(self) -> str: + return f"{self.left.value} =(Int) {self.right.value}" + + +class EqualStrNode(ComparisonOpNode): + def __str__(self) -> str: + return f"{self.left.value} =(Str) {self.right.value}" + + +class EqualAddrNode(ComparisonOpNode): + def __str__(self) -> str: + return f"{self.left.value} =(Addr) {self.right.value}" + + +class LessOrEqualOpNode(ComparisonOpNode): + def __str__(self) -> str: + return f"{self.left.value} <= {self.right.value}" + + +class LessOpNode(ComparisonOpNode): + def __str__(self) -> str: + return f"{self.left.value} < {self.right.value}" + + +class UnaryOpNode(ReturnOpNode): + def __init__(self, atom: AtomOpNode) -> None: + super().__init__() + self.atom = atom + + +class GetTypeOpNode(UnaryOpNode): + """Extracts the type of a node""" + + def __str__(self) -> str: + return f"typeof {self.atom.value}" + + +class NotOpNode(UnaryOpNode): + def __str__(self) -> str: + return f"not {self.atom.value}" + + +class NegOpNode(UnaryOpNode): + def __str__(self) -> str: + return f"neg {self.atom.value}" + + +class IsVoidOpNode(UnaryOpNode): + def __str__(self) -> str: + return f"isvoid {self.atom.value}" + + +class TypeNameOpNode(ReturnOpNode): + def __init__(self, target: str) -> None: + super().__init__() + self.target = target + + def __str__(self) -> str: + return f"type_name {self.target}" + + +class ChainOpNode(ReturnOpNode): + def __init__(self, target: str) -> None: + super().__init__() + self.target = target + + +class LoadOpNode(ChainOpNode): + def __str__(self) -> str: + return f"load {self.target}" + + +class LengthOpNode(ChainOpNode): + def __str__(self) -> str: + return f"length {self.target}" + + +class StrOpNode(ChainOpNode): + def __str__(self) -> str: + return f"str {self.target}" + + +class ConcatOpNode(ChainOpNode): + def __init__(self, source: str, target: str) -> None: + super().__init__(target) + self.source = source + + def __str__(self) -> str: + return f"concat {self.source} {self.target}" + + +class SubstringOpNode(ReturnOpNode): + def __init__(self, start: AtomOpNode, length: AtomOpNode, target: IdNode) -> None: + super().__init__() + self.start = start + self.length = length + self.target = target + + def __str__(self) -> str: + return f"substr {self.target.value} {self.start.value} {self.length.value}" + + +class AtomOpNode(ReturnOpNode): + def __init__(self, value: str) -> None: + """ + AtomNode represents all single value nodes, like ids and constants + """ + super().__init__() + self.value = str(value) + + def __str__(self) -> str: + return self.value + + +class IdNode(AtomOpNode): + def __init__(self, value: str) -> None: + super().__init__(value) + + +class ConstantNode(AtomOpNode): + def __init__(self, value: str) -> None: + super().__init__(value) + + +class IntNode(ConstantNode): + def __init__(self, value: str) -> None: + super().__init__(value) + + +class BoolNode(ConstantNode): + def __init__(self, value: str) -> None: + super().__init__(value) + + +class FlowControlNode(OperationNode): + """ + Base class for all flow control operations like If, Label, goto, etc... + """ + + def __init__(self) -> None: + super().__init__() + + +class ShallowCopyOpNode(OperationNode): + def __init__(self, dest: str, source: str) -> None: + super().__init__() + self.dest = dest + self.source = source + + def __str__(self) -> str: + return f"copy {self.dest} {self.source}" + + +class IfNode(FlowControlNode): + def __init__(self, eval_value: AtomOpNode, target: LabelNode) -> None: + super().__init__() + self.eval_value = eval_value + self.target = target + + def __str__(self) -> str: + return f"if {self.eval_value.value} goto {self.target.id}" + + +class IfFalseNode(IfNode): + def __init__(self, eval_value: AtomOpNode, target: LabelNode) -> None: + super().__init__(eval_value, target) + + def __str__(self) -> str: + return f"ifFalse {self.eval_value.value} goto {self.target.id}" + + +class GoToNode(FlowControlNode): + def __init__(self, target: LabelNode) -> None: + super().__init__() + self.target = target + + def __str__(self) -> str: + return f"goto {self.target.id}" + + +class LabelNode(FlowControlNode): + def __init__(self, idx: str) -> None: + super().__init__() + self.id = idx + + def __str__(self) -> str: + return f"label {self.id}" + + +def extract_id(storage_node: StorageNode) -> IdNode: + return IdNode(storage_node.id) diff --git a/src/compiler/asts/inferencer_ast.py b/src/compiler/asts/inferencer_ast.py new file mode 100644 index 000000000..a521493cb --- /dev/null +++ b/src/compiler/asts/inferencer_ast.py @@ -0,0 +1,225 @@ +from typing import List, Tuple + + +class Node: + def __init__(self, node) -> None: + self.line = node.line + self.col = node.col + self.inferenced_type = None + + def get_position(self) -> Tuple[int, int]: + return self.line, self.col + + +class ProgramNode(Node): + def __init__(self, declarations, scope, node: Node): + Node.__init__(self, node) + self.declarations = declarations + self.scope = scope + + +class DeclarationNode(Node): + pass + + +class ClassDeclarationNode(DeclarationNode): + def __init__(self, features, node): + Node.__init__(self, node) + self.features = features + self.id = node.id + # For debbuging purposses + self.parent = node.parent + + +class AttrDeclarationNode(DeclarationNode): + def __init__(self, node): + Node.__init__(self, node) + self.id = node.id + self.expr = None # expression is set later if it exists + # for debbugin purposes + self.type = node.type + + +class MethodDeclarationNode(DeclarationNode): + def __init__(self, params, return_type, body, node): + Node.__init__(self, node) + self.id = node.id + self.params: List[ParamNode] = params + self.type = return_type + self.body = body + # for debbugin purposes + # self.params = node.params + + +class ParamNode(DeclarationNode): + def __init__(self, node, idx: str, typex) -> None: + super().__init__(node) + self.id = idx + self.type = typex + + +class ExpressionNode(Node): + pass + + +class BlocksNode(ExpressionNode): + def __init__(self, expr_list, node): + Node.__init__(self, node) + self.expr_list = expr_list + + +class ConditionalNode(ExpressionNode): + def __init__(self, condition, then_node, else_node, node): + Node.__init__(self, node) + self.condition = condition + self.then_body = then_node + self.else_body = else_node + + +class CaseNode(ExpressionNode): + def __init__(self, case_expr, options, node): + Node.__init__(self, node) + self.case_expr = case_expr + self.options: List[CaseOptionNode] = options + + +class CaseOptionNode(ExpressionNode): + def __init__(self, ret_expr, branch_type, node): + Node.__init__(self, node) + self.id = node.id + self.expr = ret_expr + self.decl_type = node.type + self.branch_type = branch_type + # For debbuging purposes + self.type = node.type + + +class LoopNode(ExpressionNode): + def __init__(self, condition, body, node): + Node.__init__(self, node) + self.condition = condition + self.body = body + + +class LetNode(ExpressionNode): + def __init__(self, var_decl_list, in_expr, node): + Node.__init__(self, node) + self.var_decl_list = var_decl_list + self.in_expr = in_expr + + +class VarDeclarationNode(ExpressionNode): + def __init__(self, node): + Node.__init__(self, node) + self.id = node.id + self.expr = None # Expression is set later if it exists + self.index = None + # For debbugin purposes + self.type = node.type + + +class AssignNode(ExpressionNode): + def __init__(self, expr, node): + Node.__init__(self, node) + self.id = node.id + self.expr = expr + self.defined = False + + +class MethodCallNode(ExpressionNode): + def __init__(self, caller_type, expr, args, node): + Node.__init__(self, node) + self.caller_type = caller_type + self.expr = expr + self.args = args + self.id = node.id + self.type = node.type + + +class UnaryNode(ExpressionNode): + def __init__(self, expr, node): + Node.__init__(self, node) + self.expr = expr + + +class IsVoidNode(UnaryNode): + pass + + +class NotNode(UnaryNode): + pass + + +class ComplementNode(UnaryNode): + pass + + +class BinaryNode(ExpressionNode): + def __init__(self, left, right, node): + Node.__init__(self, node) + self.left = left + self.right = right + + +class ComparerNode(BinaryNode): + pass + + +class LessNode(ComparerNode): + pass + + +class LessOrEqualNode(ComparerNode): + pass + + +class EqualsNode(ComparerNode): + pass + + +class ArithmeticNode(BinaryNode): + pass + + +class PlusNode(ArithmeticNode): + pass + + +class MinusNode(ArithmeticNode): + pass + + +class StarNode(ArithmeticNode): + pass + + +class DivNode(ArithmeticNode): + pass + + +class AtomicNode(ExpressionNode): + def __init__(self, node): + Node.__init__(self, node) + self.value = node.value + + +class BooleanNode(AtomicNode): + pass + + +class IntNode(AtomicNode): + pass + + +class StringNode(AtomicNode): + pass + + +class VariableNode(AtomicNode): + def __init__(self, node): + super().__init__(node) + self.defined = False + + +class InstantiateNode(AtomicNode): + pass diff --git a/src/compiler/asts/mips_ast.py b/src/compiler/asts/mips_ast.py new file mode 100644 index 000000000..3fc47c87c --- /dev/null +++ b/src/compiler/asts/mips_ast.py @@ -0,0 +1,391 @@ +from __future__ import annotations +from typing import Dict, List, Tuple, Union + +from asts.parser_ast import BinaryNode + + +class Node: + def __init__(self, node) -> None: + # if node is not None: + # self.line: int = node.line + # self.col: int = node.col + pass + + def get_position(self) -> Tuple[int, int]: + return (self.line, self.col) + + +class MIPSProgram(Node): + """ + This node represents the entire MIPS program ( .text and .data sections ) + """ + + def __init__(self, node, text_section: List[InstructionNode], data_section) -> None: + super().__init__(node) + self.text_section = text_section + self.data_section = data_section + + +class TextNode(Node): + """ + This node represents the `.text` section in MIPS + """ + + def __init__(self, node, instructions: List[InstructionNode]) -> None: + super().__init__(node) + if instructions is None: + self.instructions = [] + self.instructions = instructions + + +class DataNode(Node): + """ + This node represents the `.data` section in MIPS + """ + + def __init__( + self, node, data: List[Tuple[LabelDeclaration, AssemblerDirective]] + ) -> None: + super().__init__(node) + if data is None: + self.data = [] + self.data = data + + +class Constant(Node): + """ + This class represents a literal integer in MIPS + """ + + def __init__(self, node, value: Union[str, int]) -> None: + super().__init__(node) + self.value = value + + +class RegisterNode(Node): + """ + This class represents a 4-bytes MIPS' register + """ + + def __init__(self, node, number) -> None: + super().__init__(node) + self.number = number + + +class Label(Node): + """ + This class represents a label declaration in MIPS + """ + + def __init__(self, node, idx) -> None: + super().__init__(node) + self.idx = idx + + +class InstructionNode(Node): + def __init__(self, node) -> None: + super().__init__(node) + + +class LabelDeclaration(InstructionNode): + """ + This class represents a label declaration in MIPS + """ + + def __init__(self, node, idx) -> None: + super().__init__(node) + self.idx = idx + + +class MemoryIndexNode(Node): + """ + This node represents a memory-indexation of the form `
()` + """ + + def __init__(self, node, address, index) -> None: + super().__init__(node) + self.address = address + self.index = index + + +class BinaryOpNode(InstructionNode): + def __init__(self, node, left, right) -> None: + super().__init__(node) + self.left = left + self.right = right + + +class TernaryOpNode(InstructionNode): + def __init__(self, node, left, middle, right) -> None: + super().__init__(node) + self.left = left + self.middle = middle + self.right = right + + +class MoveFromLo(InstructionNode): + """ + This node represents `mflo` instruction in MIPS + """ + + def __init__(self, node, register) -> None: + super().__init__(node) + self.register = register + + +class Div(BinaryOpNode): + """ + This node represents `div` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + + +class Xori(TernaryOpNode): + """ + This node represents `xori` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Not(BinaryOpNode): + """ + This node represents `not` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + + +class Equal(TernaryOpNode): + """ + This node represents `seq` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class LessOrEqual(TernaryOpNode): + """ + This node represents `sle` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Less(TernaryOpNode): + """ + This node represents `slt` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Multiply(TernaryOpNode): + """ + This node represents `mul` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Subu(TernaryOpNode): + """ + This node represents `subu` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Sub(TernaryOpNode): + """ + This node represents `sub` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Addi(TernaryOpNode): + """ + This node represents `addi` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Addu(TernaryOpNode): + """ + This node represents `addu` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Add(TernaryOpNode): + """ + This node represents `addu` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class Move(BinaryOpNode): + """ + This node represents `move` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + + +class LoadImmediate(BinaryOpNode): + """ + This node represents `li` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + + +class LoadByte(BinaryOpNode): + """ + This node represents `lb` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + +class StoreByte(BinaryOpNode): + """ + This node represents `sb` instruction in MIPS + """ + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + +class LoadWord(BinaryOpNode): + """ + This node represents `lw` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + + +class LoadAddress(BinaryOpNode): + """ + This node represents `la` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + + +class StoreWord(BinaryOpNode): + """ + This node represents `sw` instruction in MIPS + """ + + def __init__(self, node, left, right) -> None: + super().__init__(node, left, right) + + +class Jump(InstructionNode): + """ + This node represents `j` instruction in MIPS + """ + + def __init__(self, node, address) -> None: + super().__init__(node) + self.address = address + + +class JumpRegister(InstructionNode): + """ + This node represents `jr` instruction in MIPS + """ + + def __init__(self, node, register) -> None: + super().__init__(node) + self.register = register + + +class JumpAndLink(InstructionNode): + """ + This node represents `jal` instruction in MIPS + """ + + def __init__(self, node, address) -> None: + super().__init__(node) + self.address = address + + +class BranchOnEqual(TernaryOpNode): + """ + This node represents `beq` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class BranchOnNotEqual(TernaryOpNode): + """ + This node represents `bne` instruction in MIPS + """ + + def __init__(self, node, left, middle, right) -> None: + super().__init__(node, left, middle, right) + + +class AssemblerDirective(Node): + def __init__(self, node, list) -> None: + super().__init__(node) + if list is None: + list = [] + self.list = list + + +class WordDirective(AssemblerDirective): + """ + This node represents `.word` assembler directive + """ + + def __init__(self, node, list) -> None: + super().__init__(node, list) + + +class AsciizDirective(AssemblerDirective): + """ + This node represents `.asciiz` assembler directive + """ + + def __init__(self, node, list) -> None: + super().__init__(node, list) + +class SpaceDirective(AssemblerDirective): + """ + This node represents `.space` assembler directive + """ + + def __init__(self, node, list) -> None: + super().__init__(node, list) + +class Syscall(InstructionNode): + """ + This node represents `syscall` instruction in MIPS + """ + + def __init__(self, node) -> None: + super().__init__(node) diff --git a/src/compiler/asts/parser_ast.py b/src/compiler/asts/parser_ast.py new file mode 100644 index 000000000..7b1bb41e8 --- /dev/null +++ b/src/compiler/asts/parser_ast.py @@ -0,0 +1,221 @@ +from typing import List, Tuple + + +class Node: + def __init__(self) -> None: + self.line = 0 + self.col = 0 + + def get_position(self) -> Tuple[int, int]: + return self.line, self.col + + def set_position(self, line: int, col: int) -> None: + self.line = line + self.col = col + + +class ProgramNode(Node): + def __init__(self, declarations): + Node.__init__(self) + self.declarations = declarations + + +class DeclarationNode(Node): + pass + + +class ClassDeclarationNode(DeclarationNode): + def __init__(self, idx, features, parent=None): + Node.__init__(self) + self.id = idx + self.parent = parent + self.features = features + + +class AttrDeclarationNode(DeclarationNode): + def __init__(self, idx, typex, expression=None): + Node.__init__(self) + self.id = idx + self.type = typex + self.expr = expression + + +class MethodDeclarationNode(DeclarationNode): + def __init__(self, idx, params, return_type, body): + Node.__init__(self) + self.id = idx + self.params: List[ParamNode] = params + self.type = return_type + self.body = body + + +class ParamNode(DeclarationNode): + def __init__(self, idx: str, typex: str) -> None: + Node.__init__(self) + self.id = idx + self.type = typex + + +class ExpressionNode(Node): + pass + + +class VarDeclarationNode(ExpressionNode): + def __init__(self, idx, typex, expression=None): + Node.__init__(self) + self.id = idx + self.type = typex + self.expr = expression + + +class AssignNode(ExpressionNode): + def __init__(self, idx, expr): + Node.__init__(self) + self.id = idx + self.expr = expr + + +class MethodCallNode(ExpressionNode): + def __init__(self, expr=None, typex=None, idx=None, args=None): + Node.__init__(self) + self.expr = expr + self.type = typex + self.id = idx + self.args = args + + +class ConditionalNode(ExpressionNode): + def __init__( + self, + condition: ExpressionNode, + then_body: ExpressionNode, + else_body: ExpressionNode, + ): + Node.__init__(self) + self.condition = condition + self.then_body = then_body + self.else_body = else_body + + +class LoopNode(ExpressionNode): + def __init__(self, condition: ExpressionNode, body: ExpressionNode): + Node.__init__(self) + self.condition = condition + self.body = body + + +class LetNode(ExpressionNode): + def __init__( + self, var_decl_list: List[VarDeclarationNode], in_expr: ExpressionNode + ): + Node.__init__(self) + self.var_decl_list = var_decl_list + self.in_expr = in_expr + + +class CaseNode(ExpressionNode): + def __init__(self, case_expr, options): + Node.__init__(self) + self.case_expr = case_expr + self.options: List[CaseOptionNode] = options + + +class CaseOptionNode(ExpressionNode): + def __init__(self, idx, typex, ret_expr): + Node.__init__(self) + self.id = idx + self.type = typex + self.expr = ret_expr + + +class BlocksNode(ExpressionNode): + def __init__(self, expr_list): + Node.__init__(self) + self.expr_list = expr_list + + +class UnaryNode(ExpressionNode): + def __init__(self, expr): + Node.__init__(self) + self.expr = expr + + +class IsVoidNode(UnaryNode): + pass + + +class NotNode(UnaryNode): + pass + + +class ComplementNode(UnaryNode): + pass + + +class BinaryNode(ExpressionNode): + def __init__(self, left, right): + Node.__init__(self) + self.left = left + self.right = right + + +class ComparerNode(BinaryNode): + pass + + +class LessNode(ComparerNode): + pass + + +class LessOrEqualNode(ComparerNode): + pass + + +class EqualsNode(ComparerNode): + pass + + +class ArithmeticNode(BinaryNode): + pass + + +class PlusNode(ArithmeticNode): + pass + + +class MinusNode(ArithmeticNode): + pass + + +class StarNode(ArithmeticNode): + pass + + +class DivNode(ArithmeticNode): + pass + + +class AtomicNode(ExpressionNode): + def __init__(self, value): + Node.__init__(self) + self.value = value + + +class BooleanNode(AtomicNode): + pass + + +class IntNode(AtomicNode): + pass + + +class StringNode(AtomicNode): + pass + + +class VariableNode(AtomicNode): + pass + + +class InstantiateNode(AtomicNode): + pass diff --git a/src/compiler/asts/types_ast.py b/src/compiler/asts/types_ast.py new file mode 100644 index 000000000..caaab04d1 --- /dev/null +++ b/src/compiler/asts/types_ast.py @@ -0,0 +1,217 @@ +from __future__ import annotations +from typing import List, Tuple + + +class Node: + def __init__(self, node) -> None: + self.line = node.line + self.col = node.col + self.type = None + + def get_position(self) -> Tuple[int, int]: + return self.line, self.col + + +class ProgramNode(Node): + def __init__(self, declarations: List[ClassDeclarationNode], node: Node): + Node.__init__(self, node) + self.declarations = declarations + + +class DeclarationNode(Node): + pass + + +class ClassDeclarationNode(DeclarationNode): + def __init__( + self, features: List[AttrDeclarationNode | MethodDeclarationNode], node + ): + Node.__init__(self, node) + self.id: str = node.id + self.features: List[AttrDeclarationNode | MethodDeclarationNode] = features + self.parent = node.parent + + +class AttrDeclarationNode(DeclarationNode): + def __init__(self, node): + Node.__init__(self, node) + self.id = node.id + self.expr = None + + +class MethodDeclarationNode(DeclarationNode): + def __init__(self, params, return_type, body, node): + Node.__init__(self, node) + self.id = node.id + self.params: List[VarDeclarationNode] = params + self.type = return_type + self.body = body + + +class ParamNode(DeclarationNode): + def __init__(self, node, idx: str, typex) -> None: + super().__init__(node) + self.id = idx + self.type = typex + + +class ExpressionNode(Node): + pass + + +class BlocksNode(ExpressionNode): + def __init__(self, expr_list, node): + Node.__init__(self, node) + self.expr_list = expr_list + + +class ConditionalNode(ExpressionNode): + def __init__(self, condition, then_node, else_node, node): + Node.__init__(self, node) + self.condition = condition + self.then_body = then_node + self.else_body = else_node + + +class CaseNode(ExpressionNode): + def __init__(self, case_expr, options, node): + Node.__init__(self, node) + self.case_expr = case_expr + self.options: List[CaseOptionNode] = options + + +class CaseOptionNode(ExpressionNode): + def __init__(self, ret_expr, branch_type, successors: List[str], node): + Node.__init__(self, node) + self.id = node.id + self.decl_type = node.decl_type + self.branch_type = branch_type + self.successors = successors + self.expr = ret_expr + + +class LoopNode(ExpressionNode): + def __init__(self, condition, body, node): + Node.__init__(self, node) + self.condition = condition + self.body = body + + +class LetNode(ExpressionNode): + def __init__(self, var_decl_list, in_expr, node): + Node.__init__(self, node) + self.var_decl_list = var_decl_list + self.in_expr = in_expr + + +class VarDeclarationNode(ExpressionNode): + def __init__(self, node): + Node.__init__(self, node) + self.id = node.id + self.expr = None # Expression is set later if it exists + self.index = None + + +class AssignNode(ExpressionNode): + def __init__(self, expr, node): + Node.__init__(self, node) + self.id = node.id + self.expr = expr + self.defined = False + + +class MethodCallNode(ExpressionNode): + def __init__(self, caller_type, expr, args, node): + Node.__init__(self, node) + self.caller_type = caller_type + self.expr = expr + self.args = args + self.id = node.id + self.at_type = node.type + + +class UnaryNode(ExpressionNode): + def __init__(self, expr, node): + Node.__init__(self, node) + self.expr = expr + + +class IsVoidNode(UnaryNode): + pass + + +class NotNode(UnaryNode): + pass + + +class ComplementNode(UnaryNode): + pass + + +class BinaryNode(ExpressionNode): + def __init__(self, left, right, node): + Node.__init__(self, node) + self.left = left + self.right = right + + +class ComparerNode(BinaryNode): + pass + + +class LessNode(ComparerNode): + pass + + +class LessOrEqualNode(ComparerNode): + pass + + +class EqualsNode(ComparerNode): + pass + + +class ArithmeticNode(BinaryNode): + pass + + +class PlusNode(ArithmeticNode): + pass + + +class MinusNode(ArithmeticNode): + pass + + +class StarNode(ArithmeticNode): + pass + + +class DivNode(ArithmeticNode): + pass + + +class AtomicNode(ExpressionNode): + def __init__(self, node): + Node.__init__(self, node) + self.value = node.value + + +class BooleanNode(AtomicNode): + pass + + +class IntNode(AtomicNode): + pass + + +class StringNode(AtomicNode): + pass + + +class VariableNode(AtomicNode): + pass + + +class InstantiateNode(AtomicNode): + pass diff --git a/src/compiler/lexing/__init__.py b/src/compiler/lexing/__init__.py new file mode 100644 index 000000000..380c448d2 --- /dev/null +++ b/src/compiler/lexing/__init__.py @@ -0,0 +1,2 @@ +from .lexer import Lexer + diff --git a/src/compiler/lexing/errors.py b/src/compiler/lexing/errors.py new file mode 100644 index 000000000..7fb9dc98c --- /dev/null +++ b/src/compiler/lexing/errors.py @@ -0,0 +1,12 @@ +class LexicographicError: + def __init__(self, line: int, col: int, message: str) -> None: + self.line = line + self.col = col + self.message = message + + def __str__(self) -> str: + return f'({self.line},{self.col}) - LexicographicError: ERROR "{self.message}"' + + def __repr__(self) -> str: + return str(self) + \ No newline at end of file diff --git a/src/compiler/lexing/lexer.py b/src/compiler/lexing/lexer.py new file mode 100644 index 000000000..898905d16 --- /dev/null +++ b/src/compiler/lexing/lexer.py @@ -0,0 +1,349 @@ +from .errors import LexicographicError +from ply import lex + + +class Lexer: + def __init__(self) -> None: + self.errors = [] + self.literals = [ + "+", + "-", + "*", + "/", + "~", + "=", + "<", + ":", + "{", + "}", + "@", + ",", + ".", + "(", + ")", + ";", + ] + # keywords + self.reserved = { + "true": "TRUE", + "false": "FALSE", + "if": "IF", + "then": "THEN", + "else": "ELSE", + "fi": "FI", + "class": "CLASS", + "inherits": "INHERITS", + "while": "WHILE", + "loop": "LOOP", + "pool": "POOL", + "let": "LET", + "in": "IN", + "case": "CASE", + "of": "OF", + "esac": "ESAC", + "new": "NEW", + "isvoid": "ISVOID", + "not": "NOT", + } + + self.tokens = [ + "LESSEQ", + "ASSIGN", + "RET", + "ID", + "TYPE", + "STRING", + "INT", + "ONELINECOMMENT", + ] + self.tokens = self.tokens + list(self.reserved.values()) + self.states = (("comment", "exclusive"), ("string", "exclusive")) + + self._string_value = "" + self._string_start = -1 + self._string_col = -1 + self._string_line = -1 + self._string_slash = False + self._end_string = True + + self._end_comment = True + self._build() + + def _build(self): + self._lexer = lex.lex(module=self) + self._lexer.col = 1 + + def tokenize(self, data): + self._lexer.input(data) + while True: + tok = self._lexer.token() + if not self._end_comment and self._lexer.lexpos - 1 <= len(data): + line, col = self._get_current_pos(data) + self.errors.append(LexicographicError(line, col, "INVALID COMMENT")) + break + if not self._end_string and self._lexer.lexpos - 1 <= len(data): + line = self._lexer.lineno + col = self._lexer.col + 1 + self.errors.append(LexicographicError(line, col, "UNTERMINATED STRING")) + if not tok: + break + yield tok + + def _get_current_pos(self, data: str): + data = data[: self._lexer.lexpos] + line = data.count("\n") + 1 + col = len(data) - data.rfind("\n") + return line, col + + def _set_pos(self, token): + token.col = token.lexer.col + token.line = token.lexer.lineno + token.lexer.col += len(token.value) + + def t_LESSEQ(self, t): + r"\<=" + self._set_pos(t) + return t + + def t_ASSIGN(self, t): + r"\<-" + self._set_pos(t) + return t + + def t_RET(self, t): + r"\=>" + self._set_pos(t) + return t + + def t_TYPE(self, t): + r"[A-Z][a-zA-Z_0-9]*" + self._set_pos(t) + lower_case = t.value.lower() + if lower_case in ("true", "false"): + t.type = "TYPE" + return t + t.type = self.reserved.get(lower_case, "TYPE") + return t + + def t_ID(self, t): + r"[a-z][a-zA-Z_0-9]*" + self._set_pos(t) + t.type = self.reserved.get(t.value.lower(), "ID") + return t + + def t_INT(self, t): + r"\d+" + self._set_pos(t) + t.value = int(t.value) + return t + + # One-line comments rule + def t_ONELINECOMMENT(self, t): + r"(--.*(\n | $))" + t.lexer.lineno += 1 + t.col = t.lexer.col + t.lexer.col = 1 + + # String rules + def t_string(self, t): + r'"' + self._string_value = '' + t.lexer.col += 1 + self._string_value += t.value + self._string_line = t.lexer.lineno + self._string_col = t.lexer.col - 1 + self._end_string = False + t.lexer.begin("string") + + def t_string_end(self, t): + r'(? None: + self.line = line + self.token = token + self.col = col + + def __str__(self) -> str: + return f'({self.line}, {self.col}) - \ + SyntacticError: ERROR at or near "{self.token}"' diff --git a/src/compiler/parsing/parser.py b/src/compiler/parsing/parser.py new file mode 100644 index 000000000..755dd7415 --- /dev/null +++ b/src/compiler/parsing/parser.py @@ -0,0 +1,331 @@ +from ply.yacc import yacc +from asts.parser_ast import ( + AssignNode, + AttrDeclarationNode, + BlocksNode, + BooleanNode, + CaseNode, + CaseOptionNode, + ClassDeclarationNode, + ComplementNode, + ConditionalNode, + DivNode, + EqualsNode, + InstantiateNode, + IntNode, + IsVoidNode, + LessNode, + LessOrEqualNode, + LetNode, + LoopNode, + MethodCallNode, + MethodDeclarationNode, + MinusNode, + NotNode, + ParamNode, + PlusNode, + ProgramNode, + StarNode, + StringNode, + VarDeclarationNode, + VariableNode, + ParamNode, +) +from .errors import SyntacticError + + +class Parser: + def __init__(self, lexer) -> None: + self.errors = [] + self.lexer = lexer + self.tokens = lexer.tokens + + self.precedence = ( + ("right", "ASSIGN"), + ("right", "NOT"), + ("nonassoc", "LESSEQ", "<", "="), + ("left", "+", "-"), + ("left", "*", "/"), + ("right", "ISVOID"), + ("left", "~"), + ("left", "@"), + ("left", "."), + ) + self._build() + + def _build(self): + self._parser = yacc(module=self) + + def parse(self, program): + return self._parser.parse(program) + + def p_program(self, p): + """program : class_list""" + p[0] = ProgramNode(p[1]) + + def p_class_list(self, p): + """class_list : class ';' class_list + | class ';'""" + if len(p) == 4: + p[0] = [p[1]] + p[3] + elif len(p) == 3: + p[0] = [p[1]] + + def p_class(self, p): + """class : CLASS TYPE INHERITS TYPE '{' feature_list '}' + | CLASS TYPE '{' feature_list '}'""" + if len(p) == 8: + p[0] = ClassDeclarationNode(p[2], p[6], p[4]) + elif len(p) == 6: + p[0] = ClassDeclarationNode(p[2], p[4]) + + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_feature_list(self, p): + """feature_list : attribute ';' feature_list + | method ';' feature_list + | empty""" + if len(p) == 4: + p[0] = [p[1]] + p[3] + elif len(p) == 2: + p[0] = [] + + def p_attribute(self, p): + """attribute : ID ':' TYPE ASSIGN expression + | ID ':' TYPE""" + if len(p) == 6: + p[0] = AttrDeclarationNode(p[1], p[3], p[5]) + else: + p[0] = AttrDeclarationNode(p[1], p[3]) + + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_method(self, p): + """method : ID '(' params_list ')' ':' TYPE '{' expression '}' + | ID '(' empty ')' ':' TYPE '{' expression '}'""" + p[0] = MethodDeclarationNode(p[1], p[3], p[6], p[8]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_params_list(self, p): + """params_list : param ',' params_list + | param""" + if len(p) == 4: + p[0] = [p[1]] + p[3] + else: + p[0] = [p[1]] + + # def p_params_list_empty(self,p): + # """params_list : empty""" + # p[0] = [] + + def p_param(self, p): + """param : ID ':' TYPE""" + p[0] = ParamNode(p[1], p[3]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_list(self, p): + """expression_list : expression ';' expression_list + | expression ';'""" + if len(p) == 4: + p[0] = [p[1]] + p[3] + elif len(p) == 3: + p[0] = [p[1]] + + def p_expression_assigment(self, p): + """expression : ID ASSIGN expression""" + p[0] = AssignNode(p[1], p[3]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_if_then_else(self, p): + """expression : IF expression THEN expression ELSE expression FI""" + p[0] = ConditionalNode(p[2], p[4], p[6]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_while(self, p): + """expression : WHILE expression LOOP expression POOL""" + p[0] = LoopNode(p[2], p[4]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_block(self, p): + """expression : '{' expression_list '}'""" + p[0] = BlocksNode(p[2]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_let_in(self, p): + """expression : LET let_list IN expression""" + p[0] = LetNode(p[2], p[4]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_let_list(self, p): + """let_list : let_single ',' let_list + | let_single""" + if len(p) == 4: + p[0] = [p[1]] + p[3] + else: + p[0] = [p[1]] + + def p_let_single(self, p): + """let_single : ID ':' TYPE ASSIGN expression + | ID ':' TYPE""" + if len(p) == 6: + p[0] = VarDeclarationNode(p[1], p[3], p[5]) + else: + p[0] = VarDeclarationNode(p[1], p[3]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_case(self, p): + """expression : CASE expression OF case_list ESAC""" + p[0] = CaseNode(p[2], p[4]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_case_list(self, p): + """case_list : case_single case_list + | case_single""" + if len(p) == 3: + p[0] = [p[1]] + p[2] + else: + p[0] = [p[1]] + + def p_case_single(self, p): + """case_single : ID ':' TYPE RET expression ';'""" + p[0] = CaseOptionNode(p[1], p[3], p[5]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_dispatch(self, p): + """expression : expression '@' TYPE '.' ID '(' args_list ')' + | expression '.' ID '(' args_list ')' + | ID '(' args_list ')'""" + if len(p) == 9: + p[0] = MethodCallNode(p[1], p[3], p[5], p[7]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + elif len(p) == 7: + p[0] = MethodCallNode(p[1], None, p[3], p[5]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + else: + p[0] = MethodCallNode(None, None, p[1], p[3]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_dispatch_empty(self, p): + """expression : expression '@' TYPE '.' ID '(' empty ')' + | expression '.' ID '(' empty ')' + | ID '(' empty ')'""" + if len(p) == 9: + p[0] = MethodCallNode(p[1], p[3], p[5], p[7]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + elif len(p) == 7: + p[0] = MethodCallNode(p[1], None, p[3], p[5]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + else: + p[0] = MethodCallNode(None, None, p[1], p[3]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_args_list(self, p): + """args_list : expression ',' args_list + | expression""" + if len(p) == 4: + p[0] = [p[1]] + p[3] + else: + p[0] = [p[1]] + + # def p_args_list_empty(self,p): + # """args_list : empty""" + # p[0] = [] + + def p_expression_instatiate(self, p): + """expression : NEW TYPE""" + p[0] = InstantiateNode(p[2]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_isvoid(self, p): + """expression : ISVOID expression""" + p[0] = IsVoidNode(p[2]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_not(self, p): + """expression : NOT expression""" + p[0] = NotNode(p[2]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_complement(self, p): + """expression : '~' expression""" + p[0] = ComplementNode(p[2]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_plus(self, p): + """expression : expression '+' expression""" + p[0] = PlusNode(p[1], p[3]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + def p_expression_minus(self, p): + """expression : expression '-' expression""" + p[0] = MinusNode(p[1], p[3]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + def p_expression_div(self, p): + """expression : expression '/' expression""" + p[0] = DivNode(p[1], p[3]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + def p_expression_star(self, p): + """expression : expression '*' expression""" + p[0] = StarNode(p[1], p[3]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + def p_expression_less(self, p): + """expression : expression '<' expression""" + p[0] = LessNode(p[1], p[3]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + def p_expression_lesseq(self, p): + """expression : expression LESSEQ expression""" + p[0] = LessOrEqualNode(p[1], p[3]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + def p_expression_equals(self, p): + """expression : expression '=' expression""" + p[0] = EqualsNode(p[1], p[3]) + p[0].set_position(p.slice[2].line, p.slice[2].col) + + def p_expression_parentheses(self, p): + """expression : '(' expression ')'""" + p[0] = p[2] + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_string(self, p): + """expression : STRING""" + p[0] = StringNode(p[1]) + p[0].set_position(p.slice[1].lineno, p.slice[1].col) + + def p_expression_variable(self, p): + """expression : ID""" + p[0] = VariableNode(p[1]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_true(self, p): + """expression : TRUE""" + p[0] = BooleanNode(True) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_false(self, p): + """expression : FALSE""" + p[0] = BooleanNode(False) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_expression_int(self, p): + """expression : INT""" + p[0] = IntNode(p[1]) + p[0].set_position(p.slice[1].line, p.slice[1].col) + + def p_empty(self, p): + """empty :""" + p[0] = [] + + def p_error(self, t): + if t is None: + self.errors.append(SyntacticError("EOF", 0, 0)) + else: + self.errors.append(SyntacticError(t.value, t.line, t.col)) diff --git a/src/compiler/parsing/parsetab.py b/src/compiler/parsing/parsetab.py new file mode 100644 index 000000000..8507e80e7 --- /dev/null +++ b/src/compiler/parsing/parsetab.py @@ -0,0 +1,86 @@ + +# parsetab.py +# This file is automatically generated. Do not edit. +# pylint: disable=W,C,R +_tabversion = '3.10' + +_lr_method = 'LALR' + +_lr_signature = "rightASSIGNrightNOTnonassocLESSEQ<=left+-left*/rightISVOIDleft~left@left.ASSIGN CASE CLASS ELSE ESAC FALSE FI ID IF IN INHERITS INT ISVOID LESSEQ LET LOOP NEW NOT OF ONELINECOMMENT POOL RET STRING THEN TRUE TYPE WHILEprogram : class_listclass_list : class ';' class_list\n | class ';'class : CLASS TYPE INHERITS TYPE '{' feature_list '}'\n | CLASS TYPE '{' feature_list '}'feature_list : attribute ';' feature_list\n | method ';' feature_list\n | emptyattribute : ID ':' TYPE ASSIGN expression\n | ID ':' TYPEmethod : ID '(' params_list ')' ':' TYPE '{' expression '}'\n | ID '(' empty ')' ':' TYPE '{' expression '}'params_list : param ',' params_list\n | paramparam : ID ':' TYPEexpression_list : expression ';' expression_list\n | expression ';'expression : ID ASSIGN expressionexpression : IF expression THEN expression ELSE expression FIexpression : WHILE expression LOOP expression POOLexpression : '{' expression_list '}'expression : LET let_list IN expressionlet_list : let_single ',' let_list\n | let_singlelet_single : ID ':' TYPE ASSIGN expression\n | ID ':' TYPEexpression : CASE expression OF case_list ESACcase_list : case_single case_list\n | case_singlecase_single : ID ':' TYPE RET expression ';'expression : expression '@' TYPE '.' ID '(' args_list ')'\n | expression '.' ID '(' args_list ')'\n | ID '(' args_list ')'expression : expression '@' TYPE '.' ID '(' empty ')'\n | expression '.' ID '(' empty ')'\n | ID '(' empty ')'args_list : expression ',' args_list\n | expressionexpression : NEW TYPEexpression : ISVOID expressionexpression : NOT expressionexpression : '~' expressionexpression : expression '+' expressionexpression : expression '-' expressionexpression : expression '/' expressionexpression : expression '*' expressionexpression : expression '<' expressionexpression : expression LESSEQ expressionexpression : expression '=' expressionexpression : '(' expression ')'expression : STRINGexpression : IDexpression : TRUEexpression : FALSEexpression : INTempty :" + +_lr_action_items = {'CLASS':([0,5,],[4,4,]),'$end':([1,2,5,7,],[0,-1,-3,-2,]),';':([3,12,13,17,25,30,36,37,48,49,50,51,70,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,114,127,129,132,133,135,136,142,144,145,146,],[5,18,19,-5,-10,-4,-52,-9,-51,-53,-54,-55,98,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-22,-20,-27,-11,-12,-32,-35,-19,-31,-34,147,]),'TYPE':([4,8,20,32,44,53,54,58,101,131,],[6,10,25,52,76,80,81,86,116,139,]),'INHERITS':([6,],[8,]),'{':([6,10,31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,80,81,95,96,98,99,104,105,108,110,126,128,134,143,],[9,16,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,104,105,40,40,40,40,40,40,40,40,40,40,40,40,]),'ID':([9,16,18,19,21,31,35,38,39,40,41,42,43,45,46,47,56,57,59,60,61,62,63,64,65,66,95,96,98,99,100,102,104,105,108,109,110,118,126,128,134,143,147,],[15,15,15,15,26,36,26,36,36,36,73,36,36,36,36,36,36,36,87,36,36,36,36,36,36,36,36,36,36,36,73,119,36,36,36,123,36,119,36,36,36,36,-30,]),'}':([9,11,14,16,18,19,22,23,24,36,48,49,50,51,69,76,77,78,79,82,88,89,90,91,92,93,94,97,98,103,106,107,113,114,120,121,127,129,135,136,142,144,145,],[-56,17,-8,-56,-56,-56,30,-6,-7,-52,-51,-53,-54,-55,97,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-17,-50,-33,-36,-16,-22,132,133,-20,-27,-32,-35,-19,-31,-34,]),':':([15,26,33,34,73,119,],[20,32,53,54,101,131,]),'(':([15,31,36,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,87,95,96,98,99,104,105,108,110,123,126,128,134,143,],[21,43,57,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,43,110,43,43,43,43,43,43,43,43,134,43,43,43,43,]),')':([21,27,28,29,36,48,49,50,51,52,55,57,75,76,77,78,79,82,83,84,85,88,89,90,91,92,93,94,97,103,106,107,110,114,122,124,125,127,129,134,135,136,140,141,142,144,145,],[-56,33,34,-14,-52,-51,-53,-54,-55,-15,-13,-56,103,-39,-40,-41,-42,-18,106,107,-38,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-56,-22,-37,135,136,-20,-27,-56,-32,-35,144,145,-19,-31,-34,]),'ASSIGN':([25,36,116,],[31,56,128,]),',':([29,36,48,49,50,51,52,72,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,114,116,127,129,135,136,138,142,144,145,],[35,-52,-51,-53,-54,-55,-15,100,-39,-40,-41,-42,-18,108,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-22,-26,-20,-27,-32,-35,-25,-19,-31,-34,]),'IF':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,]),'WHILE':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,39,]),'LET':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,41,]),'CASE':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,]),'NEW':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,]),'ISVOID':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,]),'NOT':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,]),'~':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,]),'STRING':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,]),'TRUE':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,]),'FALSE':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,]),'INT':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,]),'@':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,58,-51,-53,-54,-55,58,58,58,58,58,-39,58,58,58,58,58,58,58,58,58,58,58,58,-21,-50,-33,-36,58,58,58,58,58,-20,-27,-32,-35,58,58,-19,-31,-34,58,]),'.':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,86,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,59,-51,-53,-54,-55,59,59,59,59,59,-39,59,59,59,59,59,109,59,59,59,59,59,59,59,-21,-50,-33,-36,59,59,59,59,59,-20,-27,-32,-35,59,59,-19,-31,-34,59,]),'+':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,60,-51,-53,-54,-55,60,60,60,60,60,-39,-40,60,-42,60,60,-43,-44,-45,-46,60,60,60,-21,-50,-33,-36,60,60,60,60,60,-20,-27,-32,-35,60,60,-19,-31,-34,60,]),'-':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,61,-51,-53,-54,-55,61,61,61,61,61,-39,-40,61,-42,61,61,-43,-44,-45,-46,61,61,61,-21,-50,-33,-36,61,61,61,61,61,-20,-27,-32,-35,61,61,-19,-31,-34,61,]),'/':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,62,-51,-53,-54,-55,62,62,62,62,62,-39,-40,62,-42,62,62,62,62,-45,-46,62,62,62,-21,-50,-33,-36,62,62,62,62,62,-20,-27,-32,-35,62,62,-19,-31,-34,62,]),'*':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,63,-51,-53,-54,-55,63,63,63,63,63,-39,-40,63,-42,63,63,63,63,-45,-46,63,63,63,-21,-50,-33,-36,63,63,63,63,63,-20,-27,-32,-35,63,63,-19,-31,-34,63,]),'<':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,64,-51,-53,-54,-55,64,64,64,64,64,-39,-40,64,-42,64,64,-43,-44,-45,-46,None,None,None,-21,-50,-33,-36,64,64,64,64,64,-20,-27,-32,-35,64,64,-19,-31,-34,64,]),'LESSEQ':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,65,-51,-53,-54,-55,65,65,65,65,65,-39,-40,65,-42,65,65,-43,-44,-45,-46,None,None,None,-21,-50,-33,-36,65,65,65,65,65,-20,-27,-32,-35,65,65,-19,-31,-34,65,]),'=':([36,37,48,49,50,51,67,68,70,74,75,76,77,78,79,82,85,88,89,90,91,92,93,94,97,103,106,107,111,112,114,120,121,127,129,135,136,137,138,142,144,145,146,],[-52,66,-51,-53,-54,-55,66,66,66,66,66,-39,-40,66,-42,66,66,-43,-44,-45,-46,None,None,None,-21,-50,-33,-36,66,66,66,66,66,-20,-27,-32,-35,66,66,-19,-31,-34,66,]),'THEN':([36,48,49,50,51,67,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,114,127,129,135,136,142,144,145,],[-52,-51,-53,-54,-55,95,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-22,-20,-27,-32,-35,-19,-31,-34,]),'LOOP':([36,48,49,50,51,68,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,114,127,129,135,136,142,144,145,],[-52,-51,-53,-54,-55,96,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-22,-20,-27,-32,-35,-19,-31,-34,]),'OF':([36,48,49,50,51,74,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,114,127,129,135,136,142,144,145,],[-52,-51,-53,-54,-55,102,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-22,-20,-27,-32,-35,-19,-31,-34,]),'ELSE':([36,48,49,50,51,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,111,114,127,129,135,136,142,144,145,],[-52,-51,-53,-54,-55,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,126,-22,-20,-27,-32,-35,-19,-31,-34,]),'POOL':([36,48,49,50,51,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,112,114,127,129,135,136,142,144,145,],[-52,-51,-53,-54,-55,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,127,-22,-20,-27,-32,-35,-19,-31,-34,]),'FI':([36,48,49,50,51,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,114,127,129,135,136,137,142,144,145,],[-52,-51,-53,-54,-55,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-22,-20,-27,-32,-35,142,-19,-31,-34,]),'IN':([36,48,49,50,51,71,72,76,77,78,79,82,88,89,90,91,92,93,94,97,103,106,107,114,115,116,127,129,135,136,138,142,144,145,],[-52,-51,-53,-54,-55,99,-24,-39,-40,-41,-42,-18,-43,-44,-45,-46,-47,-48,-49,-21,-50,-33,-36,-22,-23,-26,-20,-27,-32,-35,-25,-19,-31,-34,]),'ESAC':([117,118,130,147,],[129,-29,-28,-30,]),'RET':([139,],[143,]),} + +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items + +_lr_goto_items = {'program':([0,],[1,]),'class_list':([0,5,],[2,7,]),'class':([0,5,],[3,3,]),'feature_list':([9,16,18,19,],[11,22,23,24,]),'attribute':([9,16,18,19,],[12,12,12,12,]),'method':([9,16,18,19,],[13,13,13,13,]),'empty':([9,16,18,19,21,57,110,134,],[14,14,14,14,28,84,125,141,]),'params_list':([21,35,],[27,55,]),'param':([21,35,],[29,29,]),'expression':([31,38,39,40,42,43,45,46,47,56,57,60,61,62,63,64,65,66,95,96,98,99,104,105,108,110,126,128,134,143,],[37,67,68,70,74,75,77,78,79,82,85,88,89,90,91,92,93,94,111,112,70,114,120,121,85,85,137,138,85,146,]),'expression_list':([40,98,],[69,113,]),'let_list':([41,100,],[71,115,]),'let_single':([41,100,],[72,72,]),'args_list':([57,108,110,134,],[83,122,124,140,]),'case_list':([102,118,],[117,130,]),'case_single':([102,118,],[118,118,]),} + +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +_lr_productions = [ + ("S' -> program","S'",1,None,None,None), + ('program -> class_list','program',1,'p_program','parser.py',62), + ('class_list -> class ; class_list','class_list',3,'p_class_list','parser.py',66), + ('class_list -> class ;','class_list',2,'p_class_list','parser.py',67), + ('class -> CLASS TYPE INHERITS TYPE { feature_list }','class',7,'p_class','parser.py',74), + ('class -> CLASS TYPE { feature_list }','class',5,'p_class','parser.py',75), + ('feature_list -> attribute ; feature_list','feature_list',3,'p_feature_list','parser.py',84), + ('feature_list -> method ; feature_list','feature_list',3,'p_feature_list','parser.py',85), + ('feature_list -> empty','feature_list',1,'p_feature_list','parser.py',86), + ('attribute -> ID : TYPE ASSIGN expression','attribute',5,'p_attribute','parser.py',93), + ('attribute -> ID : TYPE','attribute',3,'p_attribute','parser.py',94), + ('method -> ID ( params_list ) : TYPE { expression }','method',9,'p_method','parser.py',103), + ('method -> ID ( empty ) : TYPE { expression }','method',9,'p_method','parser.py',104), + ('params_list -> param , params_list','params_list',3,'p_params_list','parser.py',109), + ('params_list -> param','params_list',1,'p_params_list','parser.py',110), + ('param -> ID : TYPE','param',3,'p_param','parser.py',121), + ('expression_list -> expression ; expression_list','expression_list',3,'p_expression_list','parser.py',126), + ('expression_list -> expression ;','expression_list',2,'p_expression_list','parser.py',127), + ('expression -> ID ASSIGN expression','expression',3,'p_expression_assigment','parser.py',134), + ('expression -> IF expression THEN expression ELSE expression FI','expression',7,'p_expression_if_then_else','parser.py',139), + ('expression -> WHILE expression LOOP expression POOL','expression',5,'p_expression_while','parser.py',144), + ('expression -> { expression_list }','expression',3,'p_expression_block','parser.py',149), + ('expression -> LET let_list IN expression','expression',4,'p_expression_let_in','parser.py',154), + ('let_list -> let_single , let_list','let_list',3,'p_let_list','parser.py',159), + ('let_list -> let_single','let_list',1,'p_let_list','parser.py',160), + ('let_single -> ID : TYPE ASSIGN expression','let_single',5,'p_let_single','parser.py',167), + ('let_single -> ID : TYPE','let_single',3,'p_let_single','parser.py',168), + ('expression -> CASE expression OF case_list ESAC','expression',5,'p_expression_case','parser.py',176), + ('case_list -> case_single case_list','case_list',2,'p_case_list','parser.py',181), + ('case_list -> case_single','case_list',1,'p_case_list','parser.py',182), + ('case_single -> ID : TYPE RET expression ;','case_single',6,'p_case_single','parser.py',189), + ('expression -> expression @ TYPE . ID ( args_list )','expression',8,'p_expression_dispatch','parser.py',194), + ('expression -> expression . ID ( args_list )','expression',6,'p_expression_dispatch','parser.py',195), + ('expression -> ID ( args_list )','expression',4,'p_expression_dispatch','parser.py',196), + ('expression -> expression @ TYPE . ID ( empty )','expression',8,'p_expression_dispatch_empty','parser.py',210), + ('expression -> expression . ID ( empty )','expression',6,'p_expression_dispatch_empty','parser.py',211), + ('expression -> ID ( empty )','expression',4,'p_expression_dispatch_empty','parser.py',212), + ('args_list -> expression , args_list','args_list',3,'p_args_list','parser.py',226), + ('args_list -> expression','args_list',1,'p_args_list','parser.py',227), + ('expression -> NEW TYPE','expression',2,'p_expression_instatiate','parser.py',238), + ('expression -> ISVOID expression','expression',2,'p_expression_isvoid','parser.py',243), + ('expression -> NOT expression','expression',2,'p_expression_not','parser.py',248), + ('expression -> ~ expression','expression',2,'p_expression_complement','parser.py',253), + ('expression -> expression + expression','expression',3,'p_expression_plus','parser.py',258), + ('expression -> expression - expression','expression',3,'p_expression_minus','parser.py',263), + ('expression -> expression / expression','expression',3,'p_expression_div','parser.py',268), + ('expression -> expression * expression','expression',3,'p_expression_star','parser.py',273), + ('expression -> expression < expression','expression',3,'p_expression_less','parser.py',278), + ('expression -> expression LESSEQ expression','expression',3,'p_expression_lesseq','parser.py',283), + ('expression -> expression = expression','expression',3,'p_expression_equals','parser.py',288), + ('expression -> ( expression )','expression',3,'p_expression_parentheses','parser.py',293), + ('expression -> STRING','expression',1,'p_expression_string','parser.py',298), + ('expression -> ID','expression',1,'p_expression_variable','parser.py',303), + ('expression -> TRUE','expression',1,'p_expression_true','parser.py',308), + ('expression -> FALSE','expression',1,'p_expression_false','parser.py',313), + ('expression -> INT','expression',1,'p_expression_int','parser.py',318), + ('empty -> ','empty',0,'p_empty','parser.py',323), +] diff --git a/src/compiler/visitors/ast_print/__init__.py b/src/compiler/visitors/ast_print/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/compiler/visitors/ast_print/type_logger.py b/src/compiler/visitors/ast_print/type_logger.py new file mode 100644 index 000000000..356b1ddff --- /dev/null +++ b/src/compiler/visitors/ast_print/type_logger.py @@ -0,0 +1,280 @@ +from visitors.utils import visitor +from asts.inferencer_ast import ( + AssignNode, + AtomicNode, + AttrDeclarationNode, + BinaryNode, + BlocksNode, + CaseOptionNode, + MethodCallNode, + CaseNode, + ClassDeclarationNode, + MethodDeclarationNode, + ComplementNode, + ConditionalNode, + InstantiateNode, + IsVoidNode, + LetNode, + NotNode, + ProgramNode, + VarDeclarationNode, + LoopNode, +) +from visitors.semantics.tools import Context, Scope + + +class TypeLogger(object): + def __init__(self, context) -> None: + self.context: Context = context + + @visitor.on("node") + def visit(self, node, scope, tabs): + pass + + @visitor.when(ProgramNode) + def visit(self, node, scope: Scope, tabs=0): + ans = "\t" * tabs + f"\\__ProgramNode [ ... ]" + statements = "\n".join( + self.visit(child, scope.next_child(), tabs + 1) + for child in node.declarations + ) + return f"{ans}\n{statements}" + + @visitor.when(ClassDeclarationNode) + def visit(self, node, scope, tabs=0): + parent = "" if node.parent is None else f": {node.parent}" + attr_list = self.context.get_type(node.id, unpacked=True).attributes + name_list = ["self"] + for attr in attr_list: + name_list.append(attr.name) + format_scope = defined_format(name_list, scope, tabs) + ans = ( + "\t" * tabs + + f"\\__ClassDeclarationNode: class {node.id} {parent} {{ ... }}" + ) + ans += format_scope + features = "\n".join( + str(self.visit(child, scope, tabs + 1)) for child in node.features + ) + return f"{ans}\n{features}" + + @visitor.when(AttrDeclarationNode) + def visit(self, node, scope: Scope, tabs=0): + extra = computed_info(node) + ans = "\t" * tabs + f"\\__AttrDeclarationNode: {node.id} : {node.type}" + extra + if node.expr != None: + ans += f"\n{self.visit(node.expr, scope, tabs +1)}" + return f"{ans}" + + @visitor.when(VarDeclarationNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + ans = ( + "\t" * tabs + + f"\\__VarDeclarationNode: {node.id} : {node.type} = " + + extra + ) + if node.expr != None: + ans += f"\n{self.visit(node.expr, scope, tabs + 1)}" + return f"{ans}" + + @visitor.when(BlocksNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + ans = "\t" * tabs + "\\__BlocksNode: { ; ... ; }" + extra + body = "\n".join(self.visit(child, scope, tabs + 1) for child in node.expr_list) + return f"{ans}\n{body}" + + @visitor.when(ConditionalNode) + def visit(self, node, scope, tabs=0): + ifexpr = self.visit(node.condition, scope, tabs + 1) + thenexpr = self.visit(node.then_body, scope, tabs + 1) + elseexpr = self.visit(node.else_body, scope, tabs + 1) + extra = computed_info(node) + ans = ( + "\t" * tabs + + f"\\ConditionalNode: if then else {extra}\n" + ) + ifs = "\t" * (tabs + 1) + f"if:\n{ifexpr}\n" + ths = "\t" * (tabs + 1) + f"then:\n{thenexpr}\n" + els = "\t" * (tabs + 1) + f"else:\n{elseexpr}" + ans = ans + ifs + ths + els + return ans + + @visitor.when(CaseNode) + def visit(self, node, scope: Scope, tabs=0): + extra = computed_info(node) + header = ( + "\t" * tabs + f"\\CaseNode: case of ( => ...){extra}\n" + ) + caseexpr = self.visit(node.case_expr, scope, tabs + 1) + case = "\t" * (tabs + 1) + f"case:\n{caseexpr}\n" + # casevars = '\n'.join([self.visit(child, scope.next_child(), tabs + 1) for child in node.options]) + casevars = "\n".join( + [self.visit(child, scope.next_child(), tabs + 1) for child in node.options] + ) + of = "\t" * (tabs + 1) + f"of:\n{casevars}" + return header + case + of + + @visitor.when(CaseOptionNode) + def visit(self, node, scope: Scope, tabs=0): + type_info = computed_info(node) + return "\t" * (tabs) + "\\_" + node.id + ":" + node.type + type_info + + @visitor.when(LetNode) + def visit(self, node, scopex, tabs=0): + scope = scopex.next_child() + extra = computed_info(node) + header = ( + "\t" * tabs + f"\\LetNode: Let ( <- ...) in {extra}\n" + ) + name_list = [] + for name_var in node.var_decl_list: + name_list.append(name_var.id) + format_scope = defined_format(name_list, scope, tabs) + letvars = "\n".join( + self.visit(child, scope, tabs + 1) for child in node.var_decl_list + ) + expr = self.visit(node.in_expr, scope, tabs + 1) + let = "\t" * (tabs + 1) + f"let: \n{letvars}\n" + inx = "\t" * (tabs + 1) + f"in: \n{expr}" + return header + let + inx + + @visitor.when(LoopNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + header = "\t" * tabs + f"\\__LoopNode: while loop ( ){extra}\n" + body = self.visit(node.body, scope, tabs + 1) + whilex = self.visit(node.condition, scope, tabs + 1) + "\n" + text1 = "\t" * (tabs + 1) + f"while:\n {whilex}" + text2 = "\t" * (tabs + 1) + f"loop:\n {body}" + return header + text1 + text2 + + @visitor.when(AssignNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + ans = "\t" * tabs + f"\\__AssignNode: {node.id} = " + extra + expr = self.visit(node.expr, scope, tabs + 1) + return f"{ans}\n{expr}" + + @visitor.when(MethodDeclarationNode) + def visit(self, node: MethodDeclarationNode, scopex, tabs=0): + scope = scopex.next_child() + extra = computed_info(node) + params = ", ".join([f"{param.id}:{param.type}" for param in node.params]) + ans = ( + "\t" * tabs + + f"\\__MethodDeclarationNode: {node.id}({params}) : {node.type}" + + extra + ) + name_list = [] + for param in node.params: + name_list.append(param.id) + format_scope = defined_format(name_list, scope, tabs) + ans += format_scope + body = "\n" + self.visit(node.body, scope, tabs + 1) + return f"{ans}{body}" + + @visitor.when(IsVoidNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + ans1 = "\t" * tabs + f"\\__IsVoidNode: isvoid " + extra + ans2 = self.visit(node.expr, scope, tabs + 1) + return ans1 + "\n" + ans2 + + @visitor.when(ComplementNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + ans1 = "\t" * tabs + f"\\ComplementNode: ~ " + extra + ans2 = self.visit(node.expr, scope, tabs + 1) + return ans1 + "\n" + ans2 + + @visitor.when(NotNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + ans1 = "\t" * tabs + f"\\__NotNode: not " + extra + ans2 = self.visit(node.expr, scope, tabs + 1) + return ans1 + "\n" + ans2 + + # @visitor.when(IsVoidDeclarationNode) + # def visit(self, node, scope, tabs=0): + # extra = computed_info(node) + # ans1 = '\t' * tabs + f'\\__IsVoidNode: isvoid ' + extra + # ans2 = self.visit(node.lex, scope, tabs+1) + # return ans1 + "\n" + ans2 + + @visitor.when(BinaryNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + ans = "\t" * tabs + f"\\__ {node.__class__.__name__} " + extra + left = self.visit(node.left, scope, tabs + 1) + right = self.visit(node.right, scope, tabs + 1) + return f"{ans}\n{left}\n{right}" + + @visitor.when(AtomicNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + return "\t" * tabs + f"\\__ {node.__class__.__name__}: {node.value}" + extra + + @visitor.when(MethodCallNode) + def visit(self, node, scope, tabs=0): + extra = computed_info(node) + extra2 = "" + if node.caller_type: + extra2 = node.caller_type.name + "." + ans = ( + "\t" * tabs + + f"\\__MethodCallNode: {extra2}{node.id}(, ..., )" + + extra + ) + args = "\n".join(self.visit(arg, scope, tabs + 1) for arg in node.args) + if len(node.args) > 0: + return f"{ans}\n{args}" + return f"{ans}" + + @visitor.when(InstantiateNode) + def visit(self, node, scope, tabs=0): + return "\t" * tabs + f"\\__ InstantiateNode: new {node.value}()" + + +def defined_format(name_list: list, scope: Scope, tabs=0): + if len(name_list) == 0: + return "" + + header = "\n" + "\t" * tabs + f" Variables Defined:" + "\n" + "\t" * tabs + " | " + defined = str("\n" + "\t" * tabs + f" | ").join( + [defined_info(name, scope) for name in name_list] + ) + end = "\n" + "\t" * tabs + f" -----------------------------------" + return header + defined + end + + +def defined_info(name: str, scope: Scope, only_local=True): + if only_local and not scope.is_local(name): + return f'' + var = scope.find_variable(name) + if not only_local and not var: + return f'' + try: + return f"{var.name}:{var.type.name}" + except AttributeError: + return f'' + + +def computed_info(node): + return " -> " + node.inferenced_type.name + try: + if node.type != "AUTO_TYPE": + return "" + except AttributeError: + pass + try: + node.computed_type + try: + return f" -> {node.computed_type.name}" + except AttributeError: + return ( + f" -> " + ) + except AttributeError: + return " -> " diff --git a/src/compiler/visitors/code_gen/__init__.py b/src/compiler/visitors/code_gen/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/compiler/visitors/code_gen/ccil_gen.py b/src/compiler/visitors/code_gen/ccil_gen.py new file mode 100644 index 000000000..fc0b4ac5c --- /dev/null +++ b/src/compiler/visitors/code_gen/ccil_gen.py @@ -0,0 +1,1202 @@ +from visitors.utils import visitor +import asts.types_ast as sem_ast # Semantic generated ast +from asts.ccil_ast import * # CCIL generated ast +from typing import OrderedDict, Tuple, List, Dict +from visitors.code_gen.tools import * +from collections import OrderedDict + +from visitors.code_gen.constants import * + +# All operations that define an expression and where it is stored +VISITOR_RESULT = Tuple[List[OperationNode], StorageNode] +CLASS_VISITOR_RESULT = Tuple[Class, List[FunctionNode]] +METHOD_VISITOR_RESULT = FunctionNode +ATTR_VISITOR_RESULT = List[OperationNode] + +DEFAULT_STR = Data("default_str", "") +END_LINE = Data("line_end", r"\n") +ZERO = "zero" +EMPTY = "empty" + + +# CCIL stands for Cool Cows Intermediate Language ;) +class CCILGenerator: + """ + Using the visitor pattern it goes through the semantics ast and produce a ccil ast + """ + + def __init__(self) -> None: + self.program_types: Dict[str, Class] + self.program_codes: List[FunctionNode] + # To keep track of how many times a certain expression has been evaluated + self.time_record: Dict[str, int] = dict() + # Track all constant values. Only strings for now + self.data: List[Data] + # Notify about possible but senseless combination of expressions + self.warnings: List[str] = [] + + # To keep track of the current class being analysed + self.current_type: str + # Locals defined for each function + self.locals: Dict[str, str] + + # Link between cool names and their ccil name. + # It is used as scope to know which cool name it is + # referring when there are equals + self.ccil_cool_names: Scope + + @visitor.on("node") + def visit(self, node): + pass + + @visitor.when(sem_ast.ProgramNode) + def visit(self, node: sem_ast.ProgramNode) -> None: + self.data = [DEFAULT_STR, END_LINE] + self.reset_locals() + + [obj, io, str, int, bool], builtin_methods = self.define_built_ins() + self.program_types = OrderedDict( + {OBJECT: obj, IO: io, STRING: str, INT: int, BOOL: bool} + ) + self.program_codes: List[FunctionNode] = builtin_methods + + for builtin_name in [OBJECT, IO, STRING, INT, BOOL]: + self.add_data(f"{CLASS}{builtin_name}", builtin_name) + + for type in node.declarations: + classx, class_code = self.visit(type) + self.program_types[classx.id] = classx + self.program_codes += class_code + + program_types = update_self_type_attr(self.program_types.values()) + + return CCILProgram( + self.define_entry_func(), + program_types, + self.program_codes, + self.data, + ) + + @visitor.when(sem_ast.ClassDeclarationNode) + def visit(self, node: sem_ast.ClassDeclarationNode) -> CLASS_VISITOR_RESULT: + self.reset_scope() + self.current_type = node.id + self.add_data(f"{CLASS}{node.id}", node.id) + + attributes: List[Attribute] = self.get_inherited_attributes(node) + methods: List[Method] = [] + + attr_nodes = [] + func_nodes = [] + for feature in node.features: + if isinstance(feature, sem_ast.AttrDeclarationNode): + attributes.append( + Attribute(ATTR + feature.id, feature.type.name, feature.id) + ) + attr_nodes.append(feature) + else: + func_nodes.append(feature) + + # Create init func using attributes and their expressions + self.ccil_cool_names.add_new_names(*[(a.cool_id, a.id) for a in attributes]) + init_func = self.create_class_init_func(node, attr_nodes) + + self.reset_scope() + self.ccil_cool_names.add_new_names(*[(a.cool_id, a.id) for a in attributes]) + # Explore all functions + class_code: List[FunctionNode] = [] + for func in func_nodes: + f = self.visit(func) + class_code.append(f) + methods.append(Method(func.id, f)) + + methods, inherited_methods = self.get_inherited_methods(node, methods) + + return ( + Class( + node.id, + attributes, + inherited_methods + methods, + init_func, + ), + class_code, + ) + + @visitor.when(sem_ast.AttrDeclarationNode) + def visit(self, node: sem_ast.AttrDeclarationNode) -> ATTR_VISITOR_RESULT: + self.ccil_cool_names = self.ccil_cool_names.create_child() + + attr_id = ATTR + node.id + self.ccil_cool_names.add_new_name_pair(node.id, attr_id) + + if node.expr is None: + if node.type.name != STRING: + value_0 = IdNode(ZERO) + else: + value_0 = IdNode(EMPTY) + set_attr = SetAttrOpNode("self", attr_id, value_0, self.current_type) + return [set_attr] + + (expr_op, expr_fval) = self.visit(node.expr) + + set_attr = SetAttrOpNode("self", attr_id, expr_fval.id, self.current_type) + + self.ccil_cool_names = self.ccil_cool_names.get_parent + return [*expr_op, set_attr] + + @visitor.when(sem_ast.MethodDeclarationNode) + def visit(self, node: sem_ast.MethodDeclarationNode) -> METHOD_VISITOR_RESULT: + self.ccil_cool_names = self.ccil_cool_names.create_child() + + params: List[Parameter] = [Parameter("self", self.current_type)] + self.ccil_cool_names.add_new_name_pair("self", "self") + + for param in node.params: + new_param_id = PARAM + param.id + params.append(Parameter(new_param_id, param.type.name)) + self.ccil_cool_names.add_new_name_pair(param.id, new_param_id) + + self.reset_locals() + (operations, fval) = self.visit(node.body) + + self.ccil_cool_names = self.ccil_cool_names.get_parent + return FunctionNode( + f"f_{node.id}_{self.current_type}", + params, + self.dump_locals(), + operations, + fval.id, + ) + + @visitor.when(sem_ast.BlocksNode) + def visit(self, node: sem_ast.BlocksNode) -> VISITOR_RESULT: + times = self.times(node) + + operations: List[OperationNode] = [] + fvalues: List[StorageNode] = [] + for expr in node.expr_list: + (expr_ops, expr_fval) = self.visit(expr) + operations += expr_ops + fvalues.append(expr_fval) + + block_val = fvalues[-1] + fval_id = f"block_{times}" + + fval = self.create_assignation(fval_id, node.type.name, block_val.id) + operations.append(fval) + + return (operations, fval) + + @visitor.when(sem_ast.LetNode) + def visit(self, node: sem_ast.LetNode) -> VISITOR_RESULT: + self.ccil_cool_names = self.ccil_cool_names.create_child() + + operations: List[OperationNode] = self.init_default_values() + fvalues: List[StorageNode] = [] + + for var in node.var_decl_list: + (var_ops, var_fv) = self.visit(var) + operations += var_ops + fvalues.append(var_fv) + + (in_ops, in_fval) = self.visit(node.in_expr) + operations += in_ops + + self.ccil_cool_names = self.ccil_cool_names.get_parent + return (operations, in_fval) + + @visitor.when(sem_ast.VarDeclarationNode) + def visit(self, node: sem_ast.VarDeclarationNode) -> VISITOR_RESULT: + times = self.times(node, node.id) + + fvalue_id: str = f"{LET}{times}{node.id}" + self.ccil_cool_names.add_new_name_pair(node.id, fvalue_id) + + if node.expr is None: + if node.type.name != STRING: + value_0 = IdNode(ZERO) + else: + value_0 = IdNode(EMPTY) + fval = self.create_assignation(fvalue_id, node.type.name, value_0.value) + return [fval], fval + + (expr_ops, expr_fv) = self.visit(node.expr) + + self.update_locals(expr_fv.id, fvalue_id) + expr_fv.id = fvalue_id + + return (expr_ops, expr_fv) + + @visitor.when(sem_ast.AssignNode) + def visit(self, node: sem_ast.AssignNode) -> VISITOR_RESULT: + (expr_ops, expr_fval) = self.visit(node.expr) + + ccil_id, is_attr = self.ccil_cool_names.get_value_position(node.id) + + if is_attr: + # Assignation occurring to an attribute Go update the attribute + set_attr = SetAttrOpNode( + "self", ccil_id, extract_id(expr_fval), self.current_type + ) + attr_val = self.create_assignation( + f"local_attr_{node.id}_{self.times(node, node.id)}", + node.type.name, + expr_fval.id, + ) + return [*expr_ops, set_attr, attr_val], attr_val + + self.update_locals(expr_fval.id, ccil_id) + expr_fval.id = ccil_id + return (expr_ops, expr_fval) + + @visitor.when(sem_ast.ConditionalNode) + def visit(self, node: sem_ast.ConditionalNode) -> VISITOR_RESULT: + times = self.times(node) + + (if_ops, if_fval) = self.visit(node.condition) + (then_ops, then_fval) = self.visit(node.then_body) + (else_ops, else_fval) = self.visit(node.else_body) + + # translating condition to ccil + label_id = f"ifElse_{times}" + else_label = LabelNode(label_id) + if_false = IfFalseNode(extract_id(if_fval), else_label) + + endif_label = LabelNode(f"endIf_{times}") + goto_endif = GoToNode(endif_label) + + # Setting the final operation which will simbolize the return value of this expr + pre_fvalue_id = f"if_{times}_pre_fv" + pre_fvalue_then = self.create_assignation( + pre_fvalue_id, node.type.name, then_fval.id + ) + pre_fvalue_else = self.create_assignation( + pre_fvalue_id, node.type.name, else_fval.id, reuse=True + ) + + fvalue_id = f"if_{times}_fv" + fvalue = self.create_assignation(fvalue_id, node.type.name, pre_fvalue_id) + + return ( + [ + *if_ops, + if_false, + *then_ops, + pre_fvalue_then, + goto_endif, + else_label, + *else_ops, + pre_fvalue_else, + endif_label, + fvalue, + ], + fvalue, + ) + + @visitor.when(sem_ast.CaseNode) + def visit(self, node: sem_ast.CaseNode) -> VISITOR_RESULT: + times = self.times(node) + + # Visiting case expression + (case_expr_ops, case_expr_fv) = self.visit(node.case_expr) + + # Handling case expression is not void + void_expr_error_ops: List[OperationNode] = [] + if node.case_expr.type.name not in {STRING, INT, BOOL}: + case_expr_is_void = self.create_isvoid( + f"case_{times}_is_void", IdNode(case_expr_fv.id) + ) + is_not_void = LabelNode(f"case_{times}_expr_not_void") + case_expr_if_void = IfFalseNode(IdNode(case_expr_is_void.id), is_not_void) + runtime_error_ops = self.throw_runtime_error( + f"case_{times}_void_expr_error", + f"RuntimeError: Case expression in {node.line}, {node.col} is void", + ) + void_expr_error_ops = [ + case_expr_is_void, + case_expr_if_void, + *runtime_error_ops, + is_not_void, + ] + + # Storing the type of the resulting case expression + expr_type = self.create_type_name(f"case_{times}_expr_type", case_expr_fv.id) + + # Final label where all branch must jump to + final_label = LabelNode(f"case_{times}_end") + final_goto = GoToNode(final_label) + + # All branch must end in a var named like this + pre_fvalue_id = f"case_{times}_pre_fv" + # Holds strings for comparsion + type_name_holder = self.add_local(f"case_{times}_type_name_holder", STRING) + equality_holder = self.add_local(f"case_{times}_eq_holder", INT) + + pattern_match_ops = self.init_default_values() + + branch_ops = [] + visited_types = set() # To optimize and reduce redundant calling + for (i, option) in enumerate(node.options): + # Initializing the branch var + + # Label that means the start of this branch logic + branch_label = LabelNode(f"case_{times}_branch_{i}") + + # Compare expr type with node branch type and all of + # it's successors + branch_selection_ops = [] + if option.successors[0] != OBJECT: + for type_names in option.successors: + if type_names in visited_types: + continue + visited_types.add(type_names) + + load_class_name = StorageNode( + type_name_holder.id, LoadOpNode(f"{CLASS}{type_names}") + ) + select_branch = StorageNode( + equality_holder.id, + EqualStrNode( + extract_id(expr_type), extract_id(load_class_name) + ), + ) + # Conditional jump to the right branch label + if_op = IfNode(extract_id(select_branch), branch_label) + branch_selection_ops += [load_class_name, select_branch, if_op] + else: + branch_selection_ops = [GoToNode(branch_label)] + + # Storing logic to jump to branch logic if this branch is selected + pattern_match_ops += branch_selection_ops + + branch_var = self.create_assignation( + f"case_{times}_option_{i}", option.branch_type.name, case_expr_fv.id + ) + self.ccil_cool_names = self.ccil_cool_names.create_child() + self.ccil_cool_names.add_new_name_pair(option.id, branch_var.id) + + # Translating the branch logic + (expr_ops, expr_fval) = self.visit(option.expr) + # Renaming the last stored value of the expression + self.update_locals(expr_fval.id, pre_fvalue_id) + expr_fval.id = pre_fvalue_id + # Translating to ccil of branch logic + branch_ops += [branch_label, branch_var, *expr_ops, final_goto] + self.ccil_cool_names = self.ccil_cool_names.get_parent + + self.locals[pre_fvalue_id] = node.type.name + + # Error handling when there is not pattern match + pattern_match_error_ops = self.throw_runtime_error( + f"case_{times}_pattern_match_fail", + f"RuntimeError: Pattern match failure in {node.line}, {node.col}", + ) + + # Merging all expression operations in correct order + # and saving all to final value + fval_id = f"case_{times}_fv" + fval = self.create_assignation(fval_id, node.type.name, pre_fvalue_id) + operations = [ + *case_expr_ops, + *void_expr_error_ops, + expr_type, + *pattern_match_ops, + *pattern_match_error_ops, + *branch_ops, + final_label, + fval, + ] + return (operations, fval) + + @visitor.when(sem_ast.LoopNode) + def visit(self, node: sem_ast.LoopNode) -> VISITOR_RESULT: + times = self.times(node) + + (cond_ops, cond_fval) = self.visit(node.condition) + (body_ops, _) = self.visit(node.body) + + # Setting control flow labels + loop_label_id = f"loop_{times}" + loop_label = LabelNode(loop_label_id) + end_loop_label_id = f"endLoop_{times}" + end_loop_label = LabelNode(end_loop_label_id) + + # Setting control flow instructions ifFalse & GoTo + if_false = IfFalseNode(extract_id(cond_fval), end_loop_label) + go_to = GoToNode(loop_label) + + fval = self.create_uninitialized_storage(f"loop_{times}_fv", OBJECT) + # Loop Nodes have void return type, how to express it?? + return ( + [loop_label, *cond_ops, if_false, *body_ops, go_to, end_loop_label, fval], + fval, + ) + + @visitor.when(sem_ast.ArithmeticNode) + def visit(self, node: sem_ast.ArithmeticNode) -> VISITOR_RESULT: + times = self.times(node) + (left_ops, left_fval) = self.visit(node.left) + (right_ops, right_fval) = self.visit(node.right) + + left_id = extract_id(left_fval) + right_id = extract_id(right_fval) + + fval_id: str + op: ArithmeticOpNode + + # Arithmetic Binary Nodes + extra_ops: List[OperationNode] = [] + node_type = type(node) + if node_type == sem_ast.PlusNode: + op = SumOpNode(left_id, right_id) + fval_id = f"sum_{times}" + elif node_type == sem_ast.MinusNode: + op = MinusOpNode(left_id, right_id) + fval_id = f"minus_{times}" + elif node_type == sem_ast.StarNode: + op = MultOpNode(left_id, right_id) + fval_id = f"mult_{times}" + elif node_type == sem_ast.DivNode: + op = DivOpNode(left_id, right_id) + fval_id = f"div_{times}" + # Generating divison by zero runtime error + ok_label = LabelNode(f"ok_div_{times}") + right_id_is_zero = self.create_equality( + f"check_right_zero_{times}", right_id, IntNode("0") + ) + if_id_is_not_zero = IfFalseNode(extract_id(right_id_is_zero), ok_label) + error_msg = self.add_data( + f"error_msg_div_zero_{times}", + f"RuntimeError: Zero division detected on {node.line}, {node.col}.", + ) + error_var = self.create_string_load_data(f"error_var_{times}", error_msg.id) + extra_ops = [ + right_id_is_zero, + if_id_is_not_zero, + error_var, + *self.notifiy_and_abort(error_var.id), + ok_label, + ] + + else: + raise Exception( + f"Pattern match failure visiting arithmetic expression" + f" with {type(node).__name__}" + ) + + fval = self.create_storage(fval_id, node.type.name, op) + return ([*left_ops, *right_ops, *extra_ops, fval], fval) + + @visitor.when(sem_ast.ComparerNode) + def visit(self, node: sem_ast.ComparerNode) -> VISITOR_RESULT: + times = self.times(node) + + (left_ops, left_fval) = self.visit(node.left) + (right_ops, right_fval) = self.visit(node.right) + + left_id = extract_id(left_fval) + right_id = extract_id(right_fval) + + fval_id: str + op: BinaryOpNode + node_type = type(node) + # Boolean Binary Nodes + if node_type == sem_ast.EqualsNode: + if node.left.type.name == STRING: + fval_id = f"eq_str_{times}" + op = EqualStrNode(left_id, right_id) + elif node.left.type.name in {INT, BOOL}: + fval_id = ( + f"eq_{'int' if node.left.type.name == INT else 'bool'}_{times}" + ) + op = EqualIntNode(left_id, right_id) + else: + fval_id = f"eq_addr_{times}" + op = EqualAddrNode(left_id, right_id) + elif node_type == sem_ast.LessNode: + op = LessOpNode(left_id, right_id) + fval_id = f"le_{times}" + elif node_type == sem_ast.LessOrEqualNode: + op = LessOrEqualOpNode(left_id, right_id) + fval_id = f"leq_{times}" + else: + raise Exception( + f"Pattern match failure visiting binary expression with {type(node).__name__}" + ) + + fval = self.create_storage(fval_id, BOOL, op) + return ([*left_ops, *right_ops, fval], fval) + + @visitor.when(sem_ast.UnaryNode) + def visit(self, node: sem_ast.UnaryNode) -> VISITOR_RESULT: + times = self.times(node) + + (expr_op, expr_fval) = self.visit(node.expr) + expr_id = extract_id(expr_fval) + + fval_id: str + op: UnaryOpNode + + node_type = type(node) + if node_type == sem_ast.IsVoidNode: + fval_id = f"is_void_fv_{times}" + if node.expr.type.name in {BOOL, INT, STRING}: + self.add_warning( + f"Redundant isVoid expression in {node.line}, {node.col}." + f" Type {node.expr.type.name} will always evaluate to false" + ) + op = BoolNode("0") + else: + op = IsVoidOpNode(expr_id) + elif node_type == sem_ast.NotNode: + fval_id = f"not_{times}" + op = NotOpNode(expr_id) + elif node_type == sem_ast.ComplementNode: + fval_id = f"neg_{times}" + op = NegOpNode(expr_id) + else: + raise Exception("Pattern match failure while visiting unary expression") + + fval = self.create_storage(fval_id, node.type.name, op) + return [*expr_op, fval], fval + + @visitor.when(sem_ast.MethodCallNode) + def visit(self, node: sem_ast.MethodCallNode) -> VISITOR_RESULT: + call_id = f"call_{self.times(node, extra='call')}" + vcall_id = f"vcall_{self.times(node, extra='vcall')}" + times = self.times(node) + + # Translate all call arguments to ccil + # Name all fvalues as ARG + args_ops: List[OperationNode] = [] + args: List[IdNode] = [] + for arg_expr in node.args: + (arg_op, arg_fval) = self.visit(arg_expr) + args_ops += arg_op + args += [extract_id(arg_fval)] + + # id(arg1, arg2, ..., argn) + if node.expr is None: + call = self.create_vcall( + vcall_id, + node.type.name, + node.id, + node.caller_type.name, + [IdNode("self"), *args], + ) + return [*args_ops, call], call + + (expr_ops, expr_fval) = self.visit(node.expr) + + if node.caller_type.name == STRING: + call = self.create_call( + call_id, node.type.name, node.id, STRING, [extract_id(expr_fval), *args] + ) + return [*expr_ops, *args_ops, call], call + + # Runtime error depending if expr is void or not + error_ops = [] + expr_fval_is_void = self.create_isvoid( + f"expr_is_void_{times}", extract_id(expr_fval) + ) + ok_label = LabelNode(f"expr_is_not_void_{times}") + if_is_not_void = IfFalseNode(extract_id(expr_fval_is_void), ok_label) + error_msg = self.add_data( + f"caller_void_err_{times}", + f"RuntimeError: expresion in {node.line}, {node.col} is void", + ) + load_err = self.create_string_load_data( + f"caller_void_err_var_{times}", error_msg.id + ) + print_and_abort = self.notifiy_and_abort(load_err.id) + error_ops = [ + expr_fval_is_void, + if_is_not_void, + load_err, + *print_and_abort, + ok_label, + ] + + # @type.id(arg1, arg2, ..., argn) + if node.at_type is not None: + call = self.create_call( + call_id, + node.type.name, + make_unique_func_id(node.id, node.caller_type.name), + node.caller_type.name, + [extract_id(expr_fval), *args], + ) + return [*expr_ops, *error_ops, *args_ops, call], call + + # .id(arg1, arg2, ..., argn) + call = self.create_vcall( + vcall_id, + node.type.name, + node.id, + node.caller_type.name, + [extract_id(expr_fval), *args], + ) + + return [*expr_ops, *error_ops, *args_ops, call], call + + @visitor.when(sem_ast.InstantiateNode) + def visit(self, node: sem_ast.InstantiateNode) -> VISITOR_RESULT: + times = self.times(node) + + fvalue_id = f"new_type_{times}" + fvalue = self.create_new_type(fvalue_id, node.type.name) + + return [fvalue], fvalue + + @visitor.when(sem_ast.VariableNode) + def visit(self, node: sem_ast.VariableNode) -> VISITOR_RESULT: + times = self.times(node) + + id_id = f"id_{times}" + ccil_id, is_attr = self.ccil_cool_names.get_value_position(node.value) + + if is_attr: + get_attr = self.create_attr_extraction( + id_id, node.type.name, "self", ccil_id, self.current_type + ) + return [get_attr], get_attr + + fval = self.create_assignation(id_id, node.type.name, ccil_id) + return [fval], fval + + @visitor.when(sem_ast.StringNode) + def visit(self, node: sem_ast.StringNode) -> VISITOR_RESULT: + times = self.times(node) + + data_id = f"dataString_{times}" + self.data.append(Data(data_id, node.value)) + + load_id = f"load_str_{times}" + load_str = self.create_string_load_data(load_id, data_id) + return [load_str], load_str + + @visitor.when(sem_ast.IntNode) + def visit(self, node: sem_ast.IntNode) -> VISITOR_RESULT: + times = self.times(node) + + int_id = f"int_{times}" + int_node = self.create_int(int_id, node.value) + + return [int_node], int_node + + @visitor.when(sem_ast.BooleanNode) + def visit(self, node: sem_ast.BooleanNode) -> VISITOR_RESULT: + times = self.times(node) + + bool_id = f"bool_{times}" + value = "1" if node.value else "0" + + bool_node = self.create_bool(bool_id, value) + return [bool_node], bool_node + + def create_class_init_func( + self, + node: sem_ast.ClassDeclarationNode, + attr_nodes: List[sem_ast.AttrDeclarationNode], + ): + self.reset_locals() + + self.ccil_cool_names = self.ccil_cool_names.create_child() + self.ccil_cool_names.add_new_name_pair("self", "self") + init_params = self.init_func_params(node.id) + + # First operation, initalizing parent attributes + init_parent = self.create_call( + f"call_parent_{node.parent}", + INT, + f"init_{node.parent}", + node.parent, + [IdNode("self")], + ) + + # Execute all attributes operation and set them + init_attr_ops: List[OperationNode] = [init_parent, *self.init_default_values()] + for attr in attr_nodes: + attr_ops = self.visit(attr) + init_attr_ops += attr_ops + + dummy_return = self.create_storage(f"init_type_{node.id}_ret", INT, IntNode(0)) + init_attr_ops.append(dummy_return) + + self.ccil_cool_names = self.ccil_cool_names.get_parent + # return init function + return FunctionNode( + f"init_{node.id}", + init_params, + self.dump_locals(), + init_attr_ops, + dummy_return.id, + ) + + def define_built_ins(self): + # Defining Object class methods + self.reset_scope() + params = self.init_func_params(OBJECT) + abort_msg = self.add_data("abort_msg", "Abort called from class ") + part1 = self.create_string_load_data("abort_var1", abort_msg.id) + part2 = self.create_type_name("abort_var2", "self") + abort_local_msg1 = self.create_storage( + "abort_local_msg1", STRING, ConcatOpNode(part1.id, part2.id) + ) + line_end = self.create_string_load_data("line_end", END_LINE.id) + abort_local_msg2 = self.create_storage( + "abort_local_msg2", STRING, ConcatOpNode(abort_local_msg1.id, line_end.id) + ) + [print, abort] = self.notifiy_and_abort(abort_local_msg2.id) + abort_func = FunctionNode( + "abort", + params, + self.dump_locals(), + [part1, part2, line_end, abort_local_msg1, abort_local_msg2, print, abort], + "self", + ) + params = self.init_func_params(OBJECT) + get_name = self.create_type_name("get_name", "self") + type_name_func = FunctionNode( + "type_name", params, self.dump_locals(), [get_name], get_name.id + ) + params = self.init_func_params(OBJECT) + new_instance = self.create_new_type("shallow_copy", SELFTYPE) + update_instance = ShallowCopyOpNode(new_instance.id, "self") + copy_func = FunctionNode( + "copy", + params, + self.dump_locals(), + [new_instance, update_instance], + new_instance.id, + ) + object_class = Class( + OBJECT, + [], + [ + Method("abort", abort_func), + Method("type_name", type_name_func), + Method("copy", copy_func), + ], + self.define_builtin_init_func(OBJECT), + ) + + # Defining IO class methods + self.reset_scope() + params = self.init_func_params(IO) + str_input = Parameter("x", STRING) + params.append(str_input) + print = PrintStrNode(str_input.id) + out_string_func = FunctionNode( + "out_string", params, self.dump_locals(), [print], "self" + ) + params = self.init_func_params(IO) + int_input = Parameter("x", INT) + params.append(int_input) + print = PrintIntNode(int_input.id) + out_int_func = FunctionNode( + "out_int", params, self.dump_locals(), [print], "self" + ) + params = self.init_func_params(IO) + read = self.create_read_str("read_str") + in_string_func = FunctionNode( + "in_string", params, self.dump_locals(), [read], read.id + ) + params = self.init_func_params(IO) + read = self.create_read_int("read_int") + in_int_func = FunctionNode( + "in_int", params, self.dump_locals(), [read], read.id + ) + io_class = Class( + IO, + [], + [ + *object_class.methods, + Method("out_string", out_string_func), + Method("out_int", out_int_func), + Method("in_string", in_string_func), + Method("in_int", in_int_func), + ], + self.define_builtin_init_func(IO), + ) + + # Defining substring class methods + self.reset_scope() + params = self.init_func_params(STRING) + length = self.create_length("lenght_var", "self") + lenght_func = FunctionNode( + "length", params, self.dump_locals(), [length], length.id + ) + self.reset_locals() + params = self.init_func_params(STRING) + input_s = Parameter("s", STRING) + params.append(input_s) + concat = self.create_storage( + "concat_var", STRING, ConcatOpNode("self", input_s.id) + ) + concat_func = FunctionNode( + "concat", params, self.dump_locals(), [concat], concat.id + ) + self.reset_locals() + params = self.init_func_params(STRING) + start_index = Parameter("s", INT) + take = Parameter("l", INT) + params += [start_index, take] + length = self.create_length("length_var", "self") + max_take = self.create_storage( + "max_take", INT, SumOpNode(IdNode(start_index.id), IdNode(take.id)) + ) + upper_bound = self.create_storage( + "upper_bound", BOOL, LessOpNode(extract_id(length), extract_id(max_take)) + ) + lesser_bound = self.create_storage( + "lesser_bound", BOOL, LessOpNode(IdNode(start_index.id), IntNode("0")) + ) + error_label = LabelNode("substring_error") + ok_label = LabelNode("substring_success") + if_upper_bound = IfNode(extract_id(upper_bound), error_label) + if_lesser_bound = IfNode(extract_id(lesser_bound), error_label) + error_msg = self.add_data( + "substr_error", "RuntimeError: Index out of range exception" + ) + error_var = self.create_string_load_data("substr_error_var", error_msg.id) + print_and_abort = self.notifiy_and_abort(error_var.id) + substr = self.create_storage( + "substr_var", + STRING, + SubstringOpNode(IdNode(start_index.id), IdNode(take.id), IdNode("self")), + ) + goto_ok = GoToNode(ok_label) + operations = [ + length, + max_take, + upper_bound, + lesser_bound, + if_upper_bound, + if_lesser_bound, + substr, + goto_ok, + error_label, + error_var, + *print_and_abort, + ok_label, + ] + substr_func = FunctionNode( + "substr", params, self.dump_locals(), operations, substr.id + ) + attributes = [Attribute("value", STRING, "value")] + string_class = Class( + STRING, + attributes, + [ + *object_class.methods, + Method("length", lenght_func), + Method("concat", concat_func), + Method("substr", substr_func), + ], + self.define_builtin_init_func(STRING, attributes), + ) + + attributes = [Attribute("value", INT, "value")] + int_class = Class( + INT, + attributes, + object_class.methods, + self.define_builtin_init_func(INT, attributes), + ) + + attributes = [Attribute("value", BOOL, "value")] + bool_class = Class( + BOOL, + attributes, + object_class.methods, + self.define_builtin_init_func(BOOL, attributes), + ) + + return [object_class, io_class, string_class, int_class, bool_class], [ + abort_func, + type_name_func, + copy_func, + out_string_func, + out_int_func, + in_string_func, + in_int_func, + lenght_func, + concat_func, + substr_func, + ] + + def init_func_params(self, typex: str): + return [Parameter("self", typex)] + + def create_assignation(self, idx: str, type_idx: str, target: str, reuse=False): + self.add_local(idx, type_idx) if not reuse else self.soft_add_local( + idx, type_idx + ) + return StorageNode(idx, IdNode(target)) + + def create_uninitialized_storage(self, idx: str, type_idx: str): + self.add_local(idx, type_idx) + self.init_default_values() + return StorageNode(idx, ZERO if type_idx != STRING else EMPTY) + + def create_storage(self, idx: str, type_idx: str, op: ReturnOpNode): + self.add_local(idx, type_idx) + return StorageNode(idx, op) + + def create_attr_extraction( + self, idx: str, type_idx: str, from_idx: str, attr_idx: str, from_type_idx: str + ): + self.add_local(idx, type_idx) + return StorageNode(idx, GetAttrOpNode(from_type_idx, from_idx, attr_idx)) + + def create_new_type(self, idx: str, type_idx: str): + self.add_local(idx, type_idx) + return StorageNode(idx, NewOpNode(type_idx)) + + def create_call( + self, + storage_idx: str, + type_idx: str, + method_idx: str, + method_type_idx: str, + args: List[StorageNode], + ): + self.add_local(storage_idx, type_idx) + return StorageNode(storage_idx, CallOpNode(method_idx, method_type_idx, args)) + + def create_vcall( + self, + storage_idx: str, + type_idx: str, + method_idx: str, + method_type_idx: str, + args: List[StorageNode], + ): + self.add_local(storage_idx, type_idx) + return StorageNode(storage_idx, VCallOpNode(method_idx, method_type_idx, args)) + + def create_type_of(self, idx: str, target: AtomOpNode): + self.add_local(idx, ADDRESS) + return StorageNode(idx, GetTypeOpNode(target)) + + def create_equality( + self, idx, left: AtomOpNode, right: AtomOpNode, string: bool = False + ): + self.add_local(idx, BOOL) + op = EqualStrNode(left, right) if string else EqualIntNode(left, right) + return StorageNode(idx, op) + + def create_isvoid(self, idx: str, atom: IdNode): + self.add_local(idx, BOOL) + return StorageNode(idx, IsVoidOpNode(atom)) + + def notifiy_and_abort(self, target: str) -> List[OperationNode]: + print = PrintStrNode(target) + abort = Abort() + return [print, abort] + + def create_string_load_data(self, idx: str, target: str): + self.add_local(idx, STRING) + return StorageNode(idx, LoadOpNode(target)) + + def create_int(self, idx: str, value: str): + self.add_local(idx, INT) + return StorageNode(idx, IntNode(value)) + + def create_bool(self, idx: str, value: str): + self.add_local(idx, BOOL) + return StorageNode(idx, BoolNode(value)) + + def create_int_to_str(self, idx: str, target: str): + self.add_local(str, STRING) + return StorageNode(idx, StrOpNode(target)) + + def create_read_str(self, idx: str): + self.add_local(idx, STRING) + return StorageNode(idx, ReadStrNode()) + + def create_read_int(self, idx: str): + self.add_local(idx, INT) + return StorageNode(idx, ReadIntNode()) + + def create_type_name(self, idx: str, target: str): + self.add_local(idx, STRING) + return StorageNode(idx, TypeNameOpNode(target)) + + def create_length(self, idx: str, target: str): + self.add_local(idx, INT) + return StorageNode(idx, LengthOpNode(target)) + + def init_default_values(self): + if not ZERO in self.locals and not EMPTY in self.locals: + return [ + self.create_storage(ZERO, INT, IntNode("0")), + self.create_string_load_data(EMPTY, DEFAULT_STR.id), + ] + return [] + + def define_entry_func(self): + self.reset_locals() + program = self.create_new_type("program", "Main") + execute = self.create_call( + "execute_program", + INT, + make_unique_func_id("main", "Main"), + INT, + [IdNode(program.id)], + ) + return FunctionNode( + "main", [], self.dump_locals(), [program, execute], execute.id + ) + + def define_builtin_init_func( + self, class_name: str, attributes: List[Attribute] = [] + ): + self.reset_locals() + params = self.init_func_params(class_name) + dummy_return = self.create_storage( + f"init_type_{class_name}_ret", INT, IntNode(0) + ) + + set_default_ops: List[OperationNode] = self.init_default_values() + string_default = int_default = False + for attr in attributes: + if attr.type == STRING: + value = IdNode(EMPTY) + string_default = True + else: + value = IdNode(ZERO) + int_default = True + set_default_ops.append(SetAttrOpNode("self", attr.id, value, class_name)) + + if not string_default: + set_default_ops.pop(1) + del self.locals[EMPTY] + if not int_default: + set_default_ops.pop(0) + del self.locals[ZERO] + + return FunctionNode( + f"init_{class_name}", + params, + self.dump_locals(), + [*set_default_ops, dummy_return], + dummy_return.id, + ) + + def times(self, node: sem_ast.Node, extra: str = ""): + key: str = type(node).__name__ + extra + try: + self.time_record[key] += 1 + except KeyError: + self.time_record[key] = 0 + return self.time_record[key] + + def add_data(self, idx: str, value: str): + data = Data(idx, value) + self.data.append(data) + return data + + def update_locals(self, old_id: str, new_id: str): + self.locals[new_id] = self.locals[old_id] + del self.locals[old_id] + + def add_local(self, idx: str, typex: str): + if idx in self.locals: + raise KeyError(f"Trying to insert {idx} again as local") + self.locals[idx] = typex + return Local(idx, typex) + + def soft_add_local(self, idx: str, typex: str): + if not idx in self.locals: + self.locals[idx] = typex + return Local(idx, self.locals[idx]) + + def reset_locals(self): + """ + Apply at the beginning of every method to reset local vars + """ + self.locals = dict() + + def dump_locals(self): + var_locals = to_vars(self.locals, Local) + self.locals = dict() + return var_locals + + def reset_scope(self): + self.ccil_cool_names = Scope() + + def add_warning(self, msg: str): + self.warnings.append(f"Warning: {msg}") + + def get_inherited_attributes(self, node: sem_ast.ClassDeclarationNode): + return ( + [a for a in self.program_types[node.parent].attributes] + if node.parent is not None + else [] + ) + + def get_inherited_methods( + self, node: sem_ast.ClassDeclarationNode, defined_methods: List[Method] + ): + defined_methods: Dict[str, Method] = OrderedDict( + (m.id, m) for m in defined_methods + ) + + new_defined_methods: List[Method] = [] + inherited_methods: List[Method] = [] + + if node.parent is not None: + parent_class: Class = self.program_types[node.parent] + + for method in parent_class.methods: + try: + # Method override for an inherited method + override_method = defined_methods[method.id] + except KeyError: + pass + else: + method = Method(method.id, override_method.function) + del defined_methods[method.id] + + inherited_methods.append(method) + + new_defined_methods = list(defined_methods.values()) + return new_defined_methods, inherited_methods + + def throw_runtime_error(self, name: str, error_msg: str) -> List[OperationNode]: + data = self.add_data(name + "_msg", error_msg) + err_var = self.create_string_load_data(name + "_var", data.id) + abort_ops = self.notifiy_and_abort(err_var.id) + + return [err_var, *abort_ops] + + def find_function_id(self, class_name: str, method_name: str): + for method in self.program_types[class_name].methods: + if method.id == method_name: + return method.function.id + raise Exception(f"Method: {method_name} was not found in {class_name}") + + +def make_unique_func_id(method_name: str, class_name: str): + return f"f_{method_name}_{class_name}" + + +def to_vars(dict: Dict[str, str], const: BaseVar = BaseVar) -> List[BaseVar]: + return list(map(lambda x: const(*x), dict.items())) + + +def update_self_type_attr(classes: List[Class]): + new_classes: List[Class] = [] + for classx in classes: + classx.attributes = list( + map( + lambda attr: attr + if attr.type != SELFTYPE + else Attribute(attr.id, classx.id), + classx.attributes, + ) + ) + new_classes.append(classx) + return new_classes diff --git a/src/compiler/visitors/code_gen/ccil_mips_gen.py b/src/compiler/visitors/code_gen/ccil_mips_gen.py new file mode 100644 index 000000000..2974ce7fb --- /dev/null +++ b/src/compiler/visitors/code_gen/ccil_mips_gen.py @@ -0,0 +1,1800 @@ +from typing import Dict, List, Tuple + +from asts import ccil_ast, mips_ast +from visitors.utils import visitor + +from .constants import * + +WORD = 4 +DOUBLE_WORD = 8 +Location = Dict[Tuple[str, str], mips_ast.MemoryIndexNode] + + +class CCILToMIPSGenerator: + """ + This class transform CCIL AST to MIPS AST using visitor pattern + """ + + def __init__(self) -> None: + self.__id = 0 + self.__types_table: List[ccil_ast.Class] = [] + self.__location: Location = {} + self.__current_function: ccil_ast.FunctionNode + self.buffer_size = 1000 + + @visitor.on("node") + def visit(self, node): + pass + + @visitor.when(ccil_ast.CCILProgram) + def visit(self, node: ccil_ast.CCILProgram): + self.__types_table = node.types_section + + data = [] + types_table = [] + for classx in node.types_section: + word_directive = [ + mips_ast.Label(node, classx.id), + mips_ast.Label(node, classx.init_operations.id), + mips_ast.Label(node, f"class_{classx.id}"), + mips_ast.Label(node, self._get_attr_count(classx.id)), + ] + for method in classx.methods: + word_directive.append(mips_ast.Label(node, method.function.id)) + types_table.append( + ( + mips_ast.LabelDeclaration(node, classx.id), + mips_ast.WordDirective(node, word_directive), + ) + ) + data.extend(types_table) + for d in node.data_section: + data.append( + ( + mips_ast.LabelDeclaration(node, d.id), + mips_ast.AsciizDirective(node, [mips_ast.Label(node, d.value)]), + ) + ) + data.append( + ( + mips_ast.LabelDeclaration(node, "buffer"), + mips_ast.SpaceDirective( + node, [mips_ast.Constant(node, self.buffer_size)] + ), + ) + ) + + functions = [] + functions.extend(self.visit(node.entry_func)) + + for classx in node.types_section: + functions.extend(self.visit(classx.init_operations)) + for func in node.code_section: + functions.extend(self.visit(func)) + + return mips_ast.MIPSProgram( + None, + mips_ast.TextNode(node, functions), + mips_ast.DataNode(node, data), + ) + + @visitor.when(ccil_ast.FunctionNode) + def visit(self, node: ccil_ast.FunctionNode): + self.__current_function = node + instructions = [] + instructions.append(mips_ast.LabelDeclaration(node, node.id)) + + frame_size = len(node.locals) * DOUBLE_WORD + stack_pointer = mips_ast.RegisterNode(node, SP) + return_address = mips_ast.RegisterNode(node, RA) + frame_pointer = mips_ast.RegisterNode(node, FP) + + for index, local in enumerate(node.locals): + self._set_relative_location( + local.id, + mips_ast.MemoryIndexNode( + node, + mips_ast.Constant( + node, -1 * (len(node.locals) + 2 - index) * DOUBLE_WORD + ), + frame_pointer, + ), + ) + for index, param in enumerate(node.params): + self._set_relative_location( + param.id, + mips_ast.MemoryIndexNode( + node, + mips_ast.Constant( + node, ((len(node.params) - 1) - index) * DOUBLE_WORD + ), + frame_pointer, + ), + ) + + instructions.extend(self._push_stack(node, return_address)) + instructions.extend(self._push_stack(node, frame_pointer)) + instructions.append( + mips_ast.Addi( + node, frame_pointer, stack_pointer, mips_ast.Constant(node, 16) + ) + ) + instructions.append( + mips_ast.Addi( + node, + stack_pointer, + stack_pointer, + mips_ast.Constant(node, -1 * frame_size), + ) + ) + + for op in node.operations: + instructions.extend(self.visit(op)) + ret_location = self._get_relative_location(node.ret) + ret_register = mips_ast.RegisterNode(node, V0) + instructions.append(mips_ast.LoadWord(node, ret_register, ret_location)) + + instructions.append( + mips_ast.Addi( + node, stack_pointer, stack_pointer, mips_ast.Constant(node, frame_size) + ) + ) + instructions.extend(self._pop_stack(node, frame_pointer)) + instructions.extend(self._pop_stack(node, return_address)) + + if node.id == "main": + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 10) + ) + ) + instructions.append(mips_ast.Syscall(node)) + else: + instructions.append(mips_ast.JumpRegister(node, return_address)) + + return instructions + + @visitor.when(ccil_ast.StorageNode) + def visit(self, node: ccil_ast.StorageNode): + location_id = self._get_relative_location(node.id) + instructions = [] + instructions.extend(self.visit(node.operation)) + instructions.append( + mips_ast.StoreWord(node, mips_ast.RegisterNode(node, V0), location_id) + ) + return instructions + + @visitor.when(ccil_ast.IdNode) + def visit(self, node: ccil_ast.IdNode): + instructions = [] + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, V0), + self._get_relative_location(node.value), + ) + ) + return instructions + + @visitor.when(ccil_ast.BoolNode) + def visit(self, node: ccil_ast.BoolNode): + instructions = [] + + t7 = mips_ast.RegisterNode(node, T7) + instructions.append( + mips_ast.LoadImmediate(node, t7, mips_ast.Constant(node, node.value)) + ) + instructions.extend(self._set_new_bool(node)) + + return instructions + + @visitor.when(ccil_ast.IntNode) + def visit(self, node: ccil_ast.IntNode): + instructions = [] + + t7 = mips_ast.RegisterNode(node, T7) + instructions.append( + mips_ast.LoadImmediate(node, t7, mips_ast.Constant(node, node.value)) + ) + instructions.extend(self._set_new_int(node)) + return instructions + + @visitor.when(ccil_ast.CallOpNode) + def visit(self, node: ccil_ast.CallOpNode): + reg = mips_ast.RegisterNode(node, T0) + instructions = [] + for arg in node.args: + instructions.append( + mips_ast.LoadWord(node, reg, self._get_relative_location(arg.value)) + ) + instructions.extend(self._push_stack(node, reg)) + instructions.append(mips_ast.JumpAndLink(node, mips_ast.Label(node, node.id))) + + if len(node.args) > 0: + stack_pointer = mips_ast.RegisterNode(node, SP) + instructions.append( + mips_ast.Addi( + node, + stack_pointer, + stack_pointer, + mips_ast.Constant(node, len(node.args) * DOUBLE_WORD), + ) + ) + return instructions + + @visitor.when(ccil_ast.VCallOpNode) + def visit(self, node: ccil_ast.VCallOpNode): + instructions = [] + + obj_location = self._get_relative_location(node.args[0].value) + reg_obj = mips_ast.RegisterNode(node, T0) + instructions.append(mips_ast.LoadWord(node, reg_obj, obj_location)) + + obj_type = mips_ast.RegisterNode(node, T1) + instructions.append( + mips_ast.LoadWord( + node, + obj_type, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), reg_obj), + ) + ) + register_function = mips_ast.RegisterNode(node, T2) + function_index = self._get_method_index(node.type, node.id) + instructions.append( + mips_ast.LoadWord( + node, + register_function, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, function_index), obj_type + ), + ) + ) + reg_arg = mips_ast.RegisterNode(node, T3) + for arg in node.args: + instructions.append( + mips_ast.LoadWord(node, reg_arg, self._get_relative_location(arg.value)) + ) + instructions.extend(self._push_stack(node, reg_arg)) + instructions.append(mips_ast.JumpAndLink(node, register_function)) + + if len(node.args) > 0: + stack_pointer = mips_ast.RegisterNode(node, SP) + instructions.append( + mips_ast.Addi( + node, + stack_pointer, + stack_pointer, + mips_ast.Constant(node, len(node.args) * DOUBLE_WORD), + ) + ) + + return instructions + + @visitor.when(ccil_ast.NewOpNode) + def visit(self, node: ccil_ast.NewOpNode): + instructions = [] + + if node.type == "SELF_TYPE": + object_location = self._get_relative_location( + self.__current_function.params[0].id + ) + object_type = mips_ast.RegisterNode(node, T1) + attr_count = mips_ast.RegisterNode(node, T3) + object = mips_ast.RegisterNode(node, T4) + instructions.append(mips_ast.LoadWord(node, object, object_location)) + instructions.append( + mips_ast.LoadWord( + node, + object_type, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), object), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + attr_count, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 3 * WORD), object_type + ), + ) + ) + instructions.append( + mips_ast.Addi(node, attr_count, attr_count, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Multiply( + node, attr_count, attr_count, mips_ast.Constant(node, WORD) + ) + ) + instructions.append( + mips_ast.Move(node, mips_ast.RegisterNode(node, A0), attr_count) + ) + else: + size = self._get_attr_count(node.type) * WORD + WORD + instructions.append( + mips_ast.LoadImmediate( + node, + mips_ast.RegisterNode(node, A0), + mips_ast.Constant(node, size), + ) + ) + + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 9) + ) + ) + instructions.append(mips_ast.Syscall(node)) + + if node.type == "SELF_TYPE": + instructions.append( + mips_ast.Move( + node, + mips_ast.RegisterNode(node, T0), + mips_ast.RegisterNode(node, T1), + ) + ) + else: + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T0), + mips_ast.Label(node, node.type), + ) + ) + + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, T0), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), mips_ast.RegisterNode(node, V0) + ), + ) + ) + + # Initialize attibutes + init_function = mips_ast.RegisterNode(node, T2) + instructions.append( + mips_ast.LoadWord( + node, + init_function, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), mips_ast.RegisterNode(node, T0) + ), + ) + ) + + instructions.extend(self._push_stack(node, mips_ast.RegisterNode(node, V0))) + instructions.append(mips_ast.JumpAndLink(node, init_function)) + instructions.extend(self._pop_stack(node, mips_ast.RegisterNode(node, V0))) + + return instructions + + @visitor.when(ccil_ast.GetAttrOpNode) + def visit(self, node: ccil_ast.GetAttrOpNode): + instructions = [] + attr_offset = self._get_attr_index(node.instance_type, node.attr) + location_object = self._get_relative_location(node.instance) + + instructions.append( + mips_ast.LoadWord(node, mips_ast.RegisterNode(node, T0), location_object) + ) + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, V0), + mips_ast.MemoryIndexNode( + node, + mips_ast.Constant(node, attr_offset), + mips_ast.RegisterNode(node, T0), + ), + ) + ) + + return instructions + + @visitor.when(ccil_ast.GetTypeOpNode) + def visit(self, node: ccil_ast.GetTypeOpNode): + instructions = [] + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T0), + self._get_relative_location(node.atom.value), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, V0), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), mips_ast.RegisterNode(node, T0) + ), + ) + ) + return instructions + + @visitor.when(ccil_ast.SetAttrOpNode) + def visit(self, node: ccil_ast.SetAttrOpNode): + instructions = [] + + attr_offset = self._get_attr_index(node.instance_type, node.attr) + object_location = self._get_relative_location(node.instance) + + instructions.append( + mips_ast.LoadWord(node, mips_ast.RegisterNode(node, T0), object_location) + ) + + reg_new_value = mips_ast.RegisterNode(node, T1) + if isinstance(node.new_value, ccil_ast.IntNode): + instructions.append( + mips_ast.LoadImmediate( + node, reg_new_value, mips_ast.Constant(node, node.new_value.value) + ) + ) + elif isinstance(node.new_value, ccil_ast.IdNode): + instructions.append( + mips_ast.LoadWord( + node, + reg_new_value, + self._get_relative_location(node.new_value.value), + ) + ) + # NOTE: Fix: instances should not be of type str + elif isinstance(node.new_value, str): + instructions.append( + mips_ast.LoadWord( + node, + reg_new_value, + self._get_relative_location(node.new_value), + ) + ) + else: + raise Exception(f"Invalid type of ccil node: {type(node.new_value)}") + + instructions.append( + mips_ast.StoreWord( + node, + reg_new_value, + mips_ast.MemoryIndexNode( + node, + mips_ast.Constant(node, attr_offset), + mips_ast.RegisterNode(node, T0), + ), + ) + ) + return instructions + + @visitor.when(ccil_ast.SumOpNode) + def visit(self, node: ccil_ast.SumOpNode): + instructions = [] + instructions.extend(self._get_operands_value(node)) + + instructions.append( + mips_ast.Add( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, T6), + ) + ) + + instructions.extend(self._set_new_int(node)) + + return instructions + + @visitor.when(ccil_ast.MinusOpNode) + def visit(self, node: ccil_ast.MinusOpNode): + instructions = [] + + instructions.extend(self._get_operands_value(node)) + instructions.append( + mips_ast.Sub( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, T6), + ) + ) + instructions.extend(self._set_new_int(node)) + + return instructions + + @visitor.when(ccil_ast.MultOpNode) + def visit(self, node: ccil_ast.MultOpNode): + instructions = [] + + instructions.extend(self._get_operands_value(node)) + instructions.append( + mips_ast.Multiply( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, T6), + ) + ) + instructions.extend(self._set_new_int(node)) + + return instructions + + @visitor.when(ccil_ast.DivOpNode) + def visit(self, node: ccil_ast.DivOpNode): + instructions = [] + + instructions.extend(self._get_operands_value(node)) + instructions.append( + mips_ast.Div( + node, mips_ast.RegisterNode(node, T5), mips_ast.RegisterNode(node, T6) + ) + ) + instructions.append(mips_ast.MoveFromLo(node, mips_ast.RegisterNode(node, T7))) + instructions.extend(self._set_new_int(node)) + return instructions + + @visitor.when(ccil_ast.LessOpNode) + def visit(self, node: ccil_ast.LessOpNode): + instructions = [] + + instructions.extend(self._get_operands_value(node)) + instructions.append( + mips_ast.Less( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, T6), + ) + ) + instructions.extend(self._set_new_bool(node)) + + return instructions + + @visitor.when(ccil_ast.LessOrEqualOpNode) + def visit(self, node: ccil_ast.LessOrEqualOpNode): + instructions = [] + + instructions.extend(self._get_operands_value(node)) + instructions.append( + mips_ast.LessOrEqual( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, T6), + ) + ) + instructions.extend(self._set_new_bool(node)) + return instructions + + @visitor.when(ccil_ast.EqualIntNode) + def visit(self, node: ccil_ast.EqualIntNode): + instructions = [] + + instructions.extend(self._get_operands_value(node)) + instructions.append( + mips_ast.Equal( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, T6), + ) + ) + instructions.extend(self._set_new_bool(node)) + + return instructions + + @visitor.when(ccil_ast.EqualAddrNode) + def visit(self, node: ccil_ast.EqualAddrNode): + instructions = [] + + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T5), + self._get_relative_location(node.left.value), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T6), + self._get_relative_location(node.right.value), + ) + ) + instructions.append( + mips_ast.Equal( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, T6), + ) + ) + instructions.extend(self._set_new_bool(node)) + + return instructions + + @visitor.when(ccil_ast.IsVoidOpNode) + def visit(self, node: ccil_ast.IsVoidOpNode): + instructions = [] + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T5), + self._get_relative_location(node.atom.value), + ) + ) + + instructions.append( + mips_ast.Equal( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.RegisterNode(node, T5), + mips_ast.RegisterNode(node, ZERO), + ) + ) + instructions.extend(self._set_new_bool(node)) + + return instructions + + @visitor.when(ccil_ast.NegOpNode) + def visit(self, node: ccil_ast.NotOpNode): + instructions = [] + + value = mips_ast.RegisterNode(node, T7) + if isinstance(node.atom, ccil_ast.IntNode): + instructions.append( + mips_ast.LoadImmediate( + node, value, mips_ast.Constant(node, node.atom.value) + ) + ) + elif isinstance(node.atom, ccil_ast.IdNode): + object = mips_ast.RegisterNode(node, T0) + instructions.append( + mips_ast.LoadWord( + node, object, self._get_relative_location(node.atom.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + value, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), object + ), + ) + ) + instructions.append(mips_ast.Not(node, value, value)) + instructions.append( + mips_ast.Addi(node, value, value, mips_ast.Constant(node, 1)) + ) + instructions.extend(self._set_new_int(node)) + + return instructions + + @visitor.when(ccil_ast.NotOpNode) + def visit(self, node: ccil_ast.NotOpNode): + instructions = [] + + value = mips_ast.RegisterNode(node, T7) + if isinstance(node.atom, ccil_ast.IntNode): + instructions.append( + mips_ast.LoadImmediate( + node, value, mips_ast.Constant(node, node.atom.value) + ) + ) + elif isinstance(node.atom, ccil_ast.IdNode): + object = mips_ast.RegisterNode(node, T0) + instructions.append( + mips_ast.LoadWord( + node, object, self._get_relative_location(node.atom.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + value, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), object + ), + ) + ) + instructions.append( + mips_ast.Xori(node, value, value, mips_ast.Constant(node, "1")) + ) + instructions.extend(self._set_new_bool(node)) + + return instructions + + @visitor.when(ccil_ast.LoadOpNode) + def visit(self, node: ccil_ast.LoadOpNode): + instructions = [] + instructions.append( + mips_ast.LoadAddress( + node, mips_ast.RegisterNode(node, T7), mips_ast.Label(node, node.target) + ) + ) + instructions.extend(self._set_new_string(node)) + + return instructions + + @visitor.when(ccil_ast.IfFalseNode) + def visit(self, node: ccil_ast.IfFalseNode): + instructions = [] + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T0), + self._get_relative_location(node.eval_value.value), + ) + ) + + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T1), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), mips_ast.RegisterNode(node, T0) + ), + ) + ) + instructions.append( + mips_ast.BranchOnEqual( + node, + mips_ast.RegisterNode(node, T1), + mips_ast.RegisterNode(node, ZERO), + mips_ast.Label(node, node.target.id), + ) + ) + return instructions + + @visitor.when(ccil_ast.IfNode) + def visit(self, node: ccil_ast.IfFalseNode): + instructions = [] + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T0), + self._get_relative_location(node.eval_value.value), + ) + ) + + instructions.append( + mips_ast.LoadWord( + node, + mips_ast.RegisterNode(node, T1), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), mips_ast.RegisterNode(node, T0) + ), + ) + ) + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, T2), mips_ast.Constant(node, 1) + ) + ) + instructions.append( + mips_ast.BranchOnEqual( + node, + mips_ast.RegisterNode(node, T1), + mips_ast.RegisterNode(node, T2), + mips_ast.Label(node, node.target.id), + ) + ) + return instructions + + @visitor.when(ccil_ast.GoToNode) + def visit(self, node: ccil_ast.GoToNode): + instructions = [] + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, node.target.id))) + return instructions + + @visitor.when(ccil_ast.LabelNode) + def visit(self, node: ccil_ast.LabelNode): + instructions = [] + instructions.append(mips_ast.LabelDeclaration(node, node.id)) + return instructions + + @visitor.when(ccil_ast.PrintIntNode) + def visit(self, node: ccil_ast.PrintIntNode): + instructions = [] + a0 = mips_ast.RegisterNode(node, A0) + instructions.append( + mips_ast.LoadWord( + node, + a0, + self._get_relative_location(node.id), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + a0, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), a0), + ) + ) + + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 1) + ) + ) + instructions.append(mips_ast.Syscall(node)) + return instructions + + @visitor.when(ccil_ast.PrintStrNode) + def visit(self, node: ccil_ast.PrintStrNode): + instructions = [] + a0 = mips_ast.RegisterNode(node, A0) + instructions.append( + mips_ast.LoadWord( + node, + a0, + self._get_relative_location(node.id), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + a0, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), a0), + ) + ) + + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 4) + ) + ) + instructions.append(mips_ast.Syscall(node)) + return instructions + + @visitor.when(ccil_ast.ReadIntNode) + def visit(self, node: ccil_ast.ReadIntNode): + instructions = [] + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 5) + ) + ) + instructions.append(mips_ast.Syscall(node)) + instructions.append( + mips_ast.Move( + node, mips_ast.RegisterNode(node, T7), mips_ast.RegisterNode(node, V0) + ) + ) + instructions.extend(self._set_new_int(node)) + return instructions + + @visitor.when(ccil_ast.Abort) + def visit(self, node: ccil_ast.Abort): + instructions = [] + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 10) + ) + ) + instructions.append(mips_ast.Syscall(node)) + return instructions + + @visitor.when(ccil_ast.LengthOpNode) + def visit(self, node: ccil_ast.LengthOpNode): + instructions = [] + count = mips_ast.RegisterNode(node, T7) + instructions.append( + mips_ast.LoadImmediate(node, count, mips_ast.Constant(node, 0)) + ) + string = mips_ast.RegisterNode(node, T1) + instructions.append( + mips_ast.LoadWord(node, string, self._get_relative_location(node.target)) + ) + instructions.append( + mips_ast.LoadWord( + node, + string, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), string), + ) + ) + + loop = self._generate_unique_label() + instructions.append(mips_ast.LabelDeclaration(node, loop)) + + char = mips_ast.RegisterNode(node, T2) + instructions.append( + mips_ast.LoadByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), string), + ) + ) + zero = mips_ast.RegisterNode(node, ZERO) + exit = self._generate_unique_label() + instructions.append( + mips_ast.BranchOnEqual(node, char, zero, mips_ast.Label(node, exit)) + ) + instructions.append( + mips_ast.Addi(node, string, string, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Addi(node, count, count, mips_ast.Constant(node, 1)) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, loop))) + instructions.append(mips_ast.LabelDeclaration(node, exit)) + instructions.extend(self._set_new_int(node)) + return instructions + + @visitor.when(ccil_ast.EqualStrNode) + def visit(self, node: ccil_ast.EqualStrNode): + instructions = [] + left_string = mips_ast.RegisterNode(node, T0) + right_string = mips_ast.RegisterNode(node, T1) + + instructions.append( + mips_ast.LoadWord( + node, left_string, self._get_relative_location(node.left.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + left_string, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), left_string + ), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, right_string, self._get_relative_location(node.right.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + right_string, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), right_string + ), + ) + ) + + left_char = mips_ast.RegisterNode(node, T2) + right_char = mips_ast.RegisterNode(node, T3) + loop = self._generate_unique_label() + instructions.append(mips_ast.LabelDeclaration(node, loop)) + instructions.append( + mips_ast.LoadByte( + node, + left_char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), left_string), + ) + ) + instructions.append( + mips_ast.LoadByte( + node, + right_char, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), right_string + ), + ) + ) + + equals_end = self._generate_unique_label() + not_equals_end = self._generate_unique_label() + + zero = mips_ast.RegisterNode(node, ZERO) + instructions.append( + mips_ast.BranchOnNotEqual( + node, left_char, right_char, mips_ast.Label(node, not_equals_end) + ) + ) + instructions.append( + mips_ast.BranchOnEqual( + node, left_char, zero, mips_ast.Label(node, equals_end) + ) + ) + + instructions.append( + mips_ast.Addi(node, left_string, left_string, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Addi(node, right_string, right_string, mips_ast.Constant(node, 1)) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, loop))) + + end = self._generate_unique_label() + result = mips_ast.RegisterNode(node, T7) + instructions.append(mips_ast.LabelDeclaration(node, equals_end)) + instructions.append( + mips_ast.LoadImmediate(node, result, mips_ast.Constant(node, 1)) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, end))) + instructions.append(mips_ast.LabelDeclaration(node, not_equals_end)) + instructions.append( + mips_ast.LoadImmediate(node, result, mips_ast.Constant(node, 0)) + ) + instructions.append(mips_ast.LabelDeclaration(node, end)) + instructions.extend(self._set_new_bool(node)) + return instructions + + @visitor.when(ccil_ast.ConcatOpNode) + def visit(self, node: ccil_ast.ConcatOpNode): + instructions = [] + string_a = mips_ast.RegisterNode(node, T0) + string_b = mips_ast.RegisterNode(node, T1) + + len_a = mips_ast.RegisterNode(node, T4) + len_b = mips_ast.RegisterNode(node, T3) + + instructions.append( + mips_ast.LoadWord(node, string_a, self._get_relative_location(node.source)) + ) + instructions.extend(self._push_stack(node, string_a)) + instructions.append(mips_ast.JumpAndLink(node, mips_ast.Label(node, "length"))) + instructions.extend(self._pop_stack(node, string_a)) + instructions.append(mips_ast.Move(node, len_a, mips_ast.RegisterNode(node, V0))) + + instructions.append( + mips_ast.LoadWord(node, string_b, self._get_relative_location(node.target)) + ) + instructions.extend(self._push_stack(node, string_b)) + instructions.append(mips_ast.JumpAndLink(node, mips_ast.Label(node, "length"))) + instructions.extend(self._pop_stack(node, string_b)) + + instructions.append(mips_ast.Move(node, len_b, mips_ast.RegisterNode(node, V0))) + + instructions.append( + mips_ast.LoadWord( + node, + len_b, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), len_b), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + len_a, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), len_a), + ) + ) + + instructions.append( + mips_ast.Add(node, mips_ast.RegisterNode(node, A0), len_a, len_b) + ) + instructions.append( + mips_ast.Addi( + node, + mips_ast.RegisterNode(node, A0), + mips_ast.RegisterNode(node, A0), + mips_ast.Constant(node, 1), + ) + ) + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 9) + ) + ) + instructions.append(mips_ast.Syscall(node)) + + concat_string = mips_ast.RegisterNode(node, V0) + concat_char = mips_ast.RegisterNode(node, T6) + char = mips_ast.RegisterNode(node, T5) + instructions.append(mips_ast.Move(node, concat_char, concat_string)) + + instructions.append( + mips_ast.LoadWord( + node, + string_b, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), string_b), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + string_a, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), string_a), + ) + ) + + loop_string_a = self._generate_unique_label() + end_loop_string_a = self._generate_unique_label() + instructions.append(mips_ast.LabelDeclaration(node, loop_string_a)) + instructions.append( + mips_ast.LoadByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), string_a), + ) + ) + instructions.append( + mips_ast.BranchOnEqual( + node, + char, + mips_ast.RegisterNode(node, ZERO), + mips_ast.Label(node, end_loop_string_a), + ) + ) + instructions.append( + mips_ast.StoreByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), concat_char), + ) + ) + instructions.append( + mips_ast.Addi(node, string_a, string_a, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Addi(node, concat_char, concat_char, mips_ast.Constant(node, 1)) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, loop_string_a))) + instructions.append(mips_ast.LabelDeclaration(node, end_loop_string_a)) + + loop_string_b = self._generate_unique_label() + end_loop_string_b = self._generate_unique_label() + instructions.append(mips_ast.LabelDeclaration(node, loop_string_b)) + instructions.append( + mips_ast.LoadByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), string_b), + ) + ) + instructions.append( + mips_ast.BranchOnEqual( + node, + char, + mips_ast.RegisterNode(node, ZERO), + mips_ast.Label(node, end_loop_string_b), + ) + ) + instructions.append( + mips_ast.StoreByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), concat_char), + ) + ) + instructions.append( + mips_ast.Addi(node, string_b, string_b, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Addi(node, concat_char, concat_char, mips_ast.Constant(node, 1)) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, loop_string_b))) + instructions.append(mips_ast.LabelDeclaration(node, end_loop_string_b)) + + instructions.append( + mips_ast.StoreByte( + node, + mips_ast.RegisterNode(node, ZERO), + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), concat_char), + ) + ) + instructions.append( + mips_ast.Move( + node, mips_ast.RegisterNode(node, T7), mips_ast.RegisterNode(node, V0) + ) + ) + instructions.extend(self._set_new_string(node)) + + return instructions + + @visitor.when(ccil_ast.SubstringOpNode) + def visit(self, node: ccil_ast.SubstringOpNode): + instructions = [] + substring = mips_ast.RegisterNode(node, V0) + substring_length = mips_ast.RegisterNode(node, A0) + instructions.append( + mips_ast.LoadWord( + node, substring_length, self._get_relative_location(node.length.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + substring_length, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), substring_length + ), + ) + ) + instructions.append( + mips_ast.Addi( + node, substring_length, substring_length, mips_ast.Constant(node, 1) + ) + ) + instructions.append( + mips_ast.LoadImmediate(node, substring, mips_ast.Constant(node, 9)) + ) + instructions.append(mips_ast.Syscall(node)) + + char_string = mips_ast.RegisterNode(node, T0) + char_substring = mips_ast.RegisterNode(node, T1) + char = mips_ast.RegisterNode(node, T2) + start = mips_ast.RegisterNode(node, T3) + + loop = self._generate_unique_label() + end = self._generate_unique_label() + + instructions.append(mips_ast.Move(node, char_substring, substring)) + + instructions.append( + mips_ast.LoadWord( + node, char_string, self._get_relative_location(node.target.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + char_string, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), char_string + ), + ) + ) + + instructions.append( + mips_ast.LoadWord( + node, start, self._get_relative_location(node.start.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + start, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), start), + ) + ) + instructions.append(mips_ast.Add(node, char_string, char_string, start)) + instructions.append( + mips_ast.Addi( + node, substring_length, substring_length, mips_ast.Constant(node, -1) + ) + ) + instructions.append(mips_ast.LabelDeclaration(node, loop)) + + zero = mips_ast.RegisterNode(node, ZERO) + instructions.append( + mips_ast.BranchOnEqual( + node, substring_length, zero, mips_ast.Label(node, end) + ) + ) + instructions.append( + mips_ast.LoadByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), char_string), + ) + ) + instructions.append( + mips_ast.StoreByte( + node, + char, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), char_substring + ), + ) + ) + instructions.append( + mips_ast.Addi(node, char_string, char_string, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Addi( + node, char_substring, char_substring, mips_ast.Constant(node, 1) + ) + ) + instructions.append( + mips_ast.Addi( + node, substring_length, substring_length, mips_ast.Constant(node, -1) + ) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, loop))) + instructions.append(mips_ast.LabelDeclaration(node, end)) + instructions.append( + mips_ast.StoreByte( + node, + zero, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), char_substring + ), + ) + ) + instructions.append( + mips_ast.Move( + node, mips_ast.RegisterNode(node, T7), mips_ast.RegisterNode(node, V0) + ) + ) + instructions.extend(self._set_new_string(node)) + return instructions + + @visitor.when(ccil_ast.TypeNameOpNode) + def visit(self, node: ccil_ast.TypeNameOpNode): + instructions = [] + result = mips_ast.RegisterNode(node, T7) + + object = mips_ast.RegisterNode(node, T0) + instructions.append( + mips_ast.LoadWord(node, object, self._get_relative_location(node.target)) + ) + object_type = mips_ast.RegisterNode(node, T1) + + instructions.append( + mips_ast.LoadWord( + node, + object_type, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), object), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + result, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, DOUBLE_WORD), object_type + ), + ) + ) + instructions.extend(self._set_new_string(node)) + return instructions + + @visitor.when(ccil_ast.ShallowCopyOpNode) + def visit(self, node: ccil_ast.ShallowCopyOpNode): + instructions = [] + object = mips_ast.RegisterNode(node, T0) + object_type = mips_ast.RegisterNode(node, T1) + attr_total = mips_ast.RegisterNode(node, T2) + + instructions.append( + mips_ast.LoadWord(node, object, self._get_relative_location(node.source)) + ) + instructions.append( + mips_ast.LoadWord( + node, + object_type, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), object), + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + attr_total, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 3 * WORD), object_type + ), + ) + ) + instructions.append( + mips_ast.Addi(node, attr_total, attr_total, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Multiply( + node, attr_total, attr_total, mips_ast.Constant(node, WORD) + ) + ) + instructions.append( + mips_ast.Move(node, mips_ast.RegisterNode(node, A0), attr_total) + ) + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 9) + ) + ) + instructions.append(mips_ast.Syscall(node)) + + object_copy = mips_ast.RegisterNode(node, T3) + section = mips_ast.RegisterNode(node, T4) + zero = mips_ast.RegisterNode(node, ZERO) + loop = self._generate_unique_label() + end = self._generate_unique_label() + + instructions.append( + mips_ast.Move(node, object_copy, mips_ast.RegisterNode(node, V0)) + ) + + instructions.append(mips_ast.LabelDeclaration(node, loop)) + instructions.append( + mips_ast.BranchOnEqual(node, attr_total, zero, mips_ast.Label(node, end)) + ) + instructions.append( + mips_ast.LoadWord( + node, + section, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), object), + ) + ) + instructions.append( + mips_ast.StoreWord( + node, + section, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), object_copy), + ) + ) + instructions.append( + mips_ast.Addi(node, object, object, mips_ast.Constant(node, WORD)) + ) + instructions.append( + mips_ast.Addi(node, object_copy, object_copy, mips_ast.Constant(node, WORD)) + ) + instructions.append( + mips_ast.Addi( + node, attr_total, attr_total, mips_ast.Constant(node, -1 * WORD) + ) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, loop))) + + instructions.append(mips_ast.LabelDeclaration(node, end)) + + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, V0), + self._get_relative_location(node.dest), + ) + ) + return instructions + + @visitor.when(ccil_ast.ReadStrNode) + def visit(self, node: ccil_ast.ReadStrNode): + instructions = [] + + a0 = mips_ast.RegisterNode(node, A0) + a1 = mips_ast.RegisterNode(node, A1) + v0 = mips_ast.RegisterNode(node, V0) + t7 = mips_ast.RegisterNode(node, T7) + instructions.append( + mips_ast.LoadAddress(node, a0, mips_ast.Label(node, "buffer")) + ) + instructions.append( + mips_ast.LoadImmediate(node, a1, mips_ast.Constant(node, self.buffer_size)) + ) + instructions.append( + mips_ast.LoadImmediate(node, v0, mips_ast.Constant(node, 8)) + ) + instructions.append(mips_ast.Syscall(node)) + instructions.append( + mips_ast.LoadAddress(node, t7, mips_ast.Label(node, "buffer")) + ) + instructions.extend(self._set_new_string(node)) + + instructions.extend(self._push_stack(node, v0)) + instructions.append(mips_ast.JumpAndLink(node, mips_ast.Label(node, "length"))) + instructions.extend(self._pop_stack(node, a0)) + + instructions.append( + mips_ast.LoadWord( + node, + a0, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, WORD), v0), + ) + ) + instructions.append(mips_ast.Addi(node, a0, a0, mips_ast.Constant(node, 1))) + + loop = self._generate_unique_label() + end = self._generate_unique_label() + zero = mips_ast.RegisterNode(node, ZERO) + char = mips_ast.RegisterNode(node, T0) + string = mips_ast.RegisterNode(node, T1) + buffer_string = mips_ast.RegisterNode(node, T2) + buffer_char = mips_ast.RegisterNode(node, T3) + string_char = mips_ast.RegisterNode(node, T4) + + instructions.append( + mips_ast.LoadImmediate(node, v0, mips_ast.Constant(node, 9)) + ) + instructions.append(mips_ast.Syscall(node)) + + instructions.append(mips_ast.Move(node, string, v0)) + instructions.append( + mips_ast.LoadAddress(node, buffer_string, mips_ast.Label(node, "buffer")) + ) + instructions.append(mips_ast.Move(node, buffer_char, buffer_string)) + instructions.append(mips_ast.Move(node, string_char, string)) + + instructions.append(mips_ast.LabelDeclaration(node, loop)) + instructions.append( + mips_ast.LoadByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), buffer_char), + ) + ) + instructions.append( + mips_ast.StoreByte( + node, + char, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), string_char), + ) + ) + new_line = mips_ast.RegisterNode(node, T9) + instructions.append( + mips_ast.LoadImmediate(node, new_line, mips_ast.Constant(node, 10)) + ) + instructions.append( + mips_ast.BranchOnEqual(node, char, new_line, mips_ast.Label(node, end)) + ) + instructions.append( + mips_ast.BranchOnEqual(node, char, zero, mips_ast.Label(node, end)) + ) + + instructions.append( + mips_ast.Addi(node, buffer_char, buffer_char, mips_ast.Constant(node, 1)) + ) + instructions.append( + mips_ast.Addi(node, string_char, string_char, mips_ast.Constant(node, 1)) + ) + instructions.append(mips_ast.Jump(node, mips_ast.Label(node, loop))) + instructions.append(mips_ast.LabelDeclaration(node, end)) + + instructions.append( + mips_ast.StoreByte( + node, + zero, + mips_ast.MemoryIndexNode(node, mips_ast.Constant(node, 0), string_char), + ) + ) + + instructions.append(mips_ast.Move(node, t7, string)) + instructions.extend(self._set_new_string(node)) + + return instructions + + def _get_attr_index(self, typex: str, attr: str): + for _type in self.__types_table: + if _type.id == typex: + for index, _attr in enumerate(_type.attributes): + if _attr.id == attr: + return index * WORD + WORD + raise Exception(f"Attribute {attr} not found in type {typex}") + + def _get_attr_count(self, typex: str): + for _type in self.__types_table: + if _type.id == typex: + return len(_type.attributes) + raise Exception(f"Type declaration not found: {typex}") + + def _get_init_function(self, typex: str): + for _type in self.__types_table: + if _type.id == typex: + return _type.init_operations + raise Exception(f"Type's function for inicialization not found: {typex}") + + def _get_method_index(self, typex: str, method: str) -> int: + for _type in self.__types_table: + if _type.id == typex: + for index, _method in enumerate(_type.methods): + if _method.id == method: + return index * WORD + WORD + WORD + DOUBLE_WORD + + raise Exception(f"Method implementation not found:{typex} {method}") + + def _get_class_method(self, typex: str, method: str) -> str: + for _type in self.__types_table: + if _type.id == typex: + for _method in _type.methods: + if _method.id == method: + return _method.function.id + raise Exception(f"Method implementation not found") + + def _get_relative_location(self, id: str): + return self.__location[self.__current_function.id, id] + + def _set_relative_location(self, id: str, memory: mips_ast.MemoryIndexNode): + self.__location[self.__current_function.id, id] = memory + + def _generate_unique_label(self): + self.__id += 1 + return f"label_{self.__id}" + + def _push_stack(self, node, register: mips_ast.RegisterNode): + stack_pointer = mips_ast.RegisterNode(node, SP) + instructions = [] + instructions.append( + mips_ast.Addi( + node, + stack_pointer, + stack_pointer, + mips_ast.Constant(node, -1 * DOUBLE_WORD), + ) + ) + instructions.append( + mips_ast.StoreWord( + node, + register, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), stack_pointer + ), + ) + ) + return instructions + + def _pop_stack(self, node, register: mips_ast.RegisterNode): + stack_pointer = mips_ast.RegisterNode(node, SP) + instructions = [] + instructions.append( + mips_ast.LoadWord( + node, + register, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), stack_pointer + ), + ) + ) + instructions.append( + mips_ast.Addi( + node, stack_pointer, stack_pointer, mips_ast.Constant(node, DOUBLE_WORD) + ) + ) + return instructions + + def _get_operands_value(self, node): + + instructions = [] + + left_value = mips_ast.RegisterNode(node, T5) + reg_left = mips_ast.RegisterNode(node, T3) + if isinstance(node.left, ccil_ast.IdNode): + instructions.append( + mips_ast.LoadWord( + node, reg_left, self._get_relative_location(node.left.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + left_value, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), reg_left + ), + ) + ) + elif isinstance(node.left, ccil_ast.ConstantNode): + instructions.append( + mips_ast.LoadImmediate( + node, left_value, mips_ast.Constant(node, node.left.value) + ) + ) + + right_value = mips_ast.RegisterNode(node, T6) + reg_right = mips_ast.RegisterNode(node, T4) + if isinstance(node.right, ccil_ast.IdNode): + instructions.append( + mips_ast.LoadWord( + node, reg_right, self._get_relative_location(node.right.value) + ) + ) + instructions.append( + mips_ast.LoadWord( + node, + right_value, + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), reg_right + ), + ) + ) + elif isinstance(node.right, ccil_ast.ConstantNode): + instructions.append( + mips_ast.LoadImmediate( + node, right_value, mips_ast.Constant(node, node.right.value) + ) + ) + else: + raise Exception("Invalid type of ccil node") + return instructions + + def _set_new_int(self, node): + instructions = [] + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, A0), mips_ast.Constant(node, 2 * WORD) + ) + ) + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 9) + ) + ) + instructions.append(mips_ast.Syscall(node)) + instructions.append( + mips_ast.LoadAddress( + node, mips_ast.RegisterNode(node, T8), mips_ast.Label(node, "Int") + ) + ) + + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, T8), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), mips_ast.RegisterNode(node, V0) + ), + ) + ) + + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), mips_ast.RegisterNode(node, V0) + ), + ) + ) + return instructions + + def _set_new_bool(self, node): + instructions = [] + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, A0), mips_ast.Constant(node, 2 * WORD) + ) + ) + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 9) + ) + ) + instructions.append(mips_ast.Syscall(node)) + instructions.append( + mips_ast.LoadAddress( + node, mips_ast.RegisterNode(node, T8), mips_ast.Label(node, "Bool") + ) + ) + + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, T8), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), mips_ast.RegisterNode(node, V0) + ), + ) + ) + + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), mips_ast.RegisterNode(node, V0) + ), + ) + ) + return instructions + + def _set_new_string(self, node): + instructions = [] + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, A0), mips_ast.Constant(node, 2 * WORD) + ) + ) + instructions.append( + mips_ast.LoadImmediate( + node, mips_ast.RegisterNode(node, V0), mips_ast.Constant(node, 9) + ) + ) + instructions.append(mips_ast.Syscall(node)) + instructions.append( + mips_ast.LoadAddress( + node, mips_ast.RegisterNode(node, T8), mips_ast.Label(node, "String") + ) + ) + + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, T8), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, 0), mips_ast.RegisterNode(node, V0) + ), + ) + ) + instructions.append( + mips_ast.StoreWord( + node, + mips_ast.RegisterNode(node, T7), + mips_ast.MemoryIndexNode( + node, mips_ast.Constant(node, WORD), mips_ast.RegisterNode(node, V0) + ), + ) + ) + return instructions diff --git a/src/compiler/visitors/code_gen/constants.py b/src/compiler/visitors/code_gen/constants.py new file mode 100644 index 000000000..c9ac87b96 --- /dev/null +++ b/src/compiler/visitors/code_gen/constants.py @@ -0,0 +1,48 @@ +PARAM = "param_" +LET = "let_" +ATTR = "attr_" +CLASS = "class_" +CASE = "case_" + +# Registers +ZERO = 0 +AT = 1 +V0 = 2 +V1 = 3 +A0 = 4 +A1 = 5 +A2 = 6 +A3 = 7 +T0 = 8 +T1 = 9 +T2 = 10 +T3 = 11 +T4 = 12 +T5 = 13 +T6 = 14 +T7 = 15 +S0 = 16 +S1 = 17 +S2 = 18 +S3 = 19 +S4 = 20 +S5 = 21 +S6 = 22 +T7 = 23 +T8 = 24 +T9 = 25 +K0 = 26 +K1 = 27 +GP = 28 +SP = 29 +FP = 30 +RA = 31 + +OBJECT = "Object" +BOOL = "Bool" +INT = "Int" +STRING = "String" +VOID = "Void" +SELFTYPE = "SELF_TYPE" +IO = "IO" +ADDRESS = INT diff --git a/src/compiler/visitors/code_gen/mips_gen.py b/src/compiler/visitors/code_gen/mips_gen.py new file mode 100644 index 000000000..d9cb38d6d --- /dev/null +++ b/src/compiler/visitors/code_gen/mips_gen.py @@ -0,0 +1,207 @@ +from asts.mips_ast import ( + Add, + Addi, + Addu, + AsciizDirective, + BranchOnEqual, + BranchOnNotEqual, + Constant, + Div, + Equal, + Jump, + JumpAndLink, + JumpRegister, + Label, + LabelDeclaration, + Less, + LessOrEqual, + LoadAddress, + LoadByte, + LoadImmediate, + LoadWord, + MIPSProgram, + MemoryIndexNode, + Move, + MoveFromLo, + Multiply, + Not, + RegisterNode, + SpaceDirective, + StoreByte, + StoreWord, + Sub, + Subu, + Syscall, + TextNode, + DataNode, + WordDirective, + Xori, +) +from visitors.utils import visitor + + +class MIPSGenerator: + """ + This class uses the visitor pattern to convert MIPS AST to a .mips program + """ + + def __init__(self) -> None: + pass + + @visitor.on("node") + def visit(self, node): + pass + + @visitor.when(MIPSProgram) + def visit(self, node: MIPSProgram) -> str: + global_main = "\t.globl main" + text_section = "\t.text\n" + self.visit(node.text_section) + data_section = "\t.data\n" + self.visit(node.data_section) + return f"{global_main}\n{text_section}\n{data_section}" + + @visitor.when(TextNode) + def visit(self, node: TextNode) -> str: + return "\n".join(self.visit(instruction) for instruction in node.instructions) + + @visitor.when(DataNode) + def visit(self, node: DataNode) -> str: + data_section = "" + for label_decl, directive in node.data: + data_section += f"{self.visit(label_decl)} {self.visit(directive)}\n" + return data_section + + @visitor.when(RegisterNode) + def visit(self, node: RegisterNode) -> str: + return f"${node.number}" + + @visitor.when(Constant) + def visit(self, node: Constant) -> str: + return str(node.value) + + @visitor.when(LabelDeclaration) + def visit(self, node: LabelDeclaration) -> str: + return f"{node.idx}:" + + @visitor.when(Label) + def visit(self, node: Label) -> str: + return str(node.idx) + + @visitor.when(JumpAndLink) + def visit(self, node: JumpAndLink) -> str: + return f"\tjal {self.visit(node.address)}" + + @visitor.when(Jump) + def visit(self, node: Jump) -> str: + return f"\tj {self.visit(node.address)}" + + @visitor.when(JumpRegister) + def visit(self, node: JumpRegister) -> str: + return f"\tj {self.visit(node.register)}" + + @visitor.when(MemoryIndexNode) + def visit(self, node: MemoryIndexNode) -> str: + return f"{self.visit(node.address)}({self.visit(node.index)})" + + @visitor.when(WordDirective) + def visit(self, node: WordDirective) -> str: + return ".word " + (" ".join(self.visit(i) for i in node.list)) + + @visitor.when(SpaceDirective) + def visit(self, node: SpaceDirective) -> str: + return ".space " + (" ".join(self.visit(i) for i in node.list)) + + @visitor.when(AsciizDirective) + def visit(self, node: AsciizDirective) -> str: + return ".asciiz " + (" ".join(f'"{self.visit(i)}"' for i in node.list)) + + @visitor.when(Move) + def visit(self, node: Move) -> str: + return f"\tmove {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(StoreWord) + def visit(self, node: StoreWord) -> str: + return f"\tsw {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(LoadWord) + def visit(self, node: LoadWord) -> str: + return f"\tlw {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(Subu) + def visit(self, node: Subu) -> str: + return f"\tsubu {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Sub) + def visit(self, node: Sub) -> str: + return f"\tsub {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Addu) + def visit(self, node: Addu) -> str: + return f"\taddu {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(LoadAddress) + def visit(self, node: LoadAddress) -> str: + return f"\tla {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(LoadImmediate) + def visit(self, node: LoadImmediate) -> str: + return f"\tli {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(Syscall) + def visit(self, node: Syscall) -> str: + return f"\tsyscall" + + @visitor.when(Add) + def visit(self, node: Add) -> str: + return f"\tadd {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Addi) + def visit(self, node: Addi) -> str: + return f"\taddi {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Multiply) + def visit(self, node: Multiply) -> str: + return f"\tmul {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Div) + def visit(self, node: Div) -> str: + return f"\tdiv {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(BranchOnEqual) + def visit(self, node: BranchOnEqual) -> str: + return f"\tbeq {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(BranchOnNotEqual) + def visit(self, node: BranchOnNotEqual) -> str: + return f"\tbne {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Less) + def visit(self, node: Less) -> str: + return f"\tslt {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(LessOrEqual) + def visit(self, node: LessOrEqual) -> str: + return f"\tsle {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Equal) + def visit(self, node: Equal) -> str: + return f"\tseq {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Xori) + def visit(self, node: Xori) -> str: + return f"\txori {self.visit(node.left)}, {self.visit(node.middle)}, {self.visit(node.right)}" + + @visitor.when(Not) + def visit(self, node: Not) -> str: + return f"\tnot {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(MoveFromLo) + def visit(self, node: MoveFromLo) -> str: + return f"\tmflo {self.visit(node.register)}" + + @visitor.when(LoadByte) + def visit(self, node: LoadByte) -> str: + return f"\tlb {self.visit(node.left)}, {self.visit(node.right)}" + + @visitor.when(StoreByte) + def visit(self, node: StoreByte) -> str: + return f"\tsb {self.visit(node.left)}, {self.visit(node.right)}" diff --git a/src/compiler/visitors/code_gen/tools.py b/src/compiler/visitors/code_gen/tools.py new file mode 100644 index 000000000..1b4f6eebf --- /dev/null +++ b/src/compiler/visitors/code_gen/tools.py @@ -0,0 +1,54 @@ +from __future__ import annotations +from typing import Dict, List, Tuple + + +class Scope: + def __init__(self, parent: Scope = None): + self.children: List[Scope] = [] + self.names: Dict[str, str] = dict() + self.parent = parent + + @property + def get_parent(self): + if self.parent is None: + raise Exception("Scope parent is None") + + return self.parent + + def create_child(self): + self.children.append(Scope()) + self.children[-1].parent = self + return self.children[-1] + + def add_new_name_pair(self, key: str, value: str): + if key in self.names: + raise Exception( + f"Re inserting {key} with {value}." + f" Value to be replaced is {self.names[key]}" + ) + self.names[key] = value + + def add_new_names(self, *names: List[Tuple[str, str]]): + for (k, v) in names: + self.add_new_name_pair(k, v) + + def search_value(self, key: str) -> str | None: + (key, _) = self.search_value_position(key) + return key + + def search_value_position(self, key: str) -> Tuple[str, bool] | Tuple[None, None]: + try: + return (self.names[key], self.parent is None) + except KeyError: + return ( + self.parent.search_value_position(key) + if self.parent is not None + else (None, None) + ) + + def get_value_position(self, key: str) -> Tuple[str, bool]: + result = self.search_value_position(key) + if any(map(lambda x: x is None, result)): + raise Exception(f"{key} cannot be found") + + return result diff --git a/src/compiler/visitors/semantics/__init__.py b/src/compiler/visitors/semantics/__init__.py new file mode 100644 index 000000000..62abedc2d --- /dev/null +++ b/src/compiler/visitors/semantics/__init__.py @@ -0,0 +1,2 @@ +from .type_builder import TypeBuilder +from .type_collector import TypeCollector diff --git a/src/compiler/visitors/semantics/inference/__init__.py b/src/compiler/visitors/semantics/inference/__init__.py new file mode 100644 index 000000000..f05053dbf --- /dev/null +++ b/src/compiler/visitors/semantics/inference/__init__.py @@ -0,0 +1,4 @@ +from .soft_inferencer import SoftInferencer +from .back_inferencer import BackInferencer +from .hard_inferencer import HardInferencer +from .types_inferencer import TypesInferencer diff --git a/src/compiler/visitors/semantics/inference/back_inferencer.py b/src/compiler/visitors/semantics/inference/back_inferencer.py new file mode 100644 index 000000000..eaf5fc7b5 --- /dev/null +++ b/src/compiler/visitors/semantics/inference/back_inferencer.py @@ -0,0 +1,319 @@ +from copy import copy, deepcopy +from typing import Tuple + +from asts.inferencer_ast import ( + AssignNode, + AttrDeclarationNode, + AttrDeclarationNode, + BinaryNode, + BlocksNode, + BooleanNode, + CaseNode, + CaseOptionNode, + ClassDeclarationNode, + ClassDeclarationNode, + ConditionalNode, + InstantiateNode, + IntNode, + LetNode, + LoopNode, + MethodCallNode, + MethodDeclarationNode, + Node, + ParamNode, + ProgramNode, + ProgramNode, + StringNode, + UnaryNode, + VarDeclarationNode, + VariableNode, +) +from visitors.semantics.tools import Context, Scope, TypeBag, join, join_list, unify +from visitors.semantics.tools.errors import SemanticError +from visitors.semantics.tools.type import Method, SelfType, Type +from visitors.utils import visitor + + +class BackInferencer: + def __init__(self, context: Context) -> None: + self.context = context + self.errors = [] + self.current_type: Type + self.changed = False + + @visitor.on("node") + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode) -> Tuple[ProgramNode, bool]: + scope = Scope() + self.changed = False + new_declaration = [] + for declaration in node.declarations: + new_declaration.append(self.visit(declaration, scope.create_child())) + + program = ProgramNode(new_declaration, scope, node) + return program, self.changed + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode, scope: Scope) -> ClassDeclarationNode: + self.current_type = self.context.get_type(node.id, unpacked=True) + scope.define_variable("self", TypeBag({SelfType()})) + for attr in self.current_type.attributes: + scope.define_variable(attr.name, attr.type) + + new_features = [] + for feature in node.features: + new_features.append(self.visit(feature, scope)) + + class_node = ClassDeclarationNode(new_features, node) + return class_node + + @visitor.when(AttrDeclarationNode) + def visit(self, node, scope: Scope): + attr_node = AttrDeclarationNode(node) + + if not node.expr: + attr_node.inferenced_type = node.inferenced_type + scope.get_variable(node.id).type = node.inferenced_type + return attr_node + + expr_node = self.visit(node.expr, scope) + expr_type = expr_node.inferenced_type + + decl_type = node.inferenced_type + decl_type, changed = unify(decl_type, expr_type) + self.changed |= changed + + attr_node.expr = expr_node + attr_node.inferenced_type = decl_type + return attr_node + + @visitor.when(MethodDeclarationNode) + def visit(self, node: MethodDeclarationNode, scope) -> MethodDeclarationNode: + scope = scope.create_child() + current_method: Method = self.current_type.get_method(node.id) + + new_params = [] + for idx, typex, param in zip( + current_method.param_names, current_method.param_types, node.params + ): + scope.define_variable(idx, typex) + new_params.append(ParamNode(param, idx, typex)) + + param_types = [ + unify(typex, new_param.type) + for new_param, typex in zip(new_params, current_method.param_types) + ] + current_method.param_types = [i[0] for i in param_types] + self.changed |= any([i[1] for i in param_types]) + + new_body_node = self.visit(node.body, scope) + body_type = new_body_node.inferenced_type.swap_self_type(self.current_type) + new_node = MethodDeclarationNode(new_params, node.type, new_body_node, node) + decl_type = node.inferenced_type.clone().swap_self_type(self.current_type) + body_type = new_body_node.inferenced_type + new_node.inferenced_type, changed = unify(decl_type, body_type) + new_node.inferenced_type.swap_self_type(self.current_type, back=True) + body_type.swap_self_type(self.current_type, back=True) + self.changed |= changed + return new_node + + @visitor.when(BlocksNode) + def visit(self, node: BlocksNode, scope) -> BlocksNode: + new_expr_list = [] + for expr in node.expr_list: + new_expr_list.append(self.visit(expr, scope)) + + new_node = BlocksNode(new_expr_list, node) + decl_type = node.inferenced_type + expr_type = new_expr_list[-1].inferenced_type + new_node.inferenced_type, changed = unify(decl_type, expr_type) + self.changed |= changed + return new_node + + @visitor.when(ConditionalNode) + def visit(self, node: ConditionalNode, scope) -> ConditionalNode: + new_condition_node = self.visit(node.condition, scope) + new_then_node = self.visit(node.then_body, scope) + new_else_node = self.visit(node.else_body, scope) + + join_type = join(new_then_node.inferenced_type, new_else_node.inferenced_type) + decl_type = node.inferenced_type + expr_type = join_type + new_node = ConditionalNode( + new_condition_node, new_then_node, new_else_node, node + ) + new_node.inferenced_type, changed = unify(decl_type, expr_type) + self.changed |= changed + return new_node + + @visitor.when(CaseNode) + def visit(self, node: CaseNode, scope: Scope) -> CaseNode: + new_case_node = self.visit(node.case_expr, scope) + new_options_nodes = [] + for option in node.options: + child_scope = scope.create_child() + new_options_nodes.append(self.visit(option, child_scope)) + + join_type = join_list([option.inferenced_type for option in new_options_nodes]) + + new_node = CaseNode(new_case_node, new_options_nodes, node) + decl_type = node.inferenced_type + expr_type = join_type + new_node.inferenced_type, changed = unify(decl_type, expr_type) + self.changed |= changed + return new_node + + @visitor.when(CaseOptionNode) + def visit(self, node: CaseOptionNode, scope: Scope) -> CaseOptionNode: + try: + node_type = self.context.get_type(node.type, selftype=False, autotype=False) + except SemanticError as err: + node_type = TypeBag(set()) + + scope.define_variable(node.id, node_type) + + new_node_expr = self.visit(node.expr, scope) + new_node = CaseOptionNode(new_node_expr, node_type, node) + decl_type = node.inferenced_type + expr_type = new_node_expr.inferenced_type + new_node.inferenced_type, changed = unify(decl_type, expr_type) + + self.changed |= changed + return new_node + + @visitor.when(LoopNode) + def visit(self, node: LoopNode, scope) -> LoopNode: + new_condition_node = self.visit(node.condition, scope) + new_body_node = self.visit(node.body, scope) + + new_node = LoopNode(new_condition_node, new_body_node, node) + new_node.inferenced_type = node.inferenced_type + return new_node + + @visitor.when(LetNode) + def visit(self, node: LetNode, scope) -> LetNode: + child_scope = scope.create_child() + + new_var_decl_nodes = [] + for var_decl in node.var_decl_list: + new_var_decl_nodes.append(self.visit(var_decl, child_scope)) + + new_in_node = self.visit(node.in_expr, child_scope) + + new_node = LetNode(new_var_decl_nodes, new_in_node, node) + decl_type = node.inferenced_type + expr_type = new_in_node.inferenced_type + new_node.inferenced_type, changed = unify(decl_type, expr_type) + self.changed |= changed + return new_node + + @visitor.when(VarDeclarationNode) + def visit(self, node: VarDeclarationNode, scope: Scope) -> VarDeclarationNode: + + scope.define_variable( + node.id, node.inferenced_type.swap_self_type(self.current_type) + ) + new_node = VarDeclarationNode(node) + + if node.expr: + new_expr_node = self.visit(node.expr, scope) + new_node.expr = new_expr_node + decl_type = node.inferenced_type + expr_type = new_expr_node.inferenced_type + new_node.inferenced_type, changed = unify(decl_type, expr_type) + self.changed |= changed + else: + new_node.inferenced_type = node.inferenced_type + + return new_node + + @visitor.when(AssignNode) + def visit(self, node: AssignNode, scope) -> AssignNode: + new_expr_node = self.visit(node.expr, scope) + if node.defined: + decl_type = scope.find_variable(node.id).get_type() + else: + decl_type = new_expr_node.inferenced_type + expr_type = new_expr_node.inferenced_type + new_node = AssignNode(new_expr_node, node) + new_node.defined = node.defined + new_node.inferenced_type, changed = unify(decl_type, expr_type) + self.changed |= changed + return new_node + + @visitor.when(MethodCallNode) + def visit(self, node: MethodCallNode, scope) -> MethodCallNode: + caller_type: Type = node.caller_type.swap_self_type(self.current_type).heads[0] + method: Method = caller_type.get_method(node.id) + + new_args = [] + for arg_expr, param_type in zip(node.args, method.param_types): + arg_node = self.visit(arg_expr, scope) + arg_node.inferenced_type, changed = unify( + param_type, arg_node.inferenced_type + ) + self.changed |= changed + new_args.append(arg_node) + + new_expr = self.visit(node.expr, scope) if node.expr else None + new_node = MethodCallNode(node.caller_type, new_expr, new_args, node) + + method_return_type = method.return_type.clone().swap_self_type(caller_type) + new_node.inferenced_type, changed = unify( + node.inferenced_type, method_return_type + ) + self.changed |= changed + return new_node + + @visitor.when(BinaryNode) + def visit(self, node: BinaryNode, scope) -> BinaryNode: + new_node = copy(node) + new_left_node = self.visit(node.left, scope) + new_right_node = self.visit(node.right, scope) + + new_node.left = new_left_node + new_node.right = new_right_node + + return new_node + + @visitor.when(UnaryNode) + def visit(self, node: UnaryNode, scope) -> UnaryNode: + new_node = copy(node) + new_expr_node = self.visit(node.expr, scope) + + new_node.expr = new_expr_node + return new_node + + @visitor.when(VariableNode) + def visit(self, node: VariableNode, scope: Scope) -> VariableNode: + new_node = copy(node) + if node.defined: + decl_type = node.inferenced_type.swap_self_type(self.current_type) + expr_type = scope.find_variable(node.value) + if expr_type is not None: + expr_type = expr_type.get_type().swap_self_type(self.current_type) + else: + expr_type = decl_type + new_node.inferenced_type, changed = unify(decl_type, expr_type) + self.changed |= changed + return new_node + return new_node + + @visitor.when(InstantiateNode) + def visit(self, node, scope) -> InstantiateNode: + return copy(node) + + @visitor.when(IntNode) + def visit(self, node, scope) -> IntNode: + return copy(node) + + @visitor.when(StringNode) + def visit(self, node, scope) -> StringNode: + return copy(node) + + @visitor.when(BooleanNode) + def visit(self, node, scope) -> BooleanNode: + return copy(node) diff --git a/src/compiler/visitors/semantics/inference/hard_inferencer.py b/src/compiler/visitors/semantics/inference/hard_inferencer.py new file mode 100644 index 000000000..99cf35a38 --- /dev/null +++ b/src/compiler/visitors/semantics/inference/hard_inferencer.py @@ -0,0 +1,610 @@ +from asts.inferencer_ast import ( + ArithmeticNode, + AssignNode, + AttrDeclarationNode, + BlocksNode, + BooleanNode, + CaseNode, + CaseOptionNode, + ClassDeclarationNode, + ComparerNode, + ComplementNode, + ConditionalNode, + DivNode, + EqualsNode, + InstantiateNode, + IntNode, + IsVoidNode, + LessNode, + LessOrEqualNode, + LetNode, + LoopNode, + MethodCallNode, + MethodDeclarationNode, + MinusNode, + Node, + NotNode, + ParamNode, + PlusNode, + ProgramNode, + StarNode, + StringNode, + VarDeclarationNode, + VariableNode, +) +from visitors.semantics.tools import ( + Context, + Scope, + SelfType, + Type, + TypeBag, + conforms, + equal, + join, + join_list, + smart_add, +) +from visitors.semantics.tools.errors import AttributeError, InternalError +from visitors.utils import visitor + + +class HardInferencer: + def __init__(self, context: Context) -> None: + self.context = context + self.errors = [] + self.pos = set() + self.current_type: Type + + @visitor.on("node") + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode) -> ProgramNode: + scope: Scope = node.scope + new_declaration = [] + for declaration in node.declarations: + new_declaration.append(self.visit(declaration, scope.next_child())) + + scope.reset() + program = ProgramNode(new_declaration, scope, node) + return program + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode, scope: Scope) -> ClassDeclarationNode: + self.current_type = self.context.get_type(node.id, unpacked=True) + + new_features = [] + for feature in node.features: + new_features.append(self.visit(feature, scope)) + + class_node = ClassDeclarationNode(new_features, node) + return class_node + + @visitor.when(AttrDeclarationNode) + def visit(self, node, scope): + attr_node = AttrDeclarationNode(node) + attr_node.inferenced_type = node.inferenced_type + + if not node.expr: + return attr_node + + expr_node = self.visit(node.expr, scope) + expr_type = expr_node.inferenced_type + + attr_node.expr = expr_node + if equal(expr_type, node.expr.inferenced_type): + return attr_node + + expr_name = expr_type.generate_name() + node_type = attr_node.inferenced_type + if not conforms(expr_type, attr_node.inferenced_type): + self.add_error( + node, + ( + f"TypeError: In class '{self.current_type.name}' attribue" + f"'{node.id}' expression type({expr_name}) does not conforms" + f"to declared type ({node_type.name})." + ), + ) + + return attr_node + + @visitor.when(MethodDeclarationNode) + def visit(self, node, scopex: Scope): + scope: Scope = scopex.next_child() + current_method = self.current_type.get_method(node.id) + + new_params = [] + for idx, typex, param in zip( + current_method.param_names, current_method.param_types, node.params + ): + new_params.append(ParamNode(param, idx, typex)) + + body_node = self.visit(node.body, scope) + body_type = body_node.inferenced_type + method_node = MethodDeclarationNode(new_params, node.type, body_node, node) + method_node.inferenced_type = node.inferenced_type + + if equal(body_type, node.body.inferenced_type): + return method_node + + node_type = method_node.inferenced_type + body_name = body_type.generate_name() + if not conforms(body_type, node_type): + self.add_error( + body_node, + f"TypeError: In Class '{self.current_type.name}' method " + f"'{method_node.id}' return expression type({body_name})" + f" does not conforms to declared return type ({node_type.name})", + ) + + return method_node + + @visitor.when(BlocksNode) + def visit(self, node, scope): + new_expr_list = [] + for expr in node.expr_list: + new_expr_list.append(self.visit(expr, scope)) + + block_node = BlocksNode(new_expr_list, node) + block_node.inferenced_type = block_node.expr_list[-1].inferenced_type + return block_node + + @visitor.when(ConditionalNode) + def visit(self, node, scope): + condition_node = self.visit(node.condition, scope) + then_node = self.visit(node.then_body, scope) + else_node = self.visit(node.else_body, scope) + + condition_type = condition_node.inferenced_type + if not equal(condition_type, node.condition.inferenced_type): + condition_clone = condition_type.clone() + bool_type = self.context.get_type("Bool") + if not conforms(condition_type, bool_type): + self.add_error( + node, + f"TypeError: If's condition type({condition_clone.name})" + " does not conforms to Bool type.", + ) + + if_node = ConditionalNode(condition_node, then_node, else_node, node) + + if not equal( + then_node.inferenced_type, node.then_body.inferenced_type + ) or not equal(else_node.inferenced_type, node.else_body.inferenced_type): + then_type = then_node.inferenced_type + else_type = else_node.inferenced_type + joined_type = join(then_type, else_type) + else: + joined_type = node.inferenced_type + + if_node.inferenced_type = joined_type + return if_node + + @visitor.when(CaseNode) + def visit(self, node, scope: Scope): + expr_node = self.visit(node.case_expr, scope) + + type_list = [] + new_options = [] + for option in node.options: + child = scope.next_child() + new_options.append(self.visit(option, child)) + type_list.append(new_options[-1].inferenced_type) + + join_type = join_list(type_list) + case_node = CaseNode(expr_node, new_options, node) + case_node.inferenced_type = join_type + return case_node + + @visitor.when(CaseOptionNode) + def visit(self, node, scope: Scope): + expr_node = self.visit(node.expr, scope) + opt_node = CaseOptionNode(expr_node, node.branch_type, node) + + opt_node.inferenced_type = expr_node.inferenced_type + # opt_node.branch_type = node.branch_type + + return opt_node + + @visitor.when(LoopNode) + def visit(self, node, scope): + condition_node = self.visit(node.condition, scope) + condition_type = condition_node.inferenced_type + + if not equal(condition_type, node.condition.inferenced_type): + bool_type = self.context.get_type("Bool") + condition_clone = condition_type.clone() + if not conforms(condition_type, bool_type): + self.add_error( + node, + f"TypeError: Loop condition type({condition_clone.name})" + " does not conforms to Bool type.", + ) + + body_node = self.visit(node.body, scope) + loop_node = LoopNode(condition_node, body_node, node) + loop_node.inferenced_type = node.inferenced_type + return loop_node + + @visitor.when(LetNode) + def visit(self, node, scope: Scope): + child = scope.next_child() + + new_decl_list = [] + for var in node.var_decl_list: + new_decl_list.append(self.visit(var, child)) + + in_expr_node = self.visit(node.in_expr, child) + + let_node = LetNode(new_decl_list, in_expr_node, node) + let_node.inferenced_type = in_expr_node.inferenced_type + return let_node + + @visitor.when(VarDeclarationNode) + def visit(self, node, scope: Scope): + var_decl_node = VarDeclarationNode(node) + var_decl_node.index = node.index + if node.expr is None: + var_decl_node.inferenced_type = node.inferenced_type + return var_decl_node + + expr_node = self.visit(node.expr, scope) + var_decl_node.expr = expr_node + + node_type = scope.get_local_by_index(node.index).get_type() + + expr_type = expr_node.inferenced_type + if equal(expr_type, node.expr.inferenced_type): + expr_clone = expr_type.clone() + if not conforms(expr_type, node_type): + self.add_error( + node, + f"Semantic Error: Variable '{node.id}' expression type" + f" ({expr_clone.name}) does not conforms to declared" + f" type({node_type.name}).", + ) + + var_decl_node.inferenced_type = expr_node.inferenced_type + return var_decl_node + + @visitor.when(AssignNode) + def visit(self, node, scope: Scope): + expr_node = self.visit(node.expr, scope) + assign_node = AssignNode(expr_node, node) + + if not node.defined or node.id == "self": + assign_node.inferenced_type = TypeBag(set()) + return assign_node + + assign_node.defined = True + + decl_type = scope.get_variable(node.id).get_type() + expr_type = expr_node.inferenced_type + if not equal(expr_type, node.expr.inferenced_type): + expr_clone = expr_type.clone() + if not conforms(expr_type, decl_type): + self.add_error( + node, + f"TypeError: Cannot assign new value to variable '{node.id}'." + f" Expression type({expr_clone.name}) does not conforms to" + f" declared type ({decl_type.name}).", + ) + + assign_node.inferenced_type = expr_node.inferenced_type + return assign_node + + @visitor.when(MethodCallNode) + def visit(self, node, scope): + caller_type: TypeBag = node.caller_type + expr_node = None + if node.type is not None and node.expr is not None: + expr_node = self.visit(node.expr, scope) + expr_type = expr_node.inferenced_type + if not equal(expr_type, node.expr.inferenced_type): + expr_clone = expr_type.clone() + if not conforms(expr_type, caller_type): + self.add_error( + node, + f"SemanticError: Cannot effect dispatch because expression" + f" type({expr_clone.name}) does not conforms to " + f" caller type({caller_type.name}).", + ) + elif node.expr is not None: + expr_node = self.visit(node.expr, scope) + caller_type = expr_node.inferenced_type + + if len(caller_type.type_set) > 1: + methods_by_name = self.context.get_method_by_name(node.id, len(node.args)) + types = [typex for typex, _ in methods_by_name] + conforms(caller_type, TypeBag(set(types), types)) + if len(caller_type.heads) > 1: + error = ( + f"SemanticError: Method '{node.id}' found in" + f" {len(caller_type.heads)} unrelated types:\n" + ) + error += " -Found in: " + error += ", ".join(typex.name for typex in caller_type.heads) + self.add_error(node, error) + elif len(caller_type.heads) == 0: + self.add_error( + node, + f" SemanticError: There is no method called {node.id} which takes" + f" {len(node.args)} paramters.", + ) + + if len(caller_type.heads) != 1: + new_args = [] + infered_type = TypeBag(set()) + else: + caller = caller_type.heads[0] + caller = self.current_type if isinstance(caller, SelfType) else caller + try: + method = caller.get_method(node.id) + except AttributeError as err: + # self.add_error(node, err.text) Error notified in soft inferencer + new_args = [] + infered_type = TypeBag(set()) + else: + if len(node.args) != len(method.param_types): + self.add_error( + node, + f"SemanticError: Method '{node.id}' from class " + f"'{caller_type.name}' takes {len(method.param_types)}" + f" positional arguments but {len(node.args)} were given.'", + ) + + decl_return_type = method.return_type.clone() + decl_return_type.swap_self_type(caller) + type_set = set() + heads = [] + type_set = smart_add(type_set, heads, decl_return_type) + + new_args = [] + for i in range(len(node.args)): + new_args.append(self.visit(node.args[i], scope)) + if i < len(method.param_types): + arg_type: TypeBag = new_args[-1].inferenced_type + added_type = arg_type.add_self_type(self.current_type) + arg_name = arg_type.generate_name() + param_type = method.param_types[i] + if not conforms(arg_type, param_type): + self.add_error( + new_args[-1], + f"TypeError: Argument expression type({arg_name}) does" + f" not conforms parameter declared type({param_type.name})", + ) + if added_type: + arg_type.remove_self_type(self.current_type) + infered_type = TypeBag(type_set, heads) + + call_node = MethodCallNode(caller_type, expr_node, new_args, node) + call_node.inferenced_type = infered_type + return call_node + + @visitor.when(ArithmeticNode) + def visit(self, node, scope): + left_node, right_node = self.__arithmetic_operation(node, scope) + if isinstance(node, PlusNode): + arith_node = PlusNode(left_node, right_node, node) + elif isinstance(node, MinusNode): + arith_node = MinusNode(left_node, right_node, node) + elif isinstance(node, StarNode): + arith_node = StarNode(left_node, right_node, node) + elif isinstance(node, DivNode): + arith_node = DivNode(left_node, right_node, node) + else: + raise InternalError("This should never happen") + + arith_node.inferenced_type = self.context.get_type("Int") + return arith_node + + @visitor.when(LessNode) + def visit(self, node, scope: Scope): + left_node, right_node = self.__arithmetic_operation(node, scope) + less_node = LessNode(left_node, right_node, node) + less_node.inferenced_type = self.context.get_type("Bool") + return less_node + + @visitor.when(LessOrEqualNode) + def visit(self, node, scope: Scope): + left_node, right_node = self.__arithmetic_operation(node, scope) + lesseq_node = LessOrEqualNode(left_node, right_node, node) + lesseq_node.inferenced_type = self.context.get_type("Bool") + return lesseq_node + + @visitor.when(EqualsNode) + def visit(self, node, scope): + left_node = self.visit(node.left, scope) + right_node = self.visit(node.right, scope) + + self.__check_member_types(left_node, right_node) + + eq_node = EqualsNode(left_node, right_node, node) + eq_node.inferenced_type = node.inferenced_type # Bool Type :) + return eq_node + + @visitor.when(VariableNode) + def visit(self, node, scope: Scope): + var_node = VariableNode(node) + if not node.defined: + var_node.inferenced_type = TypeBag(set()) + return var_node + + var_node.defined = True + var = scope.get_variable(node.value) + var_node.inferenced_type = var.get_type() + return var_node + + @visitor.when(NotNode) + def visit(self, node, scope): + expr_node = self.visit(node.expr, scope) + expr_type = expr_node.inferenced_type + bool_type = self.context.get_type("Bool") + if not equal(expr_type, node.expr.inferenced_type): + expr_clone = expr_type.clone() + if not conforms(expr_type, bool_type): + self.add_error( + node, + f"TypeError: Not's expresion type({expr_clone.name} does not" + " conforms to Bool type", + ) + + not_node = NotNode(expr_node, node) + not_node.inferenced_type = bool_type + return not_node + + @visitor.when(ComplementNode) + def visit(self, node, scope): + expr_node = self.visit(node.expr, scope) + expr_type = expr_node.inferenced_type + int_type = self.context.get_type("Int") + if not equal(expr_type, node.expr.inferenced_type): + expr_clone = expr_type.clone() + if not conforms(expr_type, int_type): + self.add_error( + node, + f"TypeError: ~ expresion type({expr_clone.name} does not" + " conforms to Int type", + ) + + complement_node = ComplementNode(expr_node, node) + complement_node.inferenced_type = int_type + return complement_node + + @visitor.when(IsVoidNode) + def visit(self, node, scope): + node_expr = self.visit(node.expr, scope) + is_void_node = IsVoidNode(node_expr, node) + is_void_node.inferenced_type = self.context.get_type("Bool") + return is_void_node + + @visitor.when(InstantiateNode) + def visit(self, node, scope): + instantiate_node = InstantiateNode(node) + instantiate_node.inferenced_type = node.inferenced_type + return instantiate_node + + @visitor.when(IntNode) + def visit(self, node, scope): + int_node = IntNode(node) + int_node.inferenced_type = self.context.get_type("Int") + return int_node + + @visitor.when(StringNode) + def visit(self, node, scope): + str_node = StringNode(node) + str_node.inferenced_type = self.context.get_type("String") + return str_node + + @visitor.when(BooleanNode) + def visit(self, node, scope): + bool_node = BooleanNode(node) + bool_node.inferenced_type = self.context.get_type("Bool") + return bool_node + + def add_error(self, node: Node, text: str): + line, col = node.get_position() if node else (0, 0) + if (line, col) in self.pos: + return + self.pos.add((line, col)) + self.errors.append(((line, col), f"({line}, {col}) - " + text)) + + def __check_member_types(self, left_node, right_node): + if self.__unrelated_types(left_node) or self.__unrelated_types(right_node): + return + + bag1: TypeBag = left_node.inferenced_type + bag2: TypeBag = right_node.inferenced_type + + u_obj = self.context.get_type("Object", unpacked=True) + u_int = self.context.get_type("Int", unpacked=True) + u_bool = self.context.get_type("Bool", unpacked=True) + u_string = self.context.get_type("String", unpacked=True) + + contains_obj = u_obj in bag1.type_set and u_obj in bag2.type_set + contains_int = u_int in bag1.type_set and u_int in bag2.type_set + contains_bool = u_bool in bag1.type_set and u_bool in bag2.type_set + contains_string = u_string in bag1.type_set and u_string in bag2.type_set + + if contains_obj or ( + (contains_int and not (contains_bool or contains_string)) + and (contains_bool and not (contains_int or contains_string)) + and (contains_string and not (contains_int or contains_bool)) + ): + if contains_obj: + self.__conform_to_type(left_node, TypeBag({u_obj})) + self.__conform_to_type(right_node, TypeBag({u_obj})) + elif contains_int: + self.__conform_to_type(left_node, TypeBag({u_int})) + self.__conform_to_type(right_node, TypeBag({u_int})) + elif contains_bool: + self.__conform_to_type(left_node, TypeBag({u_bool})) + self.__conform_to_type(right_node, TypeBag({u_bool})) + elif contains_string: + self.__conform_to_type(left_node, TypeBag({u_string})) + self.__conform_to_type(right_node, TypeBag({u_string})) + else: + raise InternalError( + "Compiler is not working correctly(HardInferencer.__check_member_types)" + ) + else: + basic_set = {u_int, u_bool, u_string} + if len(bag1.type_set.intersection(basic_set)) == 1: + self.__conform_to_type(right_node, bag1) + elif len(bag2.type_set.intersection(basic_set)) == 1: + self.__conform_to_type(left_node, bag2) + + def __conform_to_type(self, node: Node, bag: TypeBag): + node_type = node.inferenced_type + node_name = node_type.generate_name() + if not conforms(node_type, bag): + self.add_error( + node, + f"TypeError: Equal Node: Expression type({node_name})" + f"does not conforms to expression({bag.name})", + ) + + def __arithmetic_operation(self, node, scope): + left_node = self.visit(node.left, scope) + left_type = left_node.inferenced_type + + right_node = self.visit(node.right, scope) + right_type = right_node.inferenced_type + + int_type = self.context.get_type("Int") + if not equal(left_type, node.left.inferenced_type): + if not conforms(left_type, int_type): + left_clone = left_type.clone() + self.add_error( + node.left, + f"TypeError: Arithmetic Error: Left member type({left_clone.name})" + "does not conforms to Int type.", + ) + + if not equal(right_type, node.right.inferenced_type): + right_clone = right_type.clone() + if not conforms(right_type, int_type): + self.add_error( + node.right, + f"TypeError: Arithmetic Error: Right member " + f"type({right_clone.name})does not conforms to Int type.", + ) + + return left_node, right_node + + def __unrelated_types(self, node): + typex: TypeBag = node.inferenced_type + if typex.error_type: + return True + if len(typex.heads) > 1: + self.add_error( + node, + "AutotypeError: AUTO_TYPE is ambigous {" + + ", ".join(typez.name for typez in typex.heads), + +"}", + ) + node.inferenced_type = TypeBag(set()) + return True + return False diff --git a/src/compiler/visitors/semantics/inference/soft_inferencer.py b/src/compiler/visitors/semantics/inference/soft_inferencer.py new file mode 100644 index 000000000..e13f16b38 --- /dev/null +++ b/src/compiler/visitors/semantics/inference/soft_inferencer.py @@ -0,0 +1,590 @@ +from inspect import currentframe +from typing import Type + +import asts.inferencer_ast as inf_ast +from asts.parser_ast import ( + ArithmeticNode, + AssignNode, + AttrDeclarationNode, + BlocksNode, + BooleanNode, + CaseNode, + CaseOptionNode, + ClassDeclarationNode, + ComparerNode, + ComplementNode, + ConditionalNode, + DivNode, + EqualsNode, + InstantiateNode, + IntNode, + IsVoidNode, + LessNode, + LessOrEqualNode, + LetNode, + LoopNode, + MethodCallNode, + MethodDeclarationNode, + MinusNode, + Node, + NotNode, + PlusNode, + ProgramNode, + StarNode, + StringNode, + VarDeclarationNode, + VariableNode, +) +from visitors.semantics.tools import ( + Context, + Scope, + SelfType, + TypeBag, + conforms, + join, + join_list, + smart_add, +) +from visitors.semantics.tools.errors import AttributeError, SemanticError +from visitors.utils import visitor + + +class SoftInferencer: + def __init__(self, context: Context) -> None: + self.context = context + self.errors = [] + self.current_type: Type = None + + @visitor.on("node") + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode) -> inf_ast.ProgramNode: + scope = Scope() + new_declaration = [] + for declaration in node.declarations: + new_declaration.append(self.visit(declaration, scope.create_child())) + + program = inf_ast.ProgramNode(new_declaration, scope, node) + return program + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode, scope: Scope) -> ClassDeclarationNode: + self.current_type = self.context.get_type(node.id, unpacked=True) + scope.define_variable("self", self.context.get_type("SELF_TYPE")) + + for attr in self.current_type.attributes: + if attr.name != "self": + # Is not define, error is given later when visiting + # the attribute + scope.define_variable(attr.name, attr.type) + + new_features = [] + for feature in node.features: + new_features.append(self.visit(feature, scope)) + + class_node = inf_ast.ClassDeclarationNode(new_features, node) + return class_node + + @visitor.when(AttrDeclarationNode) + def visit(self, node, scope): + if node.id == "self": + self.add_error(node, "SemanticError: An attribute cannot be named 'self'") + + node_type = self.current_type.get_attribute(node.id).type + + attr_node = inf_ast.AttrDeclarationNode(node) + if not node.expr: + attr_node.inferenced_type = node_type + return attr_node + + expr_node = self.visit(node.expr, scope) + expr_type: TypeBag = expr_node.inferenced_type + added_type = expr_type.add_self_type(self.current_type) + + expr_name = expr_type.generate_name() + if not conforms(expr_type, node_type): + self.add_error( + node, + ( + f"TypeError: In class '{self.current_type.name}' attribue" + f" '{node.id}' expression type({expr_name}) does not conforms" + f" to declared type ({node_type.name})." + ), + ) + if added_type: + expr_type.remove_self_type(self.current_type) + + attr_node.expr = expr_node + attr_node.inferenced_type = expr_type + return attr_node + + @visitor.when(MethodDeclarationNode) + def visit(self, node: MethodDeclarationNode, scopex: Scope): + scope = scopex.create_child() + current_method = self.current_type.get_method(node.id) + + new_params = [] + for idx, typex, param in zip( + current_method.param_names, current_method.param_types, node.params + ): + scope.define_variable(idx, typex) + new_params.append(inf_ast.ParamNode(param, idx, typex)) + + ret_type_decl: TypeBag = current_method.return_type + + body_node = self.visit(node.body, scope) + ret_type_expr = body_node.inferenced_type + added_self = ret_type_expr.add_self_type(self.current_type) + + ret_expr_name = ret_type_expr.generate_name() + if not conforms(ret_type_expr, ret_type_decl): + self.add_error( + body_node, + f"TypeError: In Class '{self.current_type.name}' method" + f" '{current_method.name}' return expression type({ret_expr_name})" + f" does not conforms to declared return type ({ret_type_decl.name})", + ) + + if added_self: + ret_type_expr.remove_self_type(self.current_type) + + method_node = inf_ast.MethodDeclarationNode( + new_params, node.type, body_node, node + ) + method_node.inferenced_type = ret_type_decl + return method_node + + @visitor.when(BlocksNode) + def visit(self, node, scope): + new_expr_list = [] + for expr in node.expr_list: + new_expr_list.append(self.visit(expr, scope)) + + block_node = inf_ast.BlocksNode(new_expr_list, node) + block_node.inferenced_type = block_node.expr_list[-1].inferenced_type + return block_node + + @visitor.when(ConditionalNode) + def visit(self, node, scope): + condition_node = self.visit(node.condition, scope) + + condition_type = condition_node.inferenced_type + bool_type = self.context.get_type("Bool") + + condition_clone = condition_type.clone() + if not conforms(condition_type, bool_type): + self.add_error( + node, + f"TypeError: If's condition type({condition_clone.name})" + " does not conforms to Bool type.", + ) + + then_node = self.visit(node.then_body, scope) + else_node = self.visit(node.else_body, scope) + + if_node = inf_ast.ConditionalNode(condition_node, then_node, else_node, node) + + then_type = then_node.inferenced_type + else_type = else_node.inferenced_type + joined_type = join(then_type, else_type) + + if_node.inferenced_type = joined_type + return if_node + + @visitor.when(CaseNode) + def visit(self, node, scope: Scope): + expr_node = self.visit(node.case_expr, scope) + + types_visited = set() + type_list = [] + new_options = [] + for option in node.options: + child = scope.create_child() + new_options.append(self.visit(option, child)) + type_list.append(new_options[-1].inferenced_type) + var_type = child.get_variable(option.id).get_type() + var_type = var_type.heads[0] if not var_type.error_type else var_type + if var_type in types_visited: + self.add_error( + option, + "SemanticError: Case Expression have 2 or more branches" + f"with same case type({var_type.name})", + ) + types_visited.add(var_type) + + joined_type = join_list(type_list) + + case_node = inf_ast.CaseNode(expr_node, new_options, node) + case_node.inferenced_type = joined_type + return case_node + + @visitor.when(CaseOptionNode) + def visit(self, node, scope: Scope): + try: + node_type = self.context.get_type(node.type, selftype=False, autotype=False) + except SemanticError as err: + self.add_error( + node, err.text + f" While defining Case Option variable {node.id}." + ) + node_type = TypeBag(set()) + + scope.define_variable(node.id, node_type) + expr_node = self.visit(node.expr, scope) + + case_opt_node = inf_ast.CaseOptionNode(expr_node, node_type, node) + case_opt_node.inferenced_type = expr_node.inferenced_type + return case_opt_node + + @visitor.when(LoopNode) + def visit(self, node, scope): + condition_node = self.visit(node.condition, scope) + condition_type = condition_node.inferenced_type + + bool_type = self.context.get_type("Bool") + condition_clone = condition_type.clone() + if not conforms(condition_type, bool_type): + self.add_error( + node, + f"TypeError: Loop condition type({condition_clone.name})" + " does not conforms to Bool type.", + ) + + body_node = self.visit(node.body, scope) + loop_node = inf_ast.LoopNode(condition_node, body_node, node) + loop_node.inferenced_type = self.context.get_type("Object") + return loop_node + + @visitor.when(LetNode) + def visit(self, node, scope: Scope): + child = scope.create_child() + + new_decl_list = [] + for var in node.var_decl_list: + new_decl_list.append(self.visit(var, child)) + + in_expr_node = self.visit(node.in_expr, child) + + let_node = inf_ast.LetNode(new_decl_list, in_expr_node, node) + let_node.inferenced_type = in_expr_node.inferenced_type + return let_node + + @visitor.when(VarDeclarationNode) + def visit(self, node, scope: Scope): + var_decl_node = inf_ast.VarDeclarationNode(node) + + try: + node_type = self.context.get_type(node.type) + except SemanticError as err: + node_type = TypeBag(set(), []) + self.add_error(node, err.text) + + if node.id == "self": + self.add_error( + node, + "SemanticError: Cannot bound self in a let expression.", + ) + var_decl_node.id = "" + + var = scope.define_variable(var_decl_node.id, node_type) + var_decl_node.index = len(scope.locals) - 1 + + var_decl_node.inferenced_type = node_type + + if node.expr: + expr_node = self.visit(node.expr, scope) + expr_type: TypeBag = expr_node.inferenced_type + added_type = expr_type.add_self_type(self.current_type) + expr_clone = expr_type.clone() + if not conforms(expr_type, node_type): + self.add_error( + node, + f"TypeError: Variable '{node.id}' expression type" + f" ({expr_clone.name}) does not conforms to declared" + f" type({node_type.name}).", + ) + if added_type: + expr_type.remove_self_type(self.current_type) + var_decl_node.expr = expr_node + var_decl_node.inferenced_type = expr_node.inferenced_type + + return var_decl_node + + @visitor.when(AssignNode) + def visit(self, node, scope: Scope): + expr_node = self.visit(node.expr, scope) + assign_node = inf_ast.AssignNode(expr_node, node) + + var = scope.find_variable(node.id) + if var is None: + self.add_error( + node, + f"SemanticError: Cannot assign new value to" + f"{node.id} beacuse it is not defined in the current scope", + ) + else: + decl_type = var.get_type() + assign_node.defined = True + if var.name == "self": + self.add_error( + node, + "SemanticError: Cannot assign new value. " + "Variable 'self' is Read-Only.", + ) + + expr_type: TypeBag = expr_node.inferenced_type + added_type = expr_type.add_self_type(self.current_type) + expr_name = expr_type.name + if not conforms(expr_type, decl_type): + self.add_error( + node, + f"TypeError: Cannot assign new value to variable '{node.id}'." + f" Expression type({expr_name}) does not conforms to" + f" declared type ({decl_type.name}).", + ) + if added_type: + expr_type.remove_self_type(self.current_type) + + assign_node.inferenced_type = expr_node.inferenced_type + return assign_node + + @visitor.when(MethodCallNode) + def visit(self, node, scope): + caller_type: TypeBag + if node.expr is None: + expr_node = None + caller_type = TypeBag({self.current_type}) + elif node.type is None: + expr_node = self.visit(node.expr, scope) + caller_type = expr_node.inferenced_type + else: + try: + caller_type = self.context.get_type( + node.type, selftype=False, autotype=False + ) + except SemanticError as err: + caller_type = TypeBag(set()) + self.add_error(node, err + " While setting dispatch caller.") + + expr_node = self.visit(node.expr, scope) + expr_type = expr_node.inferenced_type + added_type = expr_type.add_self_type(self.current_type) + expr_name = expr_type.generate_name() + if not conforms(expr_type, caller_type): + self.add_error( + node, + f"TypeError: Cannot effect dispatch because expression" + f" type({expr_name}) does not conforms to" + f" caller type({caller_type.name}).", + ) + if added_type: + expr_type.remove_self_type(self.current_type) + + methods = None + if len(caller_type.type_set) > 1: + methods_by_name = self.context.get_method_by_name(node.id, len(node.args)) + types = [typex for typex, _ in methods_by_name] + caller_type_name = caller_type.generate_name() + conforms(caller_type, TypeBag(set(types), types)) + if len(caller_type.type_set): + methods = [ + (typex, typex.get_method(node.id)) for typex in caller_type.heads + ] + else: + self.add_error( + node, + f"AtributeError: There is no method '{node.id}'" + f" that recieves {len(node.params)} arguments in" + f" types {caller_type_name}.", + ) + elif len(caller_type.type_set) == 1: + caller = caller_type.heads[0] + caller = self.current_type if isinstance(caller, SelfType) else caller + try: + methods = [(caller, caller.get_method(node.id))] + except AttributeError as err: + self.add_error( + node, + err.text, + ) + + new_args = [] + for i in range(len(node.args)): + new_args.append(self.visit(node.args[i], scope)) + + method_call_node = inf_ast.MethodCallNode( + caller_type, expr_node, new_args, node + ) + + if methods: + type_set = set() + heads = [] + for typex, method in methods: + ret_type = method.return_type.clone() + ret_type.swap_self_type(typex) + type_set = smart_add(type_set, heads, ret_type) + method_call_node.inferenced_type = TypeBag(type_set, heads) + else: + # Errors already notified previuosly + method_call_node.inferenced_type = TypeBag(set()) # ErrorType + return method_call_node + + @visitor.when(ArithmeticNode) + def visit(self, node, scope): + left_node, right_node = self.__arithmetic_operation(node, scope) + if isinstance(node, PlusNode): + arith_node = inf_ast.PlusNode(left_node, right_node, node) + elif isinstance(node, MinusNode): + arith_node = inf_ast.MinusNode(left_node, right_node, node) + elif isinstance(node, StarNode): + arith_node = inf_ast.StarNode(left_node, right_node, node) + elif isinstance(node, DivNode): + arith_node = inf_ast.DivNode(left_node, right_node, node) + else: + raise Exception("Unknown arithmetic node detected") + + arith_node.inferenced_type = self.context.get_type("Int") + return arith_node + + @visitor.when(LessNode) + def visit(self, node, scope: Scope): + left_node, right_node = self.__arithmetic_operation(node, scope) + less_node = inf_ast.LessNode(left_node, right_node, node) + less_node.inferenced_type = self.context.get_type("Bool") + return less_node + + @visitor.when(LessOrEqualNode) + def visit(self, node, scope: Scope): + left_node, right_node = self.__arithmetic_operation(node, scope) + lesseq_node = inf_ast.LessOrEqualNode(left_node, right_node, node) + lesseq_node.inferenced_type = self.context.get_type("Bool") + return lesseq_node + + @visitor.when(EqualsNode) + def visit(self, node, scope: Scope): + left_node = self.visit(node.left, scope) + right_node = self.visit(node.right, scope) + + equal_node = inf_ast.EqualsNode(left_node, right_node, node) + equal_node.inferenced_type = self.context.get_type("Bool") + return equal_node + + @visitor.when(VariableNode) + def visit(self, node, scope: Scope): + var_node = inf_ast.VariableNode(node) + + var = scope.find_variable(node.value) + if var: + var_node.defined = True + var_type = var.get_type() + else: + self.add_error(node, f"NameError: Variable '{node.value}' is not defined.") + var_type = TypeBag(set()) + + var_node.inferenced_type = var_type + return var_node + + @visitor.when(NotNode) + def visit(self, node, scope): + expr_node = self.visit(node.expr, scope) + + expr_type = expr_node.inferenced_type + expr_clone = expr_type.clone() + bool_type = self.context.get_type("Bool") + if not conforms(expr_type, bool_type): + self.add_error( + node, + f"TypeError: Not's expresion type ({expr_clone.name} does not" + " conforms to Bool type", + ) + + not_node = inf_ast.NotNode(expr_node, node) + not_node.inferenced_type = bool_type + return not_node + + @visitor.when(ComplementNode) + def visit(self, node, scope): + expr_node = self.visit(node.expr, scope) + + expr_type = expr_node.inferenced_type + expr_clone = expr_type.clone() + int_type = self.context.get_type("Int") + if not conforms(expr_type, int_type): + self.add_error( + node, + f"TypeError: ~ expresion type({expr_clone.name}) does not" + " conforms to Int type", + ) + + complement_node = inf_ast.ComplementNode(expr_node, node) + complement_node.inferenced_type = int_type + return complement_node + + @visitor.when(IsVoidNode) + def visit(self, node, scope): + node_expr = self.visit(node.expr, scope) + is_void_node = inf_ast.IsVoidNode(node_expr, node) + is_void_node.inferenced_type = self.context.get_type("Bool") + return is_void_node + + @visitor.when(InstantiateNode) + def visit(self, node, scope): + instantiate_node = inf_ast.InstantiateNode(node) + try: + node_type = self.context.get_type(node.value, autotype=False) + except SemanticError as err: + self.add_error( + node, + err.text + f" Could not instantiate type '{node.value}'.", + ) + node_type = TypeBag(set()) + + instantiate_node.inferenced_type = node_type + return instantiate_node + + @visitor.when(IntNode) + def visit(self, node, scope): + int_node = inf_ast.IntNode(node) + int_node.inferenced_type = self.context.get_type("Int") + return int_node + + @visitor.when(StringNode) + def visit(self, node, scope): + str_node = inf_ast.StringNode(node) + str_node.inferenced_type = self.context.get_type("String") + return str_node + + @visitor.when(BooleanNode) + def visit(self, node, scope): + bool_node = inf_ast.BooleanNode(node) + bool_node.inferenced_type = self.context.get_type("Bool") + return bool_node + + def __arithmetic_operation(self, node, scope): + left_node = self.visit(node.left, scope) + left_type = left_node.inferenced_type + left_clone = left_type.clone() + + right_node = self.visit(node.right, scope) + right_type = right_node.inferenced_type + right_clone = right_type.clone() + + int_type = self.context.get_type("Int") + if not conforms(left_type, int_type): + self.add_error( + node.left, + f"TypeError: ArithmeticError: Left member type({left_clone.name})" + " does not conforms to Int type.", + ) + if not conforms(right_type, int_type): + self.add_error( + node.right, + f"TypeError: ArithmeticError: Right member type({right_clone.name})" + " does not conforms to Int type.", + ) + return left_node, right_node + + def add_error(self, node: Node, text: str): + line, col = node.get_position() if node else (0, 0) + self.errors.append(((line, col), f"({line}, {col}) - " + text)) diff --git a/src/compiler/visitors/semantics/inference/types_inferencer.py b/src/compiler/visitors/semantics/inference/types_inferencer.py new file mode 100644 index 000000000..dc6ebad5a --- /dev/null +++ b/src/compiler/visitors/semantics/inference/types_inferencer.py @@ -0,0 +1,318 @@ +from typing import List, Tuple +from visitors.semantics.tools.context import Context +from visitors.semantics.tools.errors import InternalError +from visitors.semantics.tools.type import Type, join_list +from visitors.utils import visitor + +from visitors.semantics.tools import TypeBag, Scope +import asts.types_ast as types_ast +from asts.inferencer_ast import ( + BinaryNode, + BooleanNode, + ComplementNode, + DivNode, + EqualsNode, + InstantiateNode, + IntNode, + LessNode, + LessOrEqualNode, + MinusNode, + NotNode, + ParamNode, + PlusNode, + ProgramNode, + ClassDeclarationNode, + MethodDeclarationNode, + AttrDeclarationNode, + BlocksNode, + ConditionalNode, + CaseNode, + CaseOptionNode, + LoopNode, + LetNode, + StarNode, + StringNode, + VarDeclarationNode, + AssignNode, + MethodCallNode, + ClassDeclarationNode, + ProgramNode, + AttrDeclarationNode, + Node, + VariableNode, + IsVoidNode, +) + + +class TypesInferencer: + def __init__(self, context: Context) -> None: + self.errors = [] + self.context = context + + @visitor.on("node") + def visit(self, node, scope): + pass + + @visitor.when(ProgramNode) + def visit(self, node: ProgramNode) -> types_ast.ProgramNode: + class_decl = [] + scope = Scope() + for decl in node.declarations: + class_decl.append(self.visit(decl, scope.create_child())) + + return types_ast.ProgramNode(class_decl, node) + + @visitor.when(ClassDeclarationNode) + def visit( + self, node: ClassDeclarationNode, scope: Scope + ) -> types_ast.ClassDeclarationNode: + features = [] + for feature in node.features: + features.append(self.visit(feature, scope)) + + return types_ast.ClassDeclarationNode(features, node) + + @visitor.when(AttrDeclarationNode) + def visit( + self, node: AttrDeclarationNode, scope: Scope + ) -> types_ast.AttrDeclarationNode: + new_node = types_ast.AttrDeclarationNode(node) + if node.expr: + new_node.expr = self.visit(node.expr, scope) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(MethodDeclarationNode) + def visit( + self, node: MethodDeclarationNode, scope: Scope + ) -> types_ast.MethodDeclarationNode: + scope = scope.create_child() + + params = [] + for param in node.params: + param_type = self._reduce_to_type(param.type, node, general=True) + scope.define_variable(param.id, param_type) + params.append(types_ast.ParamNode(param, param.id, param_type)) + + body = self.visit(node.body, scope) + + ret_type = self._reduce_to_type(node.inferenced_type, node) + return types_ast.MethodDeclarationNode(params, ret_type, body, node) + + @visitor.when(BlocksNode) + def visit(self, node: BlocksNode, scope: Scope) -> types_ast.BlocksNode: + expr_list = [self.visit(expr, scope) for expr in node.expr_list] + + new_node = types_ast.BlocksNode(expr_list, node) + new_node.type = expr_list[-1].type + return new_node + + @visitor.when(ConditionalNode) + def visit(self, node: ConditionalNode, scope: Scope) -> types_ast.ConditionalNode: + condition = self.visit(node.condition, scope) + then_body = self.visit(node.then_body, scope) + else_body = self.visit(node.else_body, scope) + + new_node = types_ast.ConditionalNode(condition, then_body, else_body, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(CaseNode) + def visit(self, node: CaseNode, scope: Scope) -> types_ast.CaseNode: + expr = self.visit(node.case_expr, scope) + + case_options: List[CaseOptionNode] = [] + for option in node.options: + child_scope = scope.create_child() + case_options.append(self.visit(option, child_scope)) + + # Order case options by specificity, most common go last + new_option_order: List[Tuple[int, CaseOptionNode]] = [] + for option1 in case_options: + specificity = 0 + for option2 in case_options: + specificity += ( + 1 if option2.branch_type.conforms_to(option1.branch_type) else 0 + ) + new_option_order.append((specificity, option1)) + + new_option_order: List[CaseOptionNode] = [ + opt for (_, opt) in sorted(new_option_order, key=lambda x: x[0]) + ] + + new_node = types_ast.CaseNode(expr, new_option_order, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(CaseOptionNode) + def visit(self, node: CaseOptionNode, scope: Scope) -> types_ast.CaseOptionNode: + successors: List[str] = self.context.get_successors(node.branch_type.name) + + return types_ast.CaseOptionNode( + self.visit(node.expr, scope), + node.branch_type.heads[0], + successors, + node, + ) + + @visitor.when(LoopNode) + def visit(self, node: LoopNode, scope: Scope) -> types_ast.LoopNode: + condition = self.visit(node.condition, scope) + body = self.visit(node.body, scope) + new_node = types_ast.LoopNode(condition, body, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(LetNode) + def visit(self, node: LetNode, scope: Scope) -> types_ast.LetNode: + scope = scope.create_child() + var_decl_list = [self.visit(var_decl, scope) for var_decl in node.var_decl_list] + in_expr = self.visit(node.in_expr, scope) + new_node = types_ast.LetNode(var_decl_list, in_expr, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(VarDeclarationNode) + def visit( + self, node: VarDeclarationNode, scope: Scope + ) -> types_ast.VarDeclarationNode: + new_node = types_ast.VarDeclarationNode(node) + if node.expr: + new_node.expr = self.visit(node.expr, scope) + + var_info = scope.find_variable(node.id) + general = var_info is not None # it's a param + new_node.type = self._reduce_to_type(node.inferenced_type, node, general) + + return new_node + + @visitor.when(AssignNode) + def visit(self, node: AssignNode, scope: Scope) -> types_ast.AssignNode: + expr = self.visit(node.expr, scope) + new_node = types_ast.AssignNode(expr, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(MethodCallNode) + def visit(self, node: MethodCallNode, scope: Scope) -> types_ast.MethodCallNode: + args = [self.visit(arg, scope) for arg in node.args] + caller_expr = self.visit(node.expr, scope) if node.expr is not None else None + new_node = types_ast.MethodCallNode(node.caller_type, caller_expr, args, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(VariableNode) + def visit(self, node: VariableNode, scope: Scope) -> types_ast.VariableNode: + new_node = types_ast.VariableNode(node) + + if node.defined: + var_info = scope.find_variable(node.value) + general = var_info is not None + new_node.type = self._reduce_to_type(node.inferenced_type, node, general) + + return new_node + + @visitor.when(IsVoidNode) + def visit(self, node: IsVoidNode, scope: Scope) -> types_ast.IsVoidNode: + expr = self.visit(node.expr, scope) + new_node = types_ast.IsVoidNode(expr, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(NotNode) + def visit(self, node: NotNode, scope: Scope) -> types_ast.NotNode: + expr = self.visit(node.expr, scope) + new_node = types_ast.NotNode(expr, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(ComplementNode) + def visit(self, node: ComplementNode, scope: Scope) -> types_ast.ComplementNode: + expr = self.visit(node.expr, scope) + new_node = types_ast.ComplementNode(expr, node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(BinaryNode) + def visit(self, node, scope: Scope) -> types_ast.BinaryNode: + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + + if isinstance(node, PlusNode): + new_node = types_ast.PlusNode(left, right, node) + elif isinstance(node, MinusNode): + new_node = types_ast.MinusNode(left, right, node) + elif isinstance(node, DivNode): + new_node = types_ast.DivNode(left, right, node) + elif isinstance(node, StarNode): + new_node = types_ast.StarNode(left, right, node) + elif isinstance(node, LessNode): + new_node = types_ast.LessNode(left, right, node) + elif isinstance(node, LessOrEqualNode): + new_node = types_ast.LessOrEqualNode(left, right, node) + elif isinstance(node, EqualsNode): + new_node = types_ast.EqualsNode(left, right, node) + else: + raise InternalError("This should never happen") + + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(BooleanNode) + def visit(self, node: BooleanNode, scope: Scope) -> types_ast.BooleanNode: + new_node = types_ast.BooleanNode(node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(StringNode) + def visit(self, node: StringNode, scope: Scope) -> types_ast.StringNode: + new_node = types_ast.StringNode(node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(IntNode) + def visit(self, node: IntNode, scope: Scope) -> types_ast.IntNode: + new_node = types_ast.IntNode(node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + @visitor.when(InstantiateNode) + def visit(self, node: InstantiateNode, scope: Scope) -> types_ast.InstantiateNode: + new_node = types_ast.InstantiateNode(node) + new_node.type = self._reduce_to_type(node.inferenced_type, node) + return new_node + + def _reduce_to_type(self, bag: TypeBag, node: Node, general=False): + if len(bag.heads) > 1: + self.add_error( + node, + f"TypeError: Ambiguous type declaration, multiple values {bag.generate_name()}", + ) + return TypeBag(set()) + if len(bag.heads) == 0: + self.add_error(node, "TypeError: Cannot infer expression's type") + return TypeBag(set()) + + if general: + return bag.heads[0] + + order_types = list(bag.type_set) + order_types.sort(key=lambda x: -1 * x.index) + + higher_index_types = [order_types[0]] + higher_index = order_types[0].index + for typex in order_types[2:]: + if typex.index == higher_index: + higher_index_types.append(typex) + + return self._join_types(higher_index_types) + + def _join_types(self, types: List[Type]): + types_bags = [] + for typex in types: + types_bags.append(TypeBag({typex}, heads=[typex])) + return join_list(types_bags).heads[0] + + def add_error(self, node, text: str): + line, col = node.get_position() if node else (0, 0) + self.errors.append(((line, col), f"({line}, {col}) - " + text)) diff --git a/src/compiler/visitors/semantics/tools/__init__.py b/src/compiler/visitors/semantics/tools/__init__.py new file mode 100644 index 000000000..0509fd4ec --- /dev/null +++ b/src/compiler/visitors/semantics/tools/__init__.py @@ -0,0 +1,15 @@ +from visitors.semantics.tools.context import Context +from visitors.semantics.tools.scope import Scope +from visitors.semantics.tools.type import ( + ErrorType, + SelfType, + TypeBag, + conforms, + equal, + join, + join_list, + smart_add, + try_conform, + Type, + unify, +) diff --git a/src/compiler/visitors/semantics/tools/context.py b/src/compiler/visitors/semantics/tools/context.py new file mode 100644 index 000000000..089d8e6c6 --- /dev/null +++ b/src/compiler/visitors/semantics/tools/context.py @@ -0,0 +1,67 @@ +from visitors.semantics.tools.errors import * +from visitors.semantics.tools.type import TypeBag, Type, SelfType +from typing import Dict, List, Union + + +class Context: + def __init__(self) -> None: + self.types = {} + self.type_graph: Dict = None + + def create_type(self, name: str) -> Type: + if name in self.types: + raise SemanticError(f"Type with the same name ({name}) already exists.") + typex = self.types[name] = Type(name) + return typex + + def get_type( + self, + name: str, + selftype=True, + autotype=True, + unpacked=False, + ) -> Union[Type, TypeBag]: + if selftype and name == "SELF_TYPE": + return TypeBag({SelfType()}) # raise TypeError(f"Cannot use SELF_TYPE.") + if autotype and name == "AUTO_TYPE": + return TypeBag(self.types, [self.types["Object"]]) + try: + if unpacked: + return self.types[name] + return TypeBag({self.types[name]}) + except KeyError: + raise TypeError(f'Type "{name}" is not defined.') + + def get_method_by_name(self, name: str, args: int) -> list: + def dfs(root: str, results: list): + try: + for typex in self.type_graph[root]: + for method in self.types[typex].methods: + if name == method.name and args == len(method.param_names): + results.append((self.types[typex], method)) + break + else: + dfs(typex, results) + except KeyError: + pass + + results = [] + dfs("Object", results) + return results + + def get_successors(self, type_name: str) -> List[str]: + successors: List[str] = [type_name] + for type in successors: + for succ in self.type_graph[type]: + successors.append(succ) + return successors + + 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) diff --git a/src/compiler/visitors/semantics/tools/errors.py b/src/compiler/visitors/semantics/tools/errors.py new file mode 100644 index 000000000..a32a01a68 --- /dev/null +++ b/src/compiler/visitors/semantics/tools/errors.py @@ -0,0 +1,22 @@ +class InternalError(Exception): + @property + def text(self): + return "InternalError: " + self.args[0] + + +class SemanticError(Exception): + @property + def text(self): + return "SemanticError: " + self.args[0] + + +class TypeError(SemanticError): + @property + def text(self): + return "TypeError: " + self.args[0] + + +class AttributeError(SemanticError): + @property + def text(self): + return "AttributeError: " + self.args[0] \ No newline at end of file diff --git a/src/compiler/visitors/semantics/tools/scope.py b/src/compiler/visitors/semantics/tools/scope.py new file mode 100644 index 000000000..01d286d72 --- /dev/null +++ b/src/compiler/visitors/semantics/tools/scope.py @@ -0,0 +1,91 @@ +from typing import Optional +from visitors.semantics.tools.type import TypeBag, ErrorType +import itertools as itt + + +class VariableInfo: + def __init__(self, name, vtype) -> None: + self.name: str = name + self.type: TypeBag = vtype + + def get_type(self) -> TypeBag or ErrorType: + # if len(self.type.type_set) == 0: + # self.type = ErrorType() + return self.type + + def __str__(self): + return self.name + ":" + self.type + + +class Scope: + def __init__(self, parent=None): + self.locals = [] + self.parent = parent + self.children = [] + self.index = 0 if parent is None else len(parent) + self.current_child = -1 + + def __len__(self): + return len(self.locals) + + def create_child(self): + child = Scope(self) + self.children.append(child) + return child + + def define_variable(self, vname, vtype): + info = VariableInfo(vname, vtype) + self.locals.append(info) + return info + + def find_variable(self, vname, index=None) -> Optional[VariableInfo]: + locals = self.locals if index is None else itt.islice(self.locals, index) + try: + return next(x for x in locals if x.name == vname) + except StopIteration: + try: + return ( + self.parent.find_variable(vname, self.index) + if self.parent is not None + else None + ) + except AttributeError: + return None + + def get_variable(self, vname, index=None) -> VariableInfo: + var = self.find_variable(vname, index) + if var is None: + raise Exception(f"Could not get variable {vname}.") + + return var + + def get_local_by_index(self, index): + return self.locals[index] + + def is_defined(self, vname): + return self.find_variable(vname) is not None + + def is_local(self, vname): + return any(True for x in self.locals if x.name == vname) + + def next_child(self): + self.current_child += 1 + return self.children[self.current_child] + + def reset(self): + self.current_child = -1 + for child in self.children: + child.reset() + + def get_all_names(self, s: str = "", level: int = 0): + if self.locals: + s += "\n ".join( + [ + x.name + ":" + str([typex.name for typex in x.type.type_set]) + for x in self.locals + ] + ) + s += "\n\n" + for child in self.children: + s = child.get_all_names(s, level + 1) + return s diff --git a/src/compiler/visitors/semantics/tools/type.py b/src/compiler/visitors/semantics/tools/type.py new file mode 100644 index 000000000..5d82509dd --- /dev/null +++ b/src/compiler/visitors/semantics/tools/type.py @@ -0,0 +1,647 @@ +from visitors.semantics.tools.errors import * +from typing import List, Set, Tuple +from collections import OrderedDict + + +class Attribute: + def __init__(self, name, typex): + self.name = name + self.type = typex + + def __str__(self): + return f"[attrib] {self.name} : {self.type.name};" + + def __repr__(self): + return str(self) + + +class Method: + def __init__(self, name, param_names, params_types, return_type): + self.name = name + self.param_names = param_names + self.param_types = params_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 + ) + + +class Type: + def __init__(self, name: str): + self.name = name + self.attributes = [] + self.methods = [] + self.parent = None + self.index = -1 + + def set_parent(self, parent): + if self.parent is not None: + raise SemanticError( + f"Type '{self.name}' already has parent type '{self.parent.name}'. Type '{parent.name}' cannot be set as parent." + ) + if parent.name in {"String", "Int", "Bool"}: + raise SemanticError( + f"Cannot set '{self.name}' parent, '{parent.name}' type cannot be inherited." + ) + self.parent = parent + + def define_attribute(self, name: str, typex): + try: + self.get_attribute(name) + except SemanticError: + attribute = Attribute(name, typex) + self.attributes.append(attribute) + return attribute + else: + raise SemanticError( + f'Attribute "{name}" is already defined in "{self.name}".' + ) + + def get_attribute(self, name: str, first=None): + if not first: + first = self.name + elif first == self.name: + raise AttributeError(f'Attribute "{name}" is not defined in {self.name}.') + + try: + return next(attr for attr in self.attributes if attr.name == name) + except StopIteration: + if self.parent is None: + raise AttributeError( + f'Attribute "{name}" is not defined in {self.name}.' + ) + try: + return self.parent.get_attribute(name, first=first) + except SemanticError: + raise AttributeError( + f'Attribute "{name}" is not defined in {self.name}.' + ) + + def get_method(self, name: str, local: bool = False, first=None): + if not first: + first = self.name + elif first == self.name: + raise AttributeError( + f'Method "{name}" is not defined in class {self.name}.' + ) + + try: + return next(method for method in self.methods if method.name == name) + except StopIteration: + if self.parent is None: + raise AttributeError( + f'Method "{name}" is not defined in class {self.name}.' + ) + try: + return self.parent.get_method(name, first=first) + except AttributeError: + raise AttributeError( + f'Method "{name}" is not defined in class {self.name}.' + ) + + def define_method( + self, name: str, param_names: list, param_types: list, return_type + ): + if name in (method.name for method in self.methods): + raise SemanticError(f"Method '{name}' already defined in '{self.name}'") + + try: + parent_method = self.get_method(name) + except SemanticError: + parent_method = None + if parent_method: + error_list = [] + return_type.swap_self_type(self) + return_clone = return_type.clone() + parent_method.return_type.swap_self_type(self) + if not conforms(return_type, parent_method.return_type): + error_list.append( + f" -> Same return type: Redefined method has '{return_clone.name}' as return type instead of '{parent_method.return_type.name}'." + ) + if len(param_types) != len(parent_method.param_types): + error_list.append( + f" -> Same amount of params: Redefined method has {len(param_types)} params instead of {len(parent_method.param_types)}." + ) + else: + count = 0 + err = [] + for param_type, parent_param_type in zip( + param_types, parent_method.param_types + ): + param_clone = param_type.clone() + if not conforms(param_type, parent_param_type): + err.append( + f" -Param number {count} has {param_clone.name} as type instead of {parent_param_type.name}" + ) + count += 1 + if err: + s = f" -> Same param types:\n" + "\n".join( + child for child in err + ) + error_list.append(s) + return_type.swap_self_type(self, back=True) + parent_method.return_type.swap_self_type(self, back=True) + if error_list: + err = ( + f"Redifined method '{name}' in class '{self.name}' does not have:\n" + + "\n".join(child for child in error_list) + ) + raise SemanticError(err) + + method = Method(name, param_names, param_types, return_type) + self.methods.append(method) + + return method + + def all_attributes(self, clean=True, first=None): + if not first: + first = self.name + elif first == self.name: + return OrderedDict.values() if clean else OrderedDict() + + plain = ( + OrderedDict() + if self.parent is None + else self.parent.all_attributes(clean=False, first=first) + ) + for attr in self.attributes: + plain[attr.name] = (attr, self) + return plain.values() if clean else plain + + def all_methods(self, clean=True, first=None): + if not first: + first = self.name + elif first == self.name: + return OrderedDict.values() if clean else OrderedDict() + + plain = ( + OrderedDict() + if self.parent is None + else self.parent.all_methods(clean=False, first=first) + ) + for method in self.methods: + plain[method.name] = (method, self) + return plain.values() if clean else plain + + def conforms_to(self, other, first=None): + if not first: + first = self.name + elif self.name == first: + return False + return ( + other.bypass() + or self == other + or self.parent + and self.parent.conforms_to(other, first) + ) + + def bypass(self): + return False + + def least_common_ancestor(self, other): + this: Type = self + if isinstance(this, ErrorType) or isinstance(other, ErrorType): + return ErrorType() + + while this.index < other.index: + other = other.parent + + while other.index < this.index: + this = this.parent + + if not (this and other): + return None + + while this.name != other.name: + this = this.parent + other = other.parent + if this == None: + return None + return this + + 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) + output += "\n" if self.methods else "" + output += "}\n" + return output + + def __repr__(self): + return str(self) + + +class TypeBag: + def __init__(self, type_set, heads=[]) -> None: + self.type_set: set = ( + type_set if isinstance(type_set, set) else from_dict_to_set(type_set) + ) + self.heads: List[Type] = heads + if len(self.type_set) == 1: + self.heads = list(self.type_set) + + self.condition_list = [] + self.conform_list = [] + + @property + def error_type(self) -> bool: + return len(self.heads) == 0 + + @property + def name(self) -> str: + return self.generate_name() + + def set_conditions(self, condition_list, conform_list) -> None: + if self.error_type: + return + + self.condition_list = condition_list + self.conform_list = conform_list + self.__update_type_set_from_conforms() + + def __update_type_set_from_conforms(self) -> None: + intersect_set = set() + for conform_set in self.conform_list: + intersect_set = intersect_set.union(conform_set) + self.type_set = self.type_set.intersection(intersect_set) + self.update_heads() + + def update_heads(self) -> None: + if self.error_type: + return + + # new_heads = [] + # visited = set() + # for head in self.heads: + # if head in self.type_set: + # new_heads.append(head) + # continue + pos_new_head = [] + lower_index = 2 ** 32 + for typex in self.type_set: + # if typex in visited: + # continue + + if typex.index < lower_index: + pos_new_head = [typex] + lower_index = typex.index + elif typex.index == lower_index: + pos_new_head.append(typex) + # new_heads += pos_new_head + self.heads = pos_new_head # new_heads + + def swap_self_type(self, swap_type, back=False): + if self.error_type: + return self + + if not back: + remove_type = SelfType() + add_type = swap_type + else: + remove_type = swap_type + add_type = SelfType() + + try: + self.type_set.remove(remove_type) + self.type_set.add(add_type) + except KeyError: + return self + + self.update_heads() + return self + + def add_self_type(self, add_type) -> bool: + if self.error_type: + return False + + if SelfType() in self.type_set and not add_type in self.type_set: + self.type_set.add(add_type) + return True + return False + + def remove_self_type(self, remove_type) -> None: + if self.error_type: + return + try: + self.type_set.remove(remove_type) + except KeyError: + pass + self.type_set.add(SelfType()) + self.update_heads() + + def generate_name(self) -> str: + if self.error_type: + return "" + + if len(self.type_set) == 1: + name = self.heads[0].name + return name + + s = "{" + s += ", ".join( + typex.name for typex in sorted(self.type_set, key=lambda t: t.index) + ) + s += "}" + return s + + def clone(self): + clone = TypeBag(self.type_set.copy(), self.heads.copy()) + clone.condition_list = self.condition_list.copy() + clone.conform_list = self.conform_list.copy() + return clone + + def update(self, other): + self.name = other.name + self.condition_list = other.condition_list + self.conform_list = other.conform_list + self.type_set = other.type_set + self.heads = other.heads + + def __str__(self): + return self.name + + def __repr__(self): + return str(self) + + +class SelfType(Type): + def __init__(self): + self.name = "SELF_TYPE" + self.index = 2 ** 31 + + def conforms_to(self, other): + if isinstance(other, SelfType): + return True + return False + raise InternalError("SELF_TYPE is yet to be assigned, cannot conform.") + + def bypass(self): + return False + raise InternalError("SELF_TYPE is yet to be assigned, cannot bypass.") + + def __hash__(self) -> int: + return hash(self.name) + + def __eq__(self, o: object) -> bool: + return isinstance(o, SelfType) + + def __str__(self): + return self.name + + def __repr__(self): + return str(self) + + +class ErrorType(TypeBag): + def __init__(self): + self.name = "" + self.index = 2 ** 32 + self.type_set = frozenset() + self.heads = frozenset() + + def conforms_to(self, other): + return True + + def bypass(self): + return True + + def swap_self_type(self, swap_type, back=False): + return self + + def set_conditions(self, *params): + return + + def generate_name(self): + return "" + + def clone(self): + return self + + +def conforms(bag1: TypeBag, bag2: TypeBag) -> bool: + if isinstance(bag1, ErrorType) or isinstance(bag2, ErrorType): + raise InternalError("Use of deprecated ErrorType in conforms") + + if bag1.error_type or bag2.error_type: + return True + + ordered_set = order_set_by_index(bag2.type_set) + + condition_list = [] + conform_list = [] + for condition in ordered_set: + conform = conform_to_condition(bag1.type_set, condition) + for i in range(len(condition_list)): + conform_i = conform_list[i] + if len(conform_i) == len(conform) and len( + conform.intersection(conform_i) + ) == len(conform): + condition_list[i].add(condition) + break + else: + condition_list.append({condition}) + conform_list.append(conform) + + bag1.set_conditions(condition_list, conform_list) + return len(bag1.type_set) >= 1 + + +def try_conform(bag1: TypeBag, bag2: TypeBag) -> TypeBag: + clone1 = bag1.clone() + if not conforms(bag1, bag2): + return clone1 + return bag2 + + +def join(bag1: TypeBag, bag2: TypeBag) -> TypeBag: + if isinstance(bag1, ErrorType) or isinstance(bag2, ErrorType): + raise InternalError("Use of deprecated ErrorType in Join") + + if bag1.error_type: + return bag2 + if bag2.error_type: + return bag1 + + ancestor_set = set() + head_list = [] + ordered_set1: Set[Type] = order_set_by_index(bag1.type_set) + ordered_set2: Set[Type] = order_set_by_index(bag2.type_set) + ordered_set1, ordered_set2 = ( + (ordered_set1, ordered_set2) + if len(ordered_set1) < len(ordered_set2) + else (ordered_set2, ordered_set1) + ) + for type1 in ordered_set1: + same_branch = False + previous_ancestor = None + previous_type = None + for type2 in ordered_set2: + if same_branch and type2.conforms_to(previous_type): + previous_type = type2 + continue + common_ancestor = type1.least_common_ancestor(type2) + previous_type = type2 + if not previous_ancestor: + smart_add(ancestor_set, head_list, common_ancestor) + previous_ancestor = common_ancestor + else: + if previous_ancestor == common_ancestor: + same_branch = True + else: + same_branch = False + smart_add(ancestor_set, head_list, common_ancestor) + previous_ancestor = common_ancestor + + join_result = TypeBag(ancestor_set, head_list) + return join_result + + +def join_list(type_list): + join_result = type_list[0] + for i in range(1, len(type_list)): + type_i = type_list[i] + join_result = join(join_result, type_i) + return join_result + + +def equal(bag1: TypeBag, bag2: TypeBag): + if isinstance(bag1, ErrorType) or isinstance(bag2, ErrorType): + raise InternalError("Use of deprecated type ErrorType") + if bag1.error_type or bag2.error_type: + return True + + set1 = bag1.type_set + set2 = bag2.type_set + return len(set1) == len(set2) and len(set1.intersection(set2)) == len(set2) + + +def smart_add(type_set: set, head_list: list, typex: Type): + if isinstance(typex, TypeBag): + return auto_add(type_set, head_list, typex) + + type_set.add(typex) + there_is = False + for i in range(len(head_list)): + head = head_list[i] + ancestor = typex.least_common_ancestor(head) + if ancestor in type_set: + there_is = True + if ancestor == typex: + head_list[i] = typex + break + if not there_is: + head_list.append(typex) + return type_set + + +def auto_add(type_set: set, head_list: List[TypeBag], bag: TypeBag): + type_set = type_set.union(bag.type_set) + aux = set(bag.heads) + for i in range(len(head_list)): + head_i = head_list[i] + for head_j in bag.heads: + ancestor = head_i.least_common_ancestor(head_j) + if ancestor in type_set: + head_list[i] = ancestor + aux.remove(head_j) + break + head_list += [typex for typex in aux] + return type_set + + +def conform_to_condition(type_set, parent) -> set: + set_result = set() + for typex in type_set: + if typex.conforms_to(parent): + set_result.add(typex) + return set_result + + +def order_set_by_index(type_set): + return sorted(list(type_set), key=lambda x: x.index) + + +def set_intersection(parent, type_set) -> set: + set_result = set() + for typex in type_set: + if typex.conforms_to(parent): + set_result.add(typex) + return set_result + + +def from_dict_to_set(types: dict): + type_set = set() + for typex in types: + type_set.add(types[typex]) + return type_set + + +def unify(a: TypeBag, b: TypeBag) -> Tuple[TypeBag, bool]: + if a.error_type: + return b, False + if b.error_type: + return a, False + + intersection = set() + if len(a.type_set) == 1 and len(b.type_set) == 1: + type_a = list(a.type_set)[0] + type_b = list(b.type_set)[0] + if type_b.conforms_to(type_a): + return a, False + return TypeBag(set()), False + + for type1 in a.type_set: + for type2 in b.type_set: + if type2 == type1: + intersection.add(type1) + + changed = (not equals_set(a.type_set, intersection)) or ( + not equals_set(b.type_set, intersection) + ) + a.type_set = intersection + a.update_heads() + b.type_set = intersection + b.update_heads() + return a, changed + + +# def unify(a: TypeBag, b: TypeBag) -> Tuple[TypeBag, bool]: +# if a.error_type: +# return b, False +# if b.error_type: +# return a, False + +# if len(a.type_set) == 1 and len(b.type_set) == 1: +# type_a = list(a.type_set)[0] +# type_b = list(b.type_set)[0] +# if type_b.conforms_to(type_a): +# return a, False +# return TypeBag(set()), False + +# a_clone = a.clone() +# change = conforms(a_clone, b) +# if not change: +# return a, change + +# a.type_set = a_clone.type_set +# a.update_heads() +# return a, change + + +def equals_set(a: set, b: set) -> bool: + return a.issubset(b) and b.issubset(a) diff --git a/src/compiler/visitors/semantics/type_builder.py b/src/compiler/visitors/semantics/type_builder.py new file mode 100644 index 000000000..6e50bf08e --- /dev/null +++ b/src/compiler/visitors/semantics/type_builder.py @@ -0,0 +1,143 @@ +from asts.parser_ast import ( + AttrDeclarationNode, + ClassDeclarationNode, + MethodDeclarationNode, + Node, + ProgramNode, +) +from visitors.utils import visitor +from visitors.semantics.tools import Context, TypeBag, Type +from visitors.semantics.tools.errors import SemanticError + + +class TypeBuilder: + def __init__(self, context: Context): + self.context = context + self.current_type: Type + self.errors: list = [] + + @visitor.on("node") + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node): + self.build_default_classes() + + for class_def in node.declarations: + self.visit(class_def) + + try: + main = self.context.get_type("Main", unpacked=True).get_method( + "main", local=True + ) + if len(main.param_names) > 0: + raise SemanticError( + "Method 'main' in class 'Main' must not have formal parameters" + ) + except SemanticError as err: + self.add_error(node, err.text) + + @visitor.when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode): + self.current_type = self.context.get_type(node.id, unpacked=True) + + if node.parent: + try: + parent_type = self.context.get_type(node.parent, unpacked=True) + self.current_type.set_parent(parent_type) + for idx, _ in list(parent_type.all_attributes(True)): + self.current_type.attributes.append(idx) + except SemanticError as err: + self.add_error(node, err.text) + + for feature in node.features: + self.visit(feature) + + @visitor.when(AttrDeclarationNode) + def visit(self, node): + try: + attr_type = self.context.get_type(node.type) + except SemanticError as err: + self.add_error(node, err.text) + attr_type = TypeBag(set()) + + try: + self.current_type.define_attribute(node.id, attr_type) + except SemanticError as err: + self.add_error(node, err.text) + + @visitor.when(MethodDeclarationNode) + def visit(self, node): + try: + ret_type = self.context.get_type(node.type) + except SemanticError as err: + self.add_error(node, err.text) + ret_type = TypeBag(set()) + + params_type = [] + params_name = [] + for var in node.params: + p_name = var.id + p_type = var.type + try: + params_type.append(self.context.get_type(p_type, selftype=False)) + except SemanticError as err: + params_type.append(TypeBag(set())) + self.add_error( + node, + err.text + + f" While defining parameter {var.id} in method {node.id}.", + ) + if p_name in params_name: + self.add_error( + node, + f"SemanticError: Formal parameter '{p_name}' has been defined multiple times.", + ) + p_name = f"error({p_name})" + if p_name == "self": + self.add_error( + node, + "SemanticError: Cannot use 'self' as formal parameter identifier.", + ) + p_name = f"error({p_name})" + params_name.append(p_name) + + try: + self.current_type.define_method(node.id, params_name, params_type, ret_type) + except SemanticError as err: + self.add_error(node, err.text) + + def build_default_classes(self): + Object = self.context.get_type("Object", unpacked=True) + String = self.context.get_type("String", unpacked=True) + Int = self.context.get_type("Int", unpacked=True) + Io = self.context.get_type("IO", unpacked=True) + Bool = self.context.get_type("Bool", unpacked=True) + + String.set_parent(Object) + Int.set_parent(Object) + Io.set_parent(Object) + Bool.set_parent(Object) + + p_Object = self.context.get_type("Object") + p_String = self.context.get_type("String") + p_Int = self.context.get_type("Int") + p_Self = self.context.get_type("SELF_TYPE") # TypeBag({SelfType()}) + + Object.define_method("abort", [], [], p_Object) + Object.define_method("type_name", [], [], p_String) + Object.define_method("copy", [], [], p_Self) + + String.define_method("length", [], [], p_Int) + String.define_method("concat", ["s"], [p_String], p_String) + String.define_method("substr", ["i", "l"], [p_Int, p_Int], p_String) + + Io.define_method("out_string", ["x"], [p_String], p_Self) + Io.define_method("out_int", ["x"], [p_Int], p_Self) + Io.define_method("in_string", [], [], p_String) + Io.define_method("in_int", [], [], p_Int) + + def add_error(self, node: Node, text: str): + line, col = node.get_position() if node else (0, 0) + self.errors.append(((line, col), f"({line}, {col}) - " + text)) diff --git a/src/compiler/visitors/semantics/type_collector.py b/src/compiler/visitors/semantics/type_collector.py new file mode 100644 index 000000000..6e31ecdc8 --- /dev/null +++ b/src/compiler/visitors/semantics/type_collector.py @@ -0,0 +1,124 @@ +from asts.parser_ast import ClassDeclarationNode, Node, ProgramNode + +from visitors.semantics.tools.errors import SemanticError +from visitors.semantics.tools import Context, SelfType +from visitors.utils import visitor + + +class TypeCollector: + def __init__(self) -> None: + self.context = Context() + self.errors = [] + self.type_graph = { + "Object": ["IO", "String", "Int", "Bool"], + "IO": [], + "String": [], + "Int": [], + "Bool": [], + } + self.node_dict = dict() + + @visitor.on("node") + def visit(self, node): + pass + + @visitor.when(ProgramNode) + def visit(self, node): + self.context = Context() + self.init_default_classes() + + for class_def in node.declarations: + self.visit(class_def) + + new_declarations = self.get_type_hierarchy() + node.declarations = new_declarations + self.context.type_graph = self.type_graph + + @visitor.when(ClassDeclarationNode) + def visit(self, node): + try: + self.context.create_type(node.id) + self.node_dict[node.id] = node + try: + self.type_graph[node.id] + except KeyError: + self.type_graph[node.id] = [] + if node.parent: + if node.parent in {"String", "Int, Bool"}: + raise SemanticError( + f"Type '{node.id}' cannot inherit from '{node.parent}' beacuse it is forbidden." + ) + try: + self.type_graph[node.parent].append(node.id) + except KeyError: + self.type_graph[node.parent] = [node.id] + else: + node.parent = "Object" + self.type_graph["Object"].append(node.id) + except SemanticError as error: + self.add_error(node, error.text) + + def get_type_hierarchy(self): + visited = set(["Object"]) + new_order = [] + self.dfs_type_graph("Object", self.type_graph, visited, new_order, 1) + + circular_heritage_errors = [] + for node in self.type_graph: + if not node in visited: + visited.add(node) + path = [node] + err = self.check_circular_heritage(node, self.type_graph, path, visited) + if len(err) > 0: # Nodes with invalid parents will be checked to ;) + circular_heritage_errors.append( + (path[1 if len(path) - 1 else 0], err) + ) + # path[1] to detect circular heritage same place tests do :( + try: + new_order = new_order + [self.node_dict[node] for node in path] + except KeyError: + pass + + for node_id, err in circular_heritage_errors: + self.add_error( + self.node_dict[node_id], "SemanticError: Circular Heritage: " + err + ) + + return new_order + + def dfs_type_graph(self, root, graph, visited: set, new_order, index): + if not root in graph: + return + + for node in graph[root]: + if node in visited: + continue + visited.add(node) + if node not in {"Int", "String", "IO", "Bool", "Object"}: + new_order.append(self.node_dict[node]) + self.context.get_type(node, unpacked=True).index = index + self.dfs_type_graph(node, graph, visited, new_order, index + 1) + + def check_circular_heritage(self, root, graph, path, visited): + for node in graph[root]: + if node in path: + return " -> ".join(child for child in path + [path[0]]) + + visited.add(node) + path.append(node) + return self.check_circular_heritage(node, graph, path, visited) + return "" + + def init_default_classes(self): + self.context.create_type("Object").index = 0 + self.context.create_type("String") + self.context.create_type("Int") + self.context.create_type("IO") + self.context.create_type("Bool") + + def init_self_type(self): + self.context.types["SELF_TYPE"] = SelfType() + + def add_error(self, node: Node, text: str): + line, col = node.get_position() if node else (0, 0) + self.errors.append(((line, col), f"({line}, {col}) - " + text)) diff --git a/src/compiler/visitors/utils/__init__.py b/src/compiler/visitors/utils/__init__.py new file mode 100644 index 000000000..b6af07e9a --- /dev/null +++ b/src/compiler/visitors/utils/__init__.py @@ -0,0 +1 @@ +from .visitor import * diff --git a/src/compiler/visitors/utils/visitor.py b/src/compiler/visitors/utils/visitor.py new file mode 100644 index 000000000..0bcba712e --- /dev/null +++ b/src/compiler/visitors/utils/visitor.py @@ -0,0 +1,85 @@ +# 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/src/coolc.sh b/src/coolc.sh index 3088de4f9..416ed8158 100755 --- a/src/coolc.sh +++ b/src/coolc.sh @@ -4,8 +4,10 @@ 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 "CoolCow 0.1" # TODO: Recuerde cambiar estas +echo "Copyright (c) 2021: Adrian, Rodrigo" # TODO: líneas a los valores correctos # Llamar al compilador -echo "Compiling $INPUT_FILE into $OUTPUT_FILE" +# echo "Compiling $INPUT_FILE into $OUTPUT_FILE" + +exec python3 compiler $INPUT_FILE diff --git a/src/makefile b/src/makefile index 30df993f5..94ae8c701 100644 --- a/src/makefile +++ b/src/makefile @@ -3,6 +3,9 @@ main: # Compiling the compiler :) +sample: + python compiler/__main__.py + clean: rm -rf build/* rm -rf ../tests/*/*.mips @@ -10,3 +13,5 @@ clean: test: pytest ../tests -v --tb=short -m=${TAG} +poetest: + poetry run make test diff --git a/src/tests/inference/ackerman.cl b/src/tests/inference/ackerman.cl new file mode 100644 index 000000000..1c70d9901 --- /dev/null +++ b/src/tests/inference/ackerman.cl @@ -0,0 +1,16 @@ +class Main { + main() :Int{ + 3 + }; + test(a:AUTO_TYPE, b:AUTO_TYPE) : AUTO_TYPE { + ackermann(a, b) + }; + + ackermann(m : AUTO_TYPE, n: AUTO_TYPE) : AUTO_TYPE { + if (m=0) then n+1 else + if (n=0) then ackermann(m-1, 1) else + ackermann(m-1, ackermann(m, n-1)) + fi + fi + }; +}; diff --git a/src/tests/inference/assign1.cl b/src/tests/inference/assign1.cl new file mode 100644 index 000000000..625171ff3 --- /dev/null +++ b/src/tests/inference/assign1.cl @@ -0,0 +1,7 @@ +class Main inherits IO { + a : AUTO_TYPE; + n : AUTO_TYPE; + main() : AUTO_TYPE { + a <- n + 1 + }; +}; diff --git a/src/tests/inference/attributes1.cl b/src/tests/inference/attributes1.cl new file mode 100644 index 000000000..5f0bee915 --- /dev/null +++ b/src/tests/inference/attributes1.cl @@ -0,0 +1,8 @@ +class Main inherits IO { + p:AUTO_TYPE <- 5; + pp:AUTO_TYPE; + main() : AUTO_TYPE + { + pp<-self + }; +}; diff --git a/src/tests/inference/attributes2.cl b/src/tests/inference/attributes2.cl new file mode 100644 index 000000000..57a828c38 --- /dev/null +++ b/src/tests/inference/attributes2.cl @@ -0,0 +1,7 @@ +class Main inherits IO { + n:AUTO_TYPE; + main() : AUTO_TYPE { + n = p + }; + p:AUTO_TYPE; +}; diff --git a/src/tests/inference/call1.cl b/src/tests/inference/call1.cl new file mode 100644 index 000000000..b665399af --- /dev/null +++ b/src/tests/inference/call1.cl @@ -0,0 +1,78 @@ +class Main inherits IO { + main(): String + { + "Kabuki" + }; + test(x : AUTO_TYPE, y : AUTO_TYPE) : AUTO_TYPE { + { + if true then x.me() else y.myself() fi; + x.he(); + if false then x else y fi; + } + }; +}; + +class A { + me():SELF_TYPE + { + self + }; + you():SELF_TYPE + { + self + }; + he():SELF_TYPE + { + self + }; +}; + +class B inherits A { + us():SELF_TYPE + { + self + }; +}; + +class C inherits B { }; + +class D inherits C { + they():SELF_TYPE + { + self + }; +}; + +class E inherits D { }; + + +class F inherits C { }; + +class G inherits F { + myself():SELF_TYPE + { + self + }; + }; + +class H inherits G { }; + + +class I{ + me():String + { + "Confusing" + }; + you():SELF_TYPE + { + self + }; + she():SELF_TYPE + { + self + }; +}; + +class J inherits I { }; + +class K inherits J { }; diff --git a/src/tests/inference/case1.cl b/src/tests/inference/case1.cl new file mode 100644 index 000000000..3b742a5cd --- /dev/null +++ b/src/tests/inference/case1.cl @@ -0,0 +1,17 @@ +class Main inherits IO { + main(): String + { + "Kabuki" + }; + + test(a : AUTO_TYPE, b : AUTO_TYPE) : AUTO_TYPE { + { + if b then not b else a + 1 fi; + case 3 + 5 of + n : Bool => b; + n : Int => not b; + n : String => b; + esac; + } + }; +}; diff --git a/src/tests/inference/case2.cl b/src/tests/inference/case2.cl new file mode 100644 index 000000000..5bc1464a3 --- /dev/null +++ b/src/tests/inference/case2.cl @@ -0,0 +1,71 @@ +(* +En este ejemplo se muestra como en la primera etapa de el inferenciador se maneja las ambiguedades en los Auto Types +*) + +class Main inherits IO { + a : AUTO_TYPE; b : AUTO_TYPE; + + main() : AUTO_TYPE { + { + if b then not b else a fi; + case 3 + 5 of + n : Bool => a.me(); + n : Int => 0; + n : String => "Yay!"; + esac; + a.you(); + a.she(); + } + }; +}; + +class A { + me():SELF_TYPE + { + self + }; + you():SELF_TYPE + { + self + }; + he():SELF_TYPE + { + self + }; +}; +(* +class B inherits A { }; + +class C inherits B { }; + +class D inherits C { }; + +class E inherits D { }; + + +class F inherits C { }; + +class G inherits F { }; + +class H inherits G { }; +*) + +class I{ + me():SELF_TYPE + { + self + }; + you():SELF_TYPE + { + self + }; + she():SELF_TYPE + { + self + }; +}; +(* +class J inherits I { }; + +class K inherits J { }; +*) diff --git a/src/tests/inference/equals1.cl b/src/tests/inference/equals1.cl new file mode 100644 index 000000000..55929242e --- /dev/null +++ b/src/tests/inference/equals1.cl @@ -0,0 +1,15 @@ +class Main inherits IO { + n:AUTO_TYPE; + p:AUTO_TYPE; + main() : AUTO_TYPE { + n = p + }; + test1(): AUTO_TYPE { + { + n <- p; + p <- "Whooooshwith4os"; + } + }; + + +}; diff --git a/src/tests/inference/fibonacci.cl b/src/tests/inference/fibonacci.cl new file mode 100644 index 000000000..ce62a5680 --- /dev/null +++ b/src/tests/inference/fibonacci.cl @@ -0,0 +1,18 @@ +class Main { + main (): Int { + 1 + }; + + iterative_fibonacci(n: AUTO_TYPE) : AUTO_TYPE { + let i: AUTO_TYPE <- 2, n1: AUTO_TYPE <- 1, n2: AUTO_TYPE <- 1 in { + while i < n loop + let temp: AUTO_TYPE <- n2 in { + n2 <- n2 + n1; + n1 <- temp; + i <- i + 1; + } + pool; + n2; + } + }; +}; \ No newline at end of file diff --git a/src/tests/inference/if1.cl b/src/tests/inference/if1.cl new file mode 100644 index 000000000..d2704c5f5 --- /dev/null +++ b/src/tests/inference/if1.cl @@ -0,0 +1,7 @@ +class Main inherits IO { + a : AUTO_TYPE; + b : AUTO_TYPE; + main() : AUTO_TYPE { + if b then a + 1 else a fi + }; +}; diff --git a/src/tests/inference/many1.cl b/src/tests/inference/many1.cl new file mode 100644 index 000000000..431484e89 --- /dev/null +++ b/src/tests/inference/many1.cl @@ -0,0 +1,77 @@ +class Main inherits IO { + a : AUTO_TYPE; + b : AUTO_TYPE; + c : AUTO_TYPE; + d : AUTO_TYPE; + main(n : AUTO_TYPE) : AUTO_TYPE { + { + a.a(); + b.a(); + c.a(); + d.a(); + b.b(); + c.b(); + d.b(); + c.c(); + d.c(); + d.d(); + } + }; +}; + +class A +{ + a() : SELF_TYPE + { + self + }; +}; + +class B +{ + a() : SELF_TYPE + { + self + }; + b() : SELF_TYPE + { + self + }; +}; + +class C +{ + a() : SELF_TYPE + { + self + }; + b() : SELF_TYPE + { + self + }; + c() : SELF_TYPE + { + self + } ; +}; + +class D +{ + a() : SELF_TYPE + { + self + }; + b() : SELF_TYPE + { + self + }; + c() : SELF_TYPE + { + self + }; + d() : SELF_TYPE + { + self + }; +}; + diff --git a/src/tests/inference/many2.cl b/src/tests/inference/many2.cl new file mode 100644 index 000000000..6005716a8 --- /dev/null +++ b/src/tests/inference/many2.cl @@ -0,0 +1,71 @@ +class Main inherits IO { + a : AUTO_TYPE; + b : AUTO_TYPE; + c : AUTO_TYPE; + d : AUTO_TYPE; + main() : AUTO_TYPE { + { + a <- b; + b <- c; + c <- d; + d.d(); + } + }; +}; + +class A +{ + a() : SELF_TYPE + { + self + }; +}; + +class B +{ + a() : SELF_TYPE + { + self + }; + b() : SELF_TYPE + { + self + }; +}; + +class C +{ + a() : SELF_TYPE + { + self + }; + b() : SELF_TYPE + { + self + }; + c() : SELF_TYPE + { + self + } ; +}; + +class D +{ + a() : SELF_TYPE + { + self + }; + b() : SELF_TYPE + { + self + }; + c() : SELF_TYPE + { + self + }; + d() : SELF_TYPE + { + self + }; +}; + diff --git a/src/tests/inference/mixed1.cl b/src/tests/inference/mixed1.cl new file mode 100644 index 000000000..fe238a0c3 --- /dev/null +++ b/src/tests/inference/mixed1.cl @@ -0,0 +1,19 @@ +class Main { + main() : Int { 0 }; + + method0( a : AUTO_TYPE) : AUTO_TYPE { + { + a.method(a); + a; + } + }; + +}; + +class A { + method(a : AUTO_TYPE): AUTO_TYPE { a }; +}; + +class B inherits A { + method(b : AUTO_TYPE) : AUTO_TYPE { b }; +}; \ No newline at end of file diff --git a/src/tests/inference/mixed2.cl b/src/tests/inference/mixed2.cl new file mode 100644 index 000000000..7a76f5767 --- /dev/null +++ b/src/tests/inference/mixed2.cl @@ -0,0 +1,34 @@ +class Main { + main() : Int {0}; + + s : AUTO_TYPE <- method1(s); -- esto debe dar error semantico porque 's' + -- todavia no esta definida cuando se pasa como + -- parametro ??? + + method0( a: A) : AUTO_TYPE { + { + a; + } + }; + + d : B; + method1(a: AUTO_TYPE) : AUTO_TYPE { + { + method0(a); + d <- a; + a; + } + }; +}; + +class A { + +}; + +class B inherits A { + +}; + +class C inherits B { + +}; \ No newline at end of file diff --git a/src/tests/inference/mixed3.cl b/src/tests/inference/mixed3.cl new file mode 100644 index 000000000..9391c765b --- /dev/null +++ b/src/tests/inference/mixed3.cl @@ -0,0 +1,32 @@ +class Main { + main() : Int {0}; + + s : AUTO_TYPE <- method1(f); + f : AUTO_TYPE; + method0( a: A) : AUTO_TYPE { + { + a; + } + }; + + d : B; + method1(a: AUTO_TYPE) : AUTO_TYPE { + { + method0(a); + d <- a; -- si se comenta esta linea 'a' deberia ser de tipo 'A' + a; + } + }; +}; + +class A { + +}; + +class B inherits A { + +}; + +class C inherits B { + +}; diff --git a/src/tests/inference/not_that_simple1.cl b/src/tests/inference/not_that_simple1.cl new file mode 100644 index 000000000..0ba6576f7 --- /dev/null +++ b/src/tests/inference/not_that_simple1.cl @@ -0,0 +1,23 @@ +class Main + { + main ( ) : Int + { { + case + { + let i: AUTO_TYPE <- 3 in + { + let i: AUTO_TYPE <- "Hola" in + { + case 5 of z: String => let i : AUTO_TYPE in + { + i<- case self of x: Int => 4; esac; + }; + esac; + }; + }; + } + of + y : Int => let i: AUTO_TYPE <- y in y ; + esac ; + } } ; + } ; diff --git a/src/tests/inference/param1.cl b/src/tests/inference/param1.cl new file mode 100644 index 000000000..0af4bd246 --- /dev/null +++ b/src/tests/inference/param1.cl @@ -0,0 +1,32 @@ +class Main { + main() : Int {0}; + + s : AUTO_TYPE <- method1(s); --Esto deberia dar error no se puede pasar s xq no debe existir + f : AUTO_TYPE; + method0( a: A) : AUTO_TYPE { + { + a; + } + }; + + d : B; + method1(a: AUTO_TYPE) : AUTO_TYPE { + { + method0(a); + d <- a; -- si se comenta esta linea 'a' deberia ser de tipo "A, B o C" si no, de tipo "B o C" + 1; + } + }; +}; + +class A { + +}; + +class B inherits A { + +}; + +class C inherits B { + +}; diff --git a/src/tests/inference/param2.cl b/src/tests/inference/param2.cl new file mode 100644 index 000000000..214fa63ce --- /dev/null +++ b/src/tests/inference/param2.cl @@ -0,0 +1,33 @@ +class Main { + main() : Int {0}; + + s : AUTO_TYPE <- method1(f); --Esto deberia dar error no se puede pasar s xq no debe existir + f : AUTO_TYPE; + method0( a: A) : AUTO_TYPE { + { + a; + } + }; + + d : B; + method1(a: AUTO_TYPE) : AUTO_TYPE { + { + method0(a); + s <- "Error"; -- s no puede ser string ni int + d <- a; -- si se comenta esta linea 'a' deberia ser de tipo "A, B o C" si no, de tipo "B o C" + 1; + } + }; +}; + +class A { + +}; + +class B inherits A { + +}; + +class C inherits B { + +}; diff --git a/src/tests/inference/point1.cl b/src/tests/inference/point1.cl new file mode 100644 index 000000000..b885cb472 --- /dev/null +++ b/src/tests/inference/point1.cl @@ -0,0 +1,35 @@ +class Main { + step(p : AUTO_TYPE) : AUTO_TYPE + { + p.translate(1,1) + }; + + main() : Int + { + let q : AUTO_TYPE <- new Point.init(0,0) in + { + step(q); + } + }; +}; + +class Point { + x : AUTO_TYPE; + y : AUTO_TYPE; + translate(a:AUTO_TYPE, b:AUTO_TYPE) : AUTO_TYPE + { + { + x <- a * b; + y <- a + b; + } + }; + init(a:AUTO_TYPE, b:AUTO_TYPE) : AUTO_TYPE + { + { + x <- a; + y <- b; + self; + } + }; + +}; diff --git a/src/tests/inference/point2.cl b/src/tests/inference/point2.cl new file mode 100644 index 000000000..6ae345d67 --- /dev/null +++ b/src/tests/inference/point2.cl @@ -0,0 +1,33 @@ +class Main { + step(p : AUTO_TYPE) : AUTO_TYPE + { + p.translate(1,1) + }; + + main() : Object { + let p : AUTO_TYPE <- new Point.init(0,0) in { + step(p); -- Puede lanzar error semantico + } + }; +}; + +class Point { + x : AUTO_TYPE; + y : AUTO_TYPE; + translate(a:AUTO_TYPE, b:AUTO_TYPE) : AUTO_TYPE + { + { + x <- a; + y <- b; + } + }; + init(a:AUTO_TYPE, b:AUTO_TYPE) : AUTO_TYPE + { + { + x <- a; + y <- b; + self; + } + }; + +}; diff --git a/src/tests/inference/recursive1.cl b/src/tests/inference/recursive1.cl new file mode 100644 index 000000000..77d0095e6 --- /dev/null +++ b/src/tests/inference/recursive1.cl @@ -0,0 +1,10 @@ +class Main { main (): Int { 0 }; }; + +class A { + f(a: AUTO_TYPE, b: AUTO_TYPE): AUTO_TYPE{ + if (a=1) then b else g(a+1, b/2) fi + }; + g(a: AUTO_TYPE, b: AUTO_TYPE): AUTO_TYPE{ + if (b=1) then a else f(a/2, b+1) fi + }; +}; \ No newline at end of file diff --git a/src/tests/inference/selftype1.cl b/src/tests/inference/selftype1.cl new file mode 100644 index 000000000..fdfd00bdb --- /dev/null +++ b/src/tests/inference/selftype1.cl @@ -0,0 +1,20 @@ +--Error en B en el metodo test, se debe terminar con un self o new Self. + +class Main inherits IO { + main() : AUTO_TYPE { + self + }; +}; + +class A { + test(): SELF_TYPE { + new A + }; + + test2(): SELF_TYPE { + new SELF_TYPE + }; + }; + + class B inherits A { }; + diff --git a/src/tests/inference/selftype2.cl b/src/tests/inference/selftype2.cl new file mode 100644 index 000000000..c5935252f --- /dev/null +++ b/src/tests/inference/selftype2.cl @@ -0,0 +1,19 @@ +--SELF TYPE no se puede usar como parametro + +class Main inherits IO { + main() : AUTO_TYPE { + self + }; +}; + +class A { + test(b:SELF_TYPE): SELF_TYPE { + b + }; + test2(): SELF_TYPE { + let b:SELF_TYPE in b + }; + }; + + class B inherits A { }; + diff --git a/src/tests/inference/selftype3.cl b/src/tests/inference/selftype3.cl new file mode 100644 index 000000000..0825a946e --- /dev/null +++ b/src/tests/inference/selftype3.cl @@ -0,0 +1,20 @@ +--SELF TYPE no se puede usar en los case + +class Main inherits IO { + main() : AUTO_TYPE { + self + }; +}; + +class A { + test(a:A): Int { + case a of + n:Int => 1; + n:Bool => 2; + n:SELF_TYPE => 4; + esac + }; + }; + + class B inherits A { }; + diff --git a/src/tests/inference/selftype4.cl b/src/tests/inference/selftype4.cl new file mode 100644 index 000000000..8cbf7e828 --- /dev/null +++ b/src/tests/inference/selftype4.cl @@ -0,0 +1,15 @@ +--test2 es de tipo A asi que debe dar error xq A no confoma con SELF_TYPE de A + +class Main inherits IO { + main() : AUTO_TYPE { + self + }; +}; + +class A { + test:A <- self; + test2:SELF_TYPE <- test; + }; + + class B inherits A { }; + diff --git a/src/tests/inference/selftype5.cl b/src/tests/inference/selftype5.cl new file mode 100644 index 000000000..660df1362 --- /dev/null +++ b/src/tests/inference/selftype5.cl @@ -0,0 +1,18 @@ +--Que pasa en test. Su expresion es del tipo A, o del tipo SELF_TYPE + +class Main inherits IO { + main() : AUTO_TYPE { + self + }; +}; + +class A inherits IO { + v:String; + test(): SELF_TYPE + { + new SELF_TYPE.out_string(v) + }; + }; + + class B inherits A { }; + diff --git a/src/tests/inference/simple1.cl b/src/tests/inference/simple1.cl new file mode 100644 index 000000000..9d30decbe --- /dev/null +++ b/src/tests/inference/simple1.cl @@ -0,0 +1,6 @@ +class Main inherits IO { + n:AUTO_TYPE; + main() : AUTO_TYPE { + n + 1 + }; +}; diff --git a/src/tests/inference/simple2.cl b/src/tests/inference/simple2.cl new file mode 100644 index 000000000..17ec0ca8e --- /dev/null +++ b/src/tests/inference/simple2.cl @@ -0,0 +1,13 @@ +class Main { + main () : Int {0}; + + method(a : AUTO_TYPE, b: AUTO_TYPE, c: AUTO_TYPE, d : AUTO_TYPE, e: AUTO_TYPE, f: AUTO_TYPE): AUTO_TYPE{{ + a <- e; + b <- a; + c <- b; + d <- c; + e <- f; + f <- 12; + f; + }}; +}; \ No newline at end of file diff --git a/src/tests/inference/simple3.cl b/src/tests/inference/simple3.cl new file mode 100644 index 000000000..18a023ac4 --- /dev/null +++ b/src/tests/inference/simple3.cl @@ -0,0 +1,13 @@ +class Main { + main () : Int {0}; + + method(a : AUTO_TYPE, b: AUTO_TYPE, c: AUTO_TYPE, d : AUTO_TYPE, e: AUTO_TYPE, f: AUTO_TYPE): AUTO_TYPE{{ + a <- e; + b <- a; + c <- b; + d <- c; -- debe dar error aqui + e <- f; + d <- "Test"; + f <- 12; + }}; +}; diff --git a/src/tests/inference/simple4.cl b/src/tests/inference/simple4.cl new file mode 100644 index 000000000..fdb664db4 --- /dev/null +++ b/src/tests/inference/simple4.cl @@ -0,0 +1,11 @@ +class Main { + main (): Int { + 0 + }; + boo (): AUTO_TYPE{ + self@Main.foo(2) + }; + foo (a: AUTO_TYPE): AUTO_TYPE{ + a=1 + }; +}; \ No newline at end of file diff --git a/src/tests/normal/abort1.cl b/src/tests/normal/abort1.cl new file mode 100644 index 000000000..d68f076e4 --- /dev/null +++ b/src/tests/normal/abort1.cl @@ -0,0 +1,5 @@ + class Main { + main(): Object { + abort() + }; +}; diff --git a/src/tests/normal/allocation_call.cl b/src/tests/normal/allocation_call.cl new file mode 100644 index 000000000..a313b756c --- /dev/null +++ b/src/tests/normal/allocation_call.cl @@ -0,0 +1,13 @@ + class Main { + main() : Int + { + (new A).f() + }; + f() : Int{ + 1 +}; +}; + +class A { + f(): Int { 4 }; +}; diff --git a/src/tests/normal/aritmetica1.cl b/src/tests/normal/aritmetica1.cl new file mode 100644 index 000000000..8a476c242 --- /dev/null +++ b/src/tests/normal/aritmetica1.cl @@ -0,0 +1,5 @@ + class Main { + main(): Int { + 3 + 4 + 5 + }; +}; diff --git a/src/tests/normal/aritmetica2.cl b/src/tests/normal/aritmetica2.cl new file mode 100644 index 000000000..744e6f62a --- /dev/null +++ b/src/tests/normal/aritmetica2.cl @@ -0,0 +1,5 @@ + class Main { + main(): Int { + 3 - 4 - 5 + }; +}; diff --git a/src/tests/normal/aritmetica3.cl b/src/tests/normal/aritmetica3.cl new file mode 100644 index 000000000..40e87765f --- /dev/null +++ b/src/tests/normal/aritmetica3.cl @@ -0,0 +1,5 @@ + class Main { + main(): Int { + 3 / 4 * 5 + }; +}; diff --git a/src/tests/normal/atributos1.cl b/src/tests/normal/atributos1.cl new file mode 100644 index 000000000..6c0fa8411 --- /dev/null +++ b/src/tests/normal/atributos1.cl @@ -0,0 +1,12 @@ + class Main { + p1: Int <- 5; + p2: Int <- 10 + 5; + p3: Int; + p4: String; + p5: Bool; + main() : Int + { + p1 + }; +}; + diff --git a/src/tests/normal/attr.cl b/src/tests/normal/attr.cl new file mode 100644 index 000000000..1b14cf58b --- /dev/null +++ b/src/tests/normal/attr.cl @@ -0,0 +1,23 @@ + class Main inherits IO { + a : A; + main(): Main { + { + a <- new B; + out_int(a.f()); + } + }; +}; + +class A { + a : Int; + f() : Int { + 1 + }; +}; + +class B inherits A{ + f() : Int { + { a <- 12; a; } + }; +}; + diff --git a/src/tests/normal/attr_inicialization.cl b/src/tests/normal/attr_inicialization.cl new file mode 100644 index 000000000..4ce41c09a --- /dev/null +++ b/src/tests/normal/attr_inicialization.cl @@ -0,0 +1,9 @@ +class Main inherits IO { + a : Int <- 12; + b: Int <- 40; + main() : Main { + { + out_int(a + b); + } + }; +}; diff --git a/src/tests/normal/bool.cl b/src/tests/normal/bool.cl new file mode 100644 index 000000000..ff555157a --- /dev/null +++ b/src/tests/normal/bool.cl @@ -0,0 +1,6 @@ + + class Main { + main(): Bool { + false + }; +}; diff --git a/src/tests/normal/case1.cl b/src/tests/normal/case1.cl new file mode 100644 index 000000000..8eb0575b4 --- /dev/null +++ b/src/tests/normal/case1.cl @@ -0,0 +1,11 @@ +class Main inherits IO { + main(): Int + { + case 3 + 5 of + n : Bool => 1; + n : Int => 2; + n : String => 3; + esac + + }; +}; diff --git a/src/tests/normal/case2.cl b/src/tests/normal/case2.cl new file mode 100644 index 000000000..4080c7fad --- /dev/null +++ b/src/tests/normal/case2.cl @@ -0,0 +1,11 @@ +class Main inherits IO { + main(): Object + { + case 3 + 5 of + n : Bool => 1; + n : Int => "estao"; + n : String => 3; + esac + + }; +}; diff --git a/src/tests/normal/case3.cl b/src/tests/normal/case3.cl new file mode 100644 index 000000000..614a999cd --- /dev/null +++ b/src/tests/normal/case3.cl @@ -0,0 +1,20 @@ +class Main inherits IO { + main(): Object + { + case 3 + 5 of + n : Object => 0; + n : Bool => 1; + n : Int => 2; + n : String => 3; + n : A => 4; + n : B => 5; + n : C => 6; + esac + }; +}; + +class A { }; + +class B inherits A { }; + +class C inherits B { }; diff --git a/src/tests/normal/case4.cl b/src/tests/normal/case4.cl new file mode 100644 index 000000000..f175f75d4 --- /dev/null +++ b/src/tests/normal/case4.cl @@ -0,0 +1,20 @@ +class Main inherits IO { + main(): Object + { + case new B of + n : Object => 0; + n : Bool => 1; + n : Int => 2; + n : String => 3; + n : A => 4; + n : B => 5; + n : C => 6; + esac + }; +}; + +class A { }; + +class B inherits A { }; + +class C inherits B { }; diff --git a/src/tests/normal/case5.cl b/src/tests/normal/case5.cl new file mode 100644 index 000000000..13fe9992a --- /dev/null +++ b/src/tests/normal/case5.cl @@ -0,0 +1,25 @@ + class Main inherits IO { + a : Int; + b : A; + main(): Object + { { + b <- new B; + a <- ( + case b of + n : Bool => 1; + n : Int => 2; + n : Object => 5; + n : String => 3; + n : A => 7; + esac); + out_int(a); +} + + }; +}; + +class A { }; + +class B inherits A { }; + +class C inherits B { }; diff --git a/src/tests/normal/case6.cl b/src/tests/normal/case6.cl new file mode 100644 index 000000000..f9379e24b --- /dev/null +++ b/src/tests/normal/case6.cl @@ -0,0 +1,20 @@ + class Main inherits IO { + a : A = new A; + main(): Object + { { + b <- new B; + a <- ( + case b of + n : C => n.f(); + n : B => n.f(); + esac); + out_int(a); +} + + }; +}; + +class A { f():Int { 3 }; + +class B inherits A { f():Int {4 }; + diff --git a/src/tests/normal/classess1.cl b/src/tests/normal/classess1.cl new file mode 100644 index 000000000..5a5cd42ce --- /dev/null +++ b/src/tests/normal/classess1.cl @@ -0,0 +1,37 @@ +class Main +{ + h:AUTO_TYPE <- new A; + a:AUTO_TYPE <- new D; + main(): Int + { + { + 3; + } + }; +}; + + +class A +{ + b:Int; +}; + +class B inherits A +{ + c:Int; +}; + +class C inherits B +{ + e:Int; +}; + +class D inherits C +{ + f:Int; +}; + +class F inherits D +{ + g:Int; +}; diff --git a/src/tests/normal/cmp_string.cl b/src/tests/normal/cmp_string.cl new file mode 100644 index 000000000..a72564d96 --- /dev/null +++ b/src/tests/normal/cmp_string.cl @@ -0,0 +1,12 @@ +class Main inherits IO { + a : Int; + b : String; + c : String; + main() : Int { + { + c <- "hola"; + b <- "hola" ; + if c=b then 1 else 0 fi; + } + }; +}; diff --git a/src/tests/normal/concat_str.cl b/src/tests/normal/concat_str.cl new file mode 100644 index 000000000..f1c868d47 --- /dev/null +++ b/src/tests/normal/concat_str.cl @@ -0,0 +1,5 @@ +class Main inherits IO { + main() : Main { + out_string("hello".concat("world")) + }; +}; diff --git a/src/tests/normal/div1.cl b/src/tests/normal/div1.cl new file mode 100644 index 000000000..2ee8acfd3 --- /dev/null +++ b/src/tests/normal/div1.cl @@ -0,0 +1,5 @@ + class Main { + main(): Int { + 3/2 + }; +}; diff --git a/src/tests/normal/fib.cl b/src/tests/normal/fib.cl new file mode 100644 index 000000000..2fea92802 --- /dev/null +++ b/src/tests/normal/fib.cl @@ -0,0 +1,8 @@ +class Main inherits IO { + main(): Main { + out_int(fib(20)) + }; + fib(n : Int): Int { + if n <= 1 then 1 else fib(n-1) + fib(n-2) fi + }; +}; diff --git a/src/tests/normal/fib1.cl b/src/tests/normal/fib1.cl new file mode 100644 index 000000000..08ceaede8 --- /dev/null +++ b/src/tests/normal/fib1.cl @@ -0,0 +1,29 @@ +class Main inherits IO { + -- the class has features. Only methods in this case. + main(): Object { + { + out_string("Enter n to find nth fibonacci number!\n"); + out_int(fib(in_int())); + out_string("\n"); + } + }; + + fib(i : Int) : Int { -- list of formals. And the return type of the method. + let a : Int <- 1, + b : Int <- 0, + c : Int <- 0 + in + { + while (not (i = 0)) loop -- expressions are nested. + { + c <- a + b; + i <- i - 1; + b <- a; + a <- c; + } + pool; + c; + } + }; + +}; diff --git a/src/tests/normal/function_call.cl b/src/tests/normal/function_call.cl new file mode 100644 index 000000000..7a484e224 --- /dev/null +++ b/src/tests/normal/function_call.cl @@ -0,0 +1,9 @@ + class Main { + main(): Int { + f(1) + }; + + f(a : Int): Int { + a + }; +}; diff --git a/src/tests/normal/get_type_name.cl b/src/tests/normal/get_type_name.cl new file mode 100644 index 000000000..17692837a --- /dev/null +++ b/src/tests/normal/get_type_name.cl @@ -0,0 +1,9 @@ +class Main inherits IO { + main(): Main { + out_string((new A).type_name()) + }; +}; + +class A { + +}; diff --git a/src/tests/normal/if1.cl b/src/tests/normal/if1.cl new file mode 100644 index 000000000..81d592d10 --- /dev/null +++ b/src/tests/normal/if1.cl @@ -0,0 +1,6 @@ + class Main { + main(): Int { + if true then 1 else 2 fi + }; +}; + diff --git a/src/tests/normal/instantiate1.cl b/src/tests/normal/instantiate1.cl new file mode 100644 index 000000000..9046024dc --- /dev/null +++ b/src/tests/normal/instantiate1.cl @@ -0,0 +1,13 @@ +class Main{ + a: A; + + main(): A { + a <- new A + }; +}; + +class A { + method(): Int{ + 1 + }; +}; diff --git a/src/tests/normal/isvoid1.cl b/src/tests/normal/isvoid1.cl new file mode 100644 index 000000000..75aa06d9c --- /dev/null +++ b/src/tests/normal/isvoid1.cl @@ -0,0 +1,5 @@ + class Main { + main(): Bool { + isVoid 3 + }; +}; diff --git a/src/tests/normal/isvoid2.cl b/src/tests/normal/isvoid2.cl new file mode 100644 index 000000000..44a79d06b --- /dev/null +++ b/src/tests/normal/isvoid2.cl @@ -0,0 +1,5 @@ + class Main { + main(): Bool { + isVoid (new Object) + }; +}; diff --git a/src/tests/normal/len.cl b/src/tests/normal/len.cl new file mode 100644 index 000000000..c2cdcf255 --- /dev/null +++ b/src/tests/normal/len.cl @@ -0,0 +1,5 @@ +class Main inherits IO { + main() : Main { + out_int(("hola").length()) + }; +}; diff --git a/src/tests/normal/let1.cl b/src/tests/normal/let1.cl new file mode 100644 index 000000000..4f4576693 --- /dev/null +++ b/src/tests/normal/let1.cl @@ -0,0 +1,7 @@ + class Main { + main(): Int { + let x : Int <- 3, + y : Int + in x + y + }; +}; diff --git a/src/tests/normal/let2.cl b/src/tests/normal/let2.cl new file mode 100644 index 000000000..642d87469 --- /dev/null +++ b/src/tests/normal/let2.cl @@ -0,0 +1,12 @@ + class Main { + main(): Int { + let x : Int <- 3, + y : Int + in let x: Int, + y: Int <- 4, + z: Int <- 3, + w: Int + in + x + y + z + w + }; +}; diff --git a/src/tests/normal/let_instantiate.cl b/src/tests/normal/let_instantiate.cl new file mode 100644 index 000000000..8a3241bd1 --- /dev/null +++ b/src/tests/normal/let_instantiate.cl @@ -0,0 +1,22 @@ + class Main inherits IO { + main() : Main { + let a : A <- new A, b : B, c : Int <- 12 in + { + b <- a.f(); + c <- b.f(1,2); + out_int(c); + } + }; + }; + +class A { + f() : B { + new B + }; +}; + +class B { + f(a : Int, b : Int) : Int { + a + b + }; +}; diff --git a/src/tests/normal/print_int.cl b/src/tests/normal/print_int.cl new file mode 100644 index 000000000..fd356d991 --- /dev/null +++ b/src/tests/normal/print_int.cl @@ -0,0 +1,5 @@ + class Main inherits IO{ + main(): Main { + out_int(3 + 4 + 5) + }; +}; diff --git a/src/tests/normal/self1.cl b/src/tests/normal/self1.cl new file mode 100644 index 000000000..d3106273e --- /dev/null +++ b/src/tests/normal/self1.cl @@ -0,0 +1,8 @@ + class Main { + main(): String { + self.f() + }; + f(): String { + "Hola mundo" + }; +}; diff --git a/src/tests/normal/selftype.cl b/src/tests/normal/selftype.cl new file mode 100644 index 000000000..3beea0c2c --- /dev/null +++ b/src/tests/normal/selftype.cl @@ -0,0 +1,18 @@ +class Main inherits IO { + a : A ; + main(): Object + { + { + a <- (new B).f(); + out_string(a.type_name()); + + } + + }; +}; + +class A { f() : SELF_TYPE { new SELF_TYPE }; }; + +class B inherits A { }; + +class C inherits B { }; diff --git a/src/tests/normal/simple.cl b/src/tests/normal/simple.cl new file mode 100644 index 000000000..73ab05054 --- /dev/null +++ b/src/tests/normal/simple.cl @@ -0,0 +1,5 @@ + class Main { + main(): Int { + 3 + }; +}; diff --git a/src/tests/normal/simple_attr.cl b/src/tests/normal/simple_attr.cl new file mode 100644 index 000000000..3455135b7 --- /dev/null +++ b/src/tests/normal/simple_attr.cl @@ -0,0 +1,22 @@ + class Main inherits IO { + a : Int; + main(): Main { + { + a <- 12; + out_int(a); + } + }; +}; + +class A { + f() : Int { + 1 + }; +}; + +class B inherits A{ + f() : Int { + 2 + }; +}; + diff --git a/src/tests/normal/substring.cl b/src/tests/normal/substring.cl new file mode 100644 index 000000000..7748f108a --- /dev/null +++ b/src/tests/normal/substring.cl @@ -0,0 +1,5 @@ +class Main inherits IO { + main() : Main { + out_string("Hello World".substr(0,5)) + }; +}; diff --git a/tests/codegen_test.py b/tests/codegen_test.py index 48df768ff..49db4080b 100644 --- a/tests/codegen_test.py +++ b/tests/codegen_test.py @@ -14,4 +14,4 @@ @pytest.mark.parametrize("cool_file", tests) def test_codegen(compiler_path, cool_file): compare_outputs(compiler_path, tests_dir + cool_file, tests_dir + cool_file[:-3] + '_input.txt',\ - tests_dir + cool_file[:-3] + '_output.txt') \ No newline at end of file + tests_dir + cool_file[:-3] + '_output.txt') diff --git a/tests/lexer/iis1_error.txt b/tests/lexer/iis1_error.txt index 9e6d66cac..8a81a2472 100644 --- a/tests/lexer/iis1_error.txt +++ b/tests/lexer/iis1_error.txt @@ -1 +1 @@ -(4, 2) - LexicographicError: ERROR "!" +(4,2) - LexicographicError: ERROR "!" diff --git a/tests/lexer_test.py b/tests/lexer_test.py index 2a27223d3..aec99e4b1 100644 --- a/tests/lexer_test.py +++ b/tests/lexer_test.py @@ -2,12 +2,13 @@ import os from utils import compare_errors -tests_dir = __file__.rpartition('/')[0] + '/lexer/' -tests = [(file) for file in os.listdir(tests_dir) if file.endswith('.cl')] - +tests_dir = __file__.rpartition("/")[0] + "/lexer/" +tests = [(file) for file in os.listdir(tests_dir) if file.endswith(".cl")] @pytest.mark.lexer @pytest.mark.error @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/parser_test.py b/tests/parser_test.py index 129c0f20a..5c98fd1b2 100644 --- a/tests/parser_test.py +++ b/tests/parser_test.py @@ -2,12 +2,13 @@ import os from utils import compare_errors -tests_dir = __file__.rpartition('/')[0] + '/parser/' -tests = [(file) for file in os.listdir(tests_dir) if file.endswith('.cl')] - +tests_dir = __file__.rpartition("/")[0] + "/parser/" +tests = [(file) for file in os.listdir(tests_dir) if file.endswith(".cl")] @pytest.mark.parser @pytest.mark.error @pytest.mark.run(order=2) @pytest.mark.parametrize("cool_file", tests) def test_parser_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/semantic_test.py b/tests/semantic_test.py index cac9cd78b..5e8fae4b7 100644 --- a/tests/semantic_test.py +++ b/tests/semantic_test.py @@ -2,8 +2,9 @@ import os from utils import compare_errors, first_error_only_line -tests_dir = __file__.rpartition('/')[0] + '/semantic/' -tests = [(file) for file in os.listdir(tests_dir) if file.endswith('.cl')] +tests_dir = __file__.rpartition("/")[0] + "/semantic/" +tests = [(file) for file in os.listdir(tests_dir) if file.endswith(".cl")] + @pytest.mark.semantic @pytest.mark.error @@ -11,4 +12,4 @@ @pytest.mark.parametrize("cool_file", tests) def test_semantic_errors(compiler_path, cool_file): compare_errors(compiler_path, tests_dir + cool_file, tests_dir + cool_file[:-3] + '_error.txt', \ - cmp=first_error_only_line) \ No newline at end of file + cmp=first_error_only_line) diff --git a/tests/utils/utils.py b/tests/utils/utils.py index 961cf7cbc..957ad9e0a 100644 --- a/tests/utils/utils.py +++ b/tests/utils/utils.py @@ -15,6 +15,7 @@ def parse_error(error: str): merror = re.fullmatch(ERROR_FORMAT, error) + assert merror, BAD_ERROR_FORMAT % error return (t(x) for t, x in zip([int, int, str, str], merror.groups())) @@ -47,6 +48,7 @@ def compare_errors(compiler_path: str, cool_file_path: str, error_file_path: str try: sp = subprocess.run(['bash', compiler_path, cool_file_path], capture_output=True, timeout=timeout) return_code, output = sp.returncode, sp.stdout.decode() + print(sp.stderr) except subprocess.TimeoutExpired: assert False, COMPILER_TIMEOUT