diff --git a/.gitignore b/.gitignore index 4acafde1..58419d1b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,10 +25,9 @@ # these rules might exclude image files for figures etc. # *.ps # *.eps -# *.pdf ## Generated if empty string is given at "Please type another file name for output:" -.pdf + ## Bibliography auxiliary files (bibtex/biblatex/biber): *.bbl @@ -393,6 +392,13 @@ dmypy.json # Pyre type checker .pyre/ +#Cool test +src/class.cl +*.cl +*.cil +*.s +*.mips + ### VisualStudioCode ### .vscode/* !.vscode/settings.json @@ -408,3 +414,7 @@ dmypy.json # Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option) +# Ignore every file who start with __ +__* + +.vscode/settings.json diff --git a/doc/Grammar.md b/doc/Grammar.md new file mode 100644 index 00000000..c4d6cf0b --- /dev/null +++ b/doc/Grammar.md @@ -0,0 +1,99 @@ +# Grammar Specification + +The following BNF grammar is based on the *COOL-2012* language specification (see: COOL Reference Manual). + + +```bnf + ::= + + ::= ; + | ; + + ::= class TYPE { } ; + + ::= inherits TYPE + | + + ::= + | + + ::= ; + | ; + + ::= ID ( ) : TYPE { } + | + + ::= + | + + ::= , + | + + ::= ID : TYPE + + ::= ID : TYPE <- + | + + ::= ID : TYPE + + ::= ID <- + | .ID( ) + | .ID( ) + | + | + | + | + | + | new TYPE + | isvoid + | + + | - + | * + | / + | ~ + | < + | <= + | = + | not + | ( ) + | SELF + | ID + | INTEGER + | STRING + | TRUE + | FALSE + + ::= + | + + ::= , + | + + ::= case of esac + + ::= ID : TYPE => + + ::= + | + + ::= if then else fi + + ::= while loop pool + + ::= { } + + ::= ; + | ; + + ::= let in + + ::= + | , + + ::= ID : TYPE <- | + + ::= ID : TYPE + + ::= +``` + diff --git a/doc/Readme.md b/doc/Readme.md index 402477c8..258cffef 100644 --- a/doc/Readme.md +++ b/doc/Readme.md @@ -1,39 +1,11 @@ # 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. - -### Sobre los Materiales a Entregar - -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. - -### Estructura del reporte - -Usted es libre de estructurar su reporte escrito como más conveniente le parezca. A continuación le sugerimos algunas secciones que no deberían faltar, aunque puede mezclar, renombrar y organizarlas de la manera que mejor le parezca: - -- **Uso del compilador**: detalles sobre las opciones de líneas de comando, si tiene opciones adicionales (e.j., `--ast` genera un AST en JSON, etc.). Básicamente lo mismo que pondrá en este Readme. -- **Arquitectura del compilador**: una explicación general de la arquitectura, en cuántos módulos se divide el proyecto, cuantas fases tiene, qué tipo de gramática se utiliza, y en general, como se organiza el proyecto. Una buena imagen siempre ayuda. -- **Problemas técnicos**: detalles sobre cualquier problema teórico o técnico interesante que haya necesitado resolver de forma particular. +Dalianys Pérez Perera | C411 | [@DalyPerez](https://github.com/DalyPerez) +Dayany Alfaro González | C411 | [@dayanyalfaro](https://github.com/dayanyalfaro) +Gilberto González Rodríguez | C411 | [@ginrod](https://github.com/ginrod) -## Sobre la Fecha de Entrega +# Uso del compilador -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. +Para usar el compilador es necesario tener instalado Python 3.7 o superior. Como requisito está el paquete **ply** el cual puede ser instalado usando pip. Si se quiere hacer uso de los tests automáticos además de PLY es necesario instalar **pytest** y **pytest-ordering**. Todos los requerimientos pueden ser instalados ejecutando **python -m pip install -r requeriments.txt** desde la raíz del proyecto. El módulo que contiene toda la lógica del compilador es **cil_to_mips.py**. Para utilizarlo el path relativo a **cil_to_mips.py** o un path absoluto de un fichero con código fuente de COOL se debe pasar como argumento al módulo, por ejemplo, ejecutar **python cil\_to\_mips.py \**. Un archivo en el mismo path del código fuente será creado, con el mismo nombre, pero con extensión .mips. Este fichero contendrá código MIPS con pseudo instrucciones y se puede probar en cualquier implementación del simulador SPIM, como por ejemplo, QtSpim.Otra alternativa para ejecutar el compilador es hacer uso del ejecutable **coolc.sh** contenido en la carpeta src de la siguientes forma: **./cool.sh \**. \ No newline at end of file diff --git a/doc/report.pdf b/doc/report.pdf new file mode 100644 index 00000000..38386bca Binary files /dev/null and b/doc/report.pdf differ diff --git a/old.travis.yml b/old.travis.yml new file mode 100644 index 00000000..1f4f9da7 --- /dev/null +++ b/old.travis.yml @@ -0,0 +1,30 @@ +language: python +python: + - "3.7" +# command to install dependencies +install: + - pip install -r requirements.txt +# command to run tests +jobs: + include: + - stage: "Lexer" + name: "Lexer" + script: + - cd src + - make clean + - make + - make test TAG=lexer + - stage: "Parser" + name: "Parser" + script: + - cd src + - make clean + - make + - make test TAG=parser + - stage: "Semantic" + name: "Semantic" + script: + - cd src + - make clean + - make + - make test TAG=semantic \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9eb0cad1..cba16ee2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pytest pytest-ordering +ply diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json new file mode 100644 index 00000000..6a2cea9e --- /dev/null +++ b/src/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "/home/dayany/anaconda3/bin/python" +} \ No newline at end of file diff --git a/src/ast_nodes.py b/src/ast_nodes.py new file mode 100644 index 00000000..1c730433 --- /dev/null +++ b/src/ast_nodes.py @@ -0,0 +1,362 @@ +class AST: + def __init__(self): + pass + + +# ::= +class Program(AST): + def __init__(self, classes): + super(Program, self).__init__() + self.classes = classes + + +# ::= class TYPE { } ; +class Class(AST): + def __init__(self, name, parent, features, line, column): + super(Class, self).__init__() + self.name = name + self.parent = parent + self.features = features + self.line = line + self.column = column + + +# ::= ID ( ) : TYPE { } +class ClassMethod(AST): + def __init__(self, name, params, return_type, expr, line, column): + super(ClassMethod, self).__init__() + self.name = name + self.params = params + self.return_type = return_type + self.expr = expr + self.line = line + self.column = column + + + +# ::= ID : TYPE <- | +class AttributeInit(AST): + def __init__(self, name, Type, expr, line, column): + super(AttributeInit, self).__init__() + self.name = name + self.type = Type + self.expr = expr + self.line = line + self.column = column + + +# ::= ID : TYPE +class AttributeDef(AST): + def __init__(self, name, Type, line, column): + super(AttributeDef, self).__init__() + self.name = name + self.type = Type + self.line = line + self.column = column + + +# ::= ID : TYPE +class FormalParameter(AST): + def __init__(self, name, param_type, line, column): + super(FormalParameter, self).__init__() + self.name = name + self.param_type = param_type + self.line = line + self.column = column + + +# +class Expr(AST): + def __init__(self): + super(Expr, self).__init__() + self.computed_type = None + +# ::= ID <- +class AssignExpr(Expr): + def __init__(self, instance, expr, line, column): + super(AssignExpr, self).__init__() + self.name = instance + self.expr = expr + self.line = line + self.column = column + +# ::= .ID( ) + + +class DynamicCall(Expr): + def __init__(self, instance, method, args, line, column): + super(DynamicCall, self).__init__() + self.instance = instance + self.method = method + self.args = args + self.line = line + self.column = column + +# ::= .ID( ) + + +class StaticCall(Expr): + def __init__(self, instance, static_type, method, args, line, column): + super(StaticCall, self).__init__() + self.instance = instance + self.method = method + self.static_type = static_type + self.args = args + self.line = line + self.column = column + +# ::= +# ::= case of esac + + +class Case(Expr): + def __init__(self, expr, actions, line, column): + super(Case, self).__init__() + self.expr = expr + self.actions = actions + self.line = line + self.column = column + +# ::= +# | +# ::= ID : TYPE => + + +class Action(AST): + def __init__(self, name, action_type, body, line, column): + super(Action, self).__init__() + self.name = name + self.action_type = action_type + self.body = body + self.line = line + self.column = column + +# ::= +# ::= if then else fi + + +class If(Expr): + def __init__(self, predicate, then_body, else_body, line, column): + super(If, self).__init__() + self.predicate = predicate + self.then_body = then_body + self.else_body = else_body + self.line = line + self.column = column + +# ::= +# ::= while loop pool + + +class While(Expr): + def __init__(self, predicate, body, line, column): + super(While, self).__init__() + self.predicate = predicate + self.body = body + self.line = line + self.column = column + +# ::= +# ::= { } +# ::= ; +# | ; + + +class Block(Expr): + def __init__(self, exprs, line, column): + super(Block, self).__init__() + self.exprs = exprs + self.line = line + self.column = column + +# ::= +# ::= let in +# ::= +# | , + + +class Let(Expr): + def __init__(self, var_list, body, line, column): + super(Let, self).__init__() + self.var_list = var_list + self.body = body + self.line = line + self.column = column + + +# ::= ID : TYPE <- | +class LetVarInit(AST): + def __init__(self, name, Type, expr, line, column): + super(LetVarInit, self).__init__() + self.name = name + self.type = Type + self.expr = expr + self.line = line + self.column = column + + +# ::= ID : TYPE +class LetVarDef(AST): + def __init__(self, name, Type, line, column): + super(LetVarDef, self).__init__() + self.name = name + self.type = Type + self.line = line + self.column = column + + +# ::= new TYPE + + +class NewType(Expr): + def __init__(self, Type, line, column): + super(NewType, self) + self.type = Type + self.line = line + self.column = column + +# ::= isvoid + + +class IsVoid(Expr): + def __init__(self, expr, line, column): + super(IsVoid, self).__init__() + self.expr = expr + self.line = line + self.column = column + +# ::= + + + +class ArithmeticBinOp(Expr): + def __init__(self): + super(ArithmeticBinOp, self).__init__() + + +class Sum(ArithmeticBinOp): + def __init__(self, summand1, summand2, line, column): + super(Sum, self).__init__() + self.left = summand1 + self.right = summand2 + self.line = line + self.column = column + + +# ::= - +class Sub(ArithmeticBinOp): + def __init__(self, minuend, subtrahend, line, column): + super(Sub, self).__init__() + self.left = minuend + self.right = subtrahend + self.line = line + self.column = column + + +# ::= * +class Mult(ArithmeticBinOp): + def __init__(self, factor1, factor2, line, column): + super(Mult, self).__init__() + self.left = factor1 + self.right = factor2 + self.line = line + self.column = column + + +# ::= / +class Div(ArithmeticBinOp): + def __init__(self, dividend, divisor, line, column): + super(Div, self).__init__() + self.left = dividend + self.right = divisor + self.line = line + self.column = column + + +class LogicBinOp(Expr): + def __init__(self): + super(LogicBinOp, self).__init__() + + +# ::= ~ +class LogicalNot(Expr): + def __init__(self, expr, line, column): + super(LogicalNot, self).__init__() + self.expr = expr + self.line = line + self.column = column + + +# ::= < +class LessThan(LogicBinOp): + def __init__(self, left, right, line, column): + super(LessThan, self).__init__() + self.left = left + self.right = right + self.line = line + self.column = column + + +# ::= <= +class LessOrEqualThan(LogicBinOp): + def __init__(self, left, right, line, column): + super(LessOrEqualThan, self).__init__() + self.left = left + self.right = right + self.line = line + self.column = column + +# ::= = + + +class Equals(Expr): + def __init__(self, left, right, line, column): + super(Equals, self).__init__() + self.left = left + self.right = right + self.line = line + self.column = column + + +# ::= not +class Not(Expr): + def __init__(self, expr, line, column): + super(Not, self).__init__() + self.expr = expr + self.line = line + self.column = column + + +# ::= ID +class Identifier(Expr): + def __init__(self, name, line, column): + super(Identifier, self).__init__() + self.name = name + self.line = line + self.column = column + + +# ::= INTEGER +class INTEGER(Expr): + def __init__(self, value, line, column): + super(INTEGER, self).__init__() + self.value = value + self.line = line + self.column = column + + +# ::= STRING +class STRING(Expr): + def __init__(self, value, line, column): + super(STRING, self).__init__() + self.value = value + self.line = line + self.column = column + +# ::= TRUE | FALSE + +class Boolean(Expr): + def __init__(self, value, line, column): + super(Boolean, self).__init__() + self.value = value + self.line = line + self.column = column \ No newline at end of file diff --git a/src/cil_ast_nodes.py b/src/cil_ast_nodes.py new file mode 100644 index 00000000..37d4ce48 --- /dev/null +++ b/src/cil_ast_nodes.py @@ -0,0 +1,391 @@ +import visitor + +class AST: + def __init__(self): + pass + + def __repr__(self): + return str(self) + +class Program(AST): + def __init__(self, dottypes, dotdata, dotcode): + super(Program, self).__init__() + self.dottypes = dottypes + self.dotdata = dotdata + self.dotcode = dotcode + +class Type(AST): + def __init__(self, name): + super(Type, self).__init__() + self.name = name + self.attributes = [] + self.methods = {} + + +class Function(AST): + def __init__(self, name, params=[], localvars=[], instructions =[]): + super(Function, self).__init__() + self.name = name + self.params = params # list of Param + self.localvars = localvars # list of LocalDec + self.instructions = instructions # list of Instructions + + +class Expr(AST): + def __init__(self): + super(Expr, self).__init__() + +class ParamDec(Expr): + def __init__(self, name): + super(ParamDec, self).__init__() + self.name = name + + +class LocalDec(Expr): + def __init__(self, name): + super(LocalDec, self).__init__() + self.name = name + +class Halt(Expr): + def __init__(self): + super(Halt, self).__init__() + + +class GetAttr(Expr): + def __init__(self, dest, instance, attr, static_type): + self.local_dest = dest + self.instance = instance + self.attr = attr + self.static_type = static_type + +class SetAttr(Expr): + def __init__(self,instance, attr, value, static_type): + self.instance = instance + self.attr = attr + self.value = value + self.static_type = static_type + + +class Call(Expr): + def __init__(self, local_dest, function, params, static_type): + self.function = function + self.params = params + self.static_type = static_type + self.local_dest = local_dest + + +class VCall(Expr): + def __init__(self, local_dest, function, params, dynamic_type, instance): + self.function = function + self.params = params + self.dynamic_type = dynamic_type + self.local_dest = local_dest + self.instance = instance + + +class INTEGER(Expr): + def __init__(self, value): + super(INTEGER, self).__init__() + self.value = value + + +class STRING(Expr): + def __init__(self, value): + super(STRING, self).__init__() + self.value = value + + +class Assign(Expr): + def __init__(self, local_dest, right_expr): + self.local_dest = local_dest + self.right_expr = right_expr + + +class UnaryOperator(Expr): + def __init__(self, local_dest, expr_value, op): + self.local_dest = local_dest + self.expr_value = expr_value + self.op = op + +class BinaryOperator(Expr): + def __init__(self, local_dest, left, right, op): + self.local_dest = local_dest + self.left = left + self.right = right + self.op = op + +class Allocate(Expr): + def __init__(self, t,tag, dest): + self.type = t + self.local_dest = dest + self.tag = tag + + +class TypeOf(Expr): + def __init__(self, variable, local_dest): + self.variable = variable + self.local_dest = local_dest + +class Param(Expr): + def __init__(self, param, shift = 0): + self.param = param + self.shift = shift + +class Arg(Expr): + def __init__(self, arg): + self.arg = arg + + +class Case(Expr): + def __init__(self, local_expr, first_label): + self.local_expr = local_expr + self.first_label = first_label + +class Action(Expr): + def __init__(self, local_expr, tag, max_tag, next_label): + self.local_expr = local_expr + self.tag = tag + self.max_tag = max_tag + self.next_label = next_label + +class IfGoto(Expr): + def __init__(self, variable, label): + self.variable = variable + self.label = label + +class Goto(Expr): + def __init__(self, label): + self.label = label + +class Label(Expr): + def __init__(self, label): + self.label = label + +class Return(Expr): + def __init__(self, value): + self.value = value + + +class LoadInt(Expr): + def __init__(self, num, dest): + self.num = num + self.local_dest = dest + +class LoadStr(Expr): + def __init__(self, msg, dest): + self.msg = msg + self.local_dest = dest + + +class LoadVoid(Expr): + def __init__(self, dest): + self.local_dest = dest + +class Length(Expr): + def __init__(self, variable, result): + self.variable = variable + self.result = result + +class Concat(Expr): + def __init__(self, str1, len1, str2, len2, result): + self.str1 = str1 + self.len1 = len1 + self.str2 = str2 + self.len2 = len2 + self.result = result + +class StringVar(Expr): + def __init__(self, variable): + self.variable = variable + +class SubStr(Expr): + def __init__(self, i, length, string, result): + self.i = i + self.length = length + self.string = string + self.result = result + +class StringEquals(Expr): + def __init__(self, s1, s2, result): + self.s1 = s1 + self.s2 = s2 + self.result = result + +class Read(Expr): + def __init__(self, result): + self.result = result + +class ReadString(Read): + pass + +class ReadInteger(Read): + pass + +class Print(Expr): + def __init__(self, variable): + self.variable = variable + +class PrintString(Print): + pass + +class PrintInteger(Print): + pass + + +class IsVoid(Expr): + def __init__(self, result_local, expre_value): + self.result_local = result_local + self.expre_value = expre_value + +class Copy(Expr): + def __init__(self, type, local_dest): + self.type = type + self.local_dest = local_dest + + + +def get_formatter(): + + class PrintVisitor(object): + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(Program) + def visit(self, node): + dottypes = '\n'.join(self.visit(t) for t in node.dottypes.values()) + dotdata = '\n'.join(f'{t}: {node.dotdata[t]}' for t in node.dotdata.keys()) + dotcode = '\n'.join(self.visit(t) for t in node.dotcode) + + return f'.TYPES\n{dottypes}\n\n.DATA\n{dotdata}\n\n.CODE\n{dotcode}' + + @visitor.when(Type) + def visit(self, node): + attributes = '\n\t'.join(f'attribute {x}' for x in node.attributes) + methods = '\n\t'.join(f'method {x} : {node.methods[x]}' for x in node.methods.keys()) + + return f'type {node.name} {{\n\t{attributes}\n\n\t{methods}\n}}' + + @visitor.when(Function) + def visit(self, node): + params = '\n\t'.join(self.visit(x) for x in node.params) + localvars = '\n\t'.join(self.visit(x) for x in node.localvars) + instructions = '\n\t'.join(self.visit(x) for x in node.instructions) + + return f'function {node.name} {{\n\t{params}\n\n\t{localvars}\n\n\t{instructions}\n}}' + + @visitor.when(ParamDec) + def visit(self, node): + return f'PARAM {node.name}' + + @visitor.when(LocalDec) + def visit(self, node): + return f'LOCAL {node.name}' + + @visitor.when(Assign) + def visit(self, node): + return f'{node.local_dest} = {node.right_expr}' + + @visitor.when(IfGoto) + def visit(self, node): + return f'IF {node.variable} GOTO {node.label}' + + @visitor.when(Label) + def visit(self, node): + return f'LABEL {node.label}' + + @visitor.when(Goto) + def visit(self, node): + return f'GOTO {node.label}' + + @visitor.when(UnaryOperator) + def visit(self, node): + return f'{node.local_dest} = {node.op} {node.expr_value}' + + @visitor.when(BinaryOperator) + def visit(self, node): + return f'{node.local_dest} = {node.left} {node.op} {node.right}' + + @visitor.when(Allocate) + def visit(self, node): + return f'{node.local_dest} = ALLOCATE {node.type}' + + @visitor.when(LoadStr) + def visit(self, node): + return f'{node.local_dest} = LOAD {node.msg}' + + @visitor.when(LoadInt) + def visit(self, node): + return f'{node.local_dest} = LOAD {node.num}' + + @visitor.when(LoadVoid) + def visit(self, node): + return f'{node.local_dest} = LOAD VOID' + + @visitor.when(GetAttr) + def visit(self, node): + return f'{node.local_dest} = GetAttr {node.instance} {node.attr} ' + + @visitor.when(SetAttr) + def visit(self, node): + return f'SetAttr {node.instance} {node.attr} {node.value}' + + + @visitor.when(TypeOf) + def visit(self, node): + return f'{node.local_dest} = TYPEOF {node.variable}' + + @visitor.when(Call) + def visit(self, node): + return f'{node.local_dest} = CALL {node.function}' + + @visitor.when(VCall) + def visit(self, node): + return f'{node.local_dest} = VCALL {node.dynamic_type} {node.function} ' + + @visitor.when(Arg) + def visit(self, node): + return f'ARG {node.arg}' + + @visitor.when(Return) + def visit(self, node): + return f'\n RETURN {node.value if node.value is not None else ""}' + + @visitor.when(IsVoid) + def visit(self, node): + return f'{node.result_local} ISVOID {node.expre_value}' + + @visitor.when(Halt) + def visit(self, node): + return 'HALT' + + @visitor.when(Copy) + def visit(self, node): + return f'{node.local_dest} = COPY {node.type}' + + @visitor.when(Length) + def visit(self, node): + return f'{node.result} = LENGTH {node.variable}' + + @visitor.when(Concat) + def visit(self, node): + return f'{node.result} = CONCAT {node.str1} {node.str2}' + + @visitor.when(SubStr) + def visit(self, node): + return f'{node.result} = SUBSTR {node.i} {node.length} {node.string}' + + @visitor.when(StringEquals) + def visit(self, node): + return f'{node.result} = {node.s1} = {node.s2}' + + @visitor.when(Read) + def visit(self, node): + return f'{node.result} = READ' + + @visitor.when(Print) + def visit(self, node): + return f'PRINT {node.variable}' + + printer = PrintVisitor() + return (lambda ast: printer.visit(ast)) diff --git a/src/cil_to_mips.py b/src/cil_to_mips.py new file mode 100644 index 00000000..4618bfa0 --- /dev/null +++ b/src/cil_to_mips.py @@ -0,0 +1,611 @@ +from semantic import Scope, VariableInfo +import visitor +import ast_nodes as COOL_AST +import cil_ast_nodes as CIL_AST + +class CILToMIPSVisitor(): + def __init__(self): + self.mips_code = '' + self.text = '' + self.data = '' + self.mips_comm_for_operators = { + '+' : 'add', + '-' : 'sub', + '*' : 'mul', + '/' : 'div', + '<' : 'slt', + '<=' : 'sle', + '=' : 'seq', + } + self.current_function = None + self.types = None + self.attr_offset = {} + self.method_offset = {} + self.var_offset = {} + self.runtime_errors = {} + self.register_runtime_errors() + + def is_param(self, name): + return name in self.current_function.params + + def register_runtime_errors(self): + self.runtime_errors['dispatch_void'] = 'Runtime Error: A dispatch (static or dynamic) on void' + self.runtime_errors['case_void'] = 'Runtime Error: A case on void' + self.runtime_errors['case_no_match'] = 'Runtime Error: Execution of a case statement without a matching branch' + self.runtime_errors['div_zero'] = 'Runtime Error: Division by zero' + self.runtime_errors['substr'] = 'Runtime Error: Substring out of range' + self.runtime_errors['heap'] = 'Runtime Error: Heap overflow' + for error in self.runtime_errors: + self.data += f'{error}: .asciiz "{self.runtime_errors[error]}"\n' + self.generate_runtime_error(error) + + def generate_runtime_error(self, error): + self.text += f'{error}_error:\n' + self.text += f'la $a0 {error}\n' + self.text += f'li $v0, 4\n' + self.text += 'syscall\n' + self.text += 'li $v0, 10\n' + self.text += 'syscall\n' + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(CIL_AST.Program) + def visit(self, node): + self.types = node.dottypes + + self.data += 'temp_string: .space 2048\n' + self.data += 'void: .word 0\n' + + for node_type in node.dottypes.values(): + self.visit(node_type) + + for node_data in node.dotdata.keys(): + self.data += f'{node_data}: .asciiz "{node.dotdata[node_data]}"\n' + + for node_function in node.dotcode: + self.visit(node_function) + + self.mips_code = '.data\n' + self.data + '.text\n' + self.text + return self.mips_code.strip() + + @visitor.when(CIL_AST.Function) + def visit(self, node): + self.current_function = node + + self.var_offset.__setitem__(self.current_function.name, {}) + + for idx, var in enumerate(self.current_function.localvars + self.current_function.params): + self.var_offset[self.current_function.name][var.name] = (idx + 1)*4 + + self.text += f'{node.name}:\n' + + self.text += f'addi $sp, $sp, {-4 * len(node.localvars)}\n' #save space for locals + + self.text += 'addi $sp, $sp, -4\n' # save return address + self.text += 'sw $ra, 0($sp)\n' + + for instruction in node.instructions: + self.visit(instruction) + + self.text += 'lw $ra, 0($sp)\n' #recover return address + total = 4 * len(node.localvars) + 4 * len(node.params) + 4 + self.text += f'addi $sp, $sp, {total}\n' #pop locals,parameters,return address from the stack + self.text += 'jr $ra\n' + + @visitor.when(CIL_AST.Type) + def visit(self, node): + self.data += f'{node.name}_name: .asciiz "{node.name}"\n' + self.data += f'{node.name}_methods:\n' + for method in node.methods.values(): + self.data += f'.word {method}\n' + + idx = 0 + self.attr_offset.__setitem__(node.name, {}) + for attr in node.attributes: + self.attr_offset[node.name][attr] = 4*idx + 16 + idx = idx + 1 + + idx = 0 + self.method_offset.__setitem__(node.name, {}) + for met in node.methods: + self.method_offset[node.name][met] = 4*idx + idx = idx + 1 + + + @visitor.when(CIL_AST.Assign) + def visit(self, node): + offset = self.var_offset[self.current_function.name][node.local_dest] + if node.right_expr: + if isinstance(node.right_expr, int): + self.text += f'li $t1, {node.right_expr}\n' + else: + right_offset = self.var_offset[self.current_function.name][node.right_expr] + self.text += f'lw $t1, {right_offset}($sp)\n' + else: + self.text += f'la $t1, void\n' + + self.text += f'sw $t1, {offset}($sp)\n' + + + @visitor.when(CIL_AST.Allocate) + def visit(self, node): + amount = len(self.types[node.type].attributes) + 4 + self.text += f'li $a0, {amount * 4}\n' + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + self.text += f'move $t0, $v0\n' + + #Initialize Object Layout + self.text += f'li $t1, {node.tag}\n' #tag + self.text += f'sw $t1, 0($t0)\n' + self.text += f'la $t1, {node.type}_name\n' #type_name + self.text += f'sw $t1, 4($t0)\n' + self.text += f'li $t1, {amount}\n' #size + self.text += f'sw $t1, 8($t0)\n' + self.text += f'la $t1, {node.type}_methods\n' #methods pointer + self.text += f'sw $t1, 12($t0)\n' + + offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t0, {offset}($sp)\n' #store instance address in local + + @visitor.when(CIL_AST.ParamDec) + def visit(self, node): + pass + + @visitor.when(CIL_AST.LocalDec) + def visit(self, node): + pass + + @visitor.when(CIL_AST.GetAttr) + def visit(self, node): + self_offset = self.var_offset[self.current_function.name][node.instance] + self.text += f'lw $t0, {self_offset}($sp)\n' #get self address + + attr_offset = self.attr_offset[node.static_type][node.attr] + self.text += f'lw $t1, {attr_offset}($t0)\n' #get attribute + + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t1, {result_offset}($sp)\n' #store attribute in local + + @visitor.when(CIL_AST.SetAttr) + def visit(self, node): + self_offset = self.var_offset[self.current_function.name][node.instance] + self.text += f'lw $t0, {self_offset}($sp)\n' #get self address + + if node.value: + value_offset = self.var_offset[self.current_function.name][node.value] # get value from local + self.text += f'lw $t1, {value_offset}($sp)\n' + else: + self.text += f'la $t1, void\n' # not initialized attribute + + attr_offset = self.attr_offset[node.static_type][node.attr] + self.text += f'sw $t1, {attr_offset}($t0)\n' #set attribute in instance + + + @visitor.when(CIL_AST.Arg) + def visit(self, node): + value_offset = self.var_offset[self.current_function.name][node.arg] # get value from local + self.text += f'lw $t1, {value_offset}($t0)\n' + self.text += 'addi $sp, $sp, -4\n' + self.text += 'sw $t1, 0($sp)\n' + + @visitor.when(CIL_AST.VCall) + def visit(self, node): + self.text += 'move $t0, $sp\n' + + for arg in node.params: + self.visit(arg) + + value_offset = self.var_offset[self.current_function.name][node.instance] + self.text += f'lw $t1, {value_offset}($t0)\n' # get instance from local + self.text += 'la $t0, void\n' + self.text += 'beq $t1, $t0, dispatch_void_error\n' + + self.text += f'lw $t2, 12($t1)\n' #get dispatch table address + + method_offset = self.method_offset[node.dynamic_type][node.function] + self.text += f'lw $t3, {method_offset}($t2)\n' # get method address + + self.text += 'jal $t3\n' + + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a1, {result_offset}($sp)\n' + + + @visitor.when(CIL_AST.Call) + def visit(self, node): + self.text += 'move $t0, $sp\n' + + for arg in node.params: + self.visit(arg) + + self.text += f'jal {node.function}\n' + + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a1, {result_offset}($sp)\n' + + @visitor.when(CIL_AST.Return) + def visit(self, node): + if node.value: + offset = self.var_offset[self.current_function.name][node.value] + self.text += f'lw $a1, {offset}($sp)\n' + else: + self.text += f'move $a1, $zero\n' + + @visitor.when(CIL_AST.Case) + def visit(self, node): + offset = self.var_offset[self.current_function.name][node.local_expr] + self.text += f'lw $t0, {offset}($sp)\n' + self.text += f'lw $t1, 0($t0)\n' + self.text += 'la $a0, void\n' + self.text += f'bne $t1 $a0 {node.first_label}\n' + self.text += 'b case_void_error\n' + + @visitor.when(CIL_AST.Action) + def visit(self, node): + self.text += f'blt $t1 {node.tag} {node.next_label}\n' + self.text += f'bgt $t1 {node.max_tag} {node.next_label}\n' + + + @visitor.when(CIL_AST.BinaryOperator) + def visit(self, node): + mips_comm = self.mips_comm_for_operators[node.op] + left_offset = self.var_offset[self.current_function.name][node.left] + right_offset = self.var_offset[self.current_function.name][node.right] + self.text += f'lw $a0, {left_offset}($sp)\n' + self.text += f'lw $t1, {right_offset}($sp)\n' + if node.op == '/': + self.text += 'beq $t1, 0, div_zero_error\n' + self.text += f'{mips_comm} $a0, $a0, $t1\n' + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a0, {result_offset}($sp)\n' + + @visitor.when(CIL_AST.UnaryOperator) + def visit(self, node): + expr_offset = self.var_offset[self.current_function.name][node.expr_value] + self.text += f'lw $t1, {expr_offset}($sp)\n' + if node.op == 'not': + self.text += f'xor $a0, $t1, 1\n' + else: + self.text += f'neg $a0, $t1 \n' + + result_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $a0, {result_offset}($sp)\n' + + @visitor.when(CIL_AST.IfGoto) + def visit(self, node): + predicate_offset = self.var_offset[self.current_function.name][node.variable] + self.text += f'lw $t0, {predicate_offset}($sp)\n' + self.text += f'lw $a0, 16($t0)\n' #get value attribute + self.text += f'bnez $a0, {node.label}\n' + + @visitor.when(CIL_AST.Goto) + def visit(self, node): + self.text += f'b {node.label}\n' + + @visitor.when(CIL_AST.Label) + def visit(self, node): + self.text += f'{node.label}:\n' + + @visitor.when(CIL_AST.PrintInteger) + def visit(self, node): + if isinstance(node.variable, int): + self.text += f'li $v0 , 1\n' + self.text += f'li $a0 , {node.variable}\n' + self.text += f'syscall\n' + else: + var_offset = self.var_offset[self.current_function.name][node.variable] + self.text += f'li $v0 , 1\n' + self.text += f'lw $a0 , {var_offset}($sp)\n' + self.text += f'syscall\n' + + @visitor.when(CIL_AST.PrintString) + def visit(self, node): + var_offset = self.var_offset[self.current_function.name][node.variable] + self.text += f'lw $a0, {var_offset}($sp)\n' + self.text += f'li $v0, 4\n' + self.text += f'syscall\n' + + @visitor.when(CIL_AST.ReadInteger) + def visit(self, node): + read_offset = self.var_offset[self.current_function.name][node.result] + self.text += f'li $v0, 5\n' + self.text += f'syscall\n' + self.text += f'sw $v0, {read_offset}($sp)\n' + + @visitor.when(CIL_AST.ReadString) + def visit(self, node): + read_offset = self.var_offset[self.current_function.name][node.result] + self.text += f'la $a0, temp_string\n' + self.text += f'li $a1, 2048\n' + self.text += f'li $v0, 8\n' + self.text += f'syscall\n' + + # Remove last chars (if they are '\n' or '\r\n') + self.text += 'move $t0, $a0\n' + self.text += 'jump_read_str_char:\n' + self.text += 'li $t1, 0\n' + self.text += 'lb $t1, 0($t0)\n' + self.text += 'beqz $t1, analize_str_end\n' # finish if the final of string is found + self.text += 'addi $t0, $t0, 1\n' + self.text += 'j jump_read_str_char\n' + + self.text += 'analize_str_end:\n' + self.text += 'addi $t0, $t0, -1\n' # go to char at length - 1 + self.text += 'li $t1, 0\n' + self.text += 'lb $t1, 0($t0)\n' + self.text += 'bne $t1, 10, finish_jump_read_str_char\n' # remove char only if it is '\n' + self.text += 'sb $0, 0($t0)\n' # remove '\r\n' + self.text += 'addi $t0, $t0, -1\n' # go to char at length - 2 + self.text += 'lb $t1, 0($t0)\n' + self.text += 'bne $t1, 13, finish_jump_read_str_char\n' # remove char only if it is '\r' + self.text += 'sb $0, 0($t0)\n' # remove '\r\n' + self.text += 'j analize_str_end\n' + self.text += 'finish_jump_read_str_char:\n' + + self.text += f'sw $a0, {read_offset}($sp)\n' + + @visitor.when(CIL_AST.LoadStr) + def visit(self, node): + self.text += f'la $t0, {node.msg}\n' + offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t0, {offset}($sp)\n' + + @visitor.when(CIL_AST.LoadInt) + def visit(self, node): + self.text += f'li $t0, {node.num}\n' + offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t0, {offset}($sp)\n' + + @visitor.when(CIL_AST.Halt) + def visit(self, node): + self.text += 'li $v0, 10\n' + self.text += 'syscall\n' + + @visitor.when(CIL_AST.TypeOf) + def visit(self, node): + obj_offset = self.var_offset[self.current_function.name][node.variable] + self.text += f'lw $t0, {obj_offset}($sp)\n' #get obj address from local + self.text += 'lw $t1, 4($t0)\n' # get type name from the sec pos in obj layout + res_offset = self.var_offset[self.current_function.name][node.local_dest] + self.text += f'sw $t1, {res_offset}($sp)\n' + + @visitor.when(CIL_AST.IsVoid) + def visit(self, node): + self.text += 'la $t0, void\n' + offset = self.var_offset[self.current_function.name][node.expre_value] + self.text += f'lw $t1, {offset}($sp)\n' + self.text += 'seq $a0, $t0, $t1\n' + res_offset = self.var_offset[self.current_function.name][node.result_local] + self.text += f'sw $a0, {res_offset}($sp)\n' + + @visitor.when(CIL_AST.Copy) + def visit(self, node): + self_offset = self.var_offset[self.current_function.name][node.type] + self.text += f'lw $t0, {self_offset}($sp)\n' # get self address + self.text += f'lw $a0, 8($t0)\n' # get self size {amount} + self.text += f'mul $a0, $a0, 4\n' # {amount * 4} + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + self.text += f'move $t1, $v0\n' + + # Copy All Slots inlcuding Tag, Size, methods ptr and each atrribute + # Tenemos q hacerlo en MIPS porq copy está a nivel de Object y en python + # en este punto no sabemos el tipo dinamico (para asi saber el tamaño real) + # hasta q se haga el VCALL por lo el ciclo hayq hacerlo en MIPS) + + self.text += 'li $a0, 0\n' + self.text += 'lw $t3, 8($t0)\n' + self.text += 'copy_object_word:\n' + self.text += 'lw $t2, 0($t0)\n' # load current object word + self.text += 'sw $t2, 0($t1)\n' # store word in copy object + self.text += 'addi $t0, $t0, 4\n' # move to the next word in orginal object + self.text += 'addi $t1, $t1, 4\n' # move to the next word in copy object + self.text += 'addi $a0, $a0, 1\n' # size count + ''' + Src2 can either be a register or an immediate value (a 16 bit integer). + blt Rsrc1, Src2, label (Branch on Less Than) + Conditionally branch to the instruction at the label if the contents of register Rsrc1 are less than Src2. + ''' + self.text += 'blt $a0, $t3, copy_object_word\n' # $t3 is the orginal object size + + offset = self.var_offset[self.current_function.name][node.local_dest] + # $t1 is pointing at the end of the object + # if $v0 is modified for any reason (it should not, but...) + # before looping we can move $t3, $t1 and use $t3 but this should work + self.text += f'sw $v0, {offset}($sp)\n' #store instance address in local + + @visitor.when(CIL_AST.Length) + def visit(self, node): + offset = self.var_offset[self.current_function.name][node.variable] + self.text += f'lw $t0, {offset}($sp)\n' + self.text += f'lw $t0, 16($t0)\n' + + self.text += 'li $a0, 0\n' + self.text += 'count_char:\n' + self.text += 'lb $t1, 0($t0)\n' # loading current char + self.text += 'beqz $t1, finish_chars_count\n' # finish if a zero is found + self.text += 'addi $t0, $t0, 1\n' # move to the next char + self.text += 'addi $a0, $a0, 1\n' # length_count += 1 + self.text += 'j count_char\n' + self.text += 'finish_chars_count:\n' + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $a0, {offset}($sp)\n' # store length count address in local + + @visitor.when(CIL_AST.Concat) + def visit(self, node): + offset_str1 = self.var_offset[self.current_function.name][node.str1] + offset_len1 = self.var_offset[self.current_function.name][node.len1] + + offset_str2 = self.var_offset[self.current_function.name][node.str2] + offset_len2 = self.var_offset[self.current_function.name][node.len2] + + # reserve space for concatenation result + self.text += f'lw $a0, {offset_len1}($sp)\n' + self.text += f'lw $t0, {offset_len2}($sp)\n' + # add Rdest, Rsrc1, Src2 Addition (with overflow) + # is similar to addi but 2do summand can be a register + self.text += 'add $a0, $a0, $t0\n' + self.text += 'addi $a0, $a0, 1\n' # reserve 1 more byte for '\0' + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + self.text += 'move $t3, $v0\n' + # the beginning of new reserved address is in $v0 and save in $t3 + + self.text += f'lw $t0, {offset_str1}($sp)\n' + self.text += f'lw $t1, {offset_str2}($sp)\n' + + # copy str1 starting in $t0 to $v0 + self.text += 'copy_str1_char:\n' + self.text += 'lb $t2, 0($t0)\n' # loading current char from str1 + self.text += 'sb $t2, 0($v0)\n' # storing current char into result_str end + self.text += 'beqz $t2, concat_str2_char\n' # finish if a zero is found + self.text += 'addi $t0, $t0, 1\n' # move to the next char + self.text += 'addi $v0, $v0, 1\n' # move to the next available byte + self.text += 'j copy_str1_char\n' + + # concat str2 starting in $t1 to $v0 + self.text += 'concat_str2_char:\n' + self.text += 'lb $t2, 0($t1)\n' # loading current char from str1 + self.text += 'sb $t2, 0($v0)\n' # storing current char into result_str end + self.text += 'beqz $t2, finish_str2_concat\n' # finish if a zero is found + self.text += 'addi $t1, $t1, 1\n' # move to the next char + self.text += 'addi $v0, $v0, 1\n' # move to the next available byte + self.text += 'j concat_str2_char\n' + self.text += 'finish_str2_concat:\n' + self.text += 'sb $0, ($v0)\n' # put '\0' at the end + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $t3, {offset}($sp)\n' # store length count address in local + + @visitor.when(CIL_AST.SubStr) + def visit(self, node): + offset_idx = self.var_offset[self.current_function.name][node.i] + offset_len = self.var_offset[self.current_function.name][node.length] + offset_str = self.var_offset[self.current_function.name][node.string] + + # reserve space for substring result + self.text += f'lw $a0, {offset_len}($sp)\n' + self.text += 'addi $a0, $a0, 1\n' # reserve 1 more byte for '\0' + self.text += f'li $v0, 9\n' + self.text += f'syscall\n' + self.text += 'bge $v0, $sp heap_error\n' + # the beginning of new reserved address is in $v0 + + self.text += f'lw $t0, {offset_idx}($sp)\n' + self.text += f'lw $t1, {offset_len}($sp)\n' + self.text += f'lw $t4, {offset_str}($sp)\n' + self.text += f'lw $t2, 16($t4)\n' + + self.text += 'bltz $t0, substr_error\n' + + # jump first i chars + self.text += 'li $a0, 0\n' + self.text += 'jump_str_char:\n' + self.text += f'beq $a0, $t0, finish_index_jump\n' # finish if we are at index i + self.text += 'addi $a0, $a0, 1\n' # chars count + self.text += 'addi $t2, $t2, 1\n' # move to the next char + self.text += 'beq $t2, $zero, substr_error\n' + self.text += 'j jump_str_char\n' + self.text += 'finish_index_jump:\n' + self.text += 'li $a0, 0\n' # reset $a0 to count the length + self.text += 'move $t3, $v0\n' # save start of substring + + # coping chars from string $t2 (starting in $t0 index) until length $t1 to $v0 + self.text += 'copy_substr_char:\n' + self.text += 'beq $a0, $t1 finish_substr_copy\n' # finish if the chars count is equals to length + self.text += 'li $t0, 0\n' # reset $t0 before loading bytes + self.text += 'lb $t0, 0($t2)\n' # loading current char from string + self.text += 'sb $t0, 0($v0)\n' # storing current char into result_str end + self.text += 'addi $t2, $t2, 1\n' # move to the next char + self.text += 'beq $t2, $zero, substr_error\n' + self.text += 'addi $v0, $v0, 1\n' # move to the next available byte + self.text += 'addi $a0, $a0, 1\n' # chars count + self.text += 'j copy_substr_char\n' + self.text += 'finish_substr_copy:\n' + self.text += 'sb $0, ($v0)\n' # put '\0' at the end + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $t3, {offset}($sp)\n' # store length count address in local + + @visitor.when(CIL_AST.StringEquals) + def visit(self, node): + offset_str1 = self.var_offset[self.current_function.name][node.s1] + offset_str2 = self.var_offset[self.current_function.name][node.s2] + + self.text += f'lw $t1, {offset_str1}($sp)\n' + self.text += f'lw $t2, {offset_str2}($sp)\n' + + # comparing char by char + self.text += 'compare_str_char:\n' + self.text += 'li $t3, 0\n' # reset $t3 before loading byte + self.text += 'lb $t3, 0($t1)\n' # loading current char from string1 + self.text += 'li $t4, 0\n' # reset $t4 before loading byte + self.text += 'lb $t4, 0($t2)\n' # loading current char from string2 + self.text += 'seq $a0, $t3, $t4\n' # comparing current bytes + self.text += 'beqz $a0, finish_compare_str\n' # finish if current chars are not equals + self.text += 'beqz $t3, finish_compare_str\n' # finish if the final of string1 is found + self.text += 'beqz $t4, finish_compare_str\n' # finish if the final of string2 is found + self.text += 'addi $t1, $t1, 1\n' # move to the next char in string1 + self.text += 'addi $t2, $t2, 1\n' # move to the next char in string2 + self.text += 'j compare_str_char\n' + self.text += 'finish_compare_str:\n' + + offset = self.var_offset[self.current_function.name][node.result] + self.text += f'sw $a0, {offset}($sp)\n' # store comparison result in local + +if __name__ == '__main__': + import sys + from cparser import Parser + from lexer import Lexer + from semantic_analyzer import SemanticAnalyzer + from cool_to_cil import COOLToCILVisitor + + lexer = Lexer() + parser = Parser() + + if len(sys.argv) > 1: + + input_file = sys.argv[1] + with open(input_file, encoding="utf-8") as file: + cool_program_code = file.read() + + lexer.input(cool_program_code) + for token in lexer: pass + + if lexer.errors: + print(lexer.errors[0]) + exit(1) + + cool_ast = parser.parse(cool_program_code) + + if parser.errors: + print(parser.errors[0]) + exit(1) + + semantic_analyzer = SemanticAnalyzer(cool_ast) + context, scope = semantic_analyzer.analyze() + + if semantic_analyzer.errors: + print(semantic_analyzer.errors[0]) + exit(1) + + cool_to_cil = COOLToCILVisitor(context) + cil_ast = cool_to_cil.visit(cool_ast, scope) + + # formatter = CIL_AST.get_formatter() + # cil_code = formatter(cil_ast) + # with open(f'{sys.argv[1][:-3]}.cil', 'w') as f: + # f.write(f'{cil_code}') + + cil_to_mips = CILToMIPSVisitor() + mips_code = cil_to_mips.visit(cil_ast) + + with open(f'{sys.argv[1][:-3]}.mips', 'w') as f: + f.write(f'{mips_code}') diff --git a/src/cool_to_cil.py b/src/cool_to_cil.py new file mode 100644 index 00000000..dd578d76 --- /dev/null +++ b/src/cool_to_cil.py @@ -0,0 +1,895 @@ +from semantic import Scope, VariableInfo +import visitor +import ast_nodes as COOL_AST +import cil_ast_nodes as CIL_AST + +class BaseCOOLToCILVisitor: + 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.context.set_type_tags() + self.context.set_type_max_tags() + + @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 get_label(self): + self.label_count += 1 + return f'label_{self.label_count}' + + def register_param(self, vinfo): + param_node = CIL_AST.ParamDec(vinfo.name) + self.params.append(param_node) + return vinfo.name + + def is_defined_param(self, name): + for p in self.params: + if p.name == name: + return True + return False + + def register_local(self, var_name): + local_node = CIL_AST.LocalDec(var_name) + self.localvars.append(local_node) + return var_name + + def define_internal_local(self, scope, name = "internal", cool_var_name = None, class_type = None): + if class_type != None: + cilname = f'{class_type}.{name}' + scope.define_cil_local(cool_var_name, cilname, None) + self.register_local(cilname) + else : + cilname = f'{name}_{len(self.localvars)}' + scope.define_cil_local(cool_var_name, cilname, None) + self.register_local(cilname) + return cilname + + def register_instruction(self, instruction): + self.instructions.append(instruction) + return instruction + + def to_function_name(self, method_name, type_name): + return f'{type_name}.{method_name}' + + def register_function(self, function_name): + function_node = CIL_AST.Function(function_name, [], [], []) + self.dotcode.append(function_node) + return function_node + + def register_type(self, name): + type_node = CIL_AST.Type(name) + self.dottypes[name] = type_node + return type_node + + def is_in_data(self, name): + return name in self.dotdata.keys + + def register_data(self, value): + vname = f's_{len(self.dotdata)}' + self.dotdata[vname] = value + return vname + + def register_builtin_types(self, scope): + for t in ['Object', 'Int', 'String', 'Bool', 'IO']: + builtin_type = self.context.get_type(t) + cil_type = self.register_type(t) + cil_type.attributes = [f'{attr.name}' for attr in builtin_type.attributes] + cil_type.methods = {f'{m}':f'{c}.{m}' for c, m in builtin_type.get_all_methods()} + if t in ['Int', 'String', 'Bool']: + cil_type.attributes.append('value') + + #----------------Object--------------------- + #init + self.current_function = self.register_function('Object_init') + self.register_param(VariableInfo('self', None)) + self.register_instruction(CIL_AST.Return(None)) + + #abort + self.current_function = self.register_function(self.to_function_name('abort', 'Object')) + self.register_param(VariableInfo('self',None)) + msg = self.define_internal_local(scope=scope, name="msg") + key_msg = '' + for s in self.dotdata.keys(): + if self.dotdata[s] == 'Abort called from class ': + key_msg = s + self.register_instruction(CIL_AST.LoadStr(key_msg, msg)) + self.register_instruction(CIL_AST.PrintString(msg)) + type_name = self.define_internal_local(scope=scope, name = "type_name" ) + self.register_instruction(CIL_AST.TypeOf('self', type_name)) + self.register_instruction(CIL_AST.PrintString(type_name)) + eol_local = self.define_internal_local(scope=scope, name="eol") + for s in self.dotdata.keys(): + if self.dotdata[s] == '\n': + eol = s + self.register_instruction(CIL_AST.LoadStr(eol, eol_local)) + self.register_instruction(CIL_AST.PrintString(eol_local)) + self.register_instruction(CIL_AST.Halt()) + + #type_name + self.current_function = self.register_function(self.to_function_name('type_name', 'Object')) + self.register_param(VariableInfo('self', None)) + type_name = self.define_internal_local(scope=scope, name = "type_name" ) + self.register_instruction(CIL_AST.TypeOf('self', type_name)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('String',self.context.get_type('String').tag ,instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'String_init', [CIL_AST.Arg(type_name),CIL_AST.Arg(instance)],"String")) + self.register_instruction(CIL_AST.Return(instance)) + + #copy + self.current_function = self.register_function(self.to_function_name('copy', 'Object')) + self.register_param(VariableInfo('self',None)) + copy = self.define_internal_local(scope=scope, name= "copy") + self.register_instruction(CIL_AST.Copy('self', copy)) + self.register_instruction(CIL_AST.Return(copy)) + + #----------------IO--------------------- + #init + self.current_function = self.register_function('IO_init') + self.register_param(VariableInfo('self', None)) + self.register_instruction(CIL_AST.Return(None)) + + #out_string + self.current_function = self.register_function(self.to_function_name('out_string', 'IO')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('x', None)) + v = self.define_internal_local(scope=scope, name="v") + self.register_instruction(CIL_AST.GetAttr(v, 'x','value','String')) + self.register_instruction(CIL_AST.PrintString(v)) + self.register_instruction(CIL_AST.Return('self')) + + #out_int + self.current_function = self.register_function(self.to_function_name('out_int', 'IO')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('x', None)) + v = self.define_internal_local(scope=scope, name="v") + self.register_instruction(CIL_AST.GetAttr(v, 'x','value','Int')) + self.register_instruction(CIL_AST.PrintInteger(v)) + self.register_instruction(CIL_AST.Return('self')) + + #in_string + self.current_function = self.register_function(self.to_function_name('in_string', 'IO')) + self.register_param(VariableInfo('self', None)) + msg = self.define_internal_local(scope=scope, name="read_str") + self.register_instruction(CIL_AST.ReadString(msg)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('String',self.context.get_type('String').tag ,instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'String_init', [CIL_AST.Arg(msg),CIL_AST.Arg(instance)],"String")) + self.register_instruction(CIL_AST.Return(instance)) + + #in_int + self.current_function = self.register_function(self.to_function_name('in_int', 'IO')) + self.register_param(VariableInfo('self', None)) + number = self.define_internal_local(scope=scope, name ="read_int") + self.register_instruction(CIL_AST.ReadInteger(number)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('Int', self.context.get_type('Int').tag,instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Int_init', [ CIL_AST.Arg(number), CIL_AST.Arg(instance)], "Int")) + self.register_instruction(CIL_AST.Return(instance)) + + # ----------------String--------------------- + #init + self.current_function=self.register_function('String_init') + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('v', None)) + self.register_instruction(CIL_AST.SetAttr('self', 'value', 'v', 'String')) + self.register_instruction(CIL_AST.Return(None)) + + #length + self.current_function = self.register_function(self.to_function_name('length', 'String')) + self.register_param(VariableInfo('self', None)) + length_result = self.define_internal_local(scope=scope, name="length") + self.register_instruction(CIL_AST.Length('self', length_result)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('Int', self.context.get_type('Int').tag,instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init,'Int_init', [CIL_AST.Arg(length_result),CIL_AST.Arg(instance)], "Int")) + self.register_instruction(CIL_AST.Return(instance)) + + #concat + self.current_function = self.register_function(self.to_function_name('concat', 'String')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('s', None)) + + str1 = self.define_internal_local(scope=scope, name="str1") + self.register_instruction(CIL_AST.GetAttr(str1, 'self','value','String')) + len1 = self.define_internal_local(scope=scope, name="len1") + self.register_instruction(CIL_AST.Call(len1, 'String.length', [CIL_AST.Arg('self')], 'String')) + + str2 = self.define_internal_local(scope=scope, name="str2") + self.register_instruction(CIL_AST.GetAttr(str2, 's', 'value', 'String')) + len2 = self.define_internal_local(scope=scope, name="len2") + self.register_instruction(CIL_AST.Call(len2, 'String.length', [CIL_AST.Arg('s')], 'String')) + + local_len1 = self.define_internal_local(scope=scope, name="local_len1") + self.register_instruction(CIL_AST.GetAttr(local_len1, len1, 'value', 'Int')) + local_len2 = self.define_internal_local(scope=scope, name="local_len2") + self.register_instruction(CIL_AST.GetAttr(local_len2, len2, 'value', 'Int')) + + concat_result = self.define_internal_local(scope=scope, name="concat") + self.register_instruction(CIL_AST.Concat(str1, local_len1, str2, local_len2, concat_result)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('String',self.context.get_type('String').tag ,instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'String_init', [CIL_AST.Arg(concat_result), CIL_AST.Arg(instance)],"String")) + self.register_instruction(CIL_AST.Return(instance)) + + + #substr + self.current_function = self.register_function(self.to_function_name('substr', 'String')) + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('i', None)) + self.register_param(VariableInfo('l', None)) + i_value=self.define_internal_local(scope=scope, name="i_value") + self.register_instruction(CIL_AST.GetAttr(i_value, 'i','value','Int')) + l_value = self.define_internal_local(scope=scope, name="l_value") + self.register_instruction(CIL_AST.GetAttr(l_value, 'l','value','Int')) + subs_result=self.define_internal_local(scope=scope, name="subs_result") + self.register_instruction(CIL_AST.SubStr(i_value, l_value, 'self', subs_result)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('String', self.context.get_type('String').tag,instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'String_init', [CIL_AST.Arg(subs_result),CIL_AST.Arg(instance)],"String")) + self.register_instruction(CIL_AST.Return(instance)) + + #----------------Bool--------------------- + #init + self.current_function=self.register_function('Bool_init') + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('v', None)) + self.register_instruction(CIL_AST.SetAttr('self', 'value', 'v', 'Bool')) + self.register_instruction(CIL_AST.Return(None)) + + #----------------Int--------------------- + #init + self.current_function=self.register_function('Int_init') + self.register_param(VariableInfo('self', None)) + self.register_param(VariableInfo('v', None)) + self.register_instruction(CIL_AST.SetAttr('self', 'value', 'v', 'Int')) + self.register_instruction(CIL_AST.Return(None)) + + def build_string_equals_function(self, scope): + self.current_function = self.register_function('String_equals') + self.register_param(VariableInfo('str1', None)) + self.register_param(VariableInfo('str2', None)) + + str1 = self.define_internal_local(scope=scope, name="str1") + self.register_instruction(CIL_AST.GetAttr(str1, 'str1', 'value','String')) + + str2 = self.define_internal_local(scope=scope, name="str2") + self.register_instruction(CIL_AST.GetAttr(str2, 'str2', 'value', 'String')) + + result = self.define_internal_local(scope=scope, name="comparison_result") + self.register_instruction(CIL_AST.StringEquals(str1, str2, result)) + self.register_instruction(CIL_AST.Return(result)) + +class COOLToCILVisitor(BaseCOOLToCILVisitor): + @visitor.on('node') + def visit(self, node, scope): + pass + + @visitor.when(COOL_AST.Program) + def visit(self, node, scope = None): + + scope = Scope() + self.current_function = self.register_function('main') + instance = self.define_internal_local(scope = scope, name = "instance") + result = self.define_internal_local(scope = scope, name = "result") + self.register_instruction(CIL_AST.Allocate('Main',self.context.get_type('Main').tag, instance)) + self.register_instruction(CIL_AST.Call(result, 'Main_init', [CIL_AST.Arg(instance)],"Main")) + self.register_instruction(CIL_AST.Call(result, self.to_function_name('main', 'Main'), [CIL_AST.Arg(instance)],"Main")) + self.register_instruction(CIL_AST.Return(None)) + self.current_function = None + + self.register_data('Abort called from class ') + self.register_data('\n') + self.dotdata['empty_str'] = '' + + #Add built-in types in .TYPES section + self.register_builtin_types(scope) + + #Add string equals function + self.build_string_equals_function(scope) + + for klass in node.classes: + self.visit(klass, scope.create_child()) + + return CIL_AST.Program(self.dottypes, self.dotdata, self.dotcode) + + @visitor.when(COOL_AST.Class) + def visit(self, node, scope): + self.current_type = self.context.get_type(node.name) + + #Handle all the .TYPE section + cil_type = self.register_type(self.current_type.name) + cil_type.attributes = [f'{attr.name}' for c, attr in self.current_type.get_all_attributes()] + cil_type.methods = {f'{m}':f'{c}.{m}' for c, m in self.current_type.get_all_methods()} + + scope.define_cil_local("self", self.current_type.name, self.current_type) + + func_declarations = [f for f in node.features if isinstance(f, COOL_AST.ClassMethod)] + attr_declarations = [a for a in node.features if not isinstance(a, COOL_AST.ClassMethod)] + for attr in attr_declarations: + scope.define_cil_local(attr.name, attr.name, node.name) + + + #-------------------------Init--------------------------------- + self.current_function = self.register_function(f'{node.name}_init') + self.register_param(VariableInfo('self', None)) + + #Init parents recursively + result = self.define_internal_local(scope=scope, name = "result") + self.register_instruction(CIL_AST.Call(result, f'{node.parent}_init',[CIL_AST.Arg('self')], node.parent)) + self.register_instruction(CIL_AST.Return(None)) + + for attr in attr_declarations: + self.visit(attr, scope) + #--------------------------------------------------------------- + self.current_function = None + + for feature in func_declarations: + self.visit(feature, scope.create_child()) + + self.current_type = None + + @visitor.when(COOL_AST.ClassMethod) + def visit(self, node, scope): + self.current_method = self.current_type.get_method(node.name) + self.dottypes[self.current_type.name].methods[node.name] = f'{self.current_type.name}.{node.name}' + cil_method_name = self.to_function_name(node.name, self.current_type.name) + self.current_function = self.register_function(cil_method_name) + + self.register_param(VariableInfo('self', self.current_type)) + for p in node.params: + self.register_param(VariableInfo(p.name, p.param_type)) + + value = self.visit(node.expr, scope) + + self.register_instruction(CIL_AST.Return(value)) + self.current_method = None + + @visitor.when(COOL_AST.AttributeDef) + def visit(self, node, scope): + instance = None + + if node.type in ['Int', 'Bool']: + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate(node.type,self.context.get_type(node.type).tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(CIL_AST.LoadInt(0,value)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, f'{node.type}_init', [ CIL_AST.Arg(value), CIL_AST.Arg(instance)], node.type)) + elif node.type == 'String': + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate(node.type,self.context.get_type(node.type).tag ,instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(CIL_AST.LoadStr('empty_str',value)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, f'{node.type}_init', [CIL_AST.Arg(value),CIL_AST.Arg(instance)], node.type)) + + self.register_instruction(CIL_AST.SetAttr('self', node.name,instance, self.current_type.name)) + + @visitor.when(COOL_AST.AttributeInit) + def visit(self, node, scope): + expr = self.visit(node.expr, scope) + self.register_instruction(CIL_AST.SetAttr('self', node.name, expr, self.current_type.name)) + + @visitor.when(COOL_AST.AssignExpr) + def visit(self, node, scope): + expr_local = self.visit(node.expr, scope) + result_local = self.define_internal_local(scope=scope, name = "result" ) + cil_node_name = scope.find_cil_local(node.name) + + if self.is_defined_param(node.name): + self.register_instruction(CIL_AST.Assign(node.name, expr_local)) + elif self.current_type.has_attr(node.name): + cil_type_name = 'self' + self.register_instruction(CIL_AST.SetAttr(cil_type_name, node.name, expr_local, self.current_type.name )) + else: + self.register_instruction(CIL_AST.Assign(cil_node_name, expr_local)) + return expr_local + + @visitor.when(COOL_AST.Block) + def visit(self, node, scope): + for e in node.exprs: + result_local = self.visit(e, scope) + return result_local + + @visitor.when(COOL_AST.If) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + + cond_value = self.visit(node.predicate, scope) + + if_then_label = self.get_label() + self.register_instruction(CIL_AST.IfGoto(cond_value, if_then_label)) + + else_value = self.visit(node.else_body, scope) + self.register_instruction(CIL_AST.Assign(result_local, else_value)) + + end_if_label = self.get_label() + self.register_instruction(CIL_AST.Goto(end_if_label)) + + self.register_instruction(CIL_AST.Label(if_then_label)) + then_value = self.visit(node.then_body, scope) + self.register_instruction(CIL_AST.Assign(result_local, then_value)) + self.register_instruction(CIL_AST.Label(end_if_label)) + + return result_local + + @visitor.when(COOL_AST.While) + def visit(self, node, scope): + result_local = self.define_internal_local(scope = scope, name = "result") + + loop_init_label = self.get_label() + loop_body_label = self.get_label() + loop_end_label = self.get_label() + self.register_instruction(CIL_AST.Label(loop_init_label)) + pred_value = self.visit(node.predicate, scope) + self.register_instruction(CIL_AST.IfGoto(pred_value, loop_body_label)) + self.register_instruction(CIL_AST.Goto(loop_end_label)) + + self.register_instruction(CIL_AST.Label(loop_body_label)) + body_value = self.visit(node.body, scope) + self.register_instruction(CIL_AST.Goto(loop_init_label)) + self.register_instruction(CIL_AST.Label(loop_end_label)) + + self.register_instruction(CIL_AST.LoadVoid(result_local)) + return result_local + + @visitor.when(COOL_AST.DynamicCall) + def visit(self, node, scope): + result_local = self.define_internal_local(scope = scope, name = "result") + expr_value = self.visit(node.instance, scope) + + call_args = [] + for arg in reversed(node.args): + param_local = self.visit(arg, scope) + call_args.append(CIL_AST.Arg(param_local)) + call_args.append(CIL_AST.Arg(expr_value)) + + dynamic_type = node.instance.computed_type.name + self.register_instruction(CIL_AST.VCall(result_local, node.method, call_args, dynamic_type, expr_value)) + + return result_local + + @visitor.when(COOL_AST.StaticCall) + def visit(self, node, scope): + result_local = self.define_internal_local(scope = scope, name = "result") + expr_value = self.visit(node.instance, scope) + + call_args = [] + for arg in reversed(node.args): + param_local = self.visit(arg, scope) + call_args.append(CIL_AST.Arg(param_local)) + call_args.append(CIL_AST.Arg(expr_value)) + + static_instance = self.define_internal_local(scope=scope, name='static_instance') + self.register_instruction(CIL_AST.Allocate(node.static_type,self.context.get_type(node.static_type).tag ,static_instance)) + + self.register_instruction(CIL_AST.VCall(result_local, node.method, call_args, node.static_type, static_instance)) + return result_local + + @visitor.when(COOL_AST.Let) + def visit(self, node, scope): + let_scope = scope.create_child() + for var in node.var_list: + self.visit(var, let_scope) + + body_value = self.visit(node.body, let_scope) + result_local = self.define_internal_local(scope = scope, name = "let_result") + self.register_instruction(CIL_AST.Assign(result_local, body_value)) + return result_local + + @visitor.when(COOL_AST.LetVarInit) + def visit(self, node, scope): + expr_value = self.visit(node.expr, scope) + var_init = self.define_internal_local(scope = scope, name = node.name, cool_var_name= node.name) + self.register_instruction(CIL_AST.Assign(var_init, expr_value)) + return var_init + + @visitor.when(COOL_AST.LetVarDef) + def visit(self, node, scope): + instance = None + + if node.type in ['Int', 'Bool']: + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate(node.type,self.context.get_type(node.type).tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(CIL_AST.LoadInt(0,value)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, f'{node.type}_init', [ CIL_AST.Arg(value), CIL_AST.Arg(instance)], node.type)) + elif node.type == 'String': + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate(node.type,self.context.get_type(node.type).tag ,instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(CIL_AST.LoadStr('empty_str',value)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, f'{node.type}_init', [CIL_AST.Arg(value), CIL_AST.Arg(instance)], node.type)) + + var_def = self.define_internal_local(scope = scope, name = node.name, cool_var_name=node.name) + self.register_instruction(CIL_AST.Assign(var_def, instance)) + return var_def + + + @visitor.when(COOL_AST.Case) + def visit(self, node, scope): + result_local = self.define_internal_local(scope = scope, name = "result") + case_expr = self.visit(node.expr, scope) + + exit_label = self.get_label() + label = self.get_label() + + self.register_instruction(CIL_AST.Case(case_expr, label)) + + tag_lst = [] + action_dict = {} + for action in node.actions: + tag = self.context.get_type(action.action_type).tag + tag_lst.append(tag) + action_dict[tag] = action + tag_lst.sort() + + for t in reversed(tag_lst): + action = action_dict[t] + self.register_instruction(CIL_AST.Label(label)) + label = self.get_label() + + action_type = self.context.get_type(action.action_type) + self.register_instruction(CIL_AST.Action(case_expr, action_type.tag, action_type.max_tag, label)) + + action_scope = scope.create_child() + action_id = self.define_internal_local(scope=action_scope, name=action.name, cool_var_name=action.name) + self.register_instruction(CIL_AST.Assign(action_id, case_expr)) + expr_result = self.visit(action.body, action_scope) + + self.register_instruction(CIL_AST.Assign(result_local, expr_result)) + self.register_instruction(CIL_AST.Goto(exit_label)) + + self.register_instruction(CIL_AST.Label(label)) + self.register_instruction(CIL_AST.Goto('case_no_match_error')) + self.register_instruction(CIL_AST.Label(exit_label)) + return result_local + + @visitor.when(COOL_AST.Action) + def visit(self, node, scope): + pass + + @visitor.when(COOL_AST.NewType) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name="result") + result_init = self.define_internal_local(scope=scope, name="init") + + if node.type == "SELF_TYPE": + self.register_instruction(CIL_AST.Allocate(self.current_type.name,self.current_type.tag ,result_local)) + self.register_instruction(CIL_AST.Call(result_init, f'{self.current_type.name}_init', [result_local], self.current_type.name)) + else: + self.register_instruction(CIL_AST.Allocate(node.type,self.context.get_type(node.type).tag ,result_local)) + self.register_instruction(CIL_AST.Call(result_init,f'{node.type}_init' ,[CIL_AST.Arg(result_local)], self.current_type.name )) + + return result_local + + @visitor.when(COOL_AST.IsVoid) + def visit(self, node, scope): + expre_value = self.visit(node.expr, scope) + result_local = self.define_internal_local(scope=scope, name ="isvoid_result") + self.register_instruction(CIL_AST.IsVoid(result_local, expre_value)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('Bool',self.context.get_type('Bool').tag, instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Bool_init', [CIL_AST.Arg(result_local),CIL_AST.Arg(instance)], "Bool")) + return instance + + @visitor.when(COOL_AST.Sum) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + left_local = self.define_internal_local(scope=scope, name = "left") + right_local = self.define_internal_local(scope=scope, name = "right") + + left_value = self.visit(node.left, scope) + right_value = self.visit(node.right, scope) + + self.register_instruction(CIL_AST.GetAttr(left_local, left_value, "value", node.left.computed_type.name)) + self.register_instruction(CIL_AST.GetAttr(right_local, right_value, "value", node.right.computed_type.name)) + + self.register_instruction(CIL_AST.BinaryOperator(op_local, left_local, right_local, "+")) + + # Allocate Int result + self.register_instruction(CIL_AST.Allocate('Int',self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Int_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Int")) + + return result_local + + @visitor.when(COOL_AST.Sub) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + left_local = self.define_internal_local(scope=scope, name = "left") + right_local = self.define_internal_local(scope=scope, name = "right") + + left_value = self.visit(node.left, scope) + right_value = self.visit(node.right, scope) + + self.register_instruction(CIL_AST.GetAttr(left_local, left_value, "value", node.left.computed_type.name)) + self.register_instruction(CIL_AST.GetAttr(right_local, right_value, "value", node.right.computed_type.name)) + + self.register_instruction(CIL_AST.BinaryOperator(op_local, left_local, right_local, "-")) + + # Allocate Int result + self.register_instruction(CIL_AST.Allocate('Int',self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Int_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Int")) + + return result_local + + @visitor.when(COOL_AST.Mult) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + left_local = self.define_internal_local(scope=scope, name = "left") + right_local = self.define_internal_local(scope=scope, name = "right") + + left_value = self.visit(node.left, scope) + right_value = self.visit(node.right, scope) + + self.register_instruction(CIL_AST.GetAttr(left_local, left_value, "value", node.left.computed_type.name)) + self.register_instruction(CIL_AST.GetAttr(right_local, right_value, "value", node.right.computed_type.name)) + + self.register_instruction(CIL_AST.BinaryOperator(op_local, left_local, right_local, "*")) + + # Allocate Int result + self.register_instruction(CIL_AST.Allocate('Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Int_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Int")) + + return result_local + + @visitor.when(COOL_AST.Div) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + left_local = self.define_internal_local(scope=scope, name = "left") + right_local = self.define_internal_local(scope=scope, name = "right") + + left_value = self.visit(node.left, scope) + right_value = self.visit(node.right, scope) + + self.register_instruction(CIL_AST.GetAttr(left_local, left_value, "value", node.left.computed_type.name)) + self.register_instruction(CIL_AST.GetAttr(right_local, right_value, "value", node.right.computed_type.name)) + + self.register_instruction(CIL_AST.BinaryOperator(op_local, left_local, right_local, "/")) + + # Allocate Int result + self.register_instruction(CIL_AST.Allocate('Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Int_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Int")) + + return result_local + + @visitor.when(COOL_AST.LogicalNot) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + expr_local = self.define_internal_local(scope=scope) + + expr_value = self.visit(node.expr, scope) + + self.register_instruction(CIL_AST.GetAttr(expr_local, expr_value, "value", node.expr.computed_type.name)) + self.register_instruction(CIL_AST.UnaryOperator(op_local, expr_local, "~")) + + # Allocate Int result + self.register_instruction(CIL_AST.Allocate('Int', self.context.get_type('Int').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Int_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Int")) + + return result_local + + @visitor.when(COOL_AST.Not) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + expr_local = self.define_internal_local(scope=scope) + + expr_value = self.visit(node.expr, scope) + + self.register_instruction(CIL_AST.GetAttr(expr_local, expr_value, "value", node.expr.computed_type.name)) + self.register_instruction(CIL_AST.UnaryOperator(op_local, expr_local, "not")) + + # Allocate Bool result + self.register_instruction(CIL_AST.Allocate('Bool',self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Bool_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Bool")) + + return result_local + + @visitor.when(COOL_AST.LessThan) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + left_local = self.define_internal_local(scope=scope, name = "left") + right_local = self.define_internal_local(scope=scope, name = "right") + + left_value = self.visit(node.left, scope) + right_value = self.visit(node.right, scope) + + self.register_instruction(CIL_AST.GetAttr(left_local, left_value, "value", node.left.computed_type.name)) + self.register_instruction(CIL_AST.GetAttr(right_local, right_value, "value", node.right.computed_type.name)) + + self.register_instruction(CIL_AST.BinaryOperator(op_local, left_local, right_local, "<")) + + # Allocate Bool result + self.register_instruction(CIL_AST.Allocate('Bool',self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Bool_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Bool")) + + return result_local + + @visitor.when(COOL_AST.LessOrEqualThan) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + left_local = self.define_internal_local(scope=scope, name = "left") + right_local = self.define_internal_local(scope=scope, name = "right") + + left_value = self.visit(node.left, scope) + right_value = self.visit(node.right, scope) + + self.register_instruction(CIL_AST.GetAttr(left_local, left_value, "value", node.left.computed_type.name)) + self.register_instruction(CIL_AST.GetAttr(right_local, right_value, "value", node.right.computed_type.name)) + + self.register_instruction(CIL_AST.BinaryOperator(op_local, left_local, right_local, "<=")) + + # Allocate Bool result + self.register_instruction(CIL_AST.Allocate('Bool',self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Bool_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Bool")) + + return result_local + + @visitor.when(COOL_AST.Equals) + def visit(self, node, scope): + result_local = self.define_internal_local(scope=scope, name = "result") + op_local = self.define_internal_local(scope=scope, name = "op") + left_local = self.define_internal_local(scope=scope, name = "left") + right_local = self.define_internal_local(scope=scope, name = "right") + + left_value = self.visit(node.left, scope) + right_value = self.visit(node.right, scope) + + if node.left.computed_type.name == 'String': + self.register_instruction(CIL_AST.Call(op_local, 'String_equals', [CIL_AST.Arg(right_value), CIL_AST.Arg(left_value)], 'String')) + + # Allocate Bool result + self.register_instruction(CIL_AST.Allocate('Bool',self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Bool_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Bool")) + + return result_local + + elif node.left.computed_type.name in ['Int', 'Bool']: + self.register_instruction(CIL_AST.GetAttr(left_local, left_value, "value", node.left.computed_type.name)) + self.register_instruction(CIL_AST.GetAttr(right_local, right_value, "value", node.right.computed_type.name)) + else: + self.register_instruction(CIL_AST.Assign(left_local, left_value)) + self.register_instruction(CIL_AST.Assign(right_local, right_value)) + + self.register_instruction(CIL_AST.BinaryOperator(op_local, left_local, right_local, "=")) + + # Allocate Bool result + self.register_instruction(CIL_AST.Allocate('Bool',self.context.get_type('Bool').tag, result_local)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Bool_init', [CIL_AST.Arg(op_local), CIL_AST.Arg(result_local)], "Bool")) + + return result_local + + @visitor.when(COOL_AST.Identifier) + def visit(self, node, scope): + if self.is_defined_param(node.name): + return node.name + elif self.current_type.has_attr(node.name): + result_local = self.define_internal_local(scope=scope, name = node.name, class_type=self.current_type.name) + self.register_instruction(CIL_AST.GetAttr(result_local, 'self' , node.name, self.current_type.name)) + return result_local + else: + return scope.find_cil_local(node.name) + + @visitor.when(COOL_AST.INTEGER) + def visit(self, node, scope): + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('Int',self.context.get_type('Int').tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(CIL_AST.LoadInt(node.value,value)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Int_init', [CIL_AST.Arg(value),CIL_AST.Arg(instance)], "Int")) + return instance + + @visitor.when(COOL_AST.STRING) + def visit(self, node, scope): + str_name = "" + for s in self.dotdata.keys(): + if self.dotdata[s] == node.value: + str_name = s + break + if str_name == "": + str_name = self.register_data(node.value) + + result_local = self.define_internal_local(scope=scope) + self.register_instruction(CIL_AST.LoadStr(str_name, result_local)) + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('String',self.context.get_type('String').tag, instance)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'String_init', [CIL_AST.Arg(result_local),CIL_AST.Arg(instance)], "String")) + return instance + + @visitor.when(COOL_AST.Boolean) + def visit(self, node, scope): + boolean = 0 + if str(node.value) == "true": + boolean = 1 + instance = self.define_internal_local(scope=scope, name="instance") + self.register_instruction(CIL_AST.Allocate('Bool',self.context.get_type('Bool').tag, instance)) + value = self.define_internal_local(scope=scope, name="value") + self.register_instruction(CIL_AST.LoadInt(boolean, value)) + result_init = self.define_internal_local(scope=scope, name="result_init") + self.register_instruction(CIL_AST.Call(result_init, 'Bool_init', [CIL_AST.Arg(value),CIL_AST.Arg(instance)], "Bool")) + return instance + +if __name__ == '__main__': + import sys + from cparser import Parser + from semantic_analyzer import SemanticAnalyzer + + parser = Parser() + + if len(sys.argv) > 1: + + input_file = sys.argv[1] + with open(input_file, encoding="utf-8") as file: + cool_program_code = file.read() + + cool_ast = parser.parse(cool_program_code) + + if parser.errors: + print(parser.errors) + + if parser.errors: + exit(1) + + semantic_analyzer = SemanticAnalyzer(cool_ast) + context, scope = semantic_analyzer.analyze() + + for e in semantic_analyzer.errors: + print(e) + + if semantic_analyzer.errors: + exit(1) + + cool_to_cil = COOLToCILVisitor(context) + cil_ast = cool_to_cil.visit(cool_ast) + + formatter = CIL_AST.get_formatter() + print(formatter(cil_ast)) + + # with open(f'{sys.argv[1][:-3]}.cil', 'w') as f: + # f.write(f'{cil_ast}') + + diff --git a/src/coolc.sh b/src/coolc.sh index 3088de4f..d599922c 100755 --- a/src/coolc.sh +++ b/src/coolc.sh @@ -4,8 +4,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 "Cool Compiler version 1.0" # TODO: Recuerde cambiar estas +echo "Copyright (c) 2020: Dalianys Perez, Dayany Alfaro, Gilberto Gonzalez" # TODO: líneas a los valores correctos # Llamar al compilador -echo "Compiling $INPUT_FILE into $OUTPUT_FILE" +python cil_to_mips.py $INPUT_FILE \ No newline at end of file diff --git a/src/cparser.py b/src/cparser.py new file mode 100644 index 00000000..82ed5c88 --- /dev/null +++ b/src/cparser.py @@ -0,0 +1,532 @@ +import ply.yacc as yacc +import ast_nodes as AST +import lexer +from lexer import Lexer + + +class ErrorParser(): + def __init__(self, message, line, column): + self.type = 'SyntacticError' + self.value = message + self.line = line + self.column = column + + def __str__(self): + return f'({self.line}, {self.column}) - {self.type}: ERROR at or near {self.value}' + + def __repr__(self): + return str(self) + + +class Parser(): + def __init__(self, + build_parser=True, + debug=False, + write_tables=True, + optimize=True, + outputdir="", + yacctab="pycoolc.yacctab", + debuglog=None, + errorlog=None, + tracking =True): + """ + Initializer. + :param debug: Debug mode flag. + :param optimize: Optimize mode flag. + :param outputdir: Output directory of parser output; by default the .out file goes in the same directory. + :param debuglog: Debug log file path; by default parser prints to stderr. + :param errorlog: Error log file path; by default parser print to stderr. + :param build_parser: If this is set to True the internal parser will be built right after initialization, + which makes it convenient for direct use. If it's set to False, then an empty parser instance will be + initialized and the parser object will have to be built by calling parser.build() after initialization. + + Example: + parser = PyCoolParser(build_parser=False) + ... + parser.build() + parser.parse(...) + ... + + :return: None + """ + # Initialize self.parser and self.tokens to None + self.tokens = None + self.lexer = None + self.parser = None + self.errors = [] + + # Save Flags - PRIVATE PROPERTIES + self._debug = debug + self._write_tables = write_tables + self._optimize = optimize + self._outputdir = outputdir + self._yacctab = yacctab + self._debuglog = debuglog + self._errorlog = errorlog + + # Build parser if build_parser flag is set to True + if build_parser is True: + self.build(debug=debug, write_tables=write_tables, optimize=optimize, outputdir=outputdir, + yacctab=yacctab, debuglog=debuglog, errorlog=errorlog) + + # ################################# PRIVATE ######################################## + + # ################################ PRECEDENCE RULES ################################ + + # precedence rules + precedence = ( + ('right', 'ASSIGN'), + ('right', 'NOT'), + ('nonassoc', 'LTEQ', 'LT', 'EQ'), + ('left', 'PLUS', 'MINUS'), + ('left', 'MULTIPLY', 'DIVIDE'), + ('right', 'ISVOID'), + ('right', 'INT_COMP'), + ('left', 'AT'), + ('left', 'DOT') + ) + + # ################### START OF FORMAL GRAMMAR RULES DECLARATION #################### + + def p_program(self, parse): + """ + program : class_list + """ + parse[0] = AST.Program(classes=parse[1]) + + def p_class_list(self, parse): + """ + class_list : class_list class SEMICOLON + | class SEMICOLON + """ + if len(parse) == 3: + parse[0] = (parse[1],) + else: + parse[0] = parse[1] + (parse[2],) + + def p_class(self, parse): + """ + class : CLASS TYPE LBRACE features_list_opt RBRACE + """ + parse[0] = AST.Class(name=parse[2], parent="Object", features=parse[4], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + + + def p_class_inherits(self, parse): + """ + class : CLASS TYPE INHERITS TYPE LBRACE features_list_opt RBRACE + """ + parse[0] = AST.Class(name=parse[2], parent=parse[4], features=parse[6], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(4)) ) + + def p_feature_list_opt(self, parse): + """ + features_list_opt : features_list + | empty + """ + parse[0] = tuple() if parse.slice[1].type == "empty" else parse[1] + + def p_feature_list(self, parse): + """ + features_list : features_list feature SEMICOLON + | feature SEMICOLON + """ + if len(parse) == 3: + parse[0] = (parse[1],) + else: + parse[0] = parse[1] + (parse[2],) + + def p_feature_method(self, parse): + """ + feature : ID LPAREN formal_params_list RPAREN COLON TYPE LBRACE expression RBRACE + """ + parse[0] = AST.ClassMethod( + name=parse[1], params=parse[3], return_type=parse[6], expr=parse[8], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_feature_method_no_formals(self, parse): + """ + feature : ID LPAREN RPAREN COLON TYPE LBRACE expression RBRACE + """ + parse[0] = AST.ClassMethod( + name=parse[1], params=tuple(), return_type=parse[5], expr=parse[7], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_feature_attr_initialized(self, parse): + """ + feature : attribute_init + """ + parse[0] = parse[1] + + def p_atrribute_init(self, parse): + """ + attribute_init : ID COLON TYPE ASSIGN expression + | attribute_def + """ + if len(parse) == 2: + parse[0] = parse[1] + else: + parse[0] = AST.AttributeInit( + name=parse[1], Type=parse[3], expr=parse[5], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_feature_attr(self, parse): + """ + attribute_def : ID COLON TYPE + """ + parse[0] = AST.AttributeDef(name=parse[1], Type=parse[3], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_formal_list_many(self, parse): + """ + formal_params_list : formal_params_list COMMA formal_param + | formal_param + """ + if len(parse) == 2: + parse[0] = (parse[1],) + else: + parse[0] = parse[1] + (parse[3],) + + def p_formal_param(self, parse): + """ + formal_param : ID COLON TYPE + """ + parse[0] = AST.FormalParameter(name=parse[1], param_type=parse[3], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_expression_object_identifier(self, parse): + """ + expression : ID + """ + parse[0] = AST.Identifier(name=parse[1], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_expression_integer_constant(self, parse): + """ + expression : INTEGER + """ + parse[0] = AST.INTEGER(value=parse[1], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_expression_boolean_constant(self, parse): + """ + expression : TRUE + | FALSE + """ + parse[0] = AST.Boolean(value=parse[1], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_expression_string_constant(self, parse): + """ + expression : STRING + """ + parse[0] = AST.STRING(value=parse[1], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_expression_block(self, parse): + """ + expression : LBRACE block_list RBRACE + """ + parse[0] = AST.Block(exprs=parse[2], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_block_list(self, parse): + """ + block_list : block_list expression SEMICOLON + | expression SEMICOLON + """ + if len(parse) == 3: + parse[0] = (parse[1],) + else: + parse[0] = parse[1] + (parse[2],) + + def p_expression_assignment(self, parse): + """ + expression : ID ASSIGN expression + """ + parse[0] = AST.AssignExpr(instance=parse[1], expr=parse[3], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + # ######################### METHODS DISPATCH ###################################### + + def p_expression_dispatch(self, parse): + """ + expression : expression DOT ID LPAREN arguments_list_opt RPAREN + """ + parse[0] = AST.DynamicCall( + instance=parse[1], method=parse[3], args=parse[5], line=parse.lineno(3), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(3))) + + def p_arguments_list_opt(self, parse): + """ + arguments_list_opt : arguments_list + | empty + """ + parse[0] = tuple() if parse.slice[1].type == "empty" else parse[1] + + def p_arguments_list(self, parse): + """ + arguments_list : arguments_list COMMA expression + | expression + """ + if len(parse) == 2: + parse[0] = (parse[1],) + else: + parse[0] = parse[1] + (parse[3],) + + def p_expression_static_dispatch(self, parse): + """ + expression : expression AT TYPE DOT ID LPAREN arguments_list_opt RPAREN + """ + parse[0] = AST.StaticCall( + instance=parse[1], static_type=parse[3], method=parse[5], args=parse[7], line=parse.lineno(5), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(5))) + + def p_expression_self_dispatch(self, parse): + """ + expression : ID LPAREN arguments_list_opt RPAREN + """ + parse[0] = AST.DynamicCall( + instance=AST.Identifier('self',line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))), method=parse[1], args=parse[3], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + # ######################### PARENTHESIZED, MATH & COMPARISONS ##################### + + def p_expression_math_operations(self, parse): + """ + expression : expression PLUS expression + | expression MINUS expression + | expression MULTIPLY expression + | expression DIVIDE expression + """ + if parse[2] == '+': + parse[0] = AST.Sum(summand1=parse[1], summand2=parse[3], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + elif parse[2] == '-': + parse[0] = AST.Sub(minuend=parse[1], subtrahend=parse[3], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + elif parse[2] == '*': + parse[0] = AST.Mult(factor1=parse[1], factor2=parse[3], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + elif parse[2] == '/': + parse[0] = AST.Div(dividend=parse[1], divisor=parse[3], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + + def p_expression_math_comparisons(self, parse): + """ + expression : expression LT expression + | expression LTEQ expression + | expression EQ expression + """ + if parse[2] == '<': + parse[0] = AST.LessThan(left=parse[1], right=parse[3], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + elif parse[2] == '<=': + parse[0] = AST.LessOrEqualThan(left=parse[1], right=parse[3], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + elif parse[2] == '=': + parse[0] = AST.Equals(left=parse[1], right=parse[3], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + + def p_expression_with_parenthesis(self, parse): + """ + expression : LPAREN expression RPAREN + """ + parse[0] = parse[2] + + # ######################### CONTROL FLOW EXPRESSIONS ############################## + + def p_expression_if_conditional(self, parse): + """ + expression : IF expression THEN expression ELSE expression FI + """ + parse[0] = AST.If(predicate=parse[2], + then_body=parse[4], else_body=parse[6], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_expression_while_loop(self, parse): + """ + expression : WHILE expression LOOP expression POOL + """ + parse[0] = AST.While(predicate=parse[2], body=parse[4], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + # ######################### LET EXPRESSIONS ######################################## + + def p_expression_let(self, parse): + """ + expression : let_expression + """ + parse[0] = parse[1] + + def p_expression_let_simple(self, parse): + """ + let_expression : LET nested_vars IN expression + """ + parse[0] = AST.Let(var_list=parse[2], body=parse[4], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + + def p_nested_let_vars(self, parse): + """ + nested_vars : let_var_init + | nested_vars COMMA let_var_init + """ + if len(parse) == 2: + parse[0] = (parse[1],) + else: + parse[0] = parse[1] + (parse[3],) + + def p_let_var_initialized(self, parse): + """ + let_var_init : ID COLON TYPE ASSIGN expression + | let_var_def + """ + if len(parse) == 2: + parse[0] = parse[1] + else: + parse[0] = AST.LetVarInit( + name=parse[1], Type=parse[3], expr=parse[5], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + + def p_let_var_def(self, parse): + """ + let_var_def : ID COLON TYPE + """ + parse[0] = AST.LetVarDef(name=parse[1], Type=parse[3], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + # ######################### CASE EXPRESSION ######################################## + + def p_expression_case(self, parse): + """ + expression : CASE expression OF actions_list ESAC + """ + parse[0] = AST.Case(expr=parse[2], actions=parse[4], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_actions_list(self, parse): + """ + actions_list : actions_list action + | action + """ + if len(parse) == 2: + parse[0] = (parse[1],) + else: + parse[0] = tuple(parse[1]) + (parse[2],) + + def p_action_expr(self, parse): + """ + action : ID COLON TYPE ARROW expression SEMICOLON + """ + parse[0] = AST.Action( + name=parse[1], action_type=parse[3], body=parse[5], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + # ######################### UNARY OPERATIONS ####################################### + + def p_expression_new(self, parse): + """ + expression : NEW TYPE + """ + parse[0] = AST.NewType(parse[2], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + + def p_expression_isvoid(self, parse): + """ + expression : ISVOID expression + """ + parse[0] = AST.IsVoid(parse[2], line=parse.lineno(2), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(2))) + + def p_expression_integer_complement(self, parse): + """ + expression : INT_COMP expression + """ + parse[0] = AST.LogicalNot(parse[2], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_expression_boolean_complement(self, parse): + """ + expression : NOT expression + """ + parse[0] = AST.Not(parse[2], line=parse.lineno(1), column=lexer.find_column(parse.lexer.lexdata, parse.lexpos(1))) + + def p_empty(self, parse): + """ + empty : + """ + parse[0] = None + + # ######################### PARSE ERROR HANDLER #################################### + + def p_error(self, parse): + """ + Error rule for Syntax Errors handling and reporting. + """ + if not parse: + error = ErrorParser('"EOF"', 0, 0) + self.errors.append(error) + return + + message = f'"{parse.value}"' + error = ErrorParser(message, parse.lineno, + lexer.find_column(parse.lexer.lexdata, parse.lexpos)) + self.errors.append(error) + self.parser.errok() + + # ################### END OF FORMAL GRAMMAR RULES SPECIFICATION #################### + + # ################################# PUBLIC ####################################### + + def build(self, **kwargs): + """ + Builds the PyCoolParser instance with yaac.yaac() by binding the lexer object and its tokens list in the + current instance scope. + :param kwargs: yaac.yaac() config parameters, complete list: + * debug: Debug mode flag. + * optimize: Optimize mode flag. + * debuglog: Debug log file path; by default parser prints to stderr. + * errorlog: Error log file path; by default parser print to stderr. + * outputdir: Output directory of parsing output; by default the .out file goes in the same directory. + :return: None + """ + # Parse the parameters + if kwargs is None or len(kwargs) == 0: + debug, write_tables, optimize, outputdir, yacctab, debuglog, errorlog = \ + self._debug, self._write_tables, self._optimize, self._outputdir, self._yacctab, self._debuglog, \ + self._errorlog + else: + debug = kwargs.get("debug", self._debug) + write_tables = kwargs.get("write_tables", self._write_tables) + optimize = kwargs.get("optimize", self._optimize) + outputdir = kwargs.get("outputdir", self._outputdir) + yacctab = kwargs.get("yacctab", self._yacctab) + debuglog = kwargs.get("debuglog", self._debuglog) + errorlog = kwargs.get("errorlog", self._errorlog) + + self.lexer = Lexer(debug=debug, optimize=optimize, outputdir=outputdir, debuglog=debuglog, + errorlog=errorlog) + + # Expose tokens collections to this instance scope + self.tokens = self.lexer.tokens + + # Build yacc parser + self.parser = yacc.yacc(module=self, write_tables=write_tables, debug=debug, optimize=optimize, + outputdir=outputdir, tabmodule=yacctab, debuglog=debuglog, errorlog=yacc.NullLogger()) + + def parse(self, program_source_code: str) -> AST.Program: + """ + Parses a COOL program source code passed as a string. + :param program_source_code: string. + :return: Parser output. + """ + if self.parser is None: + raise ValueError( + "Parser was not build, try building it first with the build() method.") + + return self.parser.parse(program_source_code) + + +# ----------------------------------------------------------------------------- +# +# Parser as a Standalone Python Program +# Usage: ./parser.py cool_program.cl +# +# ----------------------------------------------------------------------------- + +if __name__ == '__main__': + import sys + + parser = Parser() + + if len(sys.argv) > 1: + input_file = sys.argv[1] + with open(input_file, encoding="utf-8") as file: + cool_program_code = file.read() + + parse_result = parser.parse(cool_program_code) + + if parser.errors: + print(parser.errors[0]) + exit(1) + + else: + print("cool-compiler-2020 Parser: Interactive Mode.\r\n") + while True: + try: + s = input('COOL >>> ') + except EOFError: + break + if not s: + continue + result = parser.parse(s) + print('\nPRINTING result\n') + if result is not None: + print(result) diff --git a/src/lexer.py b/src/lexer.py new file mode 100644 index 00000000..82516441 --- /dev/null +++ b/src/lexer.py @@ -0,0 +1,376 @@ +#!/usr/bin/env python3 + +import ply.lex as lex +from ply.lex import TOKEN, LexToken + +class ErrorToken(LexToken): + def __init__(self, message, line, column): + super().__init__() + self.type = 'LexicographicError' + self.value = message + self.line = line + self.column = column + + def __str__(self): + return f'({self.line}, {self.column}) - {self.type}: {self.value}' + + def __repr__(self): + return str(self) + + # Compute column. + # input is the input text string + # token is a token instance +def find_column(input, lexpos): + line_start = input.rfind('\n', 0, lexpos) + 1 + return (lexpos - line_start) + 1 + +class Lexer(): + def __init__(self, + debug=False, + lextab="lextab", + optimize=False, + outputdir="", + debuglog=None, + errorlog=None): + + self.build(debug=debug, lextab=lextab, optimize=optimize, outputdir=outputdir, debuglog=debuglog, + errorlog=errorlog) + + @property + def syntax_tokens(self): + """ + COOL Syntax Tokens. + :return: Tuple. + """ + return ( + # Identifiers + "ID", "TYPE", + + # Primitive Types + "INTEGER", "STRING", "BOOLEAN", + + # Literals + "LPAREN", "RPAREN", "LBRACE", "RBRACE", "COLON", "COMMA", "DOT", "SEMICOLON", "AT", + + # Operators + "PLUS", "MINUS", "MULTIPLY", "DIVIDE", "EQ", "LT", "LTEQ", "ASSIGN", "INT_COMP", + + # Special Operators + "ARROW", + + "LexicographicError" + ) + + @property + def keywords(self): + """ + Map of COOL reserved keywords. + :return: dict. + """ + return { + "case": "CASE", + "class": "CLASS", + "else": "ELSE", + "esac": "ESAC", + "fi": "FI", + "if": "IF", + "in": "IN", + "inherits": "INHERITS", + "isvoid": "ISVOID", + "let": "LET", + "loop": "LOOP", + "new": "NEW", + "of": "OF", + "pool": "POOL", + "then": "THEN", + "while": "WHILE", + "false": "FALSE", + "true": "TRUE", + "not": "NOT" + } + + @property + def builtin_types(self): + """ + A map of the built-in types. + :return dict + """ + return { + "Bool": "BOOL_TYPE", + "Int": "INT_TYPE", + "IO": "IO_TYPE", + "Main": "MAIN_TYPE", + "Object": "OBJECT_TYPE", + "String": "STRING_TYPE", + "SELF_TYPE": "SELF_TYPE" + } + + # Ignore rule for single line comments + t_ignore_SINGLE_LINE_COMMENT = r"\-\-[^\n]*" + + # SIMPLE TOKENS + t_LPAREN = r'\(' # ( + t_RPAREN = r'\)' # ) + t_LBRACE = r'\{' # { + t_RBRACE = r'\}' # } + t_COLON = r'\:' # : + t_COMMA = r'\,' # , + t_DOT = r'\.' # . + t_SEMICOLON = r'\;' # ; + t_AT = r'\@' # @ + t_MULTIPLY = r'\*' # * + t_DIVIDE = r'\/' # / + t_PLUS = r'\+' # + + t_MINUS = r'\-' # - + t_INT_COMP = r'~' # ~ + t_LT = r'\<' # < + t_EQ = r'\=' # = + t_LTEQ = r'\<\=' # <= + t_ASSIGN = r'\<\-' # <- + t_ARROW = r'\=\>' # => + + @TOKEN(r"\d+") + def t_INTEGER(self, token): + """ + The Integer Primitive Type Token Rule. + """ + token.value = int(token.value) + return token + + @TOKEN(r"[A-Z][a-zA-Z_0-9]*") + def t_TYPE(self, token): + """ + The Type Token Rule. + """ + if self.keywords.__contains__(str.lower(token.value)): + token.type = self.keywords[str.lower(token.value)] + token.value = str.lower(token.value) + else: + token.type = 'TYPE' + return token + + @TOKEN(r"[a-z][a-zA-Z_0-9]*") + def t_ID(self, token): + """ + The Identifier Token Rule. + """ + # Check for reserved words + value_lower = str.lower(token.value) + if self.keywords.__contains__(value_lower): + token.type = self.keywords[value_lower] + token.value = value_lower + else: + token.type = 'ID' + return token + + @TOKEN(r"\n+") + def t_newline(self, token): + """ + The Newline Token Rule. + """ + token.lexer.lineno += len(token.value) + + # Ignore Whitespace Character Rule + t_ignore = ' \t\r\f' + + # To tokenize input portions who depend on a context/state + # Examples of such inputs are COOL's string literals and multiple line comments + # Strings depends on a previous existences of " char as well as comments with (* + + @property + def states(self): + return ( + ('STRING', 'exclusive'), + ('COMMENT', 'exclusive'), + ) + + # STATE OF STRING RECOGNITION + @TOKEN(r"\"") + def t_start_string_state(self, token): + """ + Starts the recognition of a COOL string literal. + """ + token.lexer.push_state('STRING') + token.lexer.backslashed = False + token.lexer.string = "" + + @TOKEN(r"\n") + def t_STRING_newline(self, token): + token.lexer.lineno += 1 + if not token.lexer.backslashed: + message = 'Unterminated string constant' + column = find_column(token.lexer.lexdata,token.lexpos) + error = ErrorToken(message, token.lineno, column) + self.errors.append(error) + token.lexer.pop_state() + return error + else: + token.lexer.backslashed = False + + @TOKEN(r"\0") + def t_STRING_null(self, token): + message = 'String contains null character' + column = find_column(token.lexer.lexdata,token.lexpos) + error = ErrorToken(message, token.lineno, column) + self.errors.append(error) + + @TOKEN(r"\"") + def t_STRING_end(self, token): + + if not token.lexer.backslashed: + token.lexer.pop_state() + token.value = token.lexer.string + token.type = "STRING" + return token + else: + token.lexer.string += '"' + token.lexer.backslashed = False + + @TOKEN(r"[^\n]") # Matches any single character not in brackets + def t_STRING_anything(self, token): + + if token.lexer.backslashed: + if token.value == 'b': + token.lexer.string += '\b' + elif token.value == 't': + token.lexer.string += '\t' + elif token.value == 'n': + token.lexer.string += '\n' + elif token.value == 'f': + token.lexer.string += '\f' + elif token.value == '\\': + token.lexer.string += '\\' + else: + token.lexer.string += token.value + token.lexer.backslashed = False + else: + if token.value != '\\': + token.lexer.string += token.value + else: + token.lexer.backslashed = True + + + # STRING ignored characters + t_STRING_ignore = '' + + # STRING error handler + def t_STRING_error(self, token): + message = f'{token.value[0]} in String' + column = find_column(token.lexer.lexdata,token.lexpos) + error = ErrorToken(message, token.lineno, column) + self.errors.append(error) + return error + + def t_STRING_eof(self, token): + message = f'EOF in string constant' + column = find_column(token.lexer.lexdata,token.lexpos) + error = ErrorToken(message, token.lineno, column) + token.lexer.pop_state() + self.errors.append(error) + + return error + + # STATE OF MULTIPLE LINE COMMENT RECOGNITION + @TOKEN(r'\(\*') + def t_start_comment_state(self, token): + token.lexer.push_state("COMMENT") + token.lexer.comment_count = 0 + + @TOKEN(r"\n+") + def t_COMMENT_newline(self, token): + """ + The Newline Token Rule. + """ + token.lexer.lineno += len(token.value) + + @TOKEN(r'\(\*') + def t_COMMENT_begin_another_comment(self, token): + token.lexer.comment_count += 1 + + @TOKEN(r'\*\)') + def t_COMMENT_end(self, token): + + if token.lexer.comment_count == 0: + token.lexer.pop_state() + else: + token.lexer.comment_count -= 1 + + # COMMENT ignored characters + t_COMMENT_ignore = '' + + # COMMENT error handler + def t_COMMENT_error(self, token): + token.lexer.skip(1) + + def t_COMMENT_eof(self, token): + message = f'EOF in comment' + column = find_column(token.lexer.lexdata,token.lexpos) + error = ErrorToken(message, token.lineno, column) + self.errors.append(error) + token.lexer.pop_state() + return error + + + def t_error(self, token): + """ + Error Handling and Reporting Rule. + """ + message = f'ERROR "{token.value[0]}"' + column = find_column(token.lexer.lexdata,token.lexpos) + error = ErrorToken(message, token.lineno, column) + self.errors.append(error) + token.lexer.skip(1) + return error + + def build(self,**kwargs): + self.errors = [] + self.last_token = None + self.reserved = tuple(self.keywords.keys()) + tuple(self.builtin_types.keys()) + self.tokens = self.syntax_tokens + tuple(self.keywords.values()) + tuple(self.builtin_types.values()) + self.lexer = lex.lex(module=self, **kwargs) + + def input(self,source_code): + self.lexer.input(source_code) + + def token(self): + self.last_token = self.lexer.token() + return self.last_token + + def clone(self): + return self.lexer.clone() + + def __iter__(self): + return self + + def __next__(self): + t = self.token() + if t is None: + raise StopIteration + return t + + def next(self): + return self.__next__() + + +if __name__ == "__main__": + import sys + + if len(sys.argv) != 2: + print("Usage: ./lexer.py program.cl") + exit() + elif not str(sys.argv[1]).endswith(".cl"): + print("Cool program source code files must end with .cl extension.") + print("Usage: ./lexer.py program.cl") + exit() + + input_file = sys.argv[1] + with open(input_file, encoding="utf-8") as file: + cool_program_code = file.read() + + lexer = Lexer() + lexer.input(cool_program_code) + for token in lexer: pass + + if lexer.errors: + print(lexer.errors[0]) + exit(1) \ No newline at end of file diff --git a/src/lextab.py b/src/lextab.py new file mode 100644 index 00000000..eac939db --- /dev/null +++ b/src/lextab.py @@ -0,0 +1,10 @@ +# lextab.py. This file automatically created by PLY (version 3.11). Don't edit! +_tabversion = '3.10' +_lextokens = set(('ARROW', 'ASSIGN', 'AT', 'BOOL_TYPE', 'CASE', 'CLASS', 'COLON', 'COMMA', 'DIVIDE', 'DOT', 'ELSE', 'EQ', 'ESAC', 'FALSE', 'FALSELPAREN', 'FI', 'ID', 'IF', 'IN', 'INHERITS', 'INTEGER', 'INT_COMP', 'INT_TYPE', 'IO_TYPE', 'ISVOID', 'LBRACE', 'LET', 'LOOP', 'LT', 'LTEQ', 'LexicographicError', 'MAIN_TYPE', 'MINUS', 'MULTIPLY', 'NEW', 'NOT', 'OBJECT_TYPE', 'OF', 'PLUS', 'POOL', 'RBRACE', 'RPAREN', 'SELF', 'SELF_TYPE', 'SEMICOLON', 'STRING', 'STRING_TYPE', 'THEN', 'TRUE', 'TYPE', 'WHILE')) +_lexreflags = 64 +_lexliterals = '' +_lexstateinfo = {'INITIAL': 'inclusive', 'STRING': 'exclusive', 'COMMENT': 'exclusive'} +_lexstatere = {'INITIAL': [('(?P\\d+)|(?P[A-Z][a-zA-Z_0-9]*)|(?P[a-z][a-zA-Z_0-9]*)|(?P\\n+)|(?P\\")|(?P\\(\\*)|(?P\\-\\-[^\\n]*)|(?P\\=\\>)|(?P\\<\\-)|(?P\\<\\=)|(?P\\@)|(?P\\:)|(?P\\,)|(?P\\/)|(?P\\.)|(?P\\=)|(?P\\{)|(?P\\()|(?P\\<)|(?P\\-)|(?P\\*)|(?P\\+)|(?P\\})|(?P\\))|(?P\\;)|(?P~)', [None, ('t_INTEGER', 'INTEGER'), ('t_TYPE', 'TYPE'), ('t_ID', 'ID'), ('t_newline', 'newline'), ('t_start_string_state', 'start_string_state'), ('t_start_comment_state', 'start_comment_state'), (None, None), (None, 'ARROW'), (None, 'ASSIGN'), (None, 'LTEQ'), (None, 'AT'), (None, 'COLON'), (None, 'COMMA'), (None, 'DIVIDE'), (None, 'DOT'), (None, 'EQ'), (None, 'LBRACE'), (None, 'LPAREN'), (None, 'LT'), (None, 'MINUS'), (None, 'MULTIPLY'), (None, 'PLUS'), (None, 'RBRACE'), (None, 'RPAREN'), (None, 'SEMICOLON'), (None, 'INT_COMP')])], 'STRING': [('(?P\\n)|(?P\\0)|(?P\\")|(?P[^\\n])', [None, ('t_STRING_newline', 'newline'), ('t_STRING_null', 'null'), ('t_STRING_end', 'end'), ('t_STRING_anything', 'anything')])], 'COMMENT': [('(?P\\n+)|(?P\\(\\*)|(?P\\*\\))', [None, ('t_COMMENT_newline', 'newline'), ('t_COMMENT_begin_another_comment', 'begin_another_comment'), ('t_COMMENT_end', 'end')])]} +_lexstateignore = {'COMMENT': '', 'STRING': '', 'INITIAL': ' \t\r\x0c'} +_lexstateerrorf = {'COMMENT': 't_COMMENT_error', 'STRING': 't_STRING_error', 'INITIAL': 't_error'} +_lexstateeoff = {'COMMENT': 't_COMMENT_eof', 'STRING': 't_STRING_eof'} diff --git a/src/semantic.py b/src/semantic.py new file mode 100644 index 00000000..2122db5c --- /dev/null +++ b/src/semantic.py @@ -0,0 +1,424 @@ +import itertools as itt + + +class SemanticException(Exception): + @property + def text(self): + return self.args[0] + +class ErrorSemantic(): + def __init__(self,message, line, column, type = 'SemanticError'): + self.type = type + self.value = message + self.line = line + self.column = column + self.text = f'({self.line}, {self.column}) - {self.type}: {self.value}' + + def __str__(self): + return f'({self.line}, {self.column}) - {self.type}: {self.value}' + + def __repr__(self): + return str(self) + + +class Attribute: + def __init__(self, name, typex): + self.name = name + self.type = typex + + def __str__(self): + return f'[attrib] {self.name} : {self.type.name};' + + def __repr__(self): + return str(self) + + +class Method: + def __init__(self, name, param_names, params_types, return_type): + self.name = name + self.param_names = param_names + self.param_types = params_types + self.return_type = return_type + + def __str__(self): + params = ', '.join(f'{n}:{t.name}' for n, t in zip( + self.param_names, self.param_types)) + return f'[method] {self.name}({params}): {self.return_type.name};' + + def __eq__(self, other): + return other.name == self.name and \ + other.return_type == self.return_type and \ + other.param_types == self.param_types + + +class Type: + def __init__(self, name: str): + self.name = name + self.sealed = False # indicates if this type is restricted for inheritance + self.attributes = [] + self.methods = {} + self.parent = None + self.tag = None + self.max_tag = None # biggest tag reachable in dfs from this type + + def set_parent(self, parent): + if self.parent is not None: + raise SemanticException(f'Parent type is already set for {self.name}.') + self.parent = parent + + def has_attr(self, name: str): + try: + attr_name = self.get_attribute(name) + except: + return False + else: + return True + + def get_attribute(self, name: str): + try: + return next(attr for attr in self.attributes if attr.name == name) + except StopIteration: + if self.parent is None: + raise SemanticException( + f'Attribute "{name}" is not defined in {self.name}.') + try: + return self.parent.get_attribute(name) + except SemanticException: + raise SemanticException( + f'Attribute "{name}" is not defined in {self.name}.') + + def define_attribute(self, name: str, typex): + if name == 'self': + raise SemanticException( + "'self' cannot be the name of an attribute") + 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 {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 {self.name}.') + try: + return self.parent.get_method(name) + except SemanticException: + raise SemanticException( + f'Method "{name}" is not defined in {self.name}.') + + + def define_method(self, name: str, param_names: list, param_types: list, return_type): + try: + method = self.get_method(name) + except SemanticException: + method = self.methods[name] = Method( + name, param_names, param_types, return_type) + return method + else: + try: + self.methods[name] + except KeyError: + if method.return_type != return_type or method.param_types != param_types: + raise SemanticException( + f'Method "{name}" is already defined in {self.name} with a different signature') + else: + self.methods[name] = Method(name, param_names, param_types, return_type) + else: + raise SemanticException( + f'Method "{name}" is already defined in {self.name}') + + return method + + def get_all_attributes(self): + all_attributes = self.parent and self.parent.get_all_attributes() or [] + all_attributes += [(self.name, attr) for attr in self.attributes] + return all_attributes + + def get_all_methods(self): + all_methods = self.parent and self.parent.get_all_methods() or [] + all_methods += [(self.name, method) for method in self.methods] + return all_methods + + def conforms_to(self, other): + return other.bypass() or self == other or self.parent is not None and self.parent.conforms_to(other) + + def bypass(self): + return False + + def ancestors_path(self): + """ + Return a list with all ancestors of the self type, starting by self + """ + l = [] + l.append(self) + current_parent = self.parent + while (current_parent is not None): + l.append(current_parent) + current_parent = current_parent.parent + return l + + def join(self, other): + """ + Return the least type C such as self <= C and other <= C + """ + + if self.name == other.name: # A |_| A = A + return self + + other_path = other.ancestors_path() + for p in self.ancestors_path(): + for o in other_path: + if o.name == p.name: + return p + return other + + def multiple_join(self, args): + """ + Return the least type C such as all type in args conforms with C + """ + least_type = self + + for t in args: + if isinstance(least_type, ErrorType) or isinstance(t, ErrorType): + least_type = ErrorType() + return least_type + least_type = least_type.join(t) + + return least_type + + 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 conforms_to(self, other): + return True + + def bypass(self): + return True + + def __eq__(self, other): + return isinstance(other, Type) + + +class ObjectType(Type): + def __init__(self): + Type.__init__(self, 'Object') + + def bypass(self): + return True + + def __eq__(self, other): + return other.name == self.name or isinstance(other, ObjectType) + + +class IOType(Type): + def __init__(self): + Type.__init__(self, 'IO') + + def __eq__(self, other): + return other.name == self.name or isinstance(other, IOType) + + +class StringType(Type): + def __init__(self): + Type.__init__(self, 'String') + self.sealed = True + + def __eq__(self, other): + return other.name == self.name or isinstance(other, StringType) + + +class IntType(Type): + def __init__(self): + Type.__init__(self, 'Int') + self.sealed = True + + def __eq__(self, other): + return other.name == self.name or isinstance(other, IntType) + + +class BoolType(Type): + def __init__(self): + Type.__init__(self, 'Bool') + self.sealed = True + + def __eq__(self, other): + return other.name == self.name or isinstance(other, BoolType) + + +class Context: + def __init__(self): + self.types = {} + self.graph = {} + self.classes = {} + self.types['ErrorType'] = ErrorType() + + def create_builtin_types(self): + self.types['SELF_TYPE'] = Type('SELF_TYPE') + + self.types['Object'] = ObjectType() + self.types['IO'] = IOType() + self.types['String'] = StringType() + self.types['Int'] = IntType() + self.types['Bool'] = BoolType() + self.graph['Object'] = ['IO', 'String', 'Bool', 'Int'] + self.graph['IO'] = [] + self.graph['String'] = [] + self.graph['Int'] = [] + self.graph['Bool'] = [] + + self.types['IO'].set_parent(self.types['Object']) + self.types['String'].set_parent(self.types['Object']) + self.types['Int'].set_parent(self.types['Object']) + self.types['Bool'].set_parent(self.types['Object']) + + self.types['Object'].define_method('abort', [], [], self.types['Object']) + self.types['Object'].define_method('type_name', [], [], self.types['String']) + self.types['Object'].define_method('copy', [], [], self.types['SELF_TYPE']) + + self.types['IO'].define_method('out_string', ['x'], [self.types['String']], self.types['SELF_TYPE']) + self.types['IO'].define_method('out_int', ['x'], [self.types['Int']], self.types['SELF_TYPE']) + self.types['IO'].define_method('in_string', [], [], self.types['String']) + self.types['IO'].define_method('in_int', [], [], self.types['Int']) + + self.types['String'].define_method('length', [], [], self.types['Int']) + self.types['String'].define_method('concat', ['s'], [self.types['String']], self.types['String']) + self.types['String'].define_method('substr', ['i', 'l'], [self.types['Int'], self.types['Int']], self.types['String']) + + + + def create_type(self, node): + if node.name in self.types: + raise SemanticException( + f'Type with the same name ({node.name}) already in context.') + typex = self.types[node.name] = Type(node.name) + self.classes[node.name] = node + if not self.graph.__contains__(node.name): + self.graph[node.name] = [] + if self.graph.__contains__(node.parent): + self.graph[node.parent].append(node.name) + else: + self.graph[node.parent] = [node.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 set_type_tags(self, node='Object', tag=0): + self.types[node].tag = tag + for i,t in enumerate(self.graph[node]): + self.set_type_tags(t, tag + i + 1) + + def set_type_max_tags(self, node='Object'): + if not self.graph[node]: + self.types[node].max_tag = self.types[node].tag + else: + for t in self.graph[node]: + self.set_type_max_tags(t) + maximum = 0 + for t in self.graph[node]: + maximum = max(maximum, self.types[t].max_tag) + self.types[node].max_tag = maximum + + + 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 VariableInfo: + def __init__(self, name, vtype): + self.name = name + self.type = vtype + + +class Scope: + def __init__(self, parent=None): + self.locals = [] + self.cil_locals = {} + self.parent = parent + self.children = [] + self.index = 0 if parent is None else len(parent) + + def __len__(self): + return len(self.locals) + + def create_child(self): + child = Scope(self) + self.children.append(child) + return child + + def define_variable(self, vname, vtype): + info = VariableInfo(vname, vtype) + self.locals.append(info) + return info + + def define_cil_local(self, vname, cilname, vtype = None): + self.define_variable(vname, vtype) + self.cil_locals[vname] = cilname + + + def get_cil_local(self, vname): + if self.cil_locals.__contains__(vname): + return self.cil_locals[vname] + else: + return None + + def find_cil_local(self, vname, index=None): + locals = self.cil_locals.items() if index is None else itt.islice(self.cil_locals.items(), index) + try: + return next(cil_name for name, cil_name in locals if name == vname) + except StopIteration: + return self.parent.find_cil_local(vname, self.index) if (self.parent is not None) else None + + def find_variable(self, vname, index=None): + locals = self.locals if index is None else itt.islice( + self.locals, index) + try: + return next(x for x in locals if x.name == vname) + except StopIteration: + return self.parent.find_variable(vname, self.index) if (self.parent is not None) else None + + def is_defined(self, vname): + return self.find_variable(vname) is not None + + def is_defined_cil_local(self, vname): + return self.find_cil_local(vname ) is not None + + def is_local(self, vname): + return any(True for x in self.locals if x.name == vname) + + def remove_local(self, vname): + self.locals = [local for local in self.locals if local.name != vname] diff --git a/src/semantic_analyzer.py b/src/semantic_analyzer.py new file mode 100644 index 00000000..07353b07 --- /dev/null +++ b/src/semantic_analyzer.py @@ -0,0 +1,727 @@ +import visitor +import ast_nodes as AST +from semantic import SemanticException, ErrorSemantic +from semantic import Attribute, Method, Type, IntType, StringType, IOType, BoolType, ObjectType +from semantic import ErrorType +from semantic import Context +from semantic import Scope + + +WRONG_SIGNATURE = 'Method "%s" already defined in "%s" with a different signature.' +SELF_IS_READONLY = 'Variable "self" is read-only.' +LOCAL_ALREADY_DEFINED = 'Variable "%s" is already defined in method "%s".' +PARAM_ALREADY_DEFINED = 'Parameter "%s" is already defined in method "%s".' +INCOMPATIBLE_TYPES = 'Cannot convert "%s" into "%s".' +VARIABLE_NOT_DEFINED = 'Variable "%s" is not defined.' +INVALID_OPERATION = 'Operation is not defined between "%s" and "%s".' +WRONG_TYPE = 'Type %s expected' +INVALID_SELF_TYPE = 'Invalid use of SELF_TYPE' + +BUILTIN_TYPES = ['Object', 'Int', 'String', 'Bool', 'IO'] + + +class TypeCollector(object): + def __init__(self, errors=[]): + self.context = None + self.errors = errors + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(AST.Program) + def visit(self, node): + self.context = Context() + self.context.create_builtin_types() + for klass in node.classes: + if klass.name in BUILTIN_TYPES: + error = ErrorSemantic("Is an error redefine a builint type", klass.line, klass.column) + self.errors.append(error) + else: + self.visit(klass) + + @visitor.when(AST.Class) + def visit(self, node): + try: + self.context.create_type(node) + except SemanticException as e: + error = ErrorSemantic(e.text, node.line, node.column) + self.errors.append(error) + + +class TypeBuilder: + def __init__(self, context, errors=[]): + self.context = context + self.current_type = None + self.errors = errors + self.sort = [] # topologic sort for all types defined + self.visited = {key: False for key in self.context.graph.keys()} # types visited in the graph by the DFS + + def visit_component(self, actual_type): + self.sort.append(actual_type) + self.visited[actual_type] = True + for children in self.context.graph[actual_type]: + self.visit_component(children) + + def topologic_sort(self): + indeg = {key: 0 for key in self.context.graph.keys()} + for u in self.context.graph.keys(): + for v in self.context.graph[u]: + indeg[v] += 1 + + roots = [key for key in indeg.keys() if indeg[key] == 0] + + for v in roots: + self.visit_component(v) + + visited = [x for x in self.visited] + visited.reverse() + for t in visited: + if not self.visited[t] and not t in BUILTIN_TYPES: + class_node = self.context.classes[t] + error = ErrorSemantic("Exist a cycle that start in type {}".format(t), class_node.line, class_node.column) + self.errors.append(error) + break + + + @visitor.on('node') + def visit(self, node): + pass + + @visitor.when(AST.Program) + def visit(self, node): + self.topologic_sort() + for t in self.sort: + if t not in BUILTIN_TYPES: + try: + class_node = self.context.classes[t] + except KeyError: + pass + else: + self.visit(class_node) + if not self.context.types.__contains__('Main'): + error = ErrorSemantic("The class Main is not defined", 0, 0,'TypeError') + self.errors.append(error) + else: + if not self.context.types['Main'].methods.__contains__('main'): + main_node = self.context.classes['Main'] + error = ErrorSemantic("The main method is not defined in class Main", main_node.line, main_node.column,'AttributeError') + self.errors.append(error) + + + @visitor.when(AST.Class) + def visit(self, node): + try: + self.current_type = self.context.get_type(node.name) + if node.parent: + try: + parent = self.context.get_type(node.parent) + except SemanticException as e: + error = ErrorSemantic(e.text, node.line, node.column, 'TypeError') + self.errors.append(error) + parent = ErrorType() + self.current_type.set_parent(parent) + else: + if parent.name in ['Int', 'String', 'Bool']: + parent = ErrorType() + error = ErrorSemantic("Type {} inherits from a builint type".format(node.name), node.line, node.column) + self.errors.append(error) + self.current_type.set_parent(parent) + + except SemanticException as e: + error = ErrorSemantic(e.text, node.line, node.column, 'TypeError') + self.errors.append(error) + + for f in node.features: + self.visit(f) + + @visitor.when(AST.ClassMethod) + def visit(self, node): + try: + param_names = [] + param_types = [] + for p in node.params: + param_names.append(p.name) + try: + param_type = self.context.get_type(p.param_type) + except SemanticException: + param_type = ErrorType() + error = ErrorSemantic("The type of param {} in method {} not exist, in the class {}.".format( + p.name, node.name, self.current_type.name), node.line, node.column, 'TypeError') + self.errors.append(error) + + param_types.append(param_type) + + try: + return_type = self.context.get_type(node.return_type) + except SemanticException: + return_type = ErrorType() + error = ErrorSemantic("The return type {} in method {} not exist, in the class {}.".format( + node.return_type, node.name, self.current_type.name), node.line, node.column, 'TypeError') + self.errors.append(error) + + self.current_type.define_method( + node.name, param_names, param_types, return_type) + except SemanticException as e: + error = ErrorSemantic(e.text, node.line, node.column) + self.errors.append(error) + + @visitor.when(AST.AttributeInit) + def visit(self, node): + try: + attr_type = self.context.get_type(node.type) + except SemanticException: + attr_type = ErrorType() + error = ErrorSemantic("The type of attr {} in class {} not exist.".format( + node.name, self.current_type.name), node.line, node.column, 'TypeError') + self.errors.append(error) + try: + self.current_type.define_attribute(node.name, attr_type) + except SemanticException as e: + error = ErrorSemantic(e.text, node.line, node.column) + self.errors.append(error) + + @visitor.when(AST.AttributeDef) + def visit(self, node): + try: + attr_type = self.context.get_type(node.type) + except SemanticException: + attr_type = ErrorType() + error = ErrorSemantic("The type of attr {} in class {} not exist.".format( + node.name, self.current_type.name), node.line, node.column, 'TypeError') + self.errors.append(error) + try: + self.current_type.define_attribute(node.name, attr_type) + except SemanticException as e: + error = ErrorSemantic(e.text, node.line, node.column) + self.errors.append(error) + + +class TypeChecker: + def __init__(self, context, errors=[]): + self.context = context + self.current_type = None + self.current_method = None + self.errors = errors + + @visitor.on('node') + def visit(self, node, scope): + pass + + @visitor.when(AST.Program) + def visit(self, node, scope=None): + scope = Scope() + for c in node.classes: + self.visit(c, scope.create_child()) + return scope + + @visitor.when(AST.Class) + def visit(self, node, scope): + self.current_type = self.context.get_type(node.name) + scope.define_variable('self', self.current_type) + + attributes = self.current_type.get_all_attributes() + for _, attr in attributes: + if attr.type.name == 'SELF_TYPE': + scope.define_variable(attr.name, self.current_type) + else: + scope.define_variable(attr.name, attr.type) + + for feature in node.features: + self.visit(feature, scope) + + @visitor.when(AST.AttributeInit) + def visit(self, node, scope): + try: + node_type = self.current_type.get_attribute(node.name).type + except SemanticException as ex: + node_type = ErrorType() + error = ErrorSemantic(ex.text, node.line, node.column, 'AttributeError') + self.errors.append(error) + + self.visit(node.expr, scope) + expr_type = node.expr.computed_type + + if not expr_type.conforms_to(node_type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', expr_type.name, 1).replace('%s', node_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + + @visitor.when(AST.AttributeDef) + def visit(self, node, scope): + try: + self.current_type.get_attribute(node.name) + except SemanticException as ex: + error = ErrorSemantic(ex.text, node.line, node.column, 'AttributeError') + self.errors.append(error) + + @visitor.when(AST.ClassMethod) + def visit(self, node, scope): + try: + self.current_method = self.current_type.get_method(node.name) + except SemanticException as ex: + error = ErrorSemantic(ex.text, node.line, node.column, 'AttributeError') + self.errors.append(error) + + method_scope = scope.create_child() + + for param in node.params: + self.visit(param, method_scope) + + self.visit(node.expr, method_scope) + + expr_type = node.expr.computed_type + + return_type = self.current_method.return_type + + if expr_type.name == 'SELF_TYPE': + if not self.current_type.conforms_to(return_type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', expr_type.name, 1).replace('%s', self.current_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + elif not expr_type.conforms_to(return_type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', expr_type.name, 1).replace('%s', return_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + + + @visitor.when(AST.FormalParameter) + def visit(self, node, scope): + try: + node_type = self.context.get_type(node.param_type) + if node_type.name == 'SELF_TYPE': + node_type = ErrorType() + error = ErrorSemantic(INVALID_SELF_TYPE, node.line, node.column) + self.errors.append(error) + + except SemanticException as ex: + node_type = ErrorType() + error = ErrorSemantic(ex.text, node.line, node.column, 'TypeError') + self.errors.append(error) + + if node.name == 'self': + error = ErrorSemantic("self cannot be the name of a formal parameter", node.line, node.column) + self.errors.append(error) + elif not scope.is_local(node.name): + scope.define_variable(node.name, node_type) + else: + error = ErrorSemantic(PARAM_ALREADY_DEFINED.replace( + '%s', node.name, 1).replace('%s', self.current_method.name, 1), node.line, node.column) + self.errors.append(error) + + @visitor.when(AST.DynamicCall) + def visit(self, node, scope): + self.visit(node.instance, scope) + instance_type = node.instance.computed_type + + if instance_type.name == 'SELF_TYPE': + instance_type = scope.find_variable('self').type + try: + instance_method = instance_type.get_method(node.method) + + if len(node.args) == len(instance_method.param_types): + for arg, param_type in zip(node.args, instance_method.param_types): + self.visit(arg, scope) + arg_type = arg.computed_type + + if not arg_type.conforms_to(param_type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', arg_type.name, 1).replace('%s', param_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + else: + error = ErrorSemantic(f'Method "{instance_method.name}" of "{instance_type.name}" only accepts {len(instance_method.param_types)} argument(s)', node.line, node.column) + self.errors.append(error) + + if instance_method.return_type.name == 'SELF_TYPE': + node_type = instance_type + else: + node_type = instance_method.return_type + + except SemanticException as ex: + node_type = ErrorType() + error = ErrorSemantic(ex.text, node.line, node.column, 'AttributeError') + self.errors.append(error) + + node.computed_type = node_type + + @visitor.when(AST.StaticCall) + def visit(self, node, scope): + self.visit(node.instance, scope) + instance_type = node.instance.computed_type + + try: + static_type = self.context.get_type(node.static_type) + except SemanticException as ex: + static_type = ErrorType() + error = ErrorSemantic(ex.text, node.line, node.column, 'TypeError') + self.errors.append(error) + + if not instance_type.conforms_to(static_type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', instance_type.name, 1).replace('%s', static_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + + try: + method = static_type.get_method(node.method) + + if len(node.args) == len(method.param_types): + for arg, param_type in zip(node.args, method.param_types): + self.visit(arg, scope) + arg_type = arg.computed_type + + if not arg_type.conforms_to(param_type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', arg_type.name, 1).replace('%s', param_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + else: + error = ErrorSemantic( f'Method "{method.name}" of "{static_type.name}" only accepts {len(method.param_types)} argument(s)', node.line, node.column) + self.errors.append(error) + + if method.return_type.name == 'SELF_TYPE': + node_type = instance_type + node_type = method.return_type + + except SemanticException as ex: + error = ErrorSemantic(ex.text, node.line, node.column, 'AttributeError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.AssignExpr) + def visit(self, node, scope): + self.visit(node.expr, scope) + node_type = node.expr.computed_type + + if scope.is_defined(node.name): + var = scope.find_variable(node.name) + + if var.name == 'self': + error = ErrorSemantic(SELF_IS_READONLY, node.line, node.column) + self.errors.append(error) + node_type = ErrorType() + elif not node_type.conforms_to(var.type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', node_type.name, 1).replace('%s', var.type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + else: + error = ErrorSemantic(VARIABLE_NOT_DEFINED.replace( + '%s', node.name, 1), node.line, node.column, 'NameError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.Case) + def visit(self, node, scope): + self.visit(node.expr, scope) + action_expr_types = [] + var_declared = [] + + for action in node.actions: + var_type = action.action_type + if not var_type in var_declared: + var_declared.append(var_type) + else: + error = ErrorSemantic("The type {} is declared in another branch".format(var_type), action.line, action.column) + self.errors.append(error) + self.visit(action, scope.create_child()) + action_expr_types.append(action.computed_type) + + t_0 = action_expr_types.pop(0) + node_type = t_0.multiple_join(action_expr_types) + + node.computed_type = node_type + + @visitor.when(AST.Action) + def visit(self, node, scope): + try: + action_type = self.context.get_type(node.action_type) + except SemanticException as ex: + error = ErrorSemantic(ex.text, node.line, node.column , 'TypeError') + self.errors.append(error) + action_type = ErrorType() + + scope.define_variable(node.name, action_type) + + self.visit(node.body, scope) + node.computed_type = node.body.computed_type + + @visitor.when(AST.If) + def visit(self, node, scope): + self.visit(node.predicate, scope) + predicate_type = node.predicate.computed_type + + if predicate_type.name != 'Bool': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Bool', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + + self.visit(node.then_body, scope) + then_type = node.then_body.computed_type + self.visit(node.else_body, scope) + else_type = node.else_body.computed_type + + node.computed_type = then_type.join(else_type) + + + @visitor.when(AST.While) + def visit(self, node, scope): + self.visit(node.predicate, scope) + predicate_type = node.predicate.computed_type + + if predicate_type.name != 'Bool': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Bool', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + + self.visit(node.body, scope) + + node.computed_type = self.context.get_type('Object') + + @visitor.when(AST.Block) + def visit(self, node, scope): + for expr in node.exprs: + self.visit(expr, scope) + + node.computed_type = node.exprs[-1].computed_type + + @visitor.when(AST.Let) + def visit(self, node, scope): + let_scope = scope.create_child() + + for var in node.var_list: + self.visit(var, let_scope) + + self.visit(node.body, let_scope) + + node.computed_type = node.body.computed_type + + @visitor.when(AST.LetVarInit) + def visit(self, node, scope): + try: + node_type = self.context.get_type(node.type) + if node_type.name == 'SELF_TYPE': + node_type = scope.find_variable('self').type + except SemanticException as ex: + error = ErrorSemantic(ex.text, node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + self.visit(node.expr, scope) + expr_type = node.expr.computed_type + + if not expr_type.conforms_to(node_type): + error = ErrorSemantic(INCOMPATIBLE_TYPES.replace( + '%s', expr_type.name, 1).replace('%s', node_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + + if node.name == 'self': + error = ErrorSemantic("'self' cannot be bound in a 'let' expression", node.line, node.column) + self.errors.append(error) + else: + if scope.is_local(node.name): + scope.remove_local(node.name) + + scope.define_variable(node.name, node_type) + + @visitor.when(AST.LetVarDef) + def visit(self, node, scope): + try: + node_type = self.context.get_type(node.type) + if node_type.name == 'SELF_TYPE': + node_type = scope.find_variable('self').type + except SemanticException as ex: + error = ErrorSemantic(ex.text, node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + if node.name == 'self': + error = ErrorSemantic("'self' cannot be bound in a 'let' expression", node.line, node.column) + self.errors.append(error) + else: + if scope.is_local(node.name): + scope.remove_local(node.name) + + scope.define_variable(node.name, node_type) + + @visitor.when(AST.NewType) + def visit(self, node, scope): + try: + node_type = self.context.get_type(node.type) + if node_type.name == 'SELF_TYPE': + node_type = scope.find_variable('self').type + except SemanticException as ex: + error = ErrorSemantic(ex.text, node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.IsVoid) + def visit(self, node, scope): + self.visit(node.expr, scope) + node.computed_type = self.context.get_type('Bool') + + @visitor.when(AST.ArithmeticBinOp) + def visit(self, node, scope): + node_type = self.context.get_type('Int') + + self.visit(node.left, scope) + left_type = node.left.computed_type + + if left_type.name != 'Int': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Int', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + self.visit(node.right, scope) + right_type = node.right.computed_type + + if right_type.name != 'Int': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Int', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.LogicBinOp) + def visit(self, node, scope): + node_type = self.context.get_type('Bool') + + self.visit(node.left, scope) + left_type = node.left.computed_type + + if left_type.name != 'Int': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Int', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + self.visit(node.right, scope) + right_type = node.right.computed_type + + if right_type.name != 'Int': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Int', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.Not) + def visit(self, node, scope): + node_type = self.context.get_type('Bool') + + self.visit(node.expr, scope) + expr_type = node.expr.computed_type + + if expr_type.name != 'Bool': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Bool', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.LogicalNot) + def visit(self, node, scope): + node_type = self.context.get_type('Int') + + self.visit(node.expr, scope) + expr_type = node.expr.computed_type + + if expr_type.name != 'Int': + error = ErrorSemantic(WRONG_TYPE.replace('%s', 'Int', 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.Equals) + def visit(self, node, scope): + node_type = self.context.get_type('Bool') + + self.visit(node.left, scope) + left_type = node.left.computed_type + + self.visit(node.right, scope) + right_type = node.right.computed_type + + if (left_type.name in ['Int', 'Bool', 'String'] or right_type.name in ['Int', 'Bool', 'String']) and left_type.name != right_type.name: + error = ErrorSemantic(WRONG_TYPE.replace('%s', left_type.name, 1), node.line, node.column, 'TypeError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.Identifier) + def visit(self, node, scope): + if scope.is_defined(node.name): + node_type = scope.find_variable(node.name).type + else: + error = ErrorSemantic(VARIABLE_NOT_DEFINED.replace( + '%s', node.name, 1), node.line, node.column, 'NameError') + self.errors.append(error) + node_type = ErrorType() + + node.computed_type = node_type + + @visitor.when(AST.INTEGER) + def visit(self, node, scope): + node.computed_type = self.context.get_type('Int') + + @visitor.when(AST.STRING) + def visit(self, node, scope): + node.computed_type = self.context.get_type('String') + + @visitor.when(AST.Boolean) + def visit(self, node, scope): + node.computed_type = self.context.get_type('Bool') + + +class SemanticAnalyzer: + def __init__(self, ast, *args, **kwargs): + self.ast = ast + self.errors = [] + + def analyze(self): + #'============== COLLECTING TYPES ===============' + collector = TypeCollector(self.errors) + + collector.visit(self.ast) + context = collector.context + + # #'=============== BUILDING TYPES ================' + builder = TypeBuilder(context, self.errors) + builder.visit(self.ast) + + #'=============== CHECKING TYPES ================' + scope = None + if not self.errors: + checker = TypeChecker(context, self.errors) + scope = checker.visit(self.ast) + + return context, scope + + +if __name__ == '__main__': + import sys + from cparser import Parser + + parser = Parser() + + if len(sys.argv) > 1: + + input_file = sys.argv[1] + with open(input_file, encoding="utf-8") as file: + cool_program_code = file.read() + + parse_result = parser.parse(cool_program_code) + + if parser.errors: + print(parser.errors[0]) + exit(1) + + semantic_analyzer = SemanticAnalyzer(parse_result) + semantic_analyzer.analyze() + + for e in semantic_analyzer.errors: + print(e) + exit(1) diff --git a/src/visitor.py b/src/visitor.py new file mode 100644 index 00000000..96484283 --- /dev/null +++ b/src/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/yacctab.py b/src/yacctab.py new file mode 100644 index 00000000..cd4eddb3 --- /dev/null +++ b/src/yacctab.py @@ -0,0 +1,90 @@ + +# yacctab.py +# This file is automatically generated. Do not edit. +# pylint: disable=W,C,R +_tabversion = '3.10' + +_lr_method = 'LALR' + +_lr_signature = 'rightASSIGNrightNOTnonassocLTEQLTEQleftPLUSMINUSleftMULTIPLYDIVIDErightISVOIDrightINT_COMPleftATleftDOTARROW ASSIGN AT BOOLEAN BOOL_TYPE CASE CLASS COLON COMMA DIVIDE DOT ELSE EQ ESAC FALSE FI ID IF IN INHERITS INTEGER INT_COMP INT_TYPE IO_TYPE ISVOID LBRACE LET LOOP LPAREN LT LTEQ LexicographicError MAIN_TYPE MINUS MULTIPLY NEW NOT OBJECT_TYPE OF PLUS POOL RBRACE RPAREN SELF_TYPE SEMICOLON STRING STRING_TYPE THEN TRUE TYPE WHILE\n program : class_list\n \n class_list : class_list class SEMICOLON\n | class SEMICOLON\n \n class : CLASS TYPE LBRACE features_list_opt RBRACE\n \n class : CLASS TYPE INHERITS TYPE LBRACE features_list_opt RBRACE\n \n features_list_opt : features_list\n | empty\n \n features_list : features_list feature SEMICOLON\n | feature SEMICOLON\n \n feature : ID LPAREN formal_params_list RPAREN COLON TYPE LBRACE expression RBRACE\n \n feature : ID LPAREN RPAREN COLON TYPE LBRACE expression RBRACE\n \n feature : attribute_init\n \n attribute_init : ID COLON TYPE ASSIGN expression \n | attribute_def\n \n attribute_def : ID COLON TYPE\n \n formal_params_list : formal_params_list COMMA formal_param\n | formal_param\n \n formal_param : ID COLON TYPE\n \n expression : ID\n \n expression : INTEGER\n \n expression : TRUE \n | FALSE\n \n expression : STRING\n \n expression : LBRACE block_list RBRACE\n \n block_list : block_list expression SEMICOLON\n | expression SEMICOLON\n \n expression : ID ASSIGN expression\n \n expression : expression DOT ID LPAREN arguments_list_opt RPAREN\n \n arguments_list_opt : arguments_list\n | empty\n \n arguments_list : arguments_list COMMA expression\n | expression\n \n expression : expression AT TYPE DOT ID LPAREN arguments_list_opt RPAREN\n \n expression : ID LPAREN arguments_list_opt RPAREN\n \n expression : expression PLUS expression\n | expression MINUS expression\n | expression MULTIPLY expression\n | expression DIVIDE expression\n \n expression : expression LT expression\n | expression LTEQ expression\n | expression EQ expression\n \n expression : LPAREN expression RPAREN\n \n expression : IF expression THEN expression ELSE expression FI\n \n expression : WHILE expression LOOP expression POOL\n \n expression : let_expression\n \n let_expression : LET nested_vars IN expression\n \n nested_vars : let_var_init\n | nested_vars COMMA let_var_init\n \n let_var_init : ID COLON TYPE ASSIGN expression \n | let_var_def\n \n let_var_def : ID COLON TYPE\n \n expression : CASE expression OF actions_list ESAC\n \n actions_list : actions_list action\n | action\n \n action : ID COLON TYPE ARROW expression SEMICOLON\n \n expression : NEW TYPE\n \n expression : ISVOID expression\n \n expression : INT_COMP expression\n \n expression : NOT expression\n \n empty :\n ' + +_lr_action_items = {'CLASS':([0,2,6,8,],[4,4,-3,-2,]),'$end':([1,2,6,8,],[0,-1,-3,-2,]),'SEMICOLON':([3,5,14,16,17,19,20,30,37,42,43,44,45,46,47,52,73,78,79,80,81,88,95,96,97,98,99,100,101,102,103,105,113,114,124,127,132,133,137,143,145,146,],[6,8,21,-12,-14,-4,25,-15,-5,-19,-13,-20,-21,-22,-23,-45,104,-56,-57,-58,-59,-27,-35,-36,-37,-38,-39,-40,-41,-24,118,-42,-11,-34,-46,-10,-44,-52,-28,-43,-33,147,]),'TYPE':([4,10,23,32,35,39,54,64,111,135,],[7,18,30,38,41,59,78,94,126,140,]),'LBRACE':([7,18,36,41,48,49,50,51,53,55,56,57,59,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[9,24,48,60,48,48,48,48,48,48,48,48,86,48,48,48,48,48,48,48,48,48,48,48,48,-26,48,48,48,48,48,-25,48,48,48,48,]),'INHERITS':([7,],[10,]),'RBRACE':([9,11,12,13,21,24,25,31,42,44,45,46,47,52,72,78,79,80,81,87,88,95,96,97,98,99,100,101,102,104,105,112,114,118,124,132,133,137,143,145,],[-60,19,-6,-7,-9,-60,-8,37,-19,-20,-21,-22,-23,-45,102,-56,-57,-58,-59,113,-27,-35,-36,-37,-38,-39,-40,-41,-24,-26,-42,127,-34,-25,-46,-44,-52,-28,-43,-33,]),'ID':([9,12,21,22,24,25,34,36,48,49,50,51,53,55,56,57,58,60,61,62,63,65,66,67,68,69,70,71,72,86,104,106,107,108,109,110,115,116,117,118,121,122,131,134,136,138,144,147,],[15,15,-9,26,15,-8,26,42,42,42,42,42,42,42,42,42,84,42,42,42,93,42,42,42,42,42,42,42,42,42,-26,42,42,123,42,84,42,42,130,-25,123,-54,42,-53,42,42,42,-55,]),'LPAREN':([15,36,42,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,93,104,106,107,109,115,116,118,130,131,136,138,144,],[22,49,62,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,116,-26,49,49,49,49,49,-25,138,49,49,49,49,]),'COLON':([15,26,28,33,84,123,],[23,32,35,39,111,135,]),'RPAREN':([22,27,29,38,40,42,44,45,46,47,52,62,74,78,79,80,81,88,89,90,91,92,95,96,97,98,99,100,101,102,105,114,116,124,128,129,132,133,137,138,142,143,145,],[28,33,-17,-18,-16,-19,-20,-21,-22,-23,-45,-60,105,-56,-57,-58,-59,-27,114,-29,-30,-32,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,-60,-46,-31,137,-44,-52,-28,-60,145,-43,-33,]),'COMMA':([27,29,38,40,42,44,45,46,47,52,78,79,80,81,82,83,85,88,90,92,95,96,97,98,99,100,101,102,105,114,124,125,126,128,132,133,137,141,143,145,],[34,-17,-18,-16,-19,-20,-21,-22,-23,-45,-56,-57,-58,-59,110,-47,-50,-27,115,-32,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,-46,-48,-51,-31,-44,-52,-28,-49,-43,-33,]),'ASSIGN':([30,42,126,],[36,61,136,]),'INTEGER':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,-26,44,44,44,44,44,-25,44,44,44,44,]),'TRUE':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,-26,45,45,45,45,45,-25,45,45,45,45,]),'FALSE':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,46,-26,46,46,46,46,46,-25,46,46,46,46,]),'STRING':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,47,-26,47,47,47,47,47,-25,47,47,47,47,]),'IF':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,-26,50,50,50,50,50,-25,50,50,50,50,]),'WHILE':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,-26,51,51,51,51,51,-25,51,51,51,51,]),'CASE':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,53,-26,53,53,53,53,53,-25,53,53,53,53,]),'NEW':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,54,-26,54,54,54,54,54,-25,54,54,54,54,]),'ISVOID':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,55,-26,55,55,55,55,55,-25,55,55,55,55,]),'INT_COMP':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,56,-26,56,56,56,56,56,-25,56,56,56,56,]),'NOT':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,57,-26,57,57,57,57,57,-25,57,57,57,57,]),'LET':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,104,106,107,109,115,116,118,131,136,138,144,],[58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,58,-26,58,58,58,58,58,-25,58,58,58,58,]),'DOT':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,94,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,63,-20,-21,-22,-23,-45,63,63,63,63,63,-56,63,63,63,63,63,63,117,63,63,63,63,63,63,63,-24,63,-42,63,-34,63,63,63,63,-44,-52,-28,63,63,-43,-33,63,]),'AT':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,64,-20,-21,-22,-23,-45,64,64,64,64,64,-56,64,64,64,64,64,64,64,64,64,64,64,64,64,-24,64,-42,64,-34,64,64,64,64,-44,-52,-28,64,64,-43,-33,64,]),'PLUS':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,65,-20,-21,-22,-23,-45,65,65,65,65,65,-56,-57,-58,65,65,65,65,-35,-36,-37,-38,65,65,65,-24,65,-42,65,-34,65,65,65,65,-44,-52,-28,65,65,-43,-33,65,]),'MINUS':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,66,-20,-21,-22,-23,-45,66,66,66,66,66,-56,-57,-58,66,66,66,66,-35,-36,-37,-38,66,66,66,-24,66,-42,66,-34,66,66,66,66,-44,-52,-28,66,66,-43,-33,66,]),'MULTIPLY':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,67,-20,-21,-22,-23,-45,67,67,67,67,67,-56,-57,-58,67,67,67,67,67,67,-37,-38,67,67,67,-24,67,-42,67,-34,67,67,67,67,-44,-52,-28,67,67,-43,-33,67,]),'DIVIDE':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,68,-20,-21,-22,-23,-45,68,68,68,68,68,-56,-57,-58,68,68,68,68,68,68,-37,-38,68,68,68,-24,68,-42,68,-34,68,68,68,68,-44,-52,-28,68,68,-43,-33,68,]),'LT':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,69,-20,-21,-22,-23,-45,69,69,69,69,69,-56,-57,-58,69,69,69,69,-35,-36,-37,-38,None,None,None,-24,69,-42,69,-34,69,69,69,69,-44,-52,-28,69,69,-43,-33,69,]),'LTEQ':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,70,-20,-21,-22,-23,-45,70,70,70,70,70,-56,-57,-58,70,70,70,70,-35,-36,-37,-38,None,None,None,-24,70,-42,70,-34,70,70,70,70,-44,-52,-28,70,70,-43,-33,70,]),'EQ':([42,43,44,45,46,47,52,73,74,75,76,77,78,79,80,81,87,88,92,95,96,97,98,99,100,101,102,103,105,112,114,119,120,124,128,132,133,137,139,141,143,145,146,],[-19,71,-20,-21,-22,-23,-45,71,71,71,71,71,-56,-57,-58,71,71,71,71,-35,-36,-37,-38,None,None,None,-24,71,-42,71,-34,71,71,71,71,-44,-52,-28,71,71,-43,-33,71,]),'THEN':([42,44,45,46,47,52,75,78,79,80,81,88,95,96,97,98,99,100,101,102,105,114,124,132,133,137,143,145,],[-19,-20,-21,-22,-23,-45,106,-56,-57,-58,-59,-27,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,-46,-44,-52,-28,-43,-33,]),'LOOP':([42,44,45,46,47,52,76,78,79,80,81,88,95,96,97,98,99,100,101,102,105,114,124,132,133,137,143,145,],[-19,-20,-21,-22,-23,-45,107,-56,-57,-58,-59,-27,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,-46,-44,-52,-28,-43,-33,]),'OF':([42,44,45,46,47,52,77,78,79,80,81,88,95,96,97,98,99,100,101,102,105,114,124,132,133,137,143,145,],[-19,-20,-21,-22,-23,-45,108,-56,-57,-58,-59,-27,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,-46,-44,-52,-28,-43,-33,]),'ELSE':([42,44,45,46,47,52,78,79,80,81,88,95,96,97,98,99,100,101,102,105,114,119,124,132,133,137,143,145,],[-19,-20,-21,-22,-23,-45,-56,-57,-58,-59,-27,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,131,-46,-44,-52,-28,-43,-33,]),'POOL':([42,44,45,46,47,52,78,79,80,81,88,95,96,97,98,99,100,101,102,105,114,120,124,132,133,137,143,145,],[-19,-20,-21,-22,-23,-45,-56,-57,-58,-59,-27,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,132,-46,-44,-52,-28,-43,-33,]),'FI':([42,44,45,46,47,52,78,79,80,81,88,95,96,97,98,99,100,101,102,105,114,124,132,133,137,139,143,145,],[-19,-20,-21,-22,-23,-45,-56,-57,-58,-59,-27,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,-46,-44,-52,-28,143,-43,-33,]),'IN':([42,44,45,46,47,52,78,79,80,81,82,83,85,88,95,96,97,98,99,100,101,102,105,114,124,125,126,132,133,137,141,143,145,],[-19,-20,-21,-22,-23,-45,-56,-57,-58,-59,109,-47,-50,-27,-35,-36,-37,-38,-39,-40,-41,-24,-42,-34,-46,-48,-51,-44,-52,-28,-49,-43,-33,]),'ESAC':([121,122,134,147,],[133,-54,-53,-55,]),'ARROW':([140,],[144,]),} + +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items + +_lr_goto_items = {'program':([0,],[1,]),'class_list':([0,],[2,]),'class':([0,2,],[3,5,]),'features_list_opt':([9,24,],[11,31,]),'features_list':([9,24,],[12,12,]),'empty':([9,24,62,116,138,],[13,13,91,91,91,]),'feature':([9,12,24,],[14,20,14,]),'attribute_init':([9,12,24,],[16,16,16,]),'attribute_def':([9,12,24,],[17,17,17,]),'formal_params_list':([22,],[27,]),'formal_param':([22,34,],[29,40,]),'expression':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,106,107,109,115,116,131,136,138,144,],[43,73,74,75,76,77,79,80,81,87,88,92,95,96,97,98,99,100,101,103,112,119,120,124,128,92,139,141,92,146,]),'let_expression':([36,48,49,50,51,53,55,56,57,60,61,62,65,66,67,68,69,70,71,72,86,106,107,109,115,116,131,136,138,144,],[52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,52,]),'block_list':([48,],[72,]),'nested_vars':([58,],[82,]),'let_var_init':([58,110,],[83,125,]),'let_var_def':([58,110,],[85,85,]),'arguments_list_opt':([62,116,138,],[89,129,142,]),'arguments_list':([62,116,138,],[90,90,90,]),'actions_list':([108,],[121,]),'action':([108,121,],[122,134,]),} + +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +_lr_productions = [ + ("S' -> program","S'",1,None,None,None), + ('program -> class_list','program',1,'p_program','cparser.py',93), + ('class_list -> class_list class SEMICOLON','class_list',3,'p_class_list','cparser.py',99), + ('class_list -> class SEMICOLON','class_list',2,'p_class_list','cparser.py',100), + ('class -> CLASS TYPE LBRACE features_list_opt RBRACE','class',5,'p_class','cparser.py',109), + ('class -> CLASS TYPE INHERITS TYPE LBRACE features_list_opt RBRACE','class',7,'p_class_inherits','cparser.py',116), + ('features_list_opt -> features_list','features_list_opt',1,'p_feature_list_opt','cparser.py',122), + ('features_list_opt -> empty','features_list_opt',1,'p_feature_list_opt','cparser.py',123), + ('features_list -> features_list feature SEMICOLON','features_list',3,'p_feature_list','cparser.py',129), + ('features_list -> feature SEMICOLON','features_list',2,'p_feature_list','cparser.py',130), + ('feature -> ID LPAREN formal_params_list RPAREN COLON TYPE LBRACE expression RBRACE','feature',9,'p_feature_method','cparser.py',139), + ('feature -> ID LPAREN RPAREN COLON TYPE LBRACE expression RBRACE','feature',8,'p_feature_method_no_formals','cparser.py',146), + ('feature -> attribute_init','feature',1,'p_feature_attr_initialized','cparser.py',153), + ('attribute_init -> ID COLON TYPE ASSIGN expression','attribute_init',5,'p_atrribute_init','cparser.py',159), + ('attribute_init -> attribute_def','attribute_init',1,'p_atrribute_init','cparser.py',160), + ('attribute_def -> ID COLON TYPE','attribute_def',3,'p_feature_attr','cparser.py',170), + ('formal_params_list -> formal_params_list COMMA formal_param','formal_params_list',3,'p_formal_list_many','cparser.py',176), + ('formal_params_list -> formal_param','formal_params_list',1,'p_formal_list_many','cparser.py',177), + ('formal_param -> ID COLON TYPE','formal_param',3,'p_formal_param','cparser.py',186), + ('expression -> ID','expression',1,'p_expression_object_identifier','cparser.py',192), + ('expression -> INTEGER','expression',1,'p_expression_integer_constant','cparser.py',198), + ('expression -> TRUE','expression',1,'p_expression_boolean_constant','cparser.py',204), + ('expression -> FALSE','expression',1,'p_expression_boolean_constant','cparser.py',205), + ('expression -> STRING','expression',1,'p_expression_string_constant','cparser.py',211), + ('expression -> LBRACE block_list RBRACE','expression',3,'p_expression_block','cparser.py',223), + ('block_list -> block_list expression SEMICOLON','block_list',3,'p_block_list','cparser.py',229), + ('block_list -> expression SEMICOLON','block_list',2,'p_block_list','cparser.py',230), + ('expression -> ID ASSIGN expression','expression',3,'p_expression_assignment','cparser.py',239), + ('expression -> expression DOT ID LPAREN arguments_list_opt RPAREN','expression',6,'p_expression_dispatch','cparser.py',247), + ('arguments_list_opt -> arguments_list','arguments_list_opt',1,'p_arguments_list_opt','cparser.py',254), + ('arguments_list_opt -> empty','arguments_list_opt',1,'p_arguments_list_opt','cparser.py',255), + ('arguments_list -> arguments_list COMMA expression','arguments_list',3,'p_arguments_list','cparser.py',261), + ('arguments_list -> expression','arguments_list',1,'p_arguments_list','cparser.py',262), + ('expression -> expression AT TYPE DOT ID LPAREN arguments_list_opt RPAREN','expression',8,'p_expression_static_dispatch','cparser.py',271), + ('expression -> ID LPAREN arguments_list_opt RPAREN','expression',4,'p_expression_self_dispatch','cparser.py',278), + ('expression -> expression PLUS expression','expression',3,'p_expression_math_operations','cparser.py',287), + ('expression -> expression MINUS expression','expression',3,'p_expression_math_operations','cparser.py',288), + ('expression -> expression MULTIPLY expression','expression',3,'p_expression_math_operations','cparser.py',289), + ('expression -> expression DIVIDE expression','expression',3,'p_expression_math_operations','cparser.py',290), + ('expression -> expression LT expression','expression',3,'p_expression_math_comparisons','cparser.py',303), + ('expression -> expression LTEQ expression','expression',3,'p_expression_math_comparisons','cparser.py',304), + ('expression -> expression EQ expression','expression',3,'p_expression_math_comparisons','cparser.py',305), + ('expression -> LPAREN expression RPAREN','expression',3,'p_expression_with_parenthesis','cparser.py',316), + ('expression -> IF expression THEN expression ELSE expression FI','expression',7,'p_expression_if_conditional','cparser.py',324), + ('expression -> WHILE expression LOOP expression POOL','expression',5,'p_expression_while_loop','cparser.py',331), + ('expression -> let_expression','expression',1,'p_expression_let','cparser.py',339), + ('let_expression -> LET nested_vars IN expression','let_expression',4,'p_expression_let_simple','cparser.py',345), + ('nested_vars -> let_var_init','nested_vars',1,'p_nested_let_vars','cparser.py',351), + ('nested_vars -> nested_vars COMMA let_var_init','nested_vars',3,'p_nested_let_vars','cparser.py',352), + ('let_var_init -> ID COLON TYPE ASSIGN expression','let_var_init',5,'p_let_var_initialized','cparser.py',361), + ('let_var_init -> let_var_def','let_var_init',1,'p_let_var_initialized','cparser.py',362), + ('let_var_def -> ID COLON TYPE','let_var_def',3,'p_let_var_def','cparser.py',372), + ('expression -> CASE expression OF actions_list ESAC','expression',5,'p_expression_case','cparser.py',380), + ('actions_list -> actions_list action','actions_list',2,'p_actions_list','cparser.py',386), + ('actions_list -> action','actions_list',1,'p_actions_list','cparser.py',387), + ('action -> ID COLON TYPE ARROW expression SEMICOLON','action',6,'p_action_expr','cparser.py',396), + ('expression -> NEW TYPE','expression',2,'p_expression_new','cparser.py',405), + ('expression -> ISVOID expression','expression',2,'p_expression_isvoid','cparser.py',411), + ('expression -> INT_COMP expression','expression',2,'p_expression_integer_complement','cparser.py',417), + ('expression -> NOT expression','expression',2,'p_expression_boolean_complement','cparser.py',423), + ('empty -> ','empty',0,'p_empty','cparser.py',429), +]