diff --git a/.gitignore b/.gitignore index 4acafde1..8e75323c 100644 --- a/.gitignore +++ b/.gitignore @@ -385,6 +385,9 @@ celerybeat-schedule # mkdocs documentation /site +# virtual env +venv/* + # mypy .mypy_cache/ .dmypy.json @@ -394,6 +397,7 @@ dmypy.json .pyre/ ### VisualStudioCode ### +.vscode .vscode/* !.vscode/settings.json !.vscode/tasks.json @@ -407,4 +411,10 @@ dmypy.json # End of https://www.gitignore.io/api/visualstudiocode,linux,latex,python # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) +# Cool file for test purposes +src/test.cl +# Generated CIL files +*.cil +# Generated Mips files +*.mips diff --git a/Readme.md b/Readme.md index 55093d31..7d1d188e 100644 --- a/Readme.md +++ b/Readme.md @@ -1,7 +1,7 @@ # COOL: Proyecto de Compilación -[![Build Status](https://travis-ci.org/matcom/cool-compiler-2020.svg?branch=master)](https://travis-ci.org/matcom/cool-compiler-2020) +[![Build Status](https://travis-ci.com/codestrange/cool-compiler-2020.svg?branch=master)](https://travis-ci.com/codestrange/cool-compiler-2020) > Proyecto base para el compilador de 4to año en Ciencia de la Computación. diff --git a/doc/Readme.md b/doc/Readme.md index 402477c8..9ef8d6dd 100644 --- a/doc/Readme.md +++ b/doc/Readme.md @@ -1,39 +1,27 @@ # Documentación -> Introduzca sus datos (de todo el equipo) en la siguiente tabla: - **Nombre** | **Grupo** | **Github** --|--|-- -Nombre1 Apellido1 Apellido2 | C4xx | [@github_user](https://github.com/) -Nombre2 Apellido1 Apellido2 | C4xx | [@github_user](https://github.com/) -Nombre3 Apellido1 Apellido2 | C4xx | [@github_user](https://github.com/) - -## Readme - -Modifique el contenido de este documento para documentar de forma clara y concisa los siguientes aspectos: - -- Cómo ejecutar (y compilar si es necesario) su compilador. -- Requisitos adicionales, dependencias, configuración, etc. -- Opciones adicionales que tenga su compilador. - -### Sobre los Equipos de Desarrollo - -Para desarrollar el compilador del lenguaje COOL se trabajará en equipos de 2 o 3 integrantes. El proyecto de Compilación será recogido y evaluado únicamente a través de Github. Es imprescindible tener una cuenta de Github para cada participante, y que su proyecto esté correctamente hosteado en esta plataforma. Próximamente les daremos las instrucciones mínimas necesarias para ello. +Carlos Bermudez Porto | C412 | [@cbermudez97](https://github.com/cbermudez97) +Leynier Gutiérrez González | C412 | [@leynier](https://github.com/leynier) +Tony Raúl Blanco Fernández | C411 | [@70nybl4nc0](https://github.com/70nybl4nc0) -### Sobre los Materiales a Entregar +## Uso -Para la evaluación del proyecto Ud. debe entregar un informe en formato PDF (`report.pdf`) que resuma de manera organizada y comprensible la arquitectura e implementación de su compilador. -El documento **NO** debe exceder las 5 cuartillas. -En él explicará en más detalle su solución a los problemas que, durante la implementación de cada una de las fases del proceso de compilación, hayan requerido de Ud. especial atención. +Para instalar las dependencias deberá correr en la terminal el comando: -### Estructura del reporte +```bash +pip install -r requirements.txt +``` -Usted es libre de estructurar su reporte escrito como más conveniente le parezca. A continuación le sugerimos algunas secciones que no deberían faltar, aunque puede mezclar, renombrar y organizarlas de la manera que mejor le parezca: +Para ejecutar el compilador se deberá correr en la terminal el comando: -- **Uso del compilador**: detalles sobre las opciones de líneas de comando, si tiene opciones adicionales (e.j., `--ast` genera un AST en JSON, etc.). Básicamente lo mismo que pondrá en este Readme. -- **Arquitectura del compilador**: una explicación general de la arquitectura, en cuántos módulos se divide el proyecto, cuantas fases tiene, qué tipo de gramática se utiliza, y en general, como se organiza el proyecto. Una buena imagen siempre ayuda. -- **Problemas técnicos**: detalles sobre cualquier problema teórico o técnico interesante que haya necesitado resolver de forma particular. +```bash +python coolc.py +``` -## Sobre la Fecha de Entrega +Adicionalmente se le pueden adicionar al compilador diferentes argumentos opcionales. Estos argumentos son los siguientes: -Se realizarán recogidas parciales del proyecto a lo largo del curso. En el Canal de Telegram [@matcom_cmp](https://t.me/matcom_cmp) se anunciará la fecha y requisitos de cada primera entrega. +* --cil: Este argumento indica al compilador que se desea generar un archivo con el código CIL generado en las etapas intermedias del proceso de compilación. +* --verbose: Estos argumentos opcionales indican al compilador que se desea ejecutar el mismo en modo verboso. El modo verboso agrega a la salida estándar del compilador información adicional. La estructura del ast de COOL generado y datos sobre los tipos definidos en el código son parte de esta información adicional. +* --help: Estos argumentos mostrarán una ayuda mínima referente al uso del comando, así como de los demás argumentos opcionales del mismo. diff --git a/doc/report.md b/doc/report.md new file mode 100644 index 00000000..939495e7 --- /dev/null +++ b/doc/report.md @@ -0,0 +1,89 @@ +# Reporte del Proyecto de Complementos de Compilación + +## Uso del Compilador + +Para instalar las dependencias deberá correr en la terminal el comando: + +```bash +pip install -r requirements.txt +``` + +Para ejecutar el compilador se deberá correr en la terminal el comando: + +```bash +python coolc.py +``` + +Adicionalmente se le pueden adicionar al compilador diferentes argumentos opcionales. Estos argumentos son los siguientes: + +* --cil: Este argumento indica al compilador que se desea generar un archivo con el código CIL generado en las etapas intermedias del proceso de compilación. +* --verbose: Estos argumentos opcionales indican al compilador que se desea ejecutar el mismo en modo verboso. El modo verboso agrega a la salida estándar del compilador información adicional. La estructura del ast de COOL generado y datos sobre los tipos definidos en el código son parte de esta información adicional. +* --help: Estos argumentos mostrarán una ayuda mínima referente al uso del comando, así como de los demás argumentos opcionales del mismo. + +## Arquitectura del Compilador + +El compilador cuenta con un módulo principal llamado cmp en el cual se encuentra todo el código para usar el mismo. A su vez el módulo principal está dividido en dos submódulos cool_lang y cil. + +### COOL + +El submódulo cool_lang contiene las herramientas para la realización de las fases de análisis lexicográfico, análisis sintáctico y análisis semántico del código escrito en COOL. Como resultado de la utilización de las herramientas de este módulo se puede obtener del código un AST válido. + +Las principales clases para realizar estos procesos son: + +* COOL_LEXER: Esta clase es la encargada de la tokenización del código escrito en el archivo de entrada y notificar los posibles errores léxicos en los mismos. +* COOL_PARSER: Esta clase permite a partir del código tokenizado construir el AST de COOL y notificar los posibles errores sintácticos. +* COOL_CHECKER: Esta clase es la encargada de realizar los diferentes recorridos al AST de para verificar que este correcto y de notificar de los errores encontrados. En adición a la verificación de errores sintácticos, se producirá también una clase Context con todos los datos necesarios acerca de los tipos definidos en el código. + +### CIL + +El submódulo cil contiene las herramientas necesarias para obtener un código de MIPS a partir de un AST correctamente verificado. Como pasos intermedios se genera un AST de CIL, siendo posible obtener también un código en este lenguaje. + +Para esta tarea las principales clases usadas son: + +* COOL_TO_CIL_VISITOR: Esta clase es la encargada de transformar el AST de COOL en un AST de CIL. Ya en esta fase se asume que los errores en la entrada no existen y que el AST de COOL es uno correcto. +* CIL_TO_MIPS: Esta clase permite generar de un AST de CIL el código MIPS equivalente al mismo. + +## Problemas técnicos + +Como se puede observar por lo anteriormente descrito el proceso de compilación ocurrió en cinco fases, tres de ellas enfocadas en la validación y corrección de errores en la entrada y dos de ellas en la generación de código MIPS. + +A continuación se enumeran algunas de las decisiones tomadas durante las distintas fases del proceso. +Las fases de análisis léxico y sintáctico se completaron de manera normal, haciendo uso de las definiciones de tokens dadas por el Manual de COOL. Sin embargo, la gramática de COOL definida en el Manual es ambigua, y por tanto, requirió que su transformación a una gramática que no lo fuera. Para estas tareas se utilizó el módulo de Python ply. Esta herramienta se utilizó para definir las expresiones regulares para los tokens y para la definición de la gramática LR equivalente a la de COOL. + +De igual forma durante la fase de análisis semántico se utilizaron tres recorridos para realizar la validación del AST construido. + +El primero de los recorridos fue el encargado de recolectar todos los tipos definidos. Al finalizar la recolección se verificó que no existiera herencia cíclica en la jerarquía de tipos. Una vez que se verificó esto se tomó la decisión de ordenar los nodos del AST que representan a las definiciones de clases de forma que en los siguientes recorridos si se visita una definición de un tipo su tipo padre ya habrá sido visitado. Esto se puede lograr ya que la jerarquía de CIL está unificada en la clase Object. + +El segundo de los recorridos fue el encargado de construir los tipos recolectados en el primero. En este segundo recorrido fue donde se agregó la información referentes a los tipos básicos que trae el lenguaje y que el programador puede usar sin haberlos definido. Esta información, si bien carecía de estructuras en el AST, garantiza la correcta evaluación futura de las reglas de tipos. + +El tercer recorrido fue el encargado propiamente de realizar el análisis semántico del AST. Esto fue posible de hacer gracias a la información obtenida en los recorridos anteriores. Este análisis se realizó siguiendo las reglas definidas en el Manual. + +Una vez realizado el análisis semántico se pasa a la generación de código. Como primer paso para esto se definió el lenguaje intermedio CIL siendo usado como base para el mismo algunas de las ideas dadas en el curso anterior. Si bien muchas definiciones son compartidas si se agregaron muchos nuevos nodos para expresar diferentes acciones y otros fueron transformados. Es objetivo de CIL alcanzar un equilibrio entre la abstracción que brinda COOL y una cierta cercanía en estructura a MIPS que permitiera luego una mejor y más sencilla transformación hacia MIPS. + +Del AST obtenido en las fases anteriores se realizó una transformación hacia un AST de CIL que expresara el mismo comportamiento que el construido en COOL. + +En esta fase surgió la necesidad de resolver el problema de los tipos por valor Int y Bool, y de los Strings como casos especiales, al ser usados como un objeto por referencia. Para esto se definió una representación en memorias para los casos en que esto ocurriera. Todos estos tipos cuando fuera necesario tratarlos como objetos por referencias, para por ejemplo acceder a las funciones heredadas de la clase Object o las propiamente definidas por el tipo String, serían encapsulados en instancias similares a los otros tipos, la cual contaría con un atributo value en el cual se almacenaría el valor real de los mismos. + +Es también en esta fase donde se da por fin una implementación real a los tipos y funciones básicas definidas por el lenguaje. Estas hacen uso de nodos especiales del AST que solo son instanciadas para su uso desde COOL a través de las funciones básicas. Ejemplo son los métodos para la entrada y salida de datos brindada por la clase IO. Con estas estructuras definidas en el AST de CIL, durante la traducción a MIPS, ellas serán generadas automáticamente. + +Un caso notable en esta transformación es el de las expresiones del tipo case. La naturaleza de esta expresión implica que sea posible conocer cuando el tipo dinámico de una expresión es subtipo de otro. Debido a la complejidad de realizar esta operación tanto en CIL como en MIPS se decidió realizar una transformación al AST de las expresiones case durante la traducción. Para esto se agregaron nuevos nodos de forma que la evaluación del case cambiará. La idea para esto fue agregar todos los subtipos de todas las clases usadas en el case al mismo y que tuvieran como cuerpo la misma expresión que tiene el padre más cercano definido en el case original. Luego estos fueron reordenados desde el más específico hasta el más general. Esta reordenación permitió que solo hiciera falta verificar que el tipo dinámico de la expresión case fuera el mismo que el del caso analizado, y que fuera el primero que cumpliera esto el caso correcto a aplicar. + +En la última fase de generación se hizo importante definir lo que sería la estructura de la instancia un tipo en memoria. Según las principales necesidades de los nodos más abstractos de CIL, se trató de que la información necesaria para el correcto funcionamiento de los mismos estuviera presente en cada instancia de un tipo cualquiera. Esto para eliminar la necesidad de crear cualquier estructura de un tipo y evitar realizar operaciones complejas en MIPS. También se buscó lograr una forma en que se pudiera cumplir con el comportamiento polimórfico de los tipos. + +Según estas necesidades la representación de las instancias de los tipos en memoria puede verse de la siguiente forma: + +| Representación en Memoria | +|:------------------------------------------:| +| Tipo | +| Nombre del Tipo | +| Tamaño en Memoria | +| Atributos y funciones de las clases padres | +| Atributos propios | +| Funciones propias | + +En esta estructura los tipos tendrán siempre alcanzable y a posiciones fijas los siguientes datos: + +* Un entero que identifica únicamente a las instancias de un mismo tipo. Dos instancias serán del mismo tipo si sus enteros de tipo son iguales. +* Un entero que será la referencia al inicio de un string, previamente almacenado en memoria, que contiene el nombre del tipo. +* Un entero que es el valor el tamaño de la instancia en memoria. +* Luego enteros que referencian a los atributos y funciones del tipo instanciado. Estos, para permitir el polimorfismo, se ordenarán siguiendo el orden en que fueron declarados a lo largo de la jerarquía del tipo instanciado, desde el más general hasta el más específico. Por ejemplo si tenemos los tipos A, B y C. Primero se encontrarán los atributos de A, luego las funciones de A, los atributos de B, las funciones de B, los atributos de C y por último las funciones de C. Cada grupo de atributos o funciones estarán ordenados según el orden en que fueron declarados en ese tipo. Si alguna función de un tipo más general es sobre escrita por algún tipo más específico esta seguirá apareciendo en el conjunto del tipo más general y no en la del específico, pero con la referencia cambiada hacia la dirección de la nueva implementación. Esto permite que si un C es tratado como un A llamadas a funciones de A ejecutarán las funciones sobre escritas de C. diff --git a/doc/report.pdf b/doc/report.pdf new file mode 100644 index 00000000..3290719a Binary files /dev/null and b/doc/report.pdf differ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..83c3600e --- /dev/null +++ b/poetry.lock @@ -0,0 +1,473 @@ +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "20.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] + +[[package]] +name = "black" +version = "20.8b1" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +appdirs = "*" +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.6,<1" +regex = ">=2020.1.8" +toml = ">=0.10.1" +typed-ast = ">=1.4.0" +typing-extensions = ">=3.7.4" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] + +[[package]] +name = "click" +version = "7.1.2" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.6.0a1,<2.7.0" +pyflakes = ">=2.2.0,<2.3.0" + +[[package]] +name = "isort" +version = "5.6.4" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "more-itertools" +version = "8.6.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "20.4" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pyparsing = ">=2.0.2" +six = "*" + +[[package]] +name = "pathspec" +version = "0.8.1" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +dev = ["pre-commit", "tox"] + +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "py" +version = "1.9.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=17.4.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +more-itertools = ">=4.0.0" +packaging = "*" +pluggy = ">=0.12,<1.0" +py = ">=1.5.0" +wcwidth = "*" + +[package.extras] +checkqa-mypy = ["mypy (==v0.761)"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-ordering" +version = "0.6" +description = "pytest plugin to run your tests in a specific order" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pytest = "*" + +[[package]] +name = "regex" +version = "2020.11.13" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typer" +version = "0.3.2" +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,<7.2.0" + +[package.extras] +test = ["pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.782)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)", "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)"] +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)"] + +[[package]] +name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.8" +content-hash = "8afdf5700effa7074ff04820a417238f12132d36cda3b8a6f21a4b0eb40b151a" + +[metadata.files] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +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-20.2.0-py2.py3-none-any.whl", hash = "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"}, + {file = "attrs-20.2.0.tar.gz", hash = "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594"}, +] +black = [ + {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +flake8 = [ + {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, + {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, +] +isort = [ + {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, + {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +more-itertools = [ + {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, + {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, +] +pathspec = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +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.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, +] +pycodestyle = [ + {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, + {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, +] +pyflakes = [ + {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, + {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytest = [ + {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, + {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, +] +pytest-ordering = [ + {file = "pytest-ordering-0.6.tar.gz", hash = "sha256:561ad653626bb171da78e682f6d39ac33bb13b3e272d406cd555adb6b006bda6"}, + {file = "pytest_ordering-0.6-py2-none-any.whl", hash = "sha256:27fba3fc265f5d0f8597e7557885662c1bdc1969497cd58aff6ed21c3b617de2"}, + {file = "pytest_ordering-0.6-py3-none-any.whl", hash = "sha256:3f314a178dbeb6777509548727dc69edf22d6d9a2867bf2d310ab85c403380b6"}, +] +regex = [ + {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, + {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, + {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, + {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, + {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, + {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, + {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, + {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, + {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, + {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, + {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, + {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, + {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, + {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, + {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, + {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, + {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, +] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, + {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, + {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, + {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, +] +typer = [ + {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, + {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, +] +typing-extensions = [ + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..bfbdc16e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[tool.poetry] +name = "cool-compiler" +version = "0.1.0" +description = "" +authors = ["Carlos Bermudez Porto ", "Leynier Gutiérrez González ", "Tony Raúl Blanco Fernández "] +homepage = "https://github.com/codestrange/cool-compiler-2020" +repository = "https://github.com/codestrange/cool-compiler-2020" +documentation = "https://github.com/codestrange/cool-compiler-2020" +readme = "README.md" +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.8" +ply = "^3.11" +typer = "^0.3.2" + +[tool.poetry.dev-dependencies] +pytest = "^5.2" +pytest-ordering = "^0.6" +flake8 = "^3.8.4" +black = "^20.8b1" +isort = "^5.6.4" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 9eb0cad1..5772bf11 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,28 @@ -pytest -pytest-ordering +appdirs==1.4.4; python_version >= "3.6" +atomicwrites==1.4.0; python_version >= "3.5" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.5" and python_full_version >= "3.4.0" +attrs==20.2.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +black==20.8b1; python_version >= "3.6" +click==7.1.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +colorama==0.4.4; python_version >= "3.5" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.5" and python_full_version >= "3.5.0" +flake8==3.8.4; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") +isort==5.6.4; python_version >= "3.6" and python_version < "4.0" +mccabe==0.6.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +more-itertools==8.6.0; python_version >= "3.5" +mypy-extensions==0.4.3; python_version >= "3.6" +packaging==20.4; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +pluggy==0.13.1; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +ply==3.11 +py==1.9.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +pycodestyle==2.6.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +pyflakes==2.2.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +pyparsing==2.4.7; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +pytest-ordering==0.6 +pytest==5.4.3; python_version >= "3.5" +regex==2020.11.13; python_version >= "3.6" +six==1.15.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" +typed-ast==1.4.1; python_version >= "3.6" +typer==0.3.2; python_version >= "3.6" +typing-extensions==3.7.4.3; python_version >= "3.6" +wcwidth==0.2.5; python_version >= "3.5" diff --git a/src/cmp/__init__.py b/src/cmp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/cmp/cil/__init__.py b/src/cmp/cil/__init__.py new file mode 100644 index 00000000..fa98c4b2 --- /dev/null +++ b/src/cmp/cil/__init__.py @@ -0,0 +1,3 @@ +from .cil_to_mips import CIL_TO_MIPS # noqa:F401 +from .cool_to_cil import COOL_TO_CIL_VISITOR # noqa:F401 +from .formatter import CIL_FORMATTER # noqa:F401 diff --git a/src/cmp/cil/ast.py b/src/cmp/cil/ast.py new file mode 100644 index 00000000..31e8aa67 --- /dev/null +++ b/src/cmp/cil/ast.py @@ -0,0 +1,284 @@ +class Node: + pass + + +class ProgramNode(Node): + def __init__(self, dottypes, dotdata, dotcode): + self.dottypes = dottypes + self.dotdata = dotdata + self.dotcode = dotcode + + +class TypeNode(Node): + def __init__(self, name): + self.name = name + self.name_dir = "" + self.attributes = [] + self.methods = [] + self.features = [] + + +class DataNode(Node): + def __init__(self, vname, value): + self.name = vname + self.value = value + + +class FunctionNode(Node): + def __init__(self, fname, params, localvars, instructions): + self.name = fname + self.params = params + self.localvars = localvars + self.instructions = instructions + + +class ParamNode(Node): + def __init__(self, name): + self.name = name + + +class LocalNode(Node): + def __init__(self, name): + self.name = name + + +class InstructionNode(Node): + pass + + +class ErrorNode(InstructionNode): + def __init__(self, error=1): + self.error = error + + +class CopyNode(InstructionNode): + def __init__(self, dest, obj): + self.dest = dest + self.obj = obj + + +class TypeNameNode(InstructionNode): + def __init__(self, dest, typex): + self.dest = dest + self.type = typex + + +class AssignNode(InstructionNode): + def __init__(self, dest, source): + self.dest = dest + self.source = source + + +class ComplementNode(InstructionNode): + def __init__(self, dest, body): + self.dest = dest + self.body = body + + +class NotNode(InstructionNode): + def __init__(self, dest, body): + self.dest = dest + self.body = body + + +class IsVoidNode(InstructionNode): + def __init__(self, dest, body): + self.dest = dest + self.body = body + + +class VoidNode(InstructionNode): + def __init__(self, dest): + self.dest = dest + + +class ArithmeticNode(InstructionNode): + def __init__(self, dest, left, right): + self.dest = dest + self.left = left + self.right = right + + +class LessNode(ArithmeticNode): + pass + + +class EqualNode(ArithmeticNode): + pass + + +class LessEqNode(ArithmeticNode): + pass + + +class PlusNode(ArithmeticNode): + pass + + +class MinusNode(ArithmeticNode): + pass + + +class StarNode(ArithmeticNode): + pass + + +class DivNode(ArithmeticNode): + pass + + +class GetAttribNode(InstructionNode): + def __init__(self, dest, obj, attrib, typex): + self.dest = dest + self.obj = obj + self.attrib = attrib + self.type = typex + + +class SetAttribNode(InstructionNode): + def __init__(self, obj, attrib, value, typex): + self.obj = obj + self.attrib = attrib + self.value = value + self.type = typex + + +class GetIndexNode(InstructionNode): + def __init__(self, dest, array, index): + self.dest = dest + self.array = array + self.index = index + + +class SetIndexNode(InstructionNode): + def __init__(self, array, index, value): + self.array = array + self.index = index + self.value = value + + +class SetNode(InstructionNode): + def __init__(self, dest, value): + self.dest = dest + self.value = value + + +class AllocateNode(InstructionNode): + def __init__(self, dest, itype): + self.dest = dest + self.type = itype + + +class ArrayNode(InstructionNode): + def __init__(self, dest, size): + self.dest = dest + self.size = size + + +class TypeOfNode(InstructionNode): + def __init__(self, obj, dest): + self.obj = obj + self.dest = dest + + +class StaticTypeOfNode(InstructionNode): + def __init__(self, typex, dest): + self.type = typex + self.dest = dest + + +class LabelNode(InstructionNode): + def __init__(self, label): + self.label = label + + +class GotoNode(InstructionNode): + def __init__(self, label): + self.label = label + + +class GotoIfNode(InstructionNode): + def __init__(self, value, label): + self.value = value + self.label = label + + +class StaticCallNode(InstructionNode): + def __init__(self, function, dest): + self.function = function + self.dest = dest + + +class DynamicCallNode(InstructionNode): + def __init__(self, obj, xtype, method, dest): + self.obj = obj + self.type = xtype + self.method = method + self.dest = dest + + +class AbortNode(InstructionNode): + pass + + +class ArgNode(InstructionNode): + def __init__(self, name): + self.name = name + + +class CleanArgsNode(InstructionNode): + def __init__(self, nargs): + self.nargs = nargs + + +class ReturnNode(InstructionNode): + def __init__(self, value=None): + self.value = value + + +class LengthNode(InstructionNode): + def __init__(self, dest, msg): + self.dest = dest + self.msg = msg + + +class ConcatNode(InstructionNode): + def __init__(self, dest, msg1, msg2): + self.dest = dest + self.msg1 = msg1 + self.msg2 = msg2 + + +class SubstringNode(InstructionNode): + def __init__(self, dest, msg1, start, length): + self.dest = dest + self.msg1 = msg1 + self.start = start + self.length = length + + +class StringEqualNode(InstructionNode): + def __init__(self, dest, msg1, msg2): + self.dest = dest + self.msg1 = msg1 + self.msg2 = msg2 + + +class ReadIntNode(InstructionNode): + def __init__(self, dest): + self.dest = dest + + +class ReadStrNode(InstructionNode): + def __init__(self, dest): + self.dest = dest + + +class PrintIntNode(InstructionNode): + def __init__(self, str_addr): + self.str_addr = str_addr + + +class PrintStrNode(InstructionNode): + def __init__(self, str_addr): + self.str_addr = str_addr diff --git a/src/cmp/cil/basic_transform.py b/src/cmp/cil/basic_transform.py new file mode 100644 index 00000000..1572ce94 --- /dev/null +++ b/src/cmp/cil/basic_transform.py @@ -0,0 +1,401 @@ +import re +from typing import Any, Union, cast + +from ..cool_lang.semantics.semantic_utils import Attribute, Type +from .ast import ( + AbortNode, + ConcatNode, + CopyNode, + DataNode, + ErrorNode, + FunctionNode, + GetAttribNode, + GotoIfNode, + LabelNode, + LengthNode, + LessEqNode, + LocalNode, + ParamNode, + PlusNode, + PrintIntNode, + PrintStrNode, + ReadIntNode, + ReadStrNode, + ReturnNode, + SetAttribNode, + SetNode, + StaticCallNode, + SubstringNode, + TypeNameNode, + TypeNode, +) + + +class VariableInfo: + def __init__(self, name, vtype): + self.name = name + self.type = vtype + + +class BASE_COOL_CIL_TRANSFORM: + def __init__(self, context): + self.dottypes = [] + self.dotdata = [] + self.dotcode = [] + self.current_type = None + self.current_method = None + self.current_function = None + self.context = context + self.label_count = 0 + self.build_basics() + + @property + def params(self): + return self.current_function.params + + @property + def localvars(self): + return self.current_function.localvars + + @property + def instructions(self): + return self.current_function.instructions + + def register_param(self, vinfo): + param_node = ParamNode(vinfo.name) + self.params.append(param_node) + return vinfo.name + + def unpack_type_by_value(self, value: str, static_type: Union[Type, str]): + arg = static_type if isinstance(static_type, str) else static_type.name + if arg in ["Int", "String", "Bool"]: + raw_value = self.define_internal_local() + self.register_instruction( + GetAttribNode( + raw_value, + value, + "value", + arg, + ) + ) + return raw_value + return value + + def pack_type_by_value(self, value: str, static_type: Union[Type, str]): + arg = static_type if isinstance(static_type, str) else static_type.name + if arg in ["Int", "Bool", "String"]: + packed = self.define_internal_local() + self.register_instruction(StaticCallNode(f"init_{arg}", packed)) + self.register_instruction(SetAttribNode(packed, "value", value, arg)) + return packed + return value + + def register_local(self, vinfo): + func_name = re.findall(r"^function_(.+)$", self.current_function.name) + if func_name: + func_name = func_name[0] + else: + func_name = self.current_function.name + vinfo.name = f"local_{func_name}_{vinfo.name}_{len(self.localvars)}" + local_node = LocalNode(vinfo.name) + self.localvars.append(local_node) + return vinfo.name + + def define_internal_local(self): + vinfo = VariableInfo("internal", None) + return self.register_local(vinfo) + + def register_instruction(self, instruction): + self.instructions.append(instruction) + return instruction + + def to_function_name(self, method_name, type_name): + return f"function_{method_name}_at_{type_name}" + + def to_label_name(self, label_name): + self.label_count += 1 + return ( + f"label_{label_name}{self.label_count}_at_{self.current_function.name[9:]}" + ) + + def register_function(self, function_name): + function_node = FunctionNode(function_name, [], [], []) + self.dotcode.append(function_node) + return function_node + + def register_type(self, name): + type_node = TypeNode(name) + self.dottypes.append(type_node) + return type_node + + def register_data(self, value): + data_node = None + for data in self.dotdata: + if data.value == value: + data_node = data + break + else: + vname = f"data_{len(self.dotdata)}" + data_node = DataNode(vname, value) + self.dotdata.append(data_node) + return data_node + + def build_basics(self): + self.build_basic_object() + self.build_basic_int() + self.build_basic_bool() + self.build_basic_string() + self.build_basic_io() + + def get_features(self): + return [ + feature.name + if isinstance(feature, Attribute) + else ( + feature[0].name, + self.to_function_name(feature[0].name, feature[1].name), + ) + for feature in self.current_type.get_all_features() + ] + + def build_basic_int(self): + self.current_type = self.context.get_type("Int") + type_node = self.register_type("Int") + type_node.name_dir = self.register_data("Int").name + type_node.attributes = [ + attr.name for attr in self.current_type.get_all_attributes() + ] + ["value"] + type_node.methods = [ + (method.name, self.to_function_name(method.name, typex.name)) + for method, typex in self.current_type.get_all_methods() + ] + type_node.features = self.get_features() + [cast(Any, "value")] + + def build_basic_bool(self): + self.current_type = self.context.get_type("Bool") + type_node = self.register_type("Bool") + type_node.name_dir = self.register_data("Bool").name + type_node.attributes = [ + attr.name for attr in self.current_type.get_all_attributes() + ] + ["value"] + type_node.methods = [ + (method.name, self.to_function_name(method.name, typex.name)) + for method, typex in self.current_type.get_all_methods() + ] + type_node.features = self.get_features() + [cast(Any, "value")] + + def build_basic_object(self): + self.current_type = self.context.get_type("Object") + type_node = self.register_type("Object") + type_node.name_dir = self.register_data("Object").name + type_node.attributes = [ + attr.name for attr in self.current_type.get_all_attributes() + ] + type_node.methods = [ + (method.name, self.to_function_name(method.name, typex.name)) + for method, typex in self.current_type.get_all_methods() + ] + type_node.features = self.get_features() + # abort function + self.current_method = self.current_type.get_method("abort") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + type_name = self.define_internal_local() + abort_msg = self.register_data("Abort called from class ").name + eol = self.register_data("\n").name + msg = self.define_internal_local() + msg_eol = self.define_internal_local() + self.register_instruction(TypeNameNode(type_name, self_local)) + self.register_instruction(ConcatNode(msg, abort_msg, type_name)) + self.register_instruction(ConcatNode(msg_eol, msg, eol)) + self.register_instruction(PrintStrNode(msg_eol)) + self.register_instruction(AbortNode()) + self.current_method = self.current_function = None + # copy function + self.current_method = self.current_type.get_method("copy") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + copy_inst = self.define_internal_local() + self.register_instruction(CopyNode(copy_inst, self_local)) + self.register_instruction(ReturnNode(copy_inst)) + self.current_method = self.current_function = None + # type_name + self.current_method = self.current_type.get_method("type_name") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + type_name_inst = self.define_internal_local() + self.register_instruction(TypeNameNode(type_name_inst, self_local)) + # type_name_inst = self.pack_type_by_value(type_name_inst, "String") + self.register_instruction(ReturnNode(type_name_inst)) + self.current_method = self.current_function = None + self.current_type = None + + def build_basic_io(self): + self.current_type = self.context.get_type("IO") + type_node = self.register_type("IO") + type_node.name_dir = self.register_data("IO").name + type_node.attributes = [ + attr.name for attr in self.current_type.get_all_attributes() + ] + type_node.methods = [ + (method.name, self.to_function_name(method.name, typex.name)) + for method, typex in self.current_type.get_all_methods() + ] + type_node.features = self.get_features() + # in_string + self.current_method = self.current_type.get_method("in_string") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + _ = self.register_param(VariableInfo("self", None)) + result_msg = self.define_internal_local() + self.register_instruction(ReadStrNode(result_msg)) + # string_inst = self.pack_type_by_value(result_msg, "String") + self.register_instruction(ReturnNode(result_msg)) + self.current_method = self.current_function = None + # out_string + self.current_method = self.current_type.get_method("out_string") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + string_inst = self.register_param(VariableInfo("x", None)) + # out_msg = self.unpack_type_by_value(string_inst, "String") + self.register_instruction(PrintStrNode(string_inst)) + self.register_instruction(ReturnNode(self_local)) + self.current_method = self.current_function = None + # in_int + self.current_method = self.current_type.get_method("in_int") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + _ = self.register_param(VariableInfo("self", None)) + result_int = self.define_internal_local() + self.register_instruction(ReadIntNode(result_int)) + # result = self.pack_type_by_value(result_int, "Int") + self.register_instruction(ReturnNode(result_int)) + self.current_method = self.current_function = None + # out_int + self.current_method = self.current_type.get_method("out_int") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + int_inst = self.register_param(VariableInfo("x", None)) + # out_int = self.unpack_type_by_value(int_inst, "Int") + self.register_instruction(PrintIntNode(int_inst)) + self.register_instruction(ReturnNode(self_local)) + self.current_method = self.current_function = None + self.current_type = None + + def build_basic_string(self): + self.current_type = self.context.get_type("String") + type_node = self.register_type("String") + type_node.name_dir = self.register_data("String").name + type_node.attributes = [ + attr.name for attr in self.current_type.get_all_attributes() + ] + ["value"] + type_node.methods = [ + (method.name, self.to_function_name(method.name, typex.name)) + for method, typex in self.current_type.get_all_methods() + ] + type_node.features = self.get_features() + [cast(Any, "value")] + # length + self.current_method = self.current_type.get_method("length") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + length_var = self.define_internal_local() + str_raw = self.unpack_type_by_value(self_local, "String") + self.register_instruction(LengthNode(length_var, str_raw)) + # length = self.pack_type_by_value(length_var, "Int") + self.register_instruction(ReturnNode(length_var)) + self.current_method = self.current_function = None + # concat + self.current_method = self.current_type.get_method("concat") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + param_local = self.register_param(VariableInfo("s", None)) + str_raw = self.unpack_type_by_value(self_local, "String") + # str_raw2 = self.unpack_type_by_value(param_local, "String") + result_msg = self.define_internal_local() + self.register_instruction(ConcatNode(result_msg, str_raw, param_local)) + # string_inst = self.pack_type_by_value(result_msg, "String") + self.register_instruction(ReturnNode(result_msg)) + self.current_method = self.current_function = None + # substr + self.current_method = self.current_type.get_method("substr") + type_name = self.current_type.name + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + start_parm = self.register_param(VariableInfo("i", None)) + length_param = self.register_param(VariableInfo("l", None)) + result_msg = self.define_internal_local() + length_var = self.define_internal_local() + zero = self.define_internal_local() + sum_var = self.define_internal_local() + cmp_var1 = self.define_internal_local() + cmp_var2 = self.define_internal_local() + cmp_var3 = self.define_internal_local() + no_error_label1 = self.to_label_name("error1") + no_error_label2 = self.to_label_name("error2") + no_error_label3 = self.to_label_name("error3") + str_raw = self.unpack_type_by_value(self_local, "String") + self.register_instruction(SetNode(zero, 0)) + # start_raw = self.unpack_type_by_value(start_parm, "Int") + # length_raw = self.unpack_type_by_value(length_param, "Int") + self.register_instruction(LengthNode(length_var, str_raw)) + eol = self.register_data("\n").name + msg_eol = self.define_internal_local() + # start param negative + self.register_instruction(LessEqNode(cmp_var1, zero, start_parm)) + self.register_instruction(GotoIfNode(cmp_var1, no_error_label1)) + error_msg = self.register_data("Invalid substring start").name + self.register_instruction(ConcatNode(msg_eol, error_msg, eol)) + self.register_instruction(PrintStrNode(msg_eol)) + self.register_instruction(ErrorNode()) + self.register_instruction(LabelNode(no_error_label1)) + # length param negative + self.register_instruction(LessEqNode(cmp_var2, zero, length_param)) + self.register_instruction(GotoIfNode(cmp_var2, no_error_label2)) + error_msg = self.register_data("Invalid substring length").name + self.register_instruction(ConcatNode(msg_eol, error_msg, eol)) + self.register_instruction(PrintStrNode(msg_eol)) + self.register_instruction(ErrorNode()) + self.register_instruction(LabelNode(no_error_label2)) + # substr larger than max length + self.register_instruction(PlusNode(sum_var, start_parm, length_param)) + self.register_instruction(LessEqNode(cmp_var3, sum_var, length_var)) + self.register_instruction(GotoIfNode(cmp_var3, no_error_label3)) + error_msg = self.register_data("Invalid substring").name + self.register_instruction(ConcatNode(msg_eol, error_msg, eol)) + self.register_instruction(PrintStrNode(msg_eol)) + self.register_instruction(ErrorNode()) + self.register_instruction(LabelNode(no_error_label3)) + self.register_instruction( + SubstringNode(result_msg, str_raw, start_parm, length_param) + ) + # string_inst = self.pack_type_by_value(result_msg, "String") + self.register_instruction(ReturnNode(result_msg)) + self.current_method = self.current_function = None + self.current_type = None diff --git a/src/cmp/cil/cil_to_mips.py b/src/cmp/cil/cil_to_mips.py new file mode 100644 index 00000000..671265bc --- /dev/null +++ b/src/cmp/cil/cil_to_mips.py @@ -0,0 +1,788 @@ +from functools import wraps +from typing import Dict, List + +from ..cool_lang.semantics.semantic_utils import Context +from .ast import ( + AbortNode, + AllocateNode, + ArgNode, + ArithmeticNode, + AssignNode, + CleanArgsNode, + ComplementNode, + ConcatNode, + CopyNode, + DataNode, + DivNode, + DynamicCallNode, + EqualNode, + ErrorNode, + FunctionNode, + GetAttribNode, + GotoIfNode, + GotoNode, + IsVoidNode, + LabelNode, + LengthNode, + LessEqNode, + LessNode, + LocalNode, + MinusNode, + NotNode, + ParamNode, + PlusNode, + PrintIntNode, + PrintStrNode, + ProgramNode, + ReadIntNode, + ReadStrNode, + ReturnNode, + SetAttribNode, + SetNode, + StarNode, + StaticCallNode, + StaticTypeOfNode, + StringEqualNode, + SubstringNode, + TypeNameNode, + TypeNode, + TypeOfNode, + VoidNode, +) +from .utils import TypeData, on, when +from .utils.mips_syntax import DATA_SIZE, Mips +from .utils.mips_syntax import Register as Reg + + +def mips_comment(msg: str): + def inner(fn): + @wraps(fn) + def wrapped(self: "CIL_TO_MIPS", *args, **kwargs): + self.mips.comment(msg) + result = fn(self, *args, **kwargs) + self.mips.empty() + return result + + return wrapped + + return inner + + +class CIL_TO_MIPS(object): + def __init__(self, context=Context): + self.types = [] + self.types_offsets: Dict[str, TypeData] = dict() + self.labels: List[str] = [] + self.local_vars_offsets = dict() + self.actual_args = dict() + self.mips = Mips(zip_mode=False) + self.label_count = -1 + self.registers_to_save: List[Reg] = [ + Reg.ra, + Reg.s0, + Reg.s1, + Reg.s2, + Reg.s3, + Reg.s4, + Reg.s5, + Reg.s6, + Reg.s7, + ] + self.context: Context = context + + self.mips.write_data("eol:") + self.mips.asciiz("\\n") + + def build_types_data(self, types): + for idx, typex in enumerate(types): + self.types_offsets[typex.name] = TypeData(idx, typex) + + def get_label(self): + self.label_count += 1 + return f"mip_label_{self.label_count}" + + @mips_comment("DEBUG PRINT") + def print_debug(self, msg: str): + self.mips.push(Reg.a0) + self.mips.push(Reg.v0) + + data_label = self.get_label() + + self.mips.data_label(data_label) + self.mips.asciiz(msg) + + self.mips.la(Reg.a0, data_label) + self.mips.print_string() + + self.mips.pop(Reg.v0) + self.mips.pop(Reg.a0) + + @mips_comment("DEBUG PRINT") + def print_reg(self, reg: Reg): + self.mips.push(Reg.a0) + self.mips.push(Reg.v0) + + self.mips.move(Reg.a0, reg) + self.mips.print_int() + + data_label = self.get_label() + + self.mips.data_label(data_label) + self.mips.asciiz("\\n") + + self.mips.la(Reg.a0, data_label) + self.mips.print_string() + + self.mips.pop(Reg.v0) + self.mips.pop(Reg.a0) + + def get_offset(self, arg: str): + if arg in self.actual_args or arg in self.local_vars_offsets: + offset = ( + self.actual_args[arg] + if arg in self.actual_args + else self.local_vars_offsets[arg] + ) * DATA_SIZE + return self.mips.offset(Reg.fp, offset) + else: + raise Exception(f"load_memory: The direction {arg} isn't an address") + + @mips_comment("LOAD MEMORY") + def load_memory(self, dst: Reg, arg: str): + self.mips.comment(f"from {arg} to {dst}") + if arg in self.actual_args or arg in self.local_vars_offsets: + offset = ( + self.actual_args[arg] + if arg in self.actual_args + else self.local_vars_offsets[arg] + ) * DATA_SIZE + self.mips.load_memory(dst, self.mips.offset(Reg.fp, offset)) + elif arg in self.labels: + self.mips.la(dst, arg) + else: + raise Exception(f"load_memory: The direction {arg} isn't an address") + + @mips_comment("STORE MEMORY") + def store_memory(self, src: Reg, dst: str): + self.mips.comment(f"from src: {src} to dst: {dst}") + if dst in self.actual_args or dst in self.local_vars_offsets: + offset = ( + self.actual_args[dst] + if dst in self.actual_args + else self.local_vars_offsets[dst] + ) * DATA_SIZE + offset = self.mips.offset(Reg.fp, offset) + self.mips.comment(f"store to memory src: {src} to offset: {offset}") + self.mips.store_memory(src, offset) + else: + raise Exception(f"store_memory: The direction {dst} isn't an address") + self.mips.empty() + + def load_arithmetic(self, node: ArithmeticNode): + self.load_memory(Reg.s0, node.left) + self.load_memory(Reg.s1, node.right) + + def store_registers(self): + for reg in self.registers_to_save: + self.mips.push(reg) + + def load_registers(self): + for reg in reversed(self.registers_to_save): + self.mips.pop(reg) + + @on("node") + def visit(self, node): + pass + + @when(ProgramNode) + @mips_comment("ProgramNode") + def visit(self, node: ProgramNode): # noqa: F811 + self.types = node.dottypes + self.build_types_data(self.types) + + for datanode in node.dotdata: + self.labels.append(datanode.name) + self.visit(datanode) + + self.mips.label("main") + + self.mips.jal("entry") + + self.mips.exit() + + for function in node.dotcode: + self.visit(function) + + self.mips.empty() + + @when(DataNode) + def visit(self, node: DataNode): # noqa: F811 + self.mips.data_label(node.name) + self.mips.asciiz(node.value) + + @when(TypeNode) + def visit(self, node: TypeNode): # noqa: F811 + pass + + @when(FunctionNode) + @mips_comment("FunctionNode") + def visit(self, node: FunctionNode): # noqa: F811 + self.mips.label(node.name) + + self.mips.comment("Set stack frame") + self.mips.push(Reg.fp) + self.mips.move(Reg.fp, Reg.sp) + + self.actual_args = dict() + + for idx, param in enumerate(node.params): + self.visit(param, index=idx) + + self.mips.empty() + self.mips.comment("Allocate memory for Local variables") + + localvars_count = len(node.localvars) + self.mips.addi(Reg.sp, Reg.sp, -DATA_SIZE * localvars_count) + for idx, local in enumerate(node.localvars): + self.visit(local, index=idx) + + self.mips.empty() + self.store_registers() + self.mips.empty() + self.mips.comment("Generating body code") + for instruction in node.instructions: + self.visit(instruction) + + self.mips.empty() + self.load_registers() + + self.mips.comment("Clean stack variable space") + self.mips.addi(Reg.sp, Reg.sp, len(node.localvars) * DATA_SIZE) + self.actual_args = None + self.mips.comment("Return") + self.mips.pop(Reg.fp) + self.mips.jr(Reg.ra) + + @when(ParamNode) + def visit(self, node: ParamNode, index=0): # noqa: F811 + self.actual_args[node.name] = index + 1 + + @when(LocalNode) + @mips_comment("LocalNode") + def visit(self, node: LocalNode, index=0): # noqa: F811 + assert node.name not in self.local_vars_offsets, f"Impossible {node.name}..." + self.local_vars_offsets[node.name] = -(index + 1) + + @mips_comment("Auxiliar - Copy data") + def copy_data(self, src: Reg, dst: Reg, length: Reg): + """ + length: fixed in bytes size. + """ + loop = self.get_label() + end = self.get_label() + + self.mips.move(Reg.t0, src) + self.mips.move(Reg.t1, dst) + self.mips.move(Reg.t3, length) # i = length + + self.mips.label(loop) + + self.mips.lb(Reg.t2, self.mips.offset(Reg.t0)) + self.mips.sb(Reg.t2, self.mips.offset(Reg.t1)) + + self.mips.addi(Reg.t0, Reg.t0, 1) + self.mips.addi(Reg.t1, Reg.t1, 1) + + self.mips.addi(Reg.t3, Reg.t3, -1) # i -- + + self.mips.beqz(Reg.t3, end) + self.mips.j(loop) + + self.mips.label(end) + + @when(SetNode) + @mips_comment("SetNode") + def visit(self, node: SetNode): # noqa: F811 + self.mips.li(Reg.s0, node.value) + self.store_memory(Reg.s0, node.dest) + + @when(CopyNode) + @mips_comment("CopyNode") + def visit(self, node: CopyNode): # noqa: F811 + self.load_memory(Reg.s0, node.obj) + + length = 2 * DATA_SIZE + + # reserve heap space + self.mips.load_memory(Reg.a0, self.mips.offset(Reg.s0, length)) + self.mips.move(Reg.s3, Reg.a0) + self.mips.sbrk() + self.mips.move(Reg.s1, Reg.v0) + + self.store_memory(Reg.s1, node.dest) + + # copy data raw byte to byte + self.copy_data(Reg.s0, Reg.s1, Reg.s3) + + @when(TypeNameNode) + @mips_comment("TypeNameNode") + def visit(self, node: TypeNameNode): # noqa: F811 + self.load_memory(Reg.s0, node.type) + self.mips.load_memory(Reg.s1, self.mips.offset(Reg.s0, DATA_SIZE)) + self.store_memory(Reg.s1, node.dest) + + @when(ErrorNode) + @mips_comment("ErrorNode") + def visit(self, node: ErrorNode): # noqa: F811 + self.mips.li(Reg.a0, int(node.error)) + self.mips.exit2() + + @when(AbortNode) + @mips_comment("AbortNode") + def visit(self, node: AbortNode): # noqa: F811 + self.mips.exit() + self.mips.empty() + + @when(AssignNode) + @mips_comment("AssignNode") + def visit(self, node: AssignNode): # noqa: F811 + self.load_memory(Reg.s0, node.source) + self.store_memory(Reg.s0, node.dest) + + @when(VoidNode) + @mips_comment("VoidNode") + def visit(self, node: VoidNode): # noqa: F811 + self.store_memory(Reg.zero, node.dest) + + @when(IsVoidNode) + @mips_comment("IsVoidNode") + def visit(self, node: IsVoidNode): # noqa: F811 + self.load_memory(Reg.s0, node.body) + + label = self.get_label() + + self.mips.li(Reg.s1, 0) + self.mips.bne(Reg.s0, Reg.s1, label) + self.mips.li(Reg.s1, 1) + self.mips.label(label) + self.store_memory(Reg.s1, node.dest) + + @when(ComplementNode) + @mips_comment("ComplementNode") + def visit(self, node: ComplementNode): # noqa: F811 + + self.load_memory(Reg.s0, node.body) + self.mips.li(Reg.s1, -1) + self.mips.mult(Reg.s0, Reg.s1) + self.mips.mflo(Reg.s0) + self.store_memory(Reg.s0, node.dest) + + @when(LessNode) + @mips_comment("LessNode") + def visit(self, node: LessNode): # noqa: F811 + self.load_arithmetic(node) + self.mips.slt(Reg.s2, Reg.s0, Reg.s1) + self.store_memory(Reg.s2, node.dest) + + @when(EqualNode) + @mips_comment("EqualNode") + def visit(self, node: EqualNode): # noqa: F811 + """ + ((a < b) + (b < a)) < 1 -> == + """ + self.load_arithmetic(node) + self.mips.slt(Reg.s2, Reg.s0, Reg.s1) + self.mips.slt(Reg.s3, Reg.s1, Reg.s0) + + self.mips.add(Reg.s0, Reg.s2, Reg.s3) + self.mips.slti(Reg.s1, Reg.s0, 1) + + self.store_memory(Reg.s1, node.dest) + + @when(LessEqNode) + @mips_comment("LessEqNode") + def visit(self, node: LessEqNode): # noqa: F811 + """ + a <= b -> ! b < a -> 1 - (b < a) + """ + self.load_arithmetic(node) + self.mips.slt(Reg.s2, Reg.s1, Reg.s0) + self.mips.li(Reg.s3, 1) + self.mips.sub(Reg.s0, Reg.s3, Reg.s2) + self.store_memory(Reg.s0, node.dest) + + @when(NotNode) + @mips_comment("NotNode") + def visit(self, node: NotNode): # noqa: F811 + self.load_memory(Reg.s0, node.body) + self.mips.li(Reg.s1, 1) + self.mips.sub(Reg.s2, Reg.s1, Reg.s0) # 1 - body -> !body + self.store_memory(Reg.s2, node.dest) + + @when(PlusNode) + @mips_comment("PlusNode") + def visit(self, node: PlusNode): # noqa: F811 + self.load_arithmetic(node) + self.mips.add(Reg.s2, Reg.s0, Reg.s1) + self.store_memory(Reg.s2, node.dest) + + @when(MinusNode) + @mips_comment("MinusNode") + def visit(self, node: MinusNode): # noqa: F811 + self.load_arithmetic(node) + self.mips.sub(Reg.s2, Reg.s0, Reg.s1) + self.store_memory(Reg.s2, node.dest) + + @when(StarNode) + @mips_comment("StarNode") + def visit(self, node: StarNode): # noqa: F811 + self.load_arithmetic(node) + self.mips.mult(Reg.s0, Reg.s1) + self.mips.mflo(Reg.s0) + self.store_memory(Reg.s0, node.dest) + + @when(DivNode) + @mips_comment("DivNode") + def visit(self, node: DivNode): # noqa: F811 + self.load_arithmetic(node) + self.mips.div(Reg.s0, Reg.s1) + self.mips.mflo(Reg.s0) + self.store_memory(Reg.s0, node.dest) + + @when(AllocateNode) + @mips_comment("AllocateNode") + def visit(self, node: AllocateNode): # noqa: F811 + self.mips.comment(f"dest: {node.dest}, type: {node.type}") + + type_data = self.types_offsets[node.type] + + self.mips.comment(str(type_data)) + + length = type_data.length + length *= DATA_SIZE + self.mips.li(Reg.a0, length) + self.mips.sbrk() + self.mips.move(Reg.s1, Reg.v0) + self.store_memory(Reg.s1, node.dest) + + self.mips.li(Reg.s0, type_data.type) + self.mips.store_memory(Reg.s0, self.mips.offset(Reg.s1)) + + self.mips.la(Reg.s0, type_data.str) + self.mips.store_memory(Reg.s0, self.mips.offset(Reg.s1, DATA_SIZE)) + + self.mips.li(Reg.s0, length) + self.mips.store_memory(Reg.s0, self.mips.offset(Reg.s1, 2 * DATA_SIZE)) + + for offset in type_data.attr_offsets.values(): + self.mips.store_memory( + Reg.zero, + self.mips.offset(Reg.s1, offset * DATA_SIZE), + ) + + for name, offset in type_data.func_offsets.items(): + direct_name = type_data.func_names[name] + self.mips.la(Reg.s0, direct_name) + self.mips.store_memory( + Reg.s0, + self.mips.offset( + Reg.s1, + offset * DATA_SIZE, + ), + ) + + @when(TypeOfNode) + @mips_comment("TypeOfNode") + def visit(self, node: TypeOfNode): # noqa: F811 + self.load_memory(Reg.s0, node.obj) + self.mips.load_memory(Reg.s1, self.mips.offset(Reg.s0)) + self.store_memory(Reg.s1, node.dest) + + @when(StaticTypeOfNode) + @mips_comment("StaticTypeOfNode") + def visit(self, node: StaticTypeOfNode): # noqa: F811 + self.mips.li(Reg.s1, self.types_offsets[node.type].type) + self.store_memory(Reg.s1, node.dest) + + @when(StaticCallNode) + @mips_comment("StaticCallNode") + def visit(self, node: StaticCallNode): # noqa: F811 + self.mips.jal(node.function) + self.mips.comment(f"Returning {node.dest}") + self.store_memory(Reg.v0, node.dest) + + @when(DynamicCallNode) + @mips_comment("DynamicCallNode") + def visit(self, node: DynamicCallNode): # noqa: F811 + type_data = self.types_offsets[node.type] + offset = type_data.func_offsets[node.method] * DATA_SIZE + self.load_memory(Reg.s0, node.obj) + self.mips.load_memory(Reg.s1, self.mips.offset(Reg.s0, offset)) + self.mips.jalr(Reg.s1) + self.mips.comment(f"Returning {node.dest}") + self.store_memory(Reg.v0, node.dest) + + @when(ArgNode) + @mips_comment("ArgNode") + def visit(self, node: ArgNode): # noqa: F811 + self.load_memory(Reg.s0, node.name) + self.mips.push(Reg.s0) + + @when(CleanArgsNode) + @mips_comment("CleanArgsNode") + def visit(self, node: CleanArgsNode): # noqa: F811 + self.mips.addi(Reg.sp, Reg.sp, node.nargs * DATA_SIZE) + + @when(ReturnNode) + @mips_comment("ReturnNode") + def visit(self, node: ReturnNode): # noqa: F811 + self.mips.comment(f"Returning {node.value}") + self.load_memory(Reg.v0, node.value) + + @when(ReadIntNode) + @mips_comment("ReadIntNode") + def visit(self, node: ReadIntNode): # noqa: F811 + self.mips.read_int() + self.store_memory(Reg.v0, node.dest) + + @when(ReadStrNode) + @mips_comment("ReadStrNode") + def visit(self, node: ReadStrNode): # noqa: F811 + self.mips.li(Reg.a0, 1024) + self.mips.sbrk() + self.mips.move(Reg.a0, Reg.v0) + self.store_memory(Reg.v0, node.dest) + self.mips.li(Reg.a1, 1024) # Change this later + self.mips.read_string() + # Try to remove least caracter + len_to = self.get_label() + end = self.get_label() + self.mips.label(len_to) + self.mips.lb(Reg.s2, self.mips.offset(Reg.a0)) # t2 = *a0 + self.mips.beq(Reg.s2, 0, end) # if t2 == '\n' -> stop + self.mips.addi(Reg.a0, Reg.a0, 1) # a0++ + self.mips.j(len_to) + self.mips.label(end) + self.mips.addi(Reg.a0, Reg.a0, -1) + self.mips.sb(Reg.zero, self.mips.offset(Reg.a0)) # overwrite '\n' with 0 + + @when(PrintIntNode) + @mips_comment("PrintIntNode") + def visit(self, node: PrintIntNode): # noqa: F811 + self.load_memory(Reg.a0, node.str_addr) + self.mips.print_int() + + @when(PrintStrNode) + @mips_comment("PrintStrNode") + def visit(self, node: PrintStrNode): # noqa: F811 + self.load_memory(Reg.a0, node.str_addr) + self.mips.print_string() + + @mips_comment("Auxiliar - Get string length") + def get_string_length(self, src: Reg, dst: Reg): + loop = self.get_label() + end = self.get_label() + + self.mips.move(Reg.t0, src) + self.mips.li(Reg.t1, 0) + + self.mips.label(loop) + + self.mips.lb(Reg.t3, self.mips.offset(Reg.t0)) + self.mips.beqz(Reg.t3, end) + + self.mips.addi(Reg.t1, Reg.t1, 1) + self.mips.addi(Reg.t0, Reg.t0, 1) + + self.mips.j(loop) + self.mips.label(end) + + self.mips.move(dst, Reg.t1) + + @when(LengthNode) + @mips_comment("LengthNode") + def visit(self, node: LengthNode): # noqa + self.mips.comment("LengthNode") + self.load_memory(Reg.s1, node.msg) + self.get_string_length(Reg.s1, Reg.s0) + self.store_memory(Reg.s0, node.dest) + + @mips_comment("Auxiliar - Copy string") + def copy_str(self, src: Reg, dst: Reg, result: Reg): + loop = self.get_label() + end = self.get_label() + + self.mips.move(Reg.t0, src) + self.mips.move(Reg.t1, dst) + + self.mips.label(loop) + + self.mips.lb(Reg.t2, self.mips.offset(Reg.t0)) + self.mips.sb(Reg.t2, self.mips.offset(Reg.t1)) + + self.mips.beqz(Reg.t2, end) + + self.mips.addi(Reg.t0, Reg.t0, 1) + self.mips.addi(Reg.t1, Reg.t1, 1) + + self.mips.j(loop) + self.mips.label(end) + + self.mips.move(result, Reg.t1) + + @when(ConcatNode) + @mips_comment("ConcatNode") + def visit(self, node: ConcatNode): # noqa + self.load_memory(Reg.s0, node.msg1) + self.load_memory(Reg.s1, node.msg2) + + self.get_string_length(Reg.s0, Reg.s4) + self.get_string_length(Reg.s1, Reg.s5) + + self.mips.add(Reg.a0, Reg.s4, Reg.s5) # WARNING: Divide in 2, from half to byte + self.mips.addi(Reg.a0, Reg.a0, 1) + self.mips.sbrk() + self.mips.move(Reg.s3, Reg.v0) # The new space reserved + + self.copy_str(Reg.s0, Reg.s3, Reg.v0) + self.copy_str(Reg.s1, Reg.v0, Reg.v0) + + self.store_memory(Reg.s3, node.dest) + + @mips_comment("Auxiliar - Copy substring") + def copy_substr(self, src: Reg, dst: Reg, length: Reg): + loop = self.get_label() + end = self.get_label() + + self.mips.move(Reg.t0, src) + self.mips.move(Reg.t1, dst) + + self.mips.move(Reg.t3, length) # i = length + + self.mips.label(loop) + + self.mips.lb(Reg.t2, self.mips.offset(Reg.t0)) + self.mips.sb(Reg.t2, self.mips.offset(Reg.t1)) + + self.mips.addi(Reg.t0, Reg.t0, 1) + self.mips.addi(Reg.t1, Reg.t1, 1) + + self.mips.addi(Reg.t3, Reg.t3, -1) # i -- + + self.mips.beqz(Reg.t3, end) + self.mips.j(loop) + + self.mips.label(end) + + self.mips.move(Reg.t2, Reg.zero) + self.mips.sb(Reg.t2, self.mips.offset(Reg.t1)) # copy zero to the end + + @when(SubstringNode) + @mips_comment("SubstringNode") + def visit(self, node: SubstringNode): # noqa: F811 + self.load_memory(Reg.s0, node.msg1) + self.load_memory(Reg.s1, node.length) + self.load_memory(Reg.s3, node.start) + + self.mips.add(Reg.s0, Reg.s0, Reg.s3) + + self.mips.move(Reg.a0, Reg.s1) # allocate heap memory + self.mips.addi(Reg.a0, Reg.a0, 1) + self.mips.sbrk() + self.copy_substr(Reg.s0, Reg.v0, Reg.s1) + + self.store_memory(Reg.v0, node.dest) + + @when(StringEqualNode) + @mips_comment("StringEqualNode") + def visit(self, node: StringEqualNode): # noqa: F811 + end_label = self.get_label() + end_ok_label = self.get_label() + loop_label = self.get_label() + + self.load_memory(Reg.s0, node.msg1) # load string address + self.load_memory(Reg.s1, node.msg2) + + self.get_string_length(Reg.s0, Reg.s2) # load size of string + self.get_string_length(Reg.s1, Reg.s3) + + self.mips.move(Reg.v0, Reg.zero) # return 0 + self.mips.bne(Reg.s2, Reg.s3, end_label) # end and return 0 if size not equal + + self.mips.move(Reg.s2, Reg.s0) # lets use temporal register + self.mips.move(Reg.s3, Reg.s1) + + self.mips.label(loop_label) + + self.mips.lb(Reg.s4, self.mips.offset(Reg.s2)) # load string character + self.mips.lb(Reg.s5, self.mips.offset(Reg.s3)) + + self.mips.bne(Reg.s4, Reg.s5, end_label) # if no equal then return 0 + + self.mips.addi(Reg.s2, Reg.s2, 1) # move next character + self.mips.addi(Reg.s3, Reg.s3, 1) + + self.mips.beqz( + Reg.s4, end_ok_label + ) # if end the string return 1 (they are equal) + self.mips.j(loop_label) # continue loop + + self.mips.label(end_ok_label) + self.mips.li(Reg.v0, 1) # return 1 + self.mips.label(end_label) + self.store_memory(Reg.v0, node.dest) # store value in dst + + @when(GetAttribNode) + @mips_comment("GetAttribNode") + def visit(self, node: GetAttribNode): # noqa: F811 + self.load_memory(Reg.s0, node.obj) + + type_data = self.types_offsets[node.type] + offset = type_data.attr_offsets[node.attrib] * DATA_SIZE + self.mips.load_memory(Reg.s1, self.mips.offset(Reg.s0, offset)) + + self.store_memory(Reg.s1, node.dest) + + @when(SetAttribNode) + @mips_comment("SetAttribNode") + def visit(self, node: SetAttribNode): # noqa: F811 + self.load_memory(Reg.s0, node.obj) + type_data = self.types_offsets[node.type] + offset = type_data.attr_offsets[node.attrib] * DATA_SIZE + if node.value in self.local_vars_offsets or node.value in self.actual_args: + self.mips.comment(f"Setting local var {node.value}") + self.load_memory(Reg.s1, node.value) + else: + try: + value = int(node.value) + # high = value >> 16 + # self.mips.comment(f"Seting literal int {node.value}") + # self.mips.li(Reg.s2, high) + # self.mips.li(Reg.s3, value) + # self.mips.sll(Reg.s4, Reg.s2, 16) + # self.mips.orr(Reg.s1, Reg.s3, Reg.s4) + self.mips.li(Reg.s1, value) + except ValueError: + if node.value in self.labels: + self.mips.comment(f"Setting data {node.value}") + self.mips.la(Reg.s1, node.value) + else: + raise Exception(f"SetAttribNode: label {node.value} not found.") + self.mips.store_memory(Reg.s1, self.mips.offset(Reg.s0, offset)) + + @when(LabelNode) + @mips_comment("LabelNode") + def visit(self, node: LabelNode): # noqa: F811 + self.mips.label(node.label) + + @when(GotoNode) + @mips_comment("GotoNode") + def visit(self, node: GotoNode): # noqa: F811 + self.mips.j(node.label) + + @when(GotoIfNode) + @mips_comment("GotoIfNode") + def visit(self, node: GotoIfNode): # noqa: F811 + self.load_memory(Reg.s0, node.value) + self.mips.li(Reg.s1, 0) + self.mips.bne(Reg.s0, Reg.s1, node.label) diff --git a/src/cmp/cil/cool_to_cil.py b/src/cmp/cil/cool_to_cil.py new file mode 100644 index 00000000..78ab02d9 --- /dev/null +++ b/src/cmp/cil/cool_to_cil.py @@ -0,0 +1,568 @@ +from typing import List, Set + +from ..cool_lang import ast as cool +from ..cool_lang.semantics.semantic_utils import Attribute, Type +from .ast import ( + AllocateNode, + ArgNode, + AssignNode, + CleanArgsNode, + ComplementNode, + DivNode, + DynamicCallNode, + EqualNode, + ErrorNode, + GetAttribNode, + GotoIfNode, + GotoNode, + IsVoidNode, + LabelNode, + LessEqNode, + LessNode, + MinusNode, + NotNode, + PlusNode, + ProgramNode, + ReturnNode, + SetAttribNode, + StarNode, + StaticCallNode, + StaticTypeOfNode, + StringEqualNode, + TypeOfNode, + VoidNode, + SetNode +) +from .basic_transform import BASE_COOL_CIL_TRANSFORM, VariableInfo +from .utils import Scope, on, when + + +class COOL_TO_CIL_VISITOR(BASE_COOL_CIL_TRANSFORM): + @on("node") + def visit(self, node, scope: Scope): # noqa:F811 + pass + + def order_caseof(self, node: cool.CaseOfNode): + ini_dict = {case.type: case for case in node.cases} + list_types: List[Type] = list() + set_types: Set[str] = set() + queue: List[Type] = [self.context.get_type(case.type) for case in node.cases] + while queue: + item = queue.pop(0) + if item.name not in set_types: + set_types.add(item.name) + list_types.append(item) + queue += item.children + list_types_for_sorted = [(item.finish_time, item) for item in list_types] + list_types_for_sorted.sort() + list_types_for_sorted = [item for _, item in list_types_for_sorted] + result = [] + for item in list_types_for_sorted: + temp = item + while True: + assert temp is not None, "Error in case of node generator" + if temp.name in ini_dict: + case = ini_dict[temp.name] + new_case = cool.CaseNode( + case.id, + item.name, + case.expression, + case.line, + case.column, + ) + result.append(new_case) + break + temp = temp.parent + return result + + def find_type_name(self, typex, func_name): + if func_name in typex.methods: + return typex.name + return self.find_type_name(typex.parent, func_name) + + def init_class_attr(self, scope: Scope, class_id, self_inst): + attr_nodes = self.attr_init[class_id] + for attr in attr_nodes: + attr_scope = Scope(parent=scope) + attr_scope.define_var("self", self_inst) + self.visit(attr, attr_scope, class_id) + + def build_attr_init(self, node: cool.ProgramNode): + self.attr_init = dict() + self.attr_init["IO"] = [] + self.build_init_type_func("IO") + self.attr_init["Object"] = [] + self.build_init_type_func("Object") + self.attr_init["Int"] = [ + cool.AttrDeclarationNode("value", None, None, 0, 0) # type:ignore + ] + self.build_init_type_func("Int") + self.attr_init["Bool"] = [ + cool.AttrDeclarationNode("value", None, None, 0, 0) # type:ignore + ] + self.build_init_type_func("Bool") + self.attr_init["String"] = [ + cool.AttrDeclarationNode("value", None, None, 0, 0) # type:ignore + ] + self.build_init_type_func("String") + for classx in node.classes: + self.attr_init[classx.id] = [] + if classx.parent: + self.attr_init[classx.id] += self.attr_init[classx.parent] + for feature in classx.features: + if type(feature) is cool.AttrDeclarationNode: + self.attr_init[classx.id].append(feature) + self.build_init_type_func(classx.id) + + def build_init_type_func(self, typex): + self.current_type = self.context.get_type(typex) + self.current_function = self.register_function(f"init_{typex}") + result = self.define_internal_local() + self.register_instruction(AllocateNode(result, typex)) + self.init_class_attr(Scope(), typex, result) + self.current_type = None + self.register_instruction(ReturnNode(result)) + self.current_type = None + + @when(cool.ProgramNode) + def visit(self, node: cool.ProgramNode = None, scope: Scope = None): # noqa:F811 + scope = Scope() + assert node is not None, "Node program is None" + self.build_attr_init(node) + self.current_function = self.register_function("entry") + instance = self.define_internal_local() + result = self.define_internal_local() + self.current_type = self.context.get_type("Main") + self.register_instruction(StaticCallNode("init_Main", instance)) + self.current_type = None + self.register_instruction(ArgNode(instance)) + self.register_instruction( + StaticCallNode(self.to_function_name("main", "Main"), result) + ) + self.register_instruction(CleanArgsNode(1)) + self.current_function = None + + for classx in node.classes: + self.visit(classx, scope) + + return ProgramNode(self.dottypes, self.dotdata, self.dotcode) + + @when(cool.ClassDeclarationNode) + def visit(self, node: cool.ClassDeclarationNode, scope: Scope): # noqa:F811 + self.current_type = self.context.get_type(node.id) + + type_node = self.register_type(node.id) + type_node.name_dir = self.register_data(node.id).name + type_node.attributes = [ + attr.name for attr in self.current_type.get_all_attributes() + ] + type_node.methods = [ + (method.name, self.to_function_name(method.name, typex.name)) + for method, typex in self.current_type.get_all_methods() + ] + type_node.features = [ + feature.name + if isinstance(feature, Attribute) + else ( + feature[0].name, + self.to_function_name(feature[0].name, feature[1].name), + ) + for feature in self.current_type.get_all_features() + ] + + for feature in node.features: + if isinstance(feature, cool.FuncDeclarationNode): + self.visit(feature, scope) + + self.current_type = None + + @when(cool.AttrDeclarationNode) + def visit( # noqa:F811 + self, node: cool.AttrDeclarationNode, scope: Scope, typex: str = None + ): + result = None + if node.expression: + result = self.visit(node.expression, scope) + elif node.type == "String": + result = self.register_data("").name + else: + result = self.define_internal_local() + self.register_instruction(VoidNode(result)) + self_inst = scope.get_var("self").local_name + assert typex, f"AttrDeclarationNode: {typex}" + # result = self.unpack_type_by_value( + # result, node.expression.static_type if node.expression else "Void" + # ) + self.register_instruction(SetAttribNode(self_inst, node.id, result, typex)) + + @when(cool.FuncDeclarationNode) + def visit(self, node: cool.FuncDeclarationNode, scope: Scope): # noqa:F811 + func_scope = Scope(parent=scope) + self.current_method = self.current_type.get_method(node.id) + type_name = self.current_type.name + + self.current_function = self.register_function( + self.to_function_name(self.current_method.name, type_name) + ) + self_local = self.register_param(VariableInfo("self", None)) + func_scope.define_var("self", self_local) + for param_name in self.current_method.param_names: + param_local = self.register_param(VariableInfo(param_name, None)) + func_scope.define_var(param_name, param_local) + + body = self.visit(node.expression, func_scope) + self.register_instruction(ReturnNode(body)) + + self.current_method = self.current_function = None + + @when(cool.IfThenElseNode) + def visit(self, node: cool.IfThenElseNode, scope: Scope): # noqa:F811 + if_scope = Scope(parent=scope) + cond_result = self.visit(node.condition, scope) + result = self.define_internal_local() + true_label = self.to_label_name("if_true") + end_label = self.to_label_name("end_if") + # cond_result = self.unpack_type_by_value(cond_result, node.condition.static_type) + self.register_instruction(GotoIfNode(cond_result, true_label)) + false_result = self.visit(node.else_body, if_scope) + self.register_instruction(AssignNode(result, false_result)) + self.register_instruction(GotoNode(end_label)) + self.register_instruction(LabelNode(true_label)) + true_result = self.visit(node.if_body, if_scope) + self.register_instruction(AssignNode(result, true_result)) + self.register_instruction(LabelNode(end_label)) + + return result + + @when(cool.WhileLoopNode) + def visit(self, node: cool.WhileLoopNode, scope: Scope): # noqa:F811 + while_scope = Scope(parent=scope) + loop_label = self.to_label_name("loop") + body_label = self.to_label_name("body") + end_label = self.to_label_name("pool") + self.register_instruction(LabelNode(loop_label)) + condition = self.visit(node.condition, scope) + # condition_raw = self.unpack_type_by_value(condition, node.condition.static_type) + self.register_instruction(GotoIfNode(condition, body_label)) + self.register_instruction(GotoNode(end_label)) + self.register_instruction(LabelNode(body_label)) + self.visit(node.body, while_scope) + self.register_instruction(GotoNode(loop_label)) + self.register_instruction(LabelNode(end_label)) + zero = self.define_internal_local() + self.register_instruction(VoidNode(zero)) + return zero + + @when(cool.BlockNode) + def visit(self, node: cool.BlockNode, scope: Scope): # noqa:F811 + result = None + assert node.expressions, "BlockNode empty" + for expr in node.expressions: + result = self.visit(expr, scope) + return result + + @when(cool.LetNode) + def visit(self, node: cool.LetNode, scope: Scope): # noqa:F811 + var_name = self.register_local(VariableInfo(node.id, None)) + result = self.visit(node.expression, scope) if node.expression else 0 + if result == 0: + typex = node.type + # if typex in ["Int", "String", "Bool"]: + # self.register_instruction(StaticCallNode(f"init_{typex}", var_name)) + if typex == "String": + empty = self.register_data("").name + self.register_instruction(AssignNode(var_name, empty)) + # self.register_instruction( + # SetAttribNode(var_name, "value", empty, typex) + # ) + else: + self.register_instruction(VoidNode(var_name)) + else: + self.register_instruction(AssignNode(var_name, result)) + scope.define_var(node.id, var_name) + + @when(cool.LetInNode) + def visit(self, node: cool.LetInNode, scope: Scope): # noqa:F811 + let_scope = Scope(parent=scope) + for let in node.let_body: + self.visit(let, let_scope) + + result = self.visit(node.in_body, let_scope) + return result + + @when(cool.CaseNode) + def visit( # noqa:F811 + self, + node: cool.CaseNode, + scope: Scope, + typex=None, + result_inst=None, + end_label=None, + expr_inst=None, + ): + cond = self.define_internal_local() + not_cond = self.define_internal_local() + case_label = self.to_label_name(f"case_{node.type}") + type_val = self.define_internal_local() + self.register_instruction(StaticTypeOfNode(node.type, type_val)) + self.register_instruction(EqualNode(cond, typex, type_val)) + self.register_instruction(NotNode(not_cond, cond)) + self.register_instruction(GotoIfNode(not_cond, case_label)) + case_scope = Scope(parent=scope) + case_var = self.register_local(VariableInfo(node.id, None)) + case_scope.define_var(node.id, case_var) + self.register_instruction(AssignNode(case_var, expr_inst)) + case_result = self.visit(node.expression, case_scope) + self.register_instruction(AssignNode(result_inst, case_result)) + self.register_instruction(GotoNode(end_label)) + self.register_instruction(LabelNode(case_label)) + + @when(cool.CaseOfNode) + def visit(self, node: cool.CaseOfNode, scope: Scope): # noqa:F811 + order_cases = self.order_caseof(node) + end_label = self.to_label_name("end") + error_label = self.to_label_name("error") + result = self.define_internal_local() + type_inst = self.define_internal_local() + is_void = self.define_internal_local() + obj_inst = self.visit(node.expression, scope) + self.register_instruction(IsVoidNode(is_void, obj_inst)) + self.register_instruction(GotoIfNode(is_void, error_label)) + self.register_instruction(TypeOfNode(obj_inst, type_inst)) + for case in order_cases: + self.visit(case, scope, type_inst, result, end_label, obj_inst) + self.register_instruction(LabelNode(error_label)) + self.register_instruction(ErrorNode()) + self.register_instruction(LabelNode(end_label)) + + return result + + @when(cool.AssignNode) + def visit(self, node: cool.AssignNode, scope: Scope): # noqa:F811 + value = self.visit(node.expression, scope) + pvar = scope.get_var(node.id) + if not pvar: + # value = self.unpack_type_by_value(value, node.expression.static_type) + selfx = scope.get_var("self").local_name + self.register_instruction( + SetAttribNode( + selfx, + node.id, + value, + self.current_type.name, + ) + ) + else: + pvar = pvar.local_name + self.register_instruction(AssignNode(pvar, value)) + return value + + @when(cool.MemberCallNode) + def visit(self, node: cool.MemberCallNode, scope: Scope): # noqa:F811 + type_name = self.current_type.name + result = self.define_internal_local() + rev_args = [] + for arg in node.args: + arg_value = self.visit(arg, scope) + rev_args = [arg_value] + rev_args + for arg_value in rev_args: + self.register_instruction(ArgNode(arg_value)) + self_inst = scope.get_var("self").local_name + self.register_instruction(ArgNode(self_inst)) + self.register_instruction( + DynamicCallNode(self_inst, type_name, node.id, result) + ) + self.register_instruction(CleanArgsNode(len(node.args) + 1)) + return result + + @when(cool.FunctionCallNode) + def visit(self, node: cool.FunctionCallNode, scope: Scope): # noqa:F811 + typex = None if not node.type else self.context.get_type(node.type) + type_name = self.find_type_name(typex, node.id) if typex else "" + func_name = self.to_function_name(node.id, type_name) if type_name else "" + result = self.define_internal_local() + rev_args = [] + for arg in node.args: + arg_value = self.visit(arg, scope) + rev_args = [arg_value] + rev_args + for arg_value in rev_args: + self.register_instruction(ArgNode(arg_value)) + obj_inst = self.visit(node.obj, scope) + obj_inst = self.pack_type_by_value(obj_inst, node.obj.static_type) + self.register_instruction(ArgNode(obj_inst)) + if func_name: + self.register_instruction(StaticCallNode(func_name, result)) + else: + self.register_instruction( + DynamicCallNode( + obj_inst, + node.obj.static_type.name, + node.id, + result, + ) + ) + self.register_instruction(CleanArgsNode(len(node.args) + 1)) + return result + + @when(cool.NewNode) + def visit(self, node: cool.NewNode, scope: Scope): # noqa:F811 + result = self.define_internal_local() + self.register_instruction(StaticCallNode(f"init_{node.type}", result)) + result = self.unpack_type_by_value(result, node.type) + return result + + @when(cool.IsVoidNode) + def visit(self, node: cool.IsVoidNode, scope: Scope): # noqa:F811 + body = self.visit(node.expression, scope) + result = self.define_internal_local() + self.register_instruction(IsVoidNode(result, body)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.NotNode) + def visit(self, node: cool.NotNode, scope: Scope): # noqa:F811 + value = self.visit(node.expression, scope) + # value = self.unpack_type_by_value(value, node.expression.static_type) + result = self.define_internal_local() + self.register_instruction(NotNode(result, value)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.ComplementNode) + def visit(self, node: cool.ComplementNode, scope: Scope): # noqa:F811 + value = self.visit(node.expression, scope) + # value = self.unpack_type_by_value(value, node.expression.static_type) + result = self.define_internal_local() + self.register_instruction(ComplementNode(result, value)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.PlusNode) + def visit(self, node: cool.PlusNode, scope: Scope): # noqa:F811 + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + # left = self.unpack_type_by_value(left, node.left.static_type) + # right = self.unpack_type_by_value(right, node.right.static_type) + result = self.define_internal_local() + self.register_instruction(PlusNode(result, left, right)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.MinusNode) + def visit(self, node: cool.MinusNode, scope: Scope): # noqa:F811 + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + # left = self.unpack_type_by_value(left, node.left.static_type) + # right = self.unpack_type_by_value(right, node.right.static_type) + result = self.define_internal_local() + self.register_instruction(MinusNode(result, left, right)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.StarNode) + def visit(self, node: cool.StarNode, scope: Scope): # noqa:F811 + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + # left = self.unpack_type_by_value(left, node.left.static_type) + # right = self.unpack_type_by_value(right, node.right.static_type) + result = self.define_internal_local() + self.register_instruction(StarNode(result, left, right)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.DivNode) + def visit(self, node: cool.DivNode, scope: Scope): # noqa:F811 + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + # left = self.unpack_type_by_value(left, node.left.static_type) + # right = self.unpack_type_by_value(right, node.right.static_type) + result = self.define_internal_local() + self.register_instruction(DivNode(result, left, right)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.EqualNode) + def visit(self, node: cool.EqualNode, scope: Scope): # noqa:F811 + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + # left = self.unpack_type_by_value(left, node.left.static_type) + # right = self.unpack_type_by_value(right, node.right.static_type) + result = self.define_internal_local() + if node.left.static_type == self.context.get_type("String"): + self.register_instruction(StringEqualNode(result, left, right)) + else: + self.register_instruction(EqualNode(result, left, right)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.LessNode) + def visit(self, node: cool.LessNode, scope: Scope): # noqa:F811 + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + # left = self.unpack_type_by_value(left, node.left.static_type) + # right = self.unpack_type_by_value(right, node.right.static_type) + result = self.define_internal_local() + self.register_instruction(LessNode(result, left, right)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.LessEqualNode) + def visit(self, node: cool.LessEqualNode, scope: Scope): # noqa:F811 + left = self.visit(node.left, scope) + right = self.visit(node.right, scope) + # left = self.unpack_type_by_value(left, node.left.static_type) + # right = self.unpack_type_by_value(right, node.right.static_type) + result = self.define_internal_local() + self.register_instruction(LessEqNode(result, left, right)) + # result = self.pack_type_by_value(result, node.static_type) + return result + + @when(cool.IdNode) + def visit(self, node: cool.IdNode, scope: Scope): # noqa:F811 + pvar = scope.get_var(node.token) + if not pvar: + selfx = scope.get_var("self").local_name + pvar = self.define_internal_local() + self.register_instruction( + GetAttribNode(pvar, selfx, node.token, self.current_type.name) + ) + vattrbs = [ + item + for item in self.current_type.get_all_attributes() + if item.name == node.token + ] + assert vattrbs, "IdNode: attributes is empty" + vattr: Attribute = vattrbs[0] + # pvar = self.pack_type_by_value(pvar, vattr.type) + else: + pvar = pvar.local_name + return pvar + + @when(cool.BoolNode) + def visit(self, node: cool.BoolNode, scope: Scope): # noqa:F811 + value = 1 if node.token.lower() == "true" else 0 + bool_inst = self.define_internal_local() + self.register_instruction(SetNode(bool_inst, value)) + # self.register_instruction(StaticCallNode("init_Bool", bool_inst)) + # if value: + # self.register_instruction(SetAttribNode(bool_inst, "value", value, "Bool")) + return bool_inst + + @when(cool.IntegerNode) + def visit(self, node: cool.IntegerNode, scope: Scope): # noqa:F811 + value = int(node.token) + int_inst = self.define_internal_local() + self.register_instruction(SetNode(int_inst, value)) + # self.register_instruction(StaticCallNode("init_Int", int_inst)) + # self.register_instruction(SetAttribNode(int_inst, "value", value, "Int")) + return int_inst + + @when(cool.StringNode) + def visit(self, node: cool.StringNode, scope: Scope): # noqa:F811 + string = self.register_data(node.token[1:-1]) + value = string.name + return value + # string_inst = self.define_internal_local() + # self.register_instruction(StaticCallNode("init_String", string_inst)) + # self.register_instruction(SetAttribNode(string_inst, "value", value, "String")) + # return string_inst diff --git a/src/cmp/cil/formatter.py b/src/cmp/cil/formatter.py new file mode 100644 index 00000000..60f5c7a8 --- /dev/null +++ b/src/cmp/cil/formatter.py @@ -0,0 +1,248 @@ +from .ast import ( + AbortNode, + AllocateNode, + ArgNode, + AssignNode, + CleanArgsNode, + ComplementNode, + ConcatNode, + CopyNode, + DataNode, + DivNode, + DynamicCallNode, + EqualNode, + ErrorNode, + FunctionNode, + GetAttribNode, + GotoIfNode, + GotoNode, + IsVoidNode, + LabelNode, + LengthNode, + LessEqNode, + LessNode, + LocalNode, + MinusNode, + NotNode, + ParamNode, + PlusNode, + PrintIntNode, + PrintStrNode, + ProgramNode, + ReadIntNode, + ReadStrNode, + ReturnNode, + SetAttribNode, + SetNode, + StarNode, + StaticCallNode, + StaticTypeOfNode, + StringEqualNode, + SubstringNode, + TypeNameNode, + TypeNode, + TypeOfNode, + VoidNode, +) +from .utils import on, when + + +class CIL_FORMATTER(object): + @on("node") + def visit(self, node): + pass + + @when(ProgramNode) + def visit(self, node: ProgramNode): # noqa:F811 + dottypes = "\n".join(self.visit(t) for t in node.dottypes) + dotdata = "\n".join(self.visit(t) for t in node.dotdata) + dotcode = "\n".join(self.visit(t) for t in node.dotcode) + + return f".TYPES\n{dottypes}\n\n.DATA\n{dotdata}\n\n.CODE\n{dotcode}" + + @when(DataNode) + def visit(self, node: DataNode): # noqa:F811 + return f'{node.name} = "{node.value}"' + + @when(TypeNode) + def visit(self, node: TypeNode): # noqa:F811 + features = "\n\t".join( + f"attribute {feature}" + if isinstance(feature, str) + else f"method {feature[0]}: {feature[1]}" + for feature in node.features + ) + + return f"type {node.name} {{\n\t{features}\n}}" + + @when(FunctionNode) + def visit(self, node: FunctionNode): # noqa:F811 + params = "\n\t".join(self.visit(x) for x in node.params) + localvars = "\n\t".join(self.visit(x) for x in node.localvars) + instructions = "\n\t".join(self.visit(x) for x in node.instructions) + + result = f"function {node.name} {{\n\t{params}\n\n\t" + result += f"{localvars}\n\n\t{instructions}\n}}" + return result + + @when(ParamNode) + def visit(self, node: ParamNode): # noqa:F811 + return f"PARAM {node.name}" + + @when(LocalNode) + def visit(self, node: LocalNode): # noqa:F811 + return f"LOCAL {node.name}" + + @when(CopyNode) + def visit(self, node: CopyNode): # noqa:F811 + return f"{node.dest} = COPY {node.obj}" + + @when(TypeNameNode) + def visit(self, node: TypeNameNode): # noqa:F811 + return f"{node.dest} = TYPENAME {node.type}" + + @when(ErrorNode) + def visit(self, node: ErrorNode): # noqa:F811 + return f"ERROR {node.error}" + + @when(AssignNode) + def visit(self, node: AssignNode): # noqa:F811 + return f"{node.dest} = {node.source}" + + @when(SetNode) + def visit(self, node: SetNode): # noqa:F811 + return f"{node.dest} = {node.value}" + + @when(IsVoidNode) + def visit(self, node: IsVoidNode): # noqa:F811 + return f"{node.dest} = ISVOID {node.body}" + + @when(VoidNode) + def visit(self, node: VoidNode): # noqa:F811 + return f"{node.dest} = VOID" + + @when(ComplementNode) + def visit(self, node: ComplementNode): # noqa:F811 + return f"{node.dest} = COMPLEMENT {node.body}" + + @when(NotNode) + def visit(self, node: NotNode): # noqa:F811 + return f"{node.dest} = NOT {node.body}" + + @when(LessNode) + def visit(self, node: LessNode): # noqa:F811 + return f"{node.dest} = {node.left} < {node.right}" + + @when(EqualNode) + def visit(self, node: EqualNode): # noqa:F811 + return f"{node.dest} = {node.left} == {node.right}" + + @when(LessEqNode) + def visit(self, node: LessEqNode): # noqa:F811 + return f"{node.dest} = {node.left} <= {node.right}" + + @when(PlusNode) + def visit(self, node: PlusNode): # noqa:F811 + return f"{node.dest} = {node.left} + {node.right}" + + @when(MinusNode) + def visit(self, node: MinusNode): # noqa:F811 + return f"{node.dest} = {node.left} - {node.right}" + + @when(StarNode) + def visit(self, node: StarNode): # noqa:F811 + return f"{node.dest} = {node.left} * {node.right}" + + @when(DivNode) + def visit(self, node: DivNode): # noqa:F811 + return f"{node.dest} = {node.left} / {node.right}" + + @when(AllocateNode) + def visit(self, node: AllocateNode): # noqa:F811 + return f"{node.dest} = ALLOCATE {node.type}" + + @when(TypeOfNode) + def visit(self, node: TypeOfNode): # noqa:F811 + return f"{node.dest} = TYPEOF {node.obj}" + + @when(StaticTypeOfNode) + def visit(self, node: StaticTypeOfNode): # noqa:F811 + return f"{node.dest} = TYPE {node.type}" + + @when(StaticCallNode) + def visit(self, node: StaticCallNode): # noqa:F811 + return f"{node.dest} = CALL {node.function}" + + @when(DynamicCallNode) + def visit(self, node: DynamicCallNode): # noqa:F811 + return f"{node.dest} = VCALL {node.type} {node.method}" + + @when(ArgNode) + def visit(self, node: ArgNode): # noqa:F811 + return f"ARG {node.name}" + + @when(CleanArgsNode) + def visit(self, node: CleanArgsNode): # noqa:F811 + return f"CLEANARG {node.nargs}" + + @when(ReturnNode) + def visit(self, node: ReturnNode): # noqa:F811 + return f'RETURN {node.value if node.value is not None else ""}' + + @when(ReadIntNode) + def visit(self, node: ReadIntNode): # noqa:F811 + return f"{node.dest} = READINT" + + @when(ReadStrNode) + def visit(self, node: ReadStrNode): # noqa:F811 + return f"{node.dest} = READSTR" + + @when(PrintIntNode) + def visit(self, node: PrintIntNode): # noqa:F811 + return f"PRINTINT {node.str_addr}" + + @when(PrintStrNode) + def visit(self, node: PrintStrNode): # noqa:F811 + return f"PRINTSTR {node.str_addr}" + + @when(LengthNode) + def visit(self, node: LengthNode): # noqa:F811 + return f"{node.dest} = LENGTH {node.msg}" + + @when(ConcatNode) + def visit(self, node: ConcatNode): # noqa:F811 + return f"{node.dest} = CONCAT {node.msg1} {node.msg2}" + + @when(SubstringNode) + def visit(self, node: SubstringNode): # noqa:F811 + result = f"{node.dest} = SUBSTRING {node.msg1} " + result += f"{node.start} {node.length}" + return result + + @when(StringEqualNode) + def visit(self, node: StringEqualNode): # noqa:F811 + return f"{node.dest} = STREQ {node.msg1} {node.msg2}" + + @when(GetAttribNode) + def visit(self, node: GetAttribNode): # noqa:F811 + return f"{node.dest} = GETATTR {node.obj} {node.attrib} {node.type}" + + @when(SetAttribNode) + def visit(self, node: SetAttribNode): # noqa:F811 + return f"SETATTR {node.obj} {node.attrib} {node.value} {node.type}" + + @when(LabelNode) + def visit(self, node: LabelNode): # noqa:F811 + return f"LABEL {node.label}" + + @when(GotoNode) + def visit(self, node: GotoNode): # noqa:F811 + return f"GOTO {node.label}" + + @when(GotoIfNode) + def visit(self, node: GotoIfNode): # noqa:F811 + return f"IF {node.value} GOTO {node.label}" + + @when(AbortNode) + def visit(self, node: AbortNode): # noqa:F811 + return "ABORT" diff --git a/src/cmp/cil/utils/__init__.py b/src/cmp/cil/utils/__init__.py new file mode 100644 index 00000000..2dee58f5 --- /dev/null +++ b/src/cmp/cil/utils/__init__.py @@ -0,0 +1,3 @@ +from .cil_scope import Scope # noqa:F401 +from .type_data import TypeData # noqa:F401 +from .visitor import on, when # noqa:F401 diff --git a/src/cmp/cil/utils/cil_scope.py b/src/cmp/cil/utils/cil_scope.py new file mode 100644 index 00000000..e4bd298c --- /dev/null +++ b/src/cmp/cil/utils/cil_scope.py @@ -0,0 +1,45 @@ +from typing import Dict, Optional + + +class Var: + def __init__(self, idx: str, local_name: str): + self.id = idx + self.local_name = local_name + + def __str__(self): + return f"[var] {self.id}: {self.local_name}" + + def __repr__(self): + return str(self) + + +class Scope: + def __init__(self, parent: Optional["Scope"] = None): + self.parent = parent + self.vars: Dict[str, Var] = {} + + def child(self): + return Scope(parent=self) + + def define_var(self, name: str, local_name: str) -> Var: + var = self.vars[name] = Var(name, local_name) + return var + + def get_var(self, name: str) -> Optional[Var]: + try: + return self.vars[name] + except KeyError: + if self.parent is not None: + return self.parent.get_var(name) + return None + + def __str__(self): + cond = self.parent is None + result = "{\n" + result += "\t" if cond else "Parent:\n" + f"{self.parent}\n\t" + result += "\n\t".join(str(x) for x in self.vars.values()) + result += "\n}" + return result + + def __repr__(self): + return str(self) diff --git a/src/cmp/cil/utils/mips_syntax.py b/src/cmp/cil/utils/mips_syntax.py new file mode 100644 index 00000000..b7a7370c --- /dev/null +++ b/src/cmp/cil/utils/mips_syntax.py @@ -0,0 +1,390 @@ +from enum import Enum +from typing import List, Optional + +DATA_SIZE = 4 + + +class Register(str, Enum): + # stores value 0; cannot be modified + zero = "$zero" + # used for system calls and procedure return values + v0 = "$v0" + v1 = "$v1" + # used for passing arguments to procedures + a0 = "$a0" + a1 = "$a1" + a2 = "$a2" + a3 = "$a3" + # used for local storage; calling procedure saves these + t0 = "$t0" + t1 = "$t1" + t2 = "$t2" + t3 = "$t3" + t4 = "$t4" + t5 = "$t5" + t6 = "$t6" + t7 = "$t7" + t8 = "$t8" + t9 = "$t9" + # used for local storage; called procedure saves these + s0 = "$s0" + s1 = "$s1" + s2 = "$s2" + s3 = "$s3" + s4 = "$s4" + s5 = "$s5" + s6 = "$s6" + s7 = "$s7" + sp = "$sp" # stack pointer + fp = "$fp" # frame pointer; primarily used during stack manipulations + ra = "$ra" # used to store return address in procedure call + gp = "$gp" # pointer to area storing global data (data segment) + at = "$at" # reserved for use by the assembler + # reserved for use by OS kernel + k0 = "$k0" + k1 = "$k1" + + +class Directive(str, Enum): + word = ".word" + half = ".half" + byte = ".byte" + ascii = ".ascii" + asciiz = ".asciiz" + space = ".space" + aling = ".align" + text = ".text" + data = ".data" + + +Reg = Register + + +def autowrite(tabs: int = 1): + def inner(func): + """ + autowrite a instruction when execute string return function + """ + + def wrapper(self: "Mips", *args, **kwargs): + instruction = func(self, *args, **kwargs) + if instruction is not None: + self.write_inst(instruction, 0 if self.zip_mode else tabs) + + return wrapper + + return inner + + +def autowritedata(func): + """ + autowrite() a instruction when execute string return function + """ + + def inner(*args, **kwargs): + instructions = func(*args, **kwargs) + if instructions is list: + for instruction in instructions: + args[0].write_data(instruction) + else: + args[0].write_data(instructions) + + return inner + + +class Mips: + def __init__(self, zip_mode=False): + self.DOTTEXT: List[str] = [] + self.DOTDATA: List[str] = [] + self.zip_mode = zip_mode + + def write_inst(self, instruction: str, tabs: int = 0): + tabstr = "" + for _ in range(tabs): + tabstr += "\t" + self.DOTTEXT.append(f"{tabstr}{instruction}") + + def write_data(self, data: str, tabs: int = 0): + self.DOTDATA.append(f"{data}") + + def compile(self): + return "\n".join( + [Directive.data.value] + + self.DOTDATA + + [] + + [Directive.text.value] + + self.DOTTEXT + ) + + def push(self, register: Register): + self.addi(Reg.sp, Reg.sp, -DATA_SIZE) + self.store_memory(register, self.offset(Reg.sp)) + + def pop(self, register: Optional[Register]): + """ + First, load from to address `0($sp)` + and then write `addi $sp , $sp , 8` + to restore the stack pointer + """ + if register: + self.load_memory(register, self.offset(Reg.sp)) + self.addi(Reg.sp, Reg.sp, DATA_SIZE) + + def load_memory(self, dst: Register, address: str): + """ + Load from a specific address a 32 bits register + """ + self.lw(dst, address) + # self.lw(Reg.t8, address) + # self.sll(Reg.t8, Reg.t8, 16) + # self.la(Reg.t7, address) + # self.addi(Reg.t7, Reg.t7, 4) + # self.lw(Reg.t9, self.offset(Reg.t7)) + # self.orr(dst, Reg.t8, Reg.t9) + + def store_memory(self, src: Register, address: str): + """ + Write to a specific address a 32 bits register + """ + self.sw(src, address) + # self.la(Reg.t8, address) + + # self.srl(Reg.t9, src, 16) + + # self.sw(Reg.t9, self.offset(Reg.t8)) # store high bits + # self.sw(src, self.offset(Reg.t8, 4)) # store low bits + + # Arithmetics + @autowrite() + def add(self, dst: Register, rl: Register, rr: Register): + return f"add {dst}, {rl}, {rr}" + + @autowrite() + def sub(self, dst: Register, rl: Register, rr: Register): + return f"sub {dst}, {rl}, {rr}" + + @autowrite() + def addi(self, dst: Register, rl: Register, value: int): + return f"addi {dst}, {rl}, {int(value)}" + + @autowrite() + def addu(self, dst: Register, rl: Register, rr: Register): + return f"addu {dst}, {rl}, {rr}" + + @autowrite() + def subu(self, dst: Register, rl: Register, rr: Register): + return f"subu {dst}, {rl}, {rr}" + + @autowrite() + def addiu(self, dst: Register, rl: Register, value: int): + return f"addiu {dst}, {rl}, {int(value)}" + + @autowrite() + def multu(self, dst: Register, rl: Register, rr: Register): + return f"multu {dst}, {rl}, {rr}" + + @autowrite() + def mult(self, rl: Register, rr: Register): + return f"mult {rl}, {rr}" + + @autowrite() + def div(self, rl: Register, rr: Register): + return f"div {rl}, {rr}" + + # Logical: + @autowrite() + def andd(self, dst: Register, rl: Register, rr: Register): + return f"and {dst}, {rl}, {rr}" + + @autowrite() + def orr(self, dst: Register, rl: Register, rr: Register): + return f"or {dst}, {rl}, {rr}" + + @autowrite() + def nor(self, dst: Register, rl: Register, rr: Register): + return f"nor {dst}, {rl}, {rr}" + + @autowrite() + def andi(self, dst: Register, rl: Register, value: int): + return f"andi {dst}, {rl}, {int(value)}" + + # TODO: Check this instrucction + @autowrite() + def ori(self, dst: Register, rl: Register, value: int): + return f"or {dst}, {rl}, {int(value)}" + + @autowrite() + def sll(self, dst: Register, rl: Register, value: int): + return f"sll {dst}, {rl}, {int(value)}" + + @autowrite() + def srl(self, dst: Register, rl: Register, value: int): + return f"srl {dst}, {rl}, {int(value)}" + + # DataTransfer: + @autowrite() + def lw(self, dst: Register, address: str): + return f"lw {dst}, {address}" + + @autowrite() + def lb(self, dst: Register, address: str): + return f"lb {dst}, {address}" + + @autowrite() + def sw(self, dst: Register, address: str): + return f"sw {dst}, {address}" + + @autowrite() + def sb(self, dst: Register, address: str): + return f"sb {dst}, {address}" + + @autowrite() + def lui(self, dst: Register, value: int): + return f"lui {dst}, {int(value)}" + + @autowrite() + def la(self, dst: Register, label: str): + return f"la {dst}, {label}" + + @autowrite() + def li(self, dst: Register, value: int): + return f"li {dst}, {int(value)}" + + @autowrite() + def mfhi(self, dst: Register): + return f"mfhi {dst}" + + @autowrite() + def mflo(self, dst: Register): + return f"mflo {dst}" + + @autowrite() + def move(self, dst: Register, rl: Register): + return f"move {dst}, {rl}" + + # Brancing + @autowrite() + def beq(self, rl: Register, rr: Register, address: str): + return f"beq {rl}, {rr}, {address}" + + @autowrite() + def bne(self, rl: Register, rr: Register, address: str): + return f"bne {rl}, {rr}, {address}" + + @autowrite() + def beqz(self, rl: Register, address: str): + return f"beqz {rl}, {address}" + + @autowrite() + def bgt(self, rl: Register, rr: Register, address: str): + return f"bgt {rl}, {rr}, {address}" + + @autowrite() + def bge(self, rl: Register, rr: Register, address: str): + return f"bge {rl}, {rr}, {address}" + + @autowrite() + def blt(self, rl: Register, rr: Register, address: str): + return f"blt {rl}, {rr}, {address}" + + @autowrite() + def ble(self, rl: Register, rr: Register, address: str): + return f"ble {rl}, {rr}, {address}" + + # Comparison + @autowrite() + def slt(self, dest: Register, rl: Register, rr: Register): + return f"slt {dest}, {rl}, {rr}" + + @autowrite() + def slti(self, dest: Register, rl: Register, value: int): + return f"slt {dest}, {rl}, {int(value)}" + + # Unconditional Jump + + @autowrite() + def j(self, address: str): + return f"j {address}" + + @autowrite() + def jr(self, r: Register): + return f"jr {r}" + + @autowrite() + def jal(self, address: str): + return f"jal {address}" + + @autowrite() + def jalr(self, dest: Register): + return f"jalr {dest}" + + # System Calls + @autowrite() + def syscall(self, code: int): + self.li(Register.v0, code) + return "syscall" + + def print_int(self): + return self.syscall(1) + + def print_string(self): + return self.syscall(4) + + def read_int(self): + return self.syscall(5) + + def read_string(self): + return self.syscall(8) + + def sbrk(self): + return self.syscall(9) + + def exit(self): + return self.syscall(10) + + def print_char(self): + return self.syscall(11) + + def read_char(self): + return self.syscall(12) + + def exit2(self): + return self.syscall(17) + + # Utilities + def offset(self, r: Register, offset: int = 0): + return f"{int(offset)}({r})" + + @autowrite() + def comment(self, text: str): + return None if self.zip_mode else f"# {text}" + + @autowrite(0) + def label(self, name: str): + return f"{name}:" + + @autowrite() + def empty(self): + return None if self.zip_mode else "" + + # Data Section + + @autowrite() + def data_empty(self): + return "" + + @autowritedata + def data_label(self, name: str): + return f"{name}:" + + @autowritedata + def ascii(self, string: str): + return f'{Directive.ascii} "{string}"' + + @autowritedata + def asciiz(self, string: str): + return f'{Directive.asciiz} "{string}"' + + @autowritedata + def data_comment(self, text: str): + return f"#{text}" diff --git a/src/cmp/cil/utils/type_data.py b/src/cmp/cil/utils/type_data.py new file mode 100644 index 00000000..53e1840c --- /dev/null +++ b/src/cmp/cil/utils/type_data.py @@ -0,0 +1,33 @@ +from typing import Dict + +from ..ast import TypeNode + + +class TypeData: + def __init__(self, type_number: int, typex: TypeNode): + self.type: int = type_number + self.str: str = typex.name_dir + self.length: int = 3 + len(typex.features) + self.attr_offsets: Dict[str, int] = dict() + self.func_offsets: Dict[str, int] = dict() + self.func_names: Dict[str, str] = dict() + + # Calculate offsets for attributes and functions + for idx, feature in enumerate(typex.features): + if isinstance(feature, str): + # The plus 2 is because the two first elementes + # in the instance are the type_int and the type_str_dir. + # Also enumerate starts with 0 + self.attr_offsets[feature] = idx + 3 + else: + func_name, long_name = feature + self.func_offsets[func_name] = idx + 3 + self.func_names[func_name] = long_name + + def __str__(self) -> str: + return ( + f"TypeData-> type: {self.type}, str: {self.str}, " + + f"attr_offsets: {self.attr_offsets}, " + + f"func_offsets: {self.func_offsets}, " + + f"func_names: {self.func_names}" + ) diff --git a/src/cmp/cil/utils/visitor.py b/src/cmp/cil/utils/visitor.py new file mode 100644 index 00000000..d2b9e389 --- /dev/null +++ b/src/cmp/cil/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 # noqa:F841 + 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/cmp/cool_lang/__init__.py b/src/cmp/cool_lang/__init__.py new file mode 100644 index 00000000..eb018c3f --- /dev/null +++ b/src/cmp/cool_lang/__init__.py @@ -0,0 +1 @@ +from . import utils diff --git a/src/cmp/cool_lang/ast/__init__.py b/src/cmp/cool_lang/ast/__init__.py new file mode 100644 index 00000000..04b83a33 --- /dev/null +++ b/src/cmp/cool_lang/ast/__init__.py @@ -0,0 +1,38 @@ +from .arithmetic_node import ArithmeticNode # noqa:F401 +from .assign_node import AssignNode # noqa:F401 +from .atomic_node import AtomicNode # noqa:F401 +from .attr_declaration_node import AttrDeclarationNode # noqa:F401 +from .binary_node import BinaryNode # noqa:F401 +from .block_node import BlockNode # noqa:F401 +from .bool_node import BoolNode # noqa:F401 +from .case_node import CaseNode # noqa:F401 +from .case_of_node import CaseOfNode # noqa:F401 +from .class_declaration_node import ClassDeclarationNode # noqa:F401 +from .complement_node import ComplementNode # noqa:F401 +from .declaration_node import DeclarationNode # noqa:F401 +from .div_node import DivNode # noqa:F401 +from .equal_node import EqualNode # noqa:F401 +from .expresion_node import ExpressionNode # noqa:F401 +from .feature_declaration_node import FeatureDeclarationNode # noqa:F401 +from .func_declaration_node import FuncDeclarationNode # noqa:F401 +from .function_call_node import FunctionCallNode # noqa:F401 +from .id_node import IdNode # noqa:F401 +from .if_then_else_node import IfThenElseNode # noqa:F401 +from .integer_node import IntegerNode # noqa:F401 +from .is_void_node import IsVoidNode # noqa:F401 +from .less_equal_node import LessEqualNode # noqa:F401 +from .less_node import LessNode # noqa:F401 +from .let_in_node import LetInNode # noqa:F401 +from .let_node import LetNode # noqa:F401 +from .member_call_node import MemberCallNode # noqa:F401 +from .minus_node import MinusNode # noqa:F401 +from .new_node import NewNode # noqa:F401 +from .node import Node # noqa:F401 +from .not_node import NotNode # noqa:F401 +from .param_declaration_node import ParamDeclarationNode # noqa:F401 +from .plus_node import PlusNode # noqa:F401 +from .program_node import ProgramNode # noqa:F401 +from .star_node import StarNode # noqa:F401 +from .string_node import StringNode # noqa:F401 +from .unary_node import UnaryNode # noqa:F401 +from .while_loop_node import WhileLoopNode # noqa:F401 diff --git a/src/cmp/cool_lang/ast/arithmetic_node.py b/src/cmp/cool_lang/ast/arithmetic_node.py new file mode 100644 index 00000000..4efb6a28 --- /dev/null +++ b/src/cmp/cool_lang/ast/arithmetic_node.py @@ -0,0 +1,7 @@ +from .binary_node import BinaryNode +from .expresion_node import ExpressionNode + + +class ArithmeticNode(BinaryNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(ArithmeticNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/assign_node.py b/src/cmp/cool_lang/ast/assign_node.py new file mode 100644 index 00000000..378f1d8b --- /dev/null +++ b/src/cmp/cool_lang/ast/assign_node.py @@ -0,0 +1,8 @@ +from .expresion_node import ExpressionNode + + +class AssignNode(ExpressionNode): + def __init__(self, idx: str, expression: ExpressionNode, line: int, column: int): + super(AssignNode, self).__init__(line, column) + self.id: str = idx + self.expression: ExpressionNode = expression diff --git a/src/cmp/cool_lang/ast/atomic_node.py b/src/cmp/cool_lang/ast/atomic_node.py new file mode 100644 index 00000000..16e75ea8 --- /dev/null +++ b/src/cmp/cool_lang/ast/atomic_node.py @@ -0,0 +1,7 @@ +from .expresion_node import ExpressionNode + + +class AtomicNode(ExpressionNode): + def __init__(self, token: str, line: int, column: int): + super(AtomicNode, self).__init__(line, column) + self.token: str = token diff --git a/src/cmp/cool_lang/ast/attr_declaration_node.py b/src/cmp/cool_lang/ast/attr_declaration_node.py new file mode 100644 index 00000000..bca9285b --- /dev/null +++ b/src/cmp/cool_lang/ast/attr_declaration_node.py @@ -0,0 +1,7 @@ +from .expresion_node import ExpressionNode +from .feature_declaration_node import FeatureDeclarationNode + + +class AttrDeclarationNode(FeatureDeclarationNode): + def __init__(self, idx: str, typex: str, expression: ExpressionNode, line: int, column: int): + super(AttrDeclarationNode, self).__init__(idx, typex, expression, line, column) diff --git a/src/cmp/cool_lang/ast/binary_node.py b/src/cmp/cool_lang/ast/binary_node.py new file mode 100644 index 00000000..78db5dd3 --- /dev/null +++ b/src/cmp/cool_lang/ast/binary_node.py @@ -0,0 +1,8 @@ +from .expresion_node import ExpressionNode + + +class BinaryNode(ExpressionNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(BinaryNode, self).__init__(line, column) + self.left: ExpressionNode = left + self.right: ExpressionNode = right diff --git a/src/cmp/cool_lang/ast/block_node.py b/src/cmp/cool_lang/ast/block_node.py new file mode 100644 index 00000000..24a5e1f2 --- /dev/null +++ b/src/cmp/cool_lang/ast/block_node.py @@ -0,0 +1,8 @@ +from typing import List +from .expresion_node import ExpressionNode + + +class BlockNode(ExpressionNode): + def __init__(self, expressions: List[ExpressionNode], line: int, column: int): + super(BlockNode, self).__init__(line, column) + self.expressions: List[ExpressionNode] = expressions diff --git a/src/cmp/cool_lang/ast/bool_node.py b/src/cmp/cool_lang/ast/bool_node.py new file mode 100644 index 00000000..23de0e2f --- /dev/null +++ b/src/cmp/cool_lang/ast/bool_node.py @@ -0,0 +1,6 @@ +from .atomic_node import AtomicNode + + +class BoolNode(AtomicNode): + def __init__(self, token: str, line: int, column: int): + super(BoolNode, self).__init__(token, line, column) diff --git a/src/cmp/cool_lang/ast/case_node.py b/src/cmp/cool_lang/ast/case_node.py new file mode 100644 index 00000000..4e3794b1 --- /dev/null +++ b/src/cmp/cool_lang/ast/case_node.py @@ -0,0 +1,16 @@ +from .declaration_node import DeclarationNode +from .expresion_node import ExpressionNode + + +class CaseNode(DeclarationNode): + def __init__( + self, + idx: str, + typex: str, + expression: ExpressionNode, + line: int = -1, + column: int = -1, + ): + super(CaseNode, self).__init__(idx, line, column) + self.type: str = typex + self.expression: ExpressionNode = expression diff --git a/src/cmp/cool_lang/ast/case_of_node.py b/src/cmp/cool_lang/ast/case_of_node.py new file mode 100644 index 00000000..fd0f98d1 --- /dev/null +++ b/src/cmp/cool_lang/ast/case_of_node.py @@ -0,0 +1,10 @@ +from typing import List +from .case_node import CaseNode +from .expresion_node import ExpressionNode + + +class CaseOfNode(ExpressionNode): + def __init__(self, expression: ExpressionNode, cases: List[CaseNode], line: int, column: int): + super(CaseOfNode, self).__init__(line, column) + self.expression: ExpressionNode = expression + self.cases: List[CaseNode] = cases diff --git a/src/cmp/cool_lang/ast/class_declaration_node.py b/src/cmp/cool_lang/ast/class_declaration_node.py new file mode 100644 index 00000000..df4912da --- /dev/null +++ b/src/cmp/cool_lang/ast/class_declaration_node.py @@ -0,0 +1,10 @@ +from typing import List +from .declaration_node import DeclarationNode +from .feature_declaration_node import FeatureDeclarationNode + + +class ClassDeclarationNode(DeclarationNode): + def __init__(self, idx: str, features: List[FeatureDeclarationNode], parent: 'ClassDeclarationNode', line: int, column: int): + super(ClassDeclarationNode, self).__init__(idx, line, column) + self.parent: 'ClassDeclarationNode' = parent + self.features: List[FeatureDeclarationNode] = features diff --git a/src/cmp/cool_lang/ast/complement_node.py b/src/cmp/cool_lang/ast/complement_node.py new file mode 100644 index 00000000..48fca2f6 --- /dev/null +++ b/src/cmp/cool_lang/ast/complement_node.py @@ -0,0 +1,7 @@ +from .expresion_node import ExpressionNode +from .unary_node import UnaryNode + + +class ComplementNode(UnaryNode): + def __init__(self, expression: ExpressionNode, line: int = None, column: int = None): + super(ComplementNode, self).__init__(expression, line, column) diff --git a/src/cmp/cool_lang/ast/declaration_node.py b/src/cmp/cool_lang/ast/declaration_node.py new file mode 100644 index 00000000..fb27b88c --- /dev/null +++ b/src/cmp/cool_lang/ast/declaration_node.py @@ -0,0 +1,7 @@ +from .node import Node + + +class DeclarationNode(Node): + def __init__(self, idx: str, line: int, column: int): + super(DeclarationNode, self).__init__(line, column) + self.id: str = idx diff --git a/src/cmp/cool_lang/ast/div_node.py b/src/cmp/cool_lang/ast/div_node.py new file mode 100644 index 00000000..1bd9072b --- /dev/null +++ b/src/cmp/cool_lang/ast/div_node.py @@ -0,0 +1,7 @@ +from .arithmetic_node import ArithmeticNode +from .expresion_node import ExpressionNode + + +class DivNode(ArithmeticNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(DivNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/equal_node.py b/src/cmp/cool_lang/ast/equal_node.py new file mode 100644 index 00000000..dfbbc075 --- /dev/null +++ b/src/cmp/cool_lang/ast/equal_node.py @@ -0,0 +1,7 @@ +from .binary_node import BinaryNode +from .expresion_node import ExpressionNode + + +class EqualNode(BinaryNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(EqualNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/expresion_node.py b/src/cmp/cool_lang/ast/expresion_node.py new file mode 100644 index 00000000..94e17773 --- /dev/null +++ b/src/cmp/cool_lang/ast/expresion_node.py @@ -0,0 +1,6 @@ +from .node import Node + +class ExpressionNode(Node): + def __init__(self, line: int, column: int): + super(ExpressionNode, self).__init__(line, column) + diff --git a/src/cmp/cool_lang/ast/feature_declaration_node.py b/src/cmp/cool_lang/ast/feature_declaration_node.py new file mode 100644 index 00000000..342d44d6 --- /dev/null +++ b/src/cmp/cool_lang/ast/feature_declaration_node.py @@ -0,0 +1,9 @@ +from .declaration_node import DeclarationNode +from .expresion_node import ExpressionNode + + +class FeatureDeclarationNode(DeclarationNode): + def __init__(self, idx: str, typex: str, expression: ExpressionNode, line: int, column: int): + super(FeatureDeclarationNode, self).__init__(idx, line, column) + self.type: str = typex + self.expression: ExpressionNode = expression diff --git a/src/cmp/cool_lang/ast/func_declaration_node.py b/src/cmp/cool_lang/ast/func_declaration_node.py new file mode 100644 index 00000000..fd3ec258 --- /dev/null +++ b/src/cmp/cool_lang/ast/func_declaration_node.py @@ -0,0 +1,10 @@ +from typing import List +from .expresion_node import ExpressionNode +from .feature_declaration_node import FeatureDeclarationNode +from .param_declaration_node import ParamDeclarationNode + + +class FuncDeclarationNode(FeatureDeclarationNode): + def __init__(self, idx: str, params: List[ParamDeclarationNode], typex: str, expression: ExpressionNode, line: int, column: int): + super(FuncDeclarationNode, self).__init__(idx, typex, expression, line, column) + self.params: List[ParamDeclarationNode] = params diff --git a/src/cmp/cool_lang/ast/function_call_node.py b/src/cmp/cool_lang/ast/function_call_node.py new file mode 100644 index 00000000..6220e23f --- /dev/null +++ b/src/cmp/cool_lang/ast/function_call_node.py @@ -0,0 +1,11 @@ +from typing import List +from .expresion_node import ExpressionNode + + +class FunctionCallNode(ExpressionNode): + def __init__(self, obj: ExpressionNode, idx: str, args: List[ExpressionNode], typex: str, line: int, column: int): + super(FunctionCallNode, self).__init__(line, column) + self.obj: ExpressionNode = obj + self.id: str = idx + self.args: List[ExpressionNode] = args + self.type: str = typex diff --git a/src/cmp/cool_lang/ast/id_node.py b/src/cmp/cool_lang/ast/id_node.py new file mode 100644 index 00000000..019cbaab --- /dev/null +++ b/src/cmp/cool_lang/ast/id_node.py @@ -0,0 +1,6 @@ +from .atomic_node import AtomicNode + + +class IdNode(AtomicNode): + def __init__(self, token: str, line: int, column: int): + super(IdNode, self).__init__(token, line, column) diff --git a/src/cmp/cool_lang/ast/if_then_else_node.py b/src/cmp/cool_lang/ast/if_then_else_node.py new file mode 100644 index 00000000..474aa9b4 --- /dev/null +++ b/src/cmp/cool_lang/ast/if_then_else_node.py @@ -0,0 +1,9 @@ +from .expresion_node import ExpressionNode + + +class IfThenElseNode(ExpressionNode): + def __init__(self, condition: ExpressionNode, if_body: ExpressionNode, else_body: ExpressionNode, line: int, column: int): + super(IfThenElseNode, self).__init__(line, column) + self.condition: ExpressionNode = condition + self.if_body: ExpressionNode = if_body + self.else_body: ExpressionNode = else_body diff --git a/src/cmp/cool_lang/ast/integer_node.py b/src/cmp/cool_lang/ast/integer_node.py new file mode 100644 index 00000000..d2714d98 --- /dev/null +++ b/src/cmp/cool_lang/ast/integer_node.py @@ -0,0 +1,6 @@ +from .atomic_node import AtomicNode + + +class IntegerNode(AtomicNode): + def __init__(self, token: str, line: int, column: int): + super(IntegerNode, self).__init__(token, line, column) diff --git a/src/cmp/cool_lang/ast/is_void_node.py b/src/cmp/cool_lang/ast/is_void_node.py new file mode 100644 index 00000000..6a2aea6c --- /dev/null +++ b/src/cmp/cool_lang/ast/is_void_node.py @@ -0,0 +1,7 @@ +from .expresion_node import ExpressionNode +from .unary_node import UnaryNode + + +class IsVoidNode(UnaryNode): + def __init__(self, expression: ExpressionNode, line: int = None, column: int = None): + super(IsVoidNode, self).__init__(expression, line, column) diff --git a/src/cmp/cool_lang/ast/less_equal_node.py b/src/cmp/cool_lang/ast/less_equal_node.py new file mode 100644 index 00000000..66a798ef --- /dev/null +++ b/src/cmp/cool_lang/ast/less_equal_node.py @@ -0,0 +1,7 @@ +from .binary_node import BinaryNode +from .expresion_node import ExpressionNode + + +class LessEqualNode(BinaryNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(LessEqualNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/less_node.py b/src/cmp/cool_lang/ast/less_node.py new file mode 100644 index 00000000..c097750d --- /dev/null +++ b/src/cmp/cool_lang/ast/less_node.py @@ -0,0 +1,7 @@ +from .binary_node import BinaryNode +from .expresion_node import ExpressionNode + + +class LessNode(BinaryNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(LessNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/let_in_node.py b/src/cmp/cool_lang/ast/let_in_node.py new file mode 100644 index 00000000..cac770b1 --- /dev/null +++ b/src/cmp/cool_lang/ast/let_in_node.py @@ -0,0 +1,10 @@ +from typing import List +from .expresion_node import ExpressionNode +from .let_node import LetNode + + +class LetInNode(ExpressionNode): + def __init__(self, let_body: List[LetNode], in_body: ExpressionNode, line: int, column: int): + super(LetInNode, self).__init__(line, column) + self.let_body: List[LetNode] = let_body + self.in_body: ExpressionNode = in_body diff --git a/src/cmp/cool_lang/ast/let_node.py b/src/cmp/cool_lang/ast/let_node.py new file mode 100644 index 00000000..01f3d6a9 --- /dev/null +++ b/src/cmp/cool_lang/ast/let_node.py @@ -0,0 +1,16 @@ +from .declaration_node import DeclarationNode +from .expresion_node import ExpressionNode + + +class LetNode(DeclarationNode): + def __init__( + self, + idx: str, + typex: str, + expression: ExpressionNode, + line: int = -1, + column: int = -1, + ): + super(LetNode, self).__init__(idx, line, column) + self.type: str = typex + self.expression: ExpressionNode = expression diff --git a/src/cmp/cool_lang/ast/member_call_node.py b/src/cmp/cool_lang/ast/member_call_node.py new file mode 100644 index 00000000..a7477b49 --- /dev/null +++ b/src/cmp/cool_lang/ast/member_call_node.py @@ -0,0 +1,9 @@ +from typing import List +from .expresion_node import ExpressionNode + + +class MemberCallNode(ExpressionNode): + def __init__(self, idx: str, args: List[ExpressionNode], line: int, column: int): + super(MemberCallNode, self).__init__(line, column) + self.id: str = idx + self.args: List[ExpressionNode] = args diff --git a/src/cmp/cool_lang/ast/minus_node.py b/src/cmp/cool_lang/ast/minus_node.py new file mode 100644 index 00000000..cfd7b224 --- /dev/null +++ b/src/cmp/cool_lang/ast/minus_node.py @@ -0,0 +1,7 @@ +from .arithmetic_node import ArithmeticNode +from .expresion_node import ExpressionNode + + +class MinusNode(ArithmeticNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(MinusNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/new_node.py b/src/cmp/cool_lang/ast/new_node.py new file mode 100644 index 00000000..88111755 --- /dev/null +++ b/src/cmp/cool_lang/ast/new_node.py @@ -0,0 +1,7 @@ +from .expresion_node import ExpressionNode + + +class NewNode(ExpressionNode): + def __init__(self, typex: str, line: int, column: int): + super(NewNode, self).__init__(line, column) + self.type: str = typex diff --git a/src/cmp/cool_lang/ast/node.py b/src/cmp/cool_lang/ast/node.py new file mode 100644 index 00000000..92a3b197 --- /dev/null +++ b/src/cmp/cool_lang/ast/node.py @@ -0,0 +1,8 @@ +from typing import Any + + +class Node: + def __init__(self, line: int, column: int): + self.line: int = line + self.column: int = column + self.static_type: Any = None # type:ignore diff --git a/src/cmp/cool_lang/ast/not_node.py b/src/cmp/cool_lang/ast/not_node.py new file mode 100644 index 00000000..160748a9 --- /dev/null +++ b/src/cmp/cool_lang/ast/not_node.py @@ -0,0 +1,7 @@ +from .expresion_node import ExpressionNode +from .unary_node import UnaryNode + + +class NotNode(UnaryNode): + def __init__(self, expression: ExpressionNode, line: int = None, column: int = None): + super(NotNode, self).__init__(expression, line, column) diff --git a/src/cmp/cool_lang/ast/param_declaration_node.py b/src/cmp/cool_lang/ast/param_declaration_node.py new file mode 100644 index 00000000..1b270176 --- /dev/null +++ b/src/cmp/cool_lang/ast/param_declaration_node.py @@ -0,0 +1,7 @@ +from .declaration_node import DeclarationNode + + +class ParamDeclarationNode(DeclarationNode): + def __init__(self, idx: str, typex: str, line: int = -1, column: int = -1): + super(ParamDeclarationNode, self).__init__(idx, line, column) + self.type: str = typex diff --git a/src/cmp/cool_lang/ast/plus_node.py b/src/cmp/cool_lang/ast/plus_node.py new file mode 100644 index 00000000..f58df47a --- /dev/null +++ b/src/cmp/cool_lang/ast/plus_node.py @@ -0,0 +1,7 @@ +from .arithmetic_node import ArithmeticNode +from .expresion_node import ExpressionNode + + +class PlusNode(ArithmeticNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(PlusNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/program_node.py b/src/cmp/cool_lang/ast/program_node.py new file mode 100644 index 00000000..3528a4f7 --- /dev/null +++ b/src/cmp/cool_lang/ast/program_node.py @@ -0,0 +1,9 @@ +from typing import List +from .class_declaration_node import ClassDeclarationNode +from .node import Node + + +class ProgramNode(Node): + def __init__(self, classes: List[ClassDeclarationNode], line: int, column: int): + super(ProgramNode, self).__init__(line, column) + self.classes: List[ClassDeclarationNode] = classes diff --git a/src/cmp/cool_lang/ast/star_node.py b/src/cmp/cool_lang/ast/star_node.py new file mode 100644 index 00000000..820f41b4 --- /dev/null +++ b/src/cmp/cool_lang/ast/star_node.py @@ -0,0 +1,7 @@ +from .arithmetic_node import ArithmeticNode +from .expresion_node import ExpressionNode + + +class StarNode(ArithmeticNode): + def __init__(self, left: ExpressionNode, right: ExpressionNode, line: int, column: int): + super(StarNode, self).__init__(left, right, line, column) diff --git a/src/cmp/cool_lang/ast/string_node.py b/src/cmp/cool_lang/ast/string_node.py new file mode 100644 index 00000000..83c1d038 --- /dev/null +++ b/src/cmp/cool_lang/ast/string_node.py @@ -0,0 +1,6 @@ +from .atomic_node import AtomicNode + + +class StringNode(AtomicNode): + def __init__(self, token: str, line: int, column: int): + super(StringNode, self).__init__(token, line, column) diff --git a/src/cmp/cool_lang/ast/unary_node.py b/src/cmp/cool_lang/ast/unary_node.py new file mode 100644 index 00000000..154b8d31 --- /dev/null +++ b/src/cmp/cool_lang/ast/unary_node.py @@ -0,0 +1,7 @@ +from .expresion_node import ExpressionNode + + +class UnaryNode(ExpressionNode): + def __init__(self, expression: ExpressionNode, line: int = None, column: int = None): + super(UnaryNode, self).__init__(expression.line if line is None else line, expression.column if column is None else column) + self.expression: ExpressionNode = expression diff --git a/src/cmp/cool_lang/ast/while_loop_node.py b/src/cmp/cool_lang/ast/while_loop_node.py new file mode 100644 index 00000000..89bab0c7 --- /dev/null +++ b/src/cmp/cool_lang/ast/while_loop_node.py @@ -0,0 +1,8 @@ +from .expresion_node import ExpressionNode + + +class WhileLoopNode(ExpressionNode): + def __init__(self, condition: ExpressionNode, body: ExpressionNode, line: int, column: int): + super(WhileLoopNode, self).__init__(line, column) + self.condition: ExpressionNode = condition + self.body: ExpressionNode = body diff --git a/src/cmp/cool_lang/errors/__init__.py b/src/cmp/cool_lang/errors/__init__.py new file mode 100644 index 00000000..c1ccdb16 --- /dev/null +++ b/src/cmp/cool_lang/errors/__init__.py @@ -0,0 +1,52 @@ +class Error: + def __init__(self, error_name, description): + self.error_name = error_name + self.description = description + + def __str__(self): + return f'{self.error_name}: {self.description}' + + +class LocalizedError(Error): + def __init__(self, error_name, row, column, description): + Error.__init__(self, error_name, description) + self.row = row + self.column = column + + def __str__(self): + return f'({self.row}, {self.column}) - {self.error_name}: {self.description}' + + +class LexicographicError(LocalizedError): + def __init__(self, row, column, description): + LocalizedError.__init__(self, 'LexicographicError', row, column, description) + + +class SyntacticError(LocalizedError): + def __init__(self, row, column, description): + LocalizedError.__init__(self, 'SyntacticError', row, column, description) + + +class CNameError(LocalizedError): + def __init__(self, row, column, description): + LocalizedError.__init__(self, 'NameError', row, column, description) + + +class CTypeError(LocalizedError): + def __init__(self, row, column, description): + LocalizedError.__init__(self, 'TypeError', row, column, description) + + +class CAttributeError(LocalizedError): + def __init__(self, row, column, description): + LocalizedError.__init__(self, 'AttributeError', row, column, description) + + +class SemanticError(LocalizedError): + def __init__(self, row, column, description): + LocalizedError.__init__(self, 'SemanticError', row, column, description) + + +class CompilationError(LocalizedError): + def __init__(self, row, column, description): + LocalizedError.__init__(self, 'CompilationError', row, column, description) diff --git a/src/cmp/cool_lang/lexer/__init__.py b/src/cmp/cool_lang/lexer/__init__.py new file mode 100644 index 00000000..aff179b7 --- /dev/null +++ b/src/cmp/cool_lang/lexer/__init__.py @@ -0,0 +1,2 @@ +from .cllexer import COOL_LEXER +from .cllexer import tokens as COOL_TOKENS diff --git a/src/cmp/cool_lang/lexer/cllexer.py b/src/cmp/cool_lang/lexer/cllexer.py new file mode 100644 index 00000000..632b2dc1 --- /dev/null +++ b/src/cmp/cool_lang/lexer/cllexer.py @@ -0,0 +1,243 @@ +import ply.lex as lex +from ..errors import LexicographicError +from ..utils import find_column + + +keywords = [ + 'CLASS', + 'INHERITS', + 'IF', + 'THEN', + 'ELSE', + 'FI', + 'WHILE', + 'LOOP', + 'POOL', + 'LET', + 'IN', + 'CASE', + 'OF', + 'ESAC', + 'NEW', + 'ISVOID', + 'NOT', +] + +def check_keyword(token): + upper = token.value.upper() + if upper in keywords: + token.type = upper + +literals = [ + # Literals + 'PLUS', + 'MINUS', + 'STAR', + 'DIV', + 'COLON', + 'SEMICOLON', + 'OPAR', + 'CPAR', + 'OBRA', + 'CBRA', + 'ARROB', + 'DOT', + 'COMMA', +] + +tokens = [ + # Identifiers + 'TYPE', 'ID', + # Primitive data types + 'NUMBER', 'STRING', 'BOOL', + # Special keywords + 'ACTION', + # Operators + 'ASSIGN', 'LESS', 'LESSEQUAL', 'EQUAL', 'INT_COMPLEMENT', + # Comments + 'COMMENT', +] + literals + keywords + +class COOL_LEXER(object): + def __init__(self): + self.errors = [] + self.code = None + self.lexer = None + self.tokens = tokens + self.states = ( + ('string', 'exclusive'), + ('simpleComment', 'exclusive'), + ('multiComment', 'exclusive'), + ) + self.result = [] + + # Lexer regular expressions + self.t_PLUS = r'\+' + self.t_MINUS = r'\-' + self.t_STAR = r'\*' + self.t_DIV = r'\/' + self.t_COLON = r'\:' + self.t_SEMICOLON = r'\;' + self.t_OPAR = r'\(' + self.t_CPAR = r'\)' + self.t_OBRA = r'\{' + self.t_CBRA = r'\}' + self.t_ARROB = r'\@' + self.t_DOT = r'\.' + self.t_COMMA = r'\,' + self.t_NUMBER = r'[0-9]+' + # self.t_BOOL = r't[rR][uU][eE]|f[aA][lL][sS][eE]' + self.t_ACTION = r'=>' + self.t_ASSIGN = r'<-' + self.t_LESS = r'<' + self.t_LESSEQUAL = r'<=' + self.t_EQUAL = r'=' + self.t_INT_COMPLEMENT = r'~' + + self.t_ignore = ' \t' + self.t_string_ignore = '' + self.t_simpleComment_ignore = '' + self.t_multiComment_ignore = '' + + self.index = 0 + + # Lexer methods + def t_TYPE(self, t): + r'[A-Z][A-Za-z0-9_]*' + check_keyword(t) + return t + + def t_ID(self, t): + r'[a-z][A-Za-z0-9_]*' + check_keyword(t) + if t.type == 'ID': + upper = t.value.upper() + if upper == 'FALSE' or upper == 'TRUE': + t.type = 'BOOL' + return t + + def t_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + def t_error(self, t): + self.errors.append(LexicographicError(t.lineno, find_column(self.code, t.lexpos), f'Invalid character "{t.value[0]}".')) + + # Lexer simple comment state methods + + def t_simpleComment(self, t): + r'--' + t.lexer.simpleComment_first = t.lexer.lexpos + t.lexer.begin('simpleComment') + + def t_simpleComment_end(self, t): + r'\n' + t.value = t.lexer.lexdata[t.lexer.simpleComment_first: t.lexer.lexpos - 1] + t.type = 'COMMENT' + t.lexer.lineno += 1 + t.lexer.begin('INITIAL') + return t + + def t_simpleComment_eof(self, t): + t.value = t.lexer.lexdata[t.lexer.simpleComment_first: t.lexer.lexpos - 1] + t.type = 'COMMENT' + t.lexer.begin('INITIAL') + return t + + def t_simpleComment_error(self, t): + t.lexer.skip(1) + + # Lexer multi comment state methods + + def t_multiComment(self, t): + r'\(\*' + t.lexer.multuComment_start = t.lexer.lexpos + t.lexer.level = 1 + t.lexer.begin('multiComment') + + def t_multiComment_lbrace(self, t): + r'\(\*' + t.lexer.level += 1 + + def t_multiComment_rbrace(self, t): + r'\*\)' + t.lexer.level -= 1 + if t.lexer.level == 0: + t.value = t.lexer.lexdata[t.lexer.multuComment_start: t.lexer.lexpos - 2] + t.type = "COMMENT" + t.lexer.begin('INITIAL') + return t + + def t_multiComment_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + def t_multiComment_error(self, t): + t.lexer.skip(1) + + def t_multiComment_eof(self, t): + self.errors.append(LexicographicError(t.lexer.lineno, find_column(self.code, t.lexpos), f'EOF in comment.')) + + # Lexer string state methods + + def t_string(self, t): + r'"' + t.lexer.string_start = t.lexpos + t.lexer.begin('string') + + def t_string_end(self, t): + r'"' + if t.lexer.lexdata[t.lexer.lexpos - 2] != '\\': + t.value = t.lexer.lexdata[t.lexer.string_start: t.lexer.lexpos] + t.type = 'STRING' + t.lexer.begin('INITIAL') + return t + + def t_string_eof(self, t): + self.errors.append(LexicographicError(t.lineno, find_column(self.code, t.lexpos), f'Unexpected EOF.')) + + def t_string_error(self, t): + val = t.value[0] + if val in '\b\t\0\f': + char = '\\f' if val == '\f' else '\\b' if val == '\b' else '\\t' if val == '\t' else 'null' + self.errors.append(LexicographicError(t.lineno, find_column(self.code, t.lexpos), f'Invalid character "{char}" in a string.')) + elif val == '\n': + if t.lexer.lexdata[t.lexer.lexpos - 1] != '\\': + self.errors.append(LexicographicError(t.lineno, find_column(self.code, t.lexpos), f'Invalid character "\\n" in a string.')) + else: + t.lexer.lineno += 1 + t.lexer.skip(1) + + # Non lexer methods + def build(self, **kwargs): + self.lexer = lex.lex(module=self, **kwargs) + + def tokenize(self, data): + self.code = data + if self.lexer is None: + self.build() + self.lexer.input(data) + while True: + try: + token = self.lexer.token() + if self.errors: + return False + if not token: + break + except lex.LexError: + return False + self.result.append(token) + return True + + def token(self): + if self.index >= len(self.result): + return None + result = None + while True: + if self.index >= len(self.result): + return None + result = self.result[self.index] + self.index += 1 + if result.type != 'COMMENT': + break + return result diff --git a/src/cmp/cool_lang/parser/__init__.py b/src/cmp/cool_lang/parser/__init__.py new file mode 100644 index 00000000..72f54861 --- /dev/null +++ b/src/cmp/cool_lang/parser/__init__.py @@ -0,0 +1 @@ +from .clparser import COOL_PARSER diff --git a/src/cmp/cool_lang/parser/clparser.py b/src/cmp/cool_lang/parser/clparser.py new file mode 100644 index 00000000..304e5c02 --- /dev/null +++ b/src/cmp/cool_lang/parser/clparser.py @@ -0,0 +1,403 @@ +import ply.yacc as yacc + +from ..ast import ( + AssignNode, + AttrDeclarationNode, + BlockNode, + BoolNode, + CaseNode, + CaseOfNode, + ClassDeclarationNode, + ComplementNode, + DivNode, + EqualNode, + FuncDeclarationNode, + FunctionCallNode, + IdNode, + IfThenElseNode, + IntegerNode, + IsVoidNode, + LessEqualNode, + LessNode, + LetInNode, + LetNode, + MemberCallNode, + MinusNode, + NewNode, + NotNode, + ParamDeclarationNode, + PlusNode, + ProgramNode, + StarNode, + StringNode, + WhileLoopNode, +) +from ..errors import SyntacticError +from ..lexer import COOL_TOKENS +from ..utils import find_column + + +class COOL_PARSER: + def __init__(self): + self.start = "program" + self.tokens = COOL_TOKENS + self.parser = None + self.code = None + self.result = None + self.errors = [] + + self.precedence = ( + ("right", "ASSIGN"), + ("right", "NOT"), + ("nonassoc", "LESSEQUAL", "EQUAL", "LESS"), + ("left", "PLUS", "MINUS"), + ("left", "STAR", "DIV"), + ("right", "ISVOID"), + ("right", "INT_COMPLEMENT"), + ("left", "ARROB"), + ("left", "DOT"), + ) + + # Parser related methods + + def p_empty(self, p): + "empty :" + p[0] = [] + + def p_program(self, p): + "program : class_list" + p[0] = ProgramNode(p[1], p[1][0].line, p[1][0].column) + + def p_class_list_simple(self, p): + "class_list : def_class" + p[0] = [p[1]] + + def p_class_list_multi(self, p): + "class_list : def_class class_list" + p[0] = [p[1]] + p[2] + + def p_def_class(self, p): + "def_class : CLASS TYPE OBRA feature_list CBRA SEMICOLON" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = ClassDeclarationNode(p[2], p[4], None, line, column) # type:ignore + + def p_def_class_heritance(self, p): + "def_class : CLASS TYPE INHERITS TYPE OBRA feature_list CBRA SEMICOLON" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = ClassDeclarationNode(p[2], p[6], p[4], line, column) + + def p_feature_list(self, p): + "feature_list : feature feature_list" + p[0] = [p[1]] + p[2] + + def p_feature_list_empty(self, p): + "feature_list : empty" + p[0] = p[1] + + def p_feature_declaration(self, p): + "feature : ID COLON TYPE SEMICOLON" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = AttrDeclarationNode(p[1], p[3], None, line, column) # type:ignore + + def p_feature_assign(self, p): + "feature : ID COLON TYPE ASSIGN expr SEMICOLON" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = AttrDeclarationNode(p[1], p[3], p[5], line, column) + + def p_feature_function(self, p): + "feature : ID OPAR CPAR COLON TYPE OBRA expr CBRA SEMICOLON" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = FuncDeclarationNode(p[1], [], p[5], p[7], line, column) + + def p_feature_function_params(self, p): + "feature : ID OPAR params_list CPAR COLON TYPE OBRA expr CBRA SEMICOLON" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = FuncDeclarationNode(p[1], p[3], p[6], p[8], line, column) + + def p_params_list_simple(self, p): + "params_list : param" + p[0] = [p[1]] + + def p_params_list_multi(self, p): + "params_list : param COMMA params_list" + p[0] = [p[1]] + p[3] + + def p_param(self, p): + "param : ID COLON TYPE" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = ParamDeclarationNode(p[1], p[3], line=line, column=column) + + def p_expr_list_simple(self, p): + "expr_list : expr SEMICOLON" + p[0] = [p[1]] + + def p_expr_list_multi(self, p): + "expr_list : expr SEMICOLON expr_list" + p[0] = [p[1]] + p[3] + + def p_let_list_declaration_simple(self, p): + "let_list : ID COLON TYPE" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = [LetNode(p[1], p[3], None, line=line, column=column)] # type:ignore + + def p_let_list_declaration_multi(self, p): + "let_list : ID COLON TYPE COMMA let_list" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = [LetNode(p[1], p[3], None, line, column)] + p[5] # type:ignore + + def p_let_list_assign_simple(self, p): + "let_list : ID COLON TYPE ASSIGN expr" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = [LetNode(p[1], p[3], p[5], line=line, column=column)] + + def p_let_list_assign_multi(self, p): + "let_list : ID COLON TYPE ASSIGN expr COMMA let_list" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = [LetNode(p[1], p[3], p[5], line=line, column=column)] + p[7] + + def p_case_list_simple(self, p): + "case_list : ID COLON TYPE ACTION expr SEMICOLON" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = [CaseNode(p[1], p[3], p[5], line, column)] + + def p_case_list_multi(self, p): + "case_list : ID COLON TYPE ACTION expr SEMICOLON case_list" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = [CaseNode(p[1], p[3], p[5], line, column)] + p[7] + + def p_expr(self, p): + "expr : comp_expr" + p[0] = p[1] + + def p_comp_expr_le(self, p): + "comp_expr : comp_expr LESSEQUAL not_arith" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = LessEqualNode(p[1], p[3], line, column) + + def p_comp_expr_e(self, p): + "comp_expr : comp_expr EQUAL not_arith" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = EqualNode(p[1], p[3], line, column) + + def p_comp_expr_l(self, p): + "comp_expr : comp_expr LESS not_arith" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = LessNode(p[1], p[3], line, column) + + def p_comp_expr_s(self, p): + "comp_expr : not_arith" + p[0] = p[1] + + def p_not_arith_not(self, p): + "not_arith : NOT comp_expr" + p[0] = NotNode(p[2]) + + def p_not_arith(self, p): + "not_arith : arith" + p[0] = p[1] + + def p_arith_plus(self, p): + "arith : arith PLUS term" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = PlusNode(p[1], p[3], line, column) + + def p_arith_minus(self, p): + "arith : arith MINUS term" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = MinusNode(p[1], p[3], line, column) + + def p_arith_simple(self, p): + "arith : term" + p[0] = p[1] + + def p_term_star(self, p): + "term : term STAR vfactor" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = StarNode(p[1], p[3], line, column) + + def p_term_div(self, p): + "term : term DIV vfactor" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = DivNode(p[1], p[3], line, column) + + def p_term_simple(self, p): + "term : vfactor" + p[0] = p[1] + + def p_vfactor_is(self, p): + "vfactor : ISVOID factor" + p[0] = IsVoidNode(p[2]) + + def p_vfactor(self, p): + "vfactor : factor" + p[0] = p[1] + + def p_factor_int_comp(self, p): + "factor : INT_COMPLEMENT atom" + p[0] = ComplementNode(p[2]) + + def p_factor_atom(self, p): + "factor : atom" + p[0] = p[1] + + def p_atom_if_else(self, p): + "atom : IF expr THEN expr ELSE expr FI" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = IfThenElseNode(p[2], p[4], p[6], line, column) + + def p_atom_while(self, p): + "atom : WHILE expr LOOP expr POOL" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = WhileLoopNode(p[2], p[4], line, column) + + def p_atom_multi(self, p): + "atom : OBRA expr_list CBRA" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = BlockNode(p[2], line, column) + + def p_atom_let_in(self, p): + "atom : LET let_list IN expr" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = LetInNode(p[2], p[4], line, column) + + def p_atom_case(self, p): + "atom : CASE expr OF case_list ESAC" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = CaseOfNode(p[2], p[4], line, column) + + def p_atom_assign(self, p): + "atom : ID ASSIGN expr" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = AssignNode(p[1], p[3], line, column) + + def p_atom_func_call(self, p): + "atom : atom func_call" + line = p[1].line + column = p[1].column + p[0] = FunctionCallNode(p[1], *p[2], line, column) + + def p_atom_member_call(self, p): + "atom : member_call" + p[0] = p[1] + + def p_atom_new(self, p): + "atom : NEW TYPE" + line = p.lineno(2) + column = find_column(self.code, p.lexpos(2)) + p[0] = NewNode(p[2], line, column) + + def p_atom_par(self, p): + "atom : OPAR expr CPAR" + p[0] = p[2] + + def p_atom_var(self, p): + "atom : ID" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = IdNode(p[1], line, column) + + def p_atom_int(self, p): + "atom : NUMBER" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = IntegerNode(p[1], line, column) + + def p_atom_bool(self, p): + "atom : BOOL" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = BoolNode(p[1], line, column) + + def p_atom_str(self, p): + "atom : STRING" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = StringNode(p[1], line, column) + + def p_func_call_simple(self, p): + "func_call : DOT ID OPAR CPAR" + p[0] = (p[2], [], None) + + def p_func_call_multi(self, p): + "func_call : DOT ID OPAR arg_list CPAR" + p[0] = (p[2], p[4], None) + + def p_func_call_simple_at(self, p): + "func_call : ARROB TYPE DOT ID OPAR CPAR" + p[0] = (p[4], [], p[2]) + + def p_func_call_multi_at(self, p): + "func_call : ARROB TYPE DOT ID OPAR arg_list CPAR" + p[0] = (p[4], p[6], p[2]) + + def p_arg_list_simple(self, p): + "arg_list : expr" + p[0] = [p[1]] + + def p_arg_list_multi(self, p): + "arg_list : expr COMMA arg_list" + p[0] = [p[1]] + p[3] + + def p_member_call_simple(self, p): + "member_call : ID OPAR CPAR" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = MemberCallNode(p[1], [], line, column) + + def p_member_call_multi(self, p): + "member_call : ID OPAR arg_list CPAR" + line = p.lineno(1) + column = find_column(self.code, p.lexpos(1)) + p[0] = MemberCallNode(p[1], p[3], line, column) + + def p_error(self, p): + line = p.lineno + column = find_column(self.code, p.lexpos) + self.errors.append( + SyntacticError(line, column, f"Syntactic error!!! in token: {p}") + ) + raise SyntaxError( + SyntacticError(line, column, f"Syntactic error!!! in token: {p}") + ) + + # Non parser related methods + + def build(self, **kwargs): + self.parser = yacc.yacc(module=self, write_tables=False) + + def parse(self, lexer): + self.code = lexer.code + if self.parser is None: + self.build() + try: + result = self.parser.parse(lexer=lexer) + self.result = result + except SyntaxError: + return False + return True diff --git a/src/cmp/cool_lang/semantics/__init__.py b/src/cmp/cool_lang/semantics/__init__.py new file mode 100644 index 00000000..2ea45327 --- /dev/null +++ b/src/cmp/cool_lang/semantics/__init__.py @@ -0,0 +1,26 @@ +from .formatter import COOL_FORMATTER +from .type_builder import COOL_TYPE_BUILDER +from .type_checker import COOL_TYPE_CHECKER +from .type_collector import COOL_TYPE_COLLECTOR + + +class COOL_CHECKER: + def __init__(self): + self.context = None + self.errors = [] + + def check_semantics(self, program, verbose=False): + self.errors.clear() + # All semantics checks here + if verbose: + print(COOL_FORMATTER().visit(program, tabs=0)) + self.context = COOL_TYPE_COLLECTOR(errors=self.errors).visit(program) + if len(self.errors) == 0: + COOL_TYPE_BUILDER( + context=self.context, + errors=self.errors, + ).visit(program) + COOL_TYPE_CHECKER(self.context, errors=self.errors).visit(program) + if verbose: + print(self.context) + return not len(self.errors) > 0 diff --git a/src/cmp/cool_lang/semantics/formatter.py b/src/cmp/cool_lang/semantics/formatter.py new file mode 100644 index 00000000..6d2dfb48 --- /dev/null +++ b/src/cmp/cool_lang/semantics/formatter.py @@ -0,0 +1,169 @@ +from ..ast import ( + AssignNode, + AtomicNode, + AttrDeclarationNode, + BinaryNode, + BlockNode, + CaseNode, + CaseOfNode, + ClassDeclarationNode, + FuncDeclarationNode, + FunctionCallNode, + IfThenElseNode, + LetInNode, + LetNode, + MemberCallNode, + NewNode, + ProgramNode, + UnaryNode, + WhileLoopNode, +) +from ..utils import on, when + + +class COOL_FORMATTER(object): + @on("node") + def visit(self, node, tabs): + pass + + @when(ProgramNode) + def visit(self, node: ProgramNode, tabs=0): # noqa + ans = "\t" * tabs + "\\_ProgramNode [class, ..., class]" + class_list = "\n".join( + [self.visit(xclass, tabs + 1) for xclass in node.classes] + ) + return f"{ans}\n{class_list}" + + @when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode, tabs=0): # noqa + inherits = f"child of {node.parent} " if node.parent else "" + ans = ( + "\t" * tabs + + f"\\_ClassDeclarationNode {node.id} {inherits}" + + "[feature, ..., feature]" + ) + features = "\n".join( + [self.visit(feature, tabs + 1) for feature in node.features] + ) + return f"{ans}\n{features}" + + @when(AttrDeclarationNode) + def visit(self, node: AttrDeclarationNode, tabs=0): # noqa + expr = "" + if node.expression: + expr = f"\n{self.visit(node.expression, tabs + 1)}" + result = "\t" * tabs + result += f"\\_AttrDeclarationNode {node.id}: {node.type}{expr}" + return result + + @when(FuncDeclarationNode) + def visit(self, node: FuncDeclarationNode, tabs=0): # noqa + params = ", ".join( + [f"{param.id}:{param.type}" for param in node.params], + ) + ans = "\t" * tabs + ans += f"\\_FuncDeclarationNode {node.id} ({params}) {node.type}" + expr = self.visit(node.expression, tabs + 1) + return f"{ans}\n{expr}" + + @when(IfThenElseNode) + def visit(self, node: IfThenElseNode, tabs=0): # noqa + ans = "\t" * tabs + "\\_IfThenElseNode" + cond = self.visit(node.condition, tabs + 1) + then = self.visit(node.if_body, tabs + 1) + xelse = self.visit(node.else_body, tabs + 1) + return f"{ans}\n{cond}\n{then}\n{xelse}" + + @when(WhileLoopNode) + def visit(self, node: WhileLoopNode, tabs=0): # noqa + ans = "\t" * tabs + "\\_WhileLoopNode" + cond = self.visit(node.condition, tabs + 1) + do = "\t" * tabs + "DO" + body = self.visit(node.body, tabs + 1) + return f"{ans}\n{cond}\n{do}\n{body}" + + @when(BlockNode) + def visit(self, node: BlockNode, tabs=0): # noqa + ans = "\t" * tabs + "\\_BlockNode" + exprs = "\n".join( + [self.visit(expr, tabs + 1) for expr in node.expressions], + ) + return f"{ans}\n{exprs}" + + @when(LetNode) + def visit(self, node: LetNode, tabs=0): # noqa + ans = "\t" * tabs + f"\\_LetNode var {node.id} of type {node.type}" + expr = "" + if node.expression: + expr = f" equal to\n {self.visit(node.expression, tabs + 1)}" + return f"{ans}{expr}" + + @when(LetInNode) + def visit(self, node: LetInNode, tabs=0): # noqa + ans = "\t" * tabs + "\\_LetInNode" + lets = "\n".join([self.visit(let, tabs + 1) for let in node.let_body]) + xin = "\t" * tabs + "IN" + body = self.visit(node.in_body, tabs + 1) + return f"{ans}\n{lets}\n{xin}\n{body}" + + @when(CaseNode) + def visit(self, node: CaseNode, tabs=0): # noqa + ans = "\t" * tabs + ans += f"\\_CaseNode case {node.id} of type {node.type} equal to" + expr = self.visit(node.expression, tabs + 1) + return f"{ans}\n{expr}" + + @when(CaseOfNode) + def visit(self, node: CaseOfNode, tabs=0): # noqa + ans = "\t" * tabs + "\\_CaseOfNode" + expr = self.visit(node.expression, tabs + 1) + of = "\t" * tabs + "OF" + body = "\n".join([self.visit(case, tabs + 1) for case in node.cases]) + return f"{ans}\n{expr}\n{of}\n{body}" + + @when(AssignNode) + def visit(self, node: AssignNode, tabs=0): # noqa + ans = "\t" * tabs + f"\\_AssignNode var {node.id} equal to" + expr = self.visit(node.expression, tabs + 1) + return f"{ans}\n{expr}" + + @when(MemberCallNode) + def visit(self, node: MemberCallNode, tabs=0): # noqa + ans = "\t" * tabs + f"\\MemberCallNode {node.id} with args" + args = "\n".join([self.visit(arg, tabs + 1) for arg in node.args]) + return f"{ans}\n{args}" + + @when(FunctionCallNode) + def visit(self, node: FunctionCallNode, tabs=0): # noqa + ans = "\t" * tabs + "\\_FunctionCallNode from object" + obj = self.visit(node.obj, tabs + 1) + xid = ( + "\t" * tabs + + f"Calling function {node.id}" + + (f" as type {node.type}" if node.type else "") + ) + args = "\n".join([self.visit(arg, tabs + 1) for arg in node.args]) + return f"{ans}\n{obj}\n{xid}\n{args}" + + @when(NewNode) + def visit(self, node: NewNode, tabs=0): # noqa + ans = "\t" * tabs + f"\\_NewNode of Type {node.type}" + return f"{ans}" + + @when(AtomicNode) + def visit(self, node: AtomicNode, tabs=0): # noqa + ans = "\t" * tabs + f"\\_{node.__class__.__name__} {node.token}" + return f"{ans}" + + @when(UnaryNode) + def visit(self, node: UnaryNode, tabs=0): # noqa + ans = "\t" * tabs + f"\\_{node.__class__.__name__}" + expr = self.visit(node.expression, tabs + 1) + return f"{ans}\n{expr}" + + @when(BinaryNode) + def visit(self, node: BinaryNode, tabs=0): # noqa + ans = "\t" * tabs + f"\\_{node.__class__.__name__}" + expr1 = self.visit(node.left, tabs + 1) + expr2 = self.visit(node.right, tabs + 1) + return f"{ans}\n{expr1}\n{expr2}" diff --git a/src/cmp/cool_lang/semantics/semantic_utils.py b/src/cmp/cool_lang/semantics/semantic_utils.py new file mode 100644 index 00000000..6df4ab9a --- /dev/null +++ b/src/cmp/cool_lang/semantics/semantic_utils.py @@ -0,0 +1,326 @@ +class SemanticException(Exception): + @property + def text(self): + return self.args[0] + + +class Attribute: + def __init__(self, name, typex): + self.name = name + self.type = typex + + def is_by_value(self): + return self.type.name in ["Int", "Bool", "String"] + + 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 is_override(self, omethod): # check if self is an override of omethod + valid = False + if omethod.name == self.name: + if self.return_type.is_subtype(omethod.return_type): + if len(self.param_names) == len(omethod.param_names): + for ptype, optype in zip(self.param_types, omethod.param_types): + if not ptype == optype: + break + else: + valid = True + return valid + + def __str__(self): + params = ", ".join( + f"{n}:{t.name}" # type:ignore + for n, t in zip( + self.param_names, + self.param_types, + ) + ) + return f"[method] {self.name}({params}): {self.return_type.name};" + + +class Type: + def __init__(self, name: str): + self.name = name + self.attributes = [] + self.methods = {} + self.parent = None + self.children = [] + self.finish_time = 0 + self._visited = False + + def compute_finish_time_recursively(self, ref_int): + for child in self.children: + child.compute_finish_time_recursively(ref_int) + self.finish_time = ref_int["value"] + ref_int["value"] += 1 + + def set_parent(self, parent): + if self.parent is not None: + raise SemanticException(f"Parent type is already set for type {self.name}.") + if parent.name in ["Int", "String", "Bool"]: + raise SemanticException(f"Cannot inherit from basic type {parent.name}.") + self.parent = parent + if all(map(lambda x: x.name != self.name, parent.children)): + parent.children.append(self) + + def get_attribute(self, name: str): + try: + return next(attr for attr in self.attributes if attr.name == name) + except StopIteration: + if self.parent is None: + raise SemanticException( + f'Attribute "{name}" is not defined in type {self.name}.' + ) + try: + return self.parent.get_attribute(name) + except SemanticException: + raise SemanticException( + f'Attribute "{name}" is not defined in type {self.name}.' + ) + + def define_attribute(self, name: str, typex): + try: + self.get_attribute(name) + except SemanticException: + attribute = Attribute(name, typex) + self.attributes.append(attribute) + return attribute + else: + raise SemanticException( + f'Attribute "{name}" is already defined in type {self.name}.' + ) + + def get_method(self, name: str): + try: + return self.methods[name] + except KeyError: + if self.parent is None: + raise SemanticException( + f'Method "{name}" is not defined in type {self.name}.' + ) + try: + return self.parent.get_method(name) + except SemanticException: + raise SemanticException( + f'Method "{name}" is not defined in type {self.name}.' + ) + + def define_method( + self, name: str, param_names: list, param_types: list, return_type + ): + try: + method = self.get_method(name) + except SemanticException: + pass + else: + if name in self.methods.keys(): # duplicate? + raise SemanticException( + f'Method "{name}" already defined in type {self.name}.' + ) + else: # override? + new_method = Method(name, param_names, param_types, return_type) + if not new_method.is_override( + method + ): # override check is different from the manual, check method + # if something goes wrong here + raise SemanticException( + f'Invalid override of method "{name}" in type {self.name}.' + ) + method = self.methods[name] = Method( + name, param_names, param_types, return_type + ) + return method + + def get_all_attributes(self): + if self.parent: + for attr in self.parent.get_all_attributes(): + yield attr + for attr in self.attributes: + yield attr + + def get_all_methods(self): + done = set() + if self.parent: + for method, typex in self.parent.get_all_methods(): + if method.name in self.methods: + done.add(method.name) + yield (self.methods[method.name], self) + else: + yield (method, typex) + for method in self.methods.values(): + if method.name in done: + continue + yield (method, self) + + def get_all_features(self): + done = set() + if self.parent: + for feature in self.parent.get_all_features(): + if not isinstance(feature, Attribute): + method, typex = feature + if method.name in self.methods: + done.add(method.name) + yield (self.methods[method.name], self) + else: + yield (method, typex) + else: + yield feature + for attr in self.attributes: + yield attr + for method in self.methods.values(): + if method.name in done: + continue + yield (method, self) + + def is_subtype(self, otype): # check if self is subtype of otype + actual = self + while True: + if actual == otype: + return True + if actual.parent is None: + return False + actual = actual.parent + + def __str__(self): + output = f"type {self.name}" + parent = "" if self.parent is None else f" : {self.parent.name}" + output += parent + output += " {" + output += "\n\t" if self.attributes or self.methods else "" + output += "\n\t".join(str(x) for x in self.attributes) + output += "\n\t" if self.attributes else "" + output += "\n\t".join(str(x) for x in self.methods.values()) + output += "\n" if self.methods else "" + output += "}\n" + return output + + def __repr__(self): + return str(self) + + +class ErrorType(Type): + def __init__(self): + Type.__init__(self, "") + + def is_subtype(self, otype): + return True + + def __eq__(self, other): + return isinstance(other, Type) + + +class VoidType(Type): + def __init__(self): + Type.__init__(self, "") + + def __eq__(self, other): + return isinstance(other, VoidType) + + +def find_common_ancestor(type1: Type, type2: Type): + if type1 is ErrorType or type2 is ErrorType: + return ErrorType() + + ancestor_t1 = [] + actual = type1 + while actual: + ancestor_t1.append(actual) + actual = actual.parent + + actual = type2 + while actual: + if actual in ancestor_t1: + return actual + actual = actual.parent + + +class Context: + def __init__(self): + self.types = {} + + def create_type(self, name: str): + if name in self.types: + raise SemanticException( + f"Type with the same name ({name}) already in context." + ) + typex = self.types[name] = Type(name) + return typex + + def get_type(self, name: str): + try: + return self.types[name] + except KeyError: + raise SemanticException(f'Type "{name}" is not defined.') + + def compute_finish_time(self): + root = self.types["Object"] + root.compute_finish_time_recursively({"value": 0}) + + 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) + + +class Var: + def __init__(self, idx, typex): + self.id = idx + self.type = typex + + def __str__(self): + return f"[var] {self.id}: {self.type.name}" + + def __repr__(self): + return str(self) + + +class Scope: + def __init__(self, parent=None): + self.parent = parent + self.vars = {} + + def define_var(self, name, typex): + if name in self.vars: + raise SemanticException( + f"Variable {name} already defined in current context." + ) + var = self.vars[name] = Var(name, typex) + return var + + def get_var(self, name): + try: + return self.vars[name] + except KeyError: + if self.parent is not None: + try: + return self.parent.get_var(name) + except SemanticException as e: + raise e + raise SemanticException(f"Variable {name} is not defined.") + + def __str__(self): + return ( + "{\n" + + ("\t" if self.parent is None else "Parent:\n" + f"{self.parent}\n\t") + + "\n\t".join(str(x) for x in self.vars.values()) + + "\n}" + ) + + def __repr__(self): + return str(self) diff --git a/src/cmp/cool_lang/semantics/type_builder.py b/src/cmp/cool_lang/semantics/type_builder.py new file mode 100644 index 00000000..ccb087ce --- /dev/null +++ b/src/cmp/cool_lang/semantics/type_builder.py @@ -0,0 +1,117 @@ +from ..ast import ( + AttrDeclarationNode, + ClassDeclarationNode, + FuncDeclarationNode, + ProgramNode, +) +from ..errors import CTypeError, SemanticError +from ..utils import on, when +from .semantic_utils import Context, ErrorType, SemanticException, VoidType + + +class COOL_TYPE_BUILDER(object): + def __init__(self, context: Context, errors=[]): + self.context = context + self.current_type = None + self.errors = errors + + def build_basic_types(self): + strt = self.context.get_type("String") + intt = self.context.get_type("Int") + objt = self.context.get_type("Object") + iot = self.context.get_type("IO") + bt = self.context.get_type("Bool") + # Int + intt.set_parent(objt) + # Bool + bt.set_parent(objt) + # Object + objt.define_method("abort", [], [], objt) + objt.define_method("type_name", [], [], strt) + objt.define_method("copy", [], [], objt) # Is SELF_TYPE in the manual + # IO + iot.define_method("in_string", [], [], strt) + iot.define_method( + "out_string", ["x"], [strt], iot + ) # Is SELF_TYPE in the manual + iot.define_method("in_int", [], [], intt) + iot.define_method("out_int", ["x"], [intt], iot) # Is SELF_TYPE in the manual + iot.set_parent(objt) + # String + strt.define_method("length", [], [], intt) + strt.define_method("concat", ["s"], [strt], strt) + strt.define_method("substr", ["i", "l"], [intt, intt], strt) + strt.set_parent(objt) + + @on("node") + def visit(self, node): + pass + + @when(ProgramNode) + def visit(self, node: ProgramNode): # noqa:F811 + self.build_basic_types() + for class_def in node.classes: + self.visit(class_def) + self.context.compute_finish_time() + try: + self.context.get_type("Main") + try: + self.context.get_type("Main").get_method("main") + except SemanticException as e: + self.errors.append(SemanticError(node.line, node.column, e.text)) + except SemanticException as e: + self.errors.append(SemanticError(node.line, node.column, e.text)) + + @when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode): # noqa:F811 + try: + typex = self.context.get_type(node.id) + self.current_type = typex + parent_type = ( + self.context.get_type(node.parent) # type:ignore + if node.parent is not None + else self.context.get_type("Object") + ) + typex.set_parent(parent_type) + except SemanticException as e: + if self.current_type and node.parent in ["Int", "String", "Bool"]: + self.errors.append(SemanticError(node.line, node.column, e.text)) + else: + self.errors.append(CTypeError(node.line, node.column, e.text)) + for feature in node.features: + self.visit(feature) + + @when(AttrDeclarationNode) + def visit(self, node: AttrDeclarationNode): # noqa:F811 + atype = None + try: + atype = self.context.get_type(node.type) + except SemanticException as e: + atype = ErrorType() + self.errors.append(CTypeError(node.line, node.column, e.text)) + try: + self.current_type.define_attribute(node.id, atype) + except SemanticException as e: + self.errors.append(SemanticError(node.line, node.column, e.text)) + + @when(FuncDeclarationNode) + def visit(self, node: FuncDeclarationNode): # noqa:F811 + ret_type = ErrorType() + params_type = [ErrorType()] * len(node.params) + params_id = [] + try: + ret_type = ( + self.context.get_type(node.type) if node.type != "void" else VoidType() + ) + except SemanticException as e: + self.errors.append(CTypeError(node.line, node.column, e.text)) + for ind, param in enumerate(node.params): + try: + params_id.append(param.id) + params_type[ind] = self.context.get_type(param.type) + except SemanticException as e: + self.errors.append(CTypeError(param.line, param.column, e.text)) + try: + self.current_type.define_method(node.id, params_id, params_type, ret_type) + except SemanticException as e: + self.errors.append(SemanticError(node.line, node.column, e.text)) diff --git a/src/cmp/cool_lang/semantics/type_checker.py b/src/cmp/cool_lang/semantics/type_checker.py new file mode 100644 index 00000000..666af97d --- /dev/null +++ b/src/cmp/cool_lang/semantics/type_checker.py @@ -0,0 +1,548 @@ +from ..ast import ( + ArithmeticNode, + AssignNode, + AttrDeclarationNode, + BlockNode, + BoolNode, + CaseNode, + CaseOfNode, + ClassDeclarationNode, + ComplementNode, + EqualNode, + FuncDeclarationNode, + FunctionCallNode, + IdNode, + IfThenElseNode, + IntegerNode, + IsVoidNode, + LessEqualNode, + LessNode, + LetInNode, + LetNode, + MemberCallNode, + NewNode, + NotNode, + ProgramNode, + StringNode, + WhileLoopNode, +) +from ..errors import CAttributeError, CNameError, CTypeError, SemanticError +from ..utils import on, when +from .semantic_utils import ( + Context, + ErrorType, + Scope, + SemanticException, + Type, + find_common_ancestor, +) + + +class COOL_TYPE_CHECKER(object): + def __init__(self, context: Context, errors=[]): + self.current_type: Type = None # type:ignore + self.context: Context = context + self.errors = errors + + self.type_int = self.context.get_type("Int") + self.type_str = self.context.get_type("String") + self.type_obj = self.context.get_type("Object") + self.type_io = self.context.get_type("IO") + self.type_bool = self.context.get_type("Bool") + + def is_basic(self, typex): + return typex in [self.type_str, self.type_bool, self.type_int] + + @on("node") + def visit(self, node, scope): + pass + + @when(ProgramNode) + def visit(self, node: ProgramNode, scope: Scope = None): # noqa:F811 + scope = Scope() + for classx_node in node.classes: + self.visit(classx_node, scope) + + @when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode, scope: Scope): # noqa:F811 + self.current_type = self.context.get_type(node.id) + + attrs = [] + actual = self.current_type + while actual: + attrs += actual.attributes + actual = actual.parent + + class_scope = Scope(parent=scope) + for attr in attrs: + if attr.name == "self": + line, column = [ + (attrib.line, attrib.column) + for attrib in node.features + if type(attrib) is AttrDeclarationNode and attrib.id == "self" + ][0] + self.errors.append( + SemanticError( + line, + column, + 'Identifier "self" cannot be used in Attribute declarations.', + ) + ) + continue + class_scope.define_var(attr.name, attr.type) + + for feature_node in node.features: + self.visit( + feature_node, + scope if feature_node is AttrDeclarationNode else class_scope, + ) # Ensures an attribute cannot be defined from another one + + @when(AttrDeclarationNode) + def visit(self, node: AttrDeclarationNode, scope: Scope): # noqa:F811 + if node.expression: + new_scope = Scope(parent=scope) + new_scope.define_var("self", self.current_type) + self.visit(node.expression, new_scope) + + attr_type = self.context.get_type(node.type) + + if not node.expression.static_type.is_subtype(attr_type): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid attribute initialization. " + + f"Type {node.expression.static_type.name} " + + f"is not subtype of {attr_type.name}.", + ) + ) + + @when(FuncDeclarationNode) + def visit(self, node: FuncDeclarationNode, scope: Scope): # noqa:F811 + func_scope = Scope(parent=scope) + func_scope.define_var("self", self.current_type) + + func = self.current_type.get_method(node.id) + + for param, param_type in zip(node.params, func.param_types): + try: + func_scope.define_var(param.id, param_type) + except SemanticException: # Check if params names are differnt + self.errors.append( + SemanticError( + param.line, + param.column, + f'Identifier "{param.id}" can only be used once.', + ) + ) + + self.visit(node.expression, func_scope) + + ret_type = func.return_type + if not node.expression.static_type.is_subtype(ret_type): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid return type. " + + f"Type {node.expression.static_type.name} " + + f"is not subtype of {ret_type.name}.", + ) + ) + + @when(IfThenElseNode) + def visit(self, node: IfThenElseNode, scope: Scope): # noqa:F811 + self.visit(node.condition, scope) + if not node.condition.static_type == self.type_bool: + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid predicate type. " + + f"Found {node.condition.static_type.name} " + + f"instead of {self.type_bool.name}.", + ) + ) + + self.visit(node.if_body, Scope(parent=scope)) + self.visit(node.else_body, Scope(parent=scope)) + + node.static_type = find_common_ancestor( + node.if_body.static_type, node.else_body.static_type + ) + + @when(WhileLoopNode) + def visit(self, node: WhileLoopNode, scope: Scope): # noqa:F811 + self.visit(node.condition, scope) + if not node.condition.static_type == self.type_bool: + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid predicate type. " + + f"Found {node.condition.static_type.name} " + + f"instead of {self.type_bool.name}.", + ) + ) + + self.visit(node.body, Scope(parent=scope)) + node.static_type = self.type_obj + + @when(BlockNode) + def visit(self, node: BlockNode, scope: Scope): # noqa:F811 + block_scope = Scope(parent=scope) + for expr in node.expressions: + self.visit(expr, block_scope) + + node.static_type = node.expressions[-1].static_type + + @when(LetNode) + def visit(self, node: LetNode, scope: Scope): # noqa:F811 + node_type = ErrorType() + try: + node_type = self.context.get_type(node.type) + except SemanticException as e: + self.errors.append(CTypeError(node.line, node.column, e.text)) + + if node.expression: + self.visit(node.expression, scope) + if not node.expression.static_type.is_subtype(node_type): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid initialization. " + + f"Type {node.expression.static_type.name} " + + f"is not subtype of {node_type.name}.", + ) + ) + if node.id == "self": + self.errors.append( + SemanticError(node.line, node.column, 'Var "self" is read-only.') + ) + try: + scope.define_var(node.id, node_type) + except SemanticException as e: + self.errors.append(CNameError(node.line, node.column, e.text)) + node.static_type = node_type + + @when(LetInNode) + def visit(self, node: LetInNode, scope: Scope): # noqa:F811 + letin_scope = scope + for letnode in node.let_body: + letin_scope = Scope(parent=letin_scope) + self.visit(letnode, letin_scope) + + self.visit(node.in_body, letin_scope) + node.static_type = node.in_body.static_type + + @when(CaseNode) + def visit(self, node: CaseNode, scope: Scope): # noqa:F811 + case_scope = Scope(parent=scope) + node_type = ErrorType() + try: + node_type = self.context.get_type(node.type) + except SemanticException as e: + self.errors.append(CTypeError(node.line, node.column, e.text)) + + case_scope.define_var(node.id, node_type) + self.visit(node.expression, case_scope) + + node.static_type = node.expression.static_type + + @when(CaseOfNode) + def visit(self, node: CaseOfNode, scope: Scope): # noqa:F811 + self.visit(node.expression, scope) + + node_type = None + cases_types = set() + for case in node.cases: + self.visit(case, scope) + try: + case_type = self.context.get_type(case.type) + if case_type in cases_types: + self.errors.append( + SemanticError( + case.line, + case.column, + f"Duplicate case for type {case_type.name}.", + ) + ) + else: + cases_types.add(case_type) + except SemanticException: + pass + + if node_type: + node_type = find_common_ancestor(node_type, case.static_type) + else: + node_type = case.static_type + + node.static_type = node_type + + @when(AssignNode) + def visit(self, node: AssignNode, scope: Scope): # noqa:F811 + var_type = ErrorType() + try: + var = scope.get_var(node.id) + if node.id == "self": + raise SemanticException('Var "self" is read-only.') + var_type = var.type + except SemanticException as e: + self.errors.append(SemanticError(node.line, node.column, e.text)) + + self.visit(node.expression, scope) + if not node.expression.static_type.is_subtype(var_type): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid assignment. " + + f"Type {node.expression.static_type.name} " + + f"is not subtype of {var_type.name}.", + ) + ) + + node.static_type = node.expression.static_type + + @when(MemberCallNode) + def visit(self, node: MemberCallNode, scope: Scope): # noqa:F811 + obj_type = self.current_type + + node_type = ErrorType() + try: + method = obj_type.get_method(node.id) + except SemanticException as e: + self.errors.append(CAttributeError(node.line, node.column, e.text)) + else: + if len(node.args) != len(method.param_names): + self.errors.append( + SemanticError( + node.line, + node.column, + "Invalid dispatch. " + + f"Expected {len(method.names)} parameter(s), " + + f"found {len(node.args)}.", + ) + ) + else: + node_type = method.return_type + for pname, ptype, expr in zip( + method.param_names, method.param_types, node.args + ): + self.visit(expr, scope) + expected_type = ptype + if not expr.static_type.is_subtype(expected_type): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid dispatch. " + + f'Parameter "{pname}" type {expr.static_type.name} ' + + f"is not subtype of {expected_type.name}.", + ) + ) + + node.static_type = node_type + + @when(FunctionCallNode) + def visit(self, node: FunctionCallNode, scope: Scope): # noqa:F811 + obj_type = None + + self.visit(node.obj, scope) + if node.type: + cast_type = ErrorType() + try: + cast_type = self.context.get_type(node.type) + except SemanticException as e: + self.errors.append(CTypeError(node.line, node.column, e.text)) + node.static_type = ErrorType() + return + if not node.obj.static_type.is_subtype(cast_type): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid cast. " + + f"Type {node.obj.static_type.name} " + + f"is not subtype of {cast_type.name}.", + ) + ) + obj_type = cast_type + else: + obj_type = node.obj.static_type + + node_type = ErrorType() + try: + method = obj_type.get_method(node.id) + except SemanticException as e: + self.errors.append(CAttributeError(node.line, node.column, e.text)) + else: + if len(node.args) != len(method.param_names): + self.errors.append( + SemanticError( + node.line, + node.column, + "Invalid dispatch. " + + f"Expected {len(method.param_names)} " + + f"parameter(s), found {len(node.args)}.", + ) + ) + else: + node_type = method.return_type + for pname, ptype, expr in zip( + method.param_names, method.param_types, node.args + ): + self.visit(expr, scope) + expected_type = ptype + if not expr.static_type.is_subtype(expected_type): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid dispatch. " + + f'Parameter "{pname}" type {expr.static_type.name} ' + + f"is not subtype of {expected_type.name}.", + ) + ) + + node.static_type = node_type + + @when(NewNode) + def visit(self, node: NewNode, scope: Scope): # noqa:F811 + try: + node_type = self.context.get_type(node.type) + except SemanticException as e: + self.errors.append(CTypeError(node.line, node.column, e.text)) + node_type = ErrorType() + node.static_type = node_type + + @when(IsVoidNode) + def visit(self, node: IsVoidNode, scope: Scope): # noqa:F811 + self.visit(node.expression, scope) + node.static_type = self.type_bool + + @when(NotNode) + def visit(self, node: NotNode, scope: Scope): # noqa:F811 + self.visit(node.expression, scope) + if not node.expression.static_type == self.type_bool: + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid boolean complement over type " + + f"{node.expression.static_type.name}.", + ) + ) + node.static_type = self.type_bool + + @when(ComplementNode) + def visit(self, node: ComplementNode, scope: Scope): # noqa:F811 + self.visit(node.expression, scope) + if not node.expression.static_type == self.type_int: + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid integer complement over " + + f"type {node.expression.static_type.name}.", + ) + ) + node.static_type = self.type_int + + @when(ArithmeticNode) + def visit(self, node: ArithmeticNode, scope: Scope): # noqa:F811 + self.visit(node.left, scope) + self.visit(node.right, scope) + if not ( + node.left.static_type == self.type_int + and node.right.static_type == self.type_int + ): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid arithmetic operation between " + + f"types {node.left.static_type.name} " + + f"and {node.right.static_type.name}.", + ) + ) + node.static_type = self.type_int + + @when(EqualNode) + def visit(self, node: EqualNode, scope: Scope): # noqa:F811 + self.visit(node.left, scope) + self.visit(node.right, scope) + if self.is_basic(node.left.static_type) or self.is_basic( + node.right.static_type + ): + if not node.left.static_type == node.right.static_type: + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid comparison between " + + f"types {node.left.static_type.name} " + + f"and {node.right.static_type.name}.", + ) + ) + node.static_type = self.type_bool + + @when(LessEqualNode) + def visit(self, node: LessEqualNode, scope: Scope): # noqa:F811 + self.visit(node.left, scope) + self.visit(node.right, scope) + if not ( + node.left.static_type == self.type_int + and node.right.static_type == self.type_int + ): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid comparison between " + + f"types {node.left.static_type.name} " + + f"and {node.right.static_type.name}.", + ) + ) + node.static_type = self.type_bool + + @when(LessNode) + def visit(self, node: LessNode, scope: Scope): # noqa:F811 + self.visit(node.left, scope) + self.visit(node.right, scope) + if not ( + node.left.static_type == self.type_int + and node.right.static_type == self.type_int + ): + self.errors.append( + CTypeError( + node.line, + node.column, + "Invalid comparison between " + + f"types {node.left.static_type.name} " + + f"and {node.right.static_type.name}.", + ) + ) + node.static_type = self.type_bool + + @when(IdNode) + def visit(self, node: IdNode, scope: Scope): # noqa:F811 + try: + node_type = scope.get_var(node.token).type + except SemanticException as e: + self.errors.append(CNameError(node.line, node.column, e.text)) + node_type = ErrorType() + node.static_type = node_type + + @when(BoolNode) + def visit(self, node: BoolNode, scope: Scope): # noqa:F811 + node.static_type = self.type_bool + + @when(IntegerNode) + def visit(self, node: IntegerNode, scope: Scope): # noqa:F811 + node.static_type = self.type_int + + @when(StringNode) + def visit(self, node: StringNode, scope: Scope): # noqa:F811 + node.static_type = self.type_str diff --git a/src/cmp/cool_lang/semantics/type_collector.py b/src/cmp/cool_lang/semantics/type_collector.py new file mode 100644 index 00000000..cb5bc891 --- /dev/null +++ b/src/cmp/cool_lang/semantics/type_collector.py @@ -0,0 +1,94 @@ +from ..ast import ClassDeclarationNode, ProgramNode +from ..errors import SemanticError +from ..utils import on, when +from .semantic_utils import Context, SemanticException + + +class COOL_TYPE_COLLECTOR(object): + def __init__(self, errors=[]): + self.context = None + self.errors = errors + self._mapper = dict() + self._graph = dict() + self._to = [] + + def _order(self, actual): + actual._visited = True + for son in self._graph[actual.id]: + son_node = self._mapper[son] + if son_node._visited: + continue + self._order(son_node) + self._to.append(actual) + + def check_cyclic(self, obj, parents): + stack = [obj.id] + while True: + actual = stack[-1] + parent = parents.get(actual, None) + if parent is None: + break + else: + if parent in stack: + self.errors.append( + SemanticError( + obj.line, + obj.column, + f"Cyclic inheritance in the hierarchy of {obj.id}.", + ) + ) + break + else: + stack.append(parent) + + def define_basic_types(self): + self.context.create_type("Object") + self.context.create_type("IO") + self.context.create_type("Int") + self.context.create_type("String") + self.context.create_type("Bool") + + @on("node") + def visit(self, node): + pass + + @when(ProgramNode) + def visit(self, node: ProgramNode): # noqa:F811 + self.context = Context() + self.define_basic_types() + for class_def in node.classes: + class_def._visited = False # type:ignore + self._graph[class_def.id] = [] + self.visit(class_def) + + if len(self.errors) == 0: + for class_def in node.classes: + if ( + class_def.parent is not None + and class_def.parent in self._graph.keys() + ): + self._graph[class_def.id].append(class_def.parent) + + for class_def in node.classes: + if class_def._visited: # type:ignore + continue + else: + self._order(class_def) + + # Check for ciclyc inheritance + parents = dict() + for class_def in node.classes: + parents[class_def.id] = class_def.parent + self.check_cyclic(class_def, parents) + + node.classes = self._to + + return self.context + + @when(ClassDeclarationNode) + def visit(self, node: ClassDeclarationNode): # noqa:F811 + try: + self.context.create_type(node.id) + self._mapper[node.id] = node + except SemanticException as e: + self.errors.append(SemanticError(node.line, node.column, e.text)) diff --git a/src/cmp/cool_lang/utils/__init__.py b/src/cmp/cool_lang/utils/__init__.py new file mode 100644 index 00000000..4681854a --- /dev/null +++ b/src/cmp/cool_lang/utils/__init__.py @@ -0,0 +1,3 @@ +from .attribute_dict import AttributeDict +from .find_column import find_column +from .visitor import on, when diff --git a/src/cmp/cool_lang/utils/attribute_dict.py b/src/cmp/cool_lang/utils/attribute_dict.py new file mode 100644 index 00000000..35082af5 --- /dev/null +++ b/src/cmp/cool_lang/utils/attribute_dict.py @@ -0,0 +1,7 @@ +class AttributeDict(dict): + + def __getattr__(self, name): + return self[name] + + def __setattr__(self, name, value): + self[name] = value diff --git a/src/cmp/cool_lang/utils/find_column.py b/src/cmp/cool_lang/utils/find_column.py new file mode 100644 index 00000000..31f18e90 --- /dev/null +++ b/src/cmp/cool_lang/utils/find_column.py @@ -0,0 +1,3 @@ +def find_column(input, lexpos): + line_start = input.rfind('\n', 0, lexpos) + 1 + return (lexpos - line_start) + 1 diff --git a/src/cmp/cool_lang/utils/visitor.py b/src/cmp/cool_lang/utils/visitor.py new file mode 100644 index 00000000..96484283 --- /dev/null +++ b/src/cmp/cool_lang/utils/visitor.py @@ -0,0 +1,80 @@ +# The MIT License (MIT) +# +# Copyright (c) 2013 Curtis Schlak +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import inspect + +__all__ = ['on', 'when'] + +def on(param_name): + def f(fn): + dispatcher = Dispatcher(param_name, fn) + return dispatcher + return f + + +def when(param_type): + def f(fn): + frame = inspect.currentframe().f_back + func_name = fn.func_name if 'func_name' in dir(fn) else fn.__name__ + dispatcher = frame.f_locals[func_name] + if not isinstance(dispatcher, Dispatcher): + dispatcher = dispatcher.dispatcher + dispatcher.add_target(param_type, fn) + def ff(*args, **kw): + return dispatcher(*args, **kw) + ff.dispatcher = dispatcher + return ff + return f + + +class Dispatcher(object): + def __init__(self, param_name, fn): + frame = inspect.currentframe().f_back.f_back + top_level = frame.f_locals == frame.f_globals + self.param_index = self.__argspec(fn).args.index(param_name) + self.param_name = param_name + self.targets = {} + + def __call__(self, *args, **kw): + typ = args[self.param_index].__class__ + d = self.targets.get(typ) + if d is not None: + return d(*args, **kw) + else: + issub = issubclass + t = self.targets + ks = t.keys() + ans = [t[k](*args, **kw) for k in ks if issub(typ, k)] + if len(ans) == 1: + return ans.pop() + return ans + + def add_target(self, typ, target): + self.targets[typ] = target + + @staticmethod + def __argspec(fn): + # Support for Python 3 type hints requires inspect.getfullargspec + if hasattr(inspect, 'getfullargspec'): + return inspect.getfullargspec(fn) + else: + return inspect.getargspec(fn) diff --git a/src/coolc.py b/src/coolc.py new file mode 100644 index 00000000..0bc2ad1f --- /dev/null +++ b/src/coolc.py @@ -0,0 +1,64 @@ +import re + +import typer + +from cmp.cil import CIL_FORMATTER, CIL_TO_MIPS, COOL_TO_CIL_VISITOR +from cmp.cool_lang.lexer import COOL_LEXER +from cmp.cool_lang.parser import COOL_PARSER +from cmp.cool_lang.semantics import COOL_CHECKER + +app = typer.Typer() + + +@app.command() +def run( + input_file: typer.FileText = typer.Argument(..., help="Cool file to compile."), + output_file: typer.FileTextWrite = typer.Argument(..., help="Mips resultant file."), + verbose: bool = typer.Option(False, help="Execute in verbose mode."), + cil: bool = typer.Option(False, help="Compile to cil file."), +): + code = input_file.read() + + clexer = COOL_LEXER() + if not clexer.tokenize(code): + for error in clexer.errors: + print(error) + exit(1) + + if not list(filter(lambda x: x.type != "COMMENT", clexer.result)): + print("(0, 0) - SyntacticError: ERROR at or near EOF") + exit(1) + + cparser = COOL_PARSER() + if not cparser.parse(clexer): + for error in cparser.errors: + print(error) + exit(1) + + program = cparser.result + cchecker = COOL_CHECKER() + if not cchecker.check_semantics(program, verbose=verbose): + for error in cchecker.errors: + print(error) + exit(1) + + ctc = COOL_TO_CIL_VISITOR(cchecker.context) + cil_ast = ctc.visit(program) + + if cil: + with open( + re.findall(r"^(.+)\.(.*)$", output_file.name)[0][0] + ".cil", "w" + ) as out_fd: + out_fd.write(CIL_FORMATTER().visit(cil_ast)) + + ctm = CIL_TO_MIPS(cchecker.context) + ctm.visit(cil_ast) + mips_code = ctm.mips.compile() + + output_file.write(mips_code) + + exit(0) + + +if __name__ == "__main__": + app() diff --git a/src/coolc.sh b/src/coolc.sh index 3088de4f..eb912d03 100755 --- a/src/coolc.sh +++ b/src/coolc.sh @@ -3,9 +3,8 @@ 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 "CodeStrange Cool Compiler v0.1" +echo "Copyright (c) 2020: Carlos Bermudez Porto, Leynier Gutiérrez González, Tony Raúl Blanco Fernández" # Llamar al compilador -echo "Compiling $INPUT_FILE into $OUTPUT_FILE" +python coolc.py ${INPUT_FILE} ${OUTPUT_FILE} diff --git a/src/main.py b/src/main.py new file mode 100644 index 00000000..4a1a9f17 --- /dev/null +++ b/src/main.py @@ -0,0 +1,15 @@ +from os import system +from sys import argv + +INPUT_FILE = argv[1] +OUTPUT_FILE = f"{INPUT_FILE[0: -2]}mips" + +print("CodeStrange Cool Compiler v0.1") +print( + "Copyright (c) 2020: " + + "Carlos Bermudez Porto, " + + "Leynier Gutiérrez González, " + + "Tony Raúl Blanco Fernández" +) + +system(f"python coolc.py {INPUT_FILE} {OUTPUT_FILE}") diff --git a/src/makefile b/src/makefile index 30df993f..306e2e10 100644 --- a/src/makefile +++ b/src/makefile @@ -3,6 +3,10 @@ main: # Compiling the compiler :) +debug: + python coolc.py --cil test.cl test.mips + spim -file test.mips + clean: rm -rf build/* rm -rf ../tests/*/*.mips @@ -10,3 +14,5 @@ clean: test: pytest ../tests -v --tb=short -m=${TAG} +quick_test: + bash coolc.sh test.cl diff --git a/tests/codegen_test.py b/tests/codegen_test.py index 48df768f..f812c7cc 100644 --- a/tests/codegen_test.py +++ b/tests/codegen_test.py @@ -1,9 +1,11 @@ -import pytest import os -from utils import compare_outputs -tests_dir = __file__.rpartition('/')[0] + '/codegen/' -tests = [(file) for file in os.listdir(tests_dir) if file.endswith('.cl')] +import pytest + +from utils import compare_outputs # type: ignore + +tests_dir = __file__.rpartition("/")[0] + "/codegen/" +tests = [(file) for file in os.listdir(tests_dir) if file.endswith(".cl")] # @pytest.mark.lexer # @pytest.mark.parser @@ -13,5 +15,9 @@ @pytest.mark.run(order=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 + compare_outputs( + compiler_path, + tests_dir + cool_file, + tests_dir + cool_file[:-3] + "_input.txt", + tests_dir + cool_file[:-3] + "_output.txt", + ) diff --git a/tests/conftest.py b/tests/conftest.py index 1f44eeb7..1e990f65 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,8 @@ -import pytest import os +import pytest + + @pytest.fixture def compiler_path(): - return os.path.abspath('./coolc.sh') \ No newline at end of file + return os.path.abspath("./coolc.sh") diff --git a/tests/lexer_test.py b/tests/lexer_test.py index 2a27223d..a58ea6b2 100644 --- a/tests/lexer_test.py +++ b/tests/lexer_test.py @@ -1,13 +1,20 @@ -import pytest 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')] +import pytest + +from utils import compare_errors # type: ignore + +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 129c0f20..8de23404 100644 --- a/tests/parser_test.py +++ b/tests/parser_test.py @@ -1,13 +1,20 @@ -import pytest 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')] +import pytest + +from utils import compare_errors # type: ignore + +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 cac9cd78..c4f104c6 100644 --- a/tests/semantic_test.py +++ b/tests/semantic_test.py @@ -1,14 +1,21 @@ -import pytest 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')] +import pytest + +from utils import compare_errors, first_error_only_line # type: ignore + +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 @pytest.mark.run(order=3) @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 + compare_errors( + compiler_path, + tests_dir + cool_file, + tests_dir + cool_file[:-3] + "_error.txt", + cmp=first_error_only_line, + ) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 90f60fdd..ed5d0c55 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1 +1 @@ -from .utils import * \ No newline at end of file +from .utils import * # noqa diff --git a/tests/utils/utils.py b/tests/utils/utils.py index 961cf7cb..d7274610 100644 --- a/tests/utils/utils.py +++ b/tests/utils/utils.py @@ -1,17 +1,17 @@ -import subprocess import re +import subprocess +COMPILER_TIMEOUT = "El compilador tarda mucho en responder." +SPIM_TIMEOUT = "El spim tarda mucho en responder." +TEST_MUST_FAIL = "El test %s debe fallar al compilar" +TEST_MUST_COMPILE = "El test %s debe compilar" +BAD_ERROR_FORMAT = """El error no esta en formato: (,) - : + o no se encuentra en la 3ra linea\n\n%s""" +UNEXPECTED_ERROR = "Se esperaba un %s en (%d, %d). Su error fue un %s en (%d, %d)" +UNEXPECTED_OUTPUT = "La salida de %s no es la esperada:\n%s\nEsperada:\n%s" -COMPILER_TIMEOUT = 'El compilador tarda mucho en responder.' -SPIM_TIMEOUT = 'El spim tarda mucho en responder.' -TEST_MUST_FAIL = 'El test %s debe fallar al compilar' -TEST_MUST_COMPILE = 'El test %s debe compilar' -BAD_ERROR_FORMAT = '''El error no esta en formato: (,) - : - o no se encuentra en la 3ra linea\n\n%s''' -UNEXPECTED_ERROR = 'Se esperaba un %s en (%d, %d). Su error fue un %s en (%d, %d)' -UNEXPECTED_OUTPUT = 'La salida de %s no es la esperada:\n%s\nEsperada:\n%s' +ERROR_FORMAT = r"^\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)\s*-\s*(\w+)\s*:(.*)$" -ERROR_FORMAT = r'^\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)\s*-\s*(\w+)\s*:(.*)$' def parse_error(error: str): merror = re.fullmatch(ERROR_FORMAT, error) @@ -25,67 +25,108 @@ def first_error(compiler_output: list, errors: list): oline, ocolumn, oerror_type, _ = parse_error(compiler_output[0]) - assert line == oline and column == ocolumn and error_type == oerror_type,\ - UNEXPECTED_ERROR % (error_type, line, column, oerror_type, oline, ocolumn) + assert ( + line == oline and column == ocolumn and error_type == oerror_type + ), UNEXPECTED_ERROR % (error_type, line, column, oerror_type, oline, ocolumn) + def first_error_only_line(compiler_output: list, errors: list): line, column, error_type, _ = parse_error(errors[0]) oline, ocolumn, oerror_type, _ = parse_error(compiler_output[0]) - assert line == oline and error_type == oerror_type,\ - UNEXPECTED_ERROR % (error_type, line, column, oerror_type, oline, ocolumn) + assert line == oline and error_type == oerror_type, UNEXPECTED_ERROR % ( + error_type, + line, + column, + oerror_type, + oline, + ocolumn, + ) def get_file_name(path: str): try: - return path[path.rindex('/') + 1:] + return path[path.rindex("/") + 1 :] except ValueError: return path -def compare_errors(compiler_path: str, cool_file_path: str, error_file_path: str, cmp=first_error, timeout=100): + +def compare_errors( + compiler_path: str, + cool_file_path: str, + error_file_path: str, + cmp=first_error, + timeout=100, +): try: - sp = subprocess.run(['bash', compiler_path, cool_file_path], capture_output=True, timeout=timeout) + sp = subprocess.run( + ["bash", compiler_path, cool_file_path], + capture_output=True, + timeout=timeout, + ) return_code, output = sp.returncode, sp.stdout.decode() except subprocess.TimeoutExpired: assert False, COMPILER_TIMEOUT assert return_code == 1, TEST_MUST_FAIL % get_file_name(cool_file_path) - fd = open(error_file_path, 'r') - errors = fd.read().split('\n') + fd = open(error_file_path, "r") + errors = fd.read().split("\n") fd.close() # checking the errors of compiler - compiler_output = output.split('\n') + compiler_output = output.split("\n") cmp(compiler_output[2:], errors) -SPIM_HEADER = r'''^SPIM Version .+ of .+ + +SPIM_HEADER = r"""^SPIM Version .+ of .+ Copyright .+\, James R\. Larus\. All Rights Reserved\. See the file README for a full copyright notice\. -(?:Loaded: .+\n)*''' -def compare_outputs(compiler_path: str, cool_file_path: str, input_file_path: str, output_file_path: str, timeout=100): +(?:Loaded: .+\n)*""" + + +def compare_outputs( + compiler_path: str, + cool_file_path: str, + input_file_path: str, + output_file_path: str, + timeout=100, +): try: - sp = subprocess.run(['bash', compiler_path, cool_file_path], capture_output=True, timeout=timeout) + sp = subprocess.run( + ["bash", compiler_path, cool_file_path], + capture_output=True, + timeout=timeout, + ) assert sp.returncode == 0, TEST_MUST_COMPILE % get_file_name(cool_file_path) except subprocess.TimeoutExpired: assert False, COMPILER_TIMEOUT - spim_file = cool_file_path[:-2] + 'mips' + spim_file = cool_file_path[:-2] + "mips" try: - fd = open(input_file_path, 'rb') - sp = subprocess.run(['spim', '-file', spim_file], input=fd.read(), capture_output=True, timeout=timeout) + fd = open(input_file_path, "rb") + sp = subprocess.run( + ["spim", "-file", spim_file], + input=fd.read(), + capture_output=True, + timeout=timeout, + ) fd.close() mo = re.match(SPIM_HEADER, sp.stdout.decode()) if mo: - output = mo.string[mo.end():] + output = mo.string[mo.end() :] except subprocess.TimeoutExpired: assert False, SPIM_TIMEOUT - fd = open(output_file_path, 'r') + fd = open(output_file_path, "r") eoutput = fd.read() fd.close() - assert output == eoutput, UNEXPECTED_OUTPUT % (spim_file, repr(output), repr(eoutput)) + assert output == eoutput, UNEXPECTED_OUTPUT % ( # type: ignore + spim_file, + repr(output), # type: ignore + repr(eoutput), + )