diff --git a/2023-11-23.md b/2023-11-23.md new file mode 100644 index 0000000..0702745 --- /dev/null +++ b/2023-11-23.md @@ -0,0 +1,31 @@ +# IF688 - Teoria e Implementação de Linguagens Computacionais + +## Introdução a Análise Semântica e _Abstract Syntax Trees_ + +### Objetivo + +O objetivo desta aula é introduzir o conceito de análise semântica e apresentar árvores sintáticas abstratas (_abstract syntax trees_ - AST). + +### Questões para Discussão + +- Quais as limitações de gramáticas livres de contexto? +- Que tipo de erro ou problema não pode ser capturado pelas fases de análise léxica e sintática? +- Qual a diferença entre árvores sintáticas concretas e abstratas? + +### Material usado em aula + +- [Slides (pdf)](https://drive.google.com/file/d/1aod-7wnQcyC3SQal8vdmguik3tRij2rA/view?usp=drive_web&authuser=0) +- Código desenvolvido em sala de aula + - [versão para o início da aula (.zip com código incompleto)](https://drive.google.com/file/d/1O9EOYKHKFuTX7nPsNegCXYZKSGc0cHaK/view) + - [versão do código ao final da aula (.zip com código escrito durante a aula)](https://drive.google.com/file/d/1DeIfQtvuWHdZruoaJ7JJ-EFU2Mg7OQFG/view) + +### Vídeos + +- [Análise Semântica - Visão Geral](https://www.youtube.com/watch?v=VvLdrq-CKiI&list=PLHoVp5NAbKJYc5sSNRfOOjjxdp-WgJ8M4) +- [Análise Semântica - Introdução a ASTs](https://www.youtube.com/watch?v=Wz4TSKOrBrM&list=PLHoVp5NAbKJYc5sSNRfOOjjxdp-WgJ8M4&index=5) + +### Links Relacionados + +- [Semantic Analysis](https://en.wikipedia.org/wiki/Semantic_analysis_(compilers)) +- [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) +- [Abstract Syntax Tree Implementation Idioms, by Joel Jones](http://www.hillside.net/plop/plop2003/Papers/Jones-ImplementingASTs.pdf) diff --git a/2023-11-23/ast/1.txt b/2023-11-23/ast/1.txt new file mode 100644 index 0000000..95cc64d --- /dev/null +++ b/2023-11-23/ast/1.txt @@ -0,0 +1,2 @@ +42+(57+22); +((12+3)+((4+5)+(7))); \ No newline at end of file diff --git a/2023-11-23/ast/2.txt b/2023-11-23/ast/2.txt new file mode 100644 index 0000000..562bc03 --- /dev/null +++ b/2023-11-23/ast/2.txt @@ -0,0 +1,7 @@ +22+4*10+(3+1); +15+(12); +((((((15))+1+2)+3))); +2*3; +4/2; +1+2; +1-1; \ No newline at end of file diff --git a/2023-11-23/ast/3.txt b/2023-11-23/ast/3.txt new file mode 100644 index 0000000..a6a2f46 --- /dev/null +++ b/2023-11-23/ast/3.txt @@ -0,0 +1,3 @@ +1 + 3 - 4 * 5; +1 + 3 - 4 * x; +w + y - z * x; \ No newline at end of file diff --git a/2023-11-23/ast/astnodes.py b/2023-11-23/ast/astnodes.py new file mode 100644 index 0000000..ac85efa --- /dev/null +++ b/2023-11-23/ast/astnodes.py @@ -0,0 +1,42 @@ +class Expr(object): + pass + +class NumExpr(Expr): + def __init__(self, valor): + self.valor = valor + def __str__(self): + return "NumExpr("+str(self.valor)+")" + +class IdExpr(Expr): + def __init__(self, nome): + self.nome = nome + def __str__(self): + return "IdExpr("+str(self.valor)+")" + +class SumExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "SumExpr("+str(self.esq)+","+str(self.dir)+")" + +class SubExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "SubExpr("+str(self.esq)+","+str(self.dir)+")" + +class MulExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "MulExpr("+str(self.esq)+","+str(self.dir)+")" + +class DivExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "DivExpr("+str(self.esq)+","+str(self.dir)+")" diff --git a/2023-11-23/ast/astvisitor.py b/2023-11-23/ast/astvisitor.py new file mode 100644 index 0000000..7b57097 --- /dev/null +++ b/2023-11-23/ast/astvisitor.py @@ -0,0 +1,9 @@ +class NodeVisitor(object): + def visit(self, node): + method_name = 'visit_' + type(node).__name__ + visitor = getattr(self, method_name, self.generic_visit) + return visitor(node) + + def generic_visit(self, node): + raise Exception('No visit_{} method'.format(type(node).__name__)) + diff --git a/2023-11-23/ast/lexer.py b/2023-11-23/ast/lexer.py new file mode 100644 index 0000000..c294a0c --- /dev/null +++ b/2023-11-23/ast/lexer.py @@ -0,0 +1,165 @@ +import enum +import sys + +class Lexer: + def __init__(self, input): + self.source = input + '\n' #código-fonte (entrada) + self.curChar = '' #caractere atual dentro do código-fonte + self.curPos = -1 + self.nextChar() + pass + + # Processa o proximo caractere + def nextChar(self): + self.curPos = self.curPos + 1 + if self.curPos >= len(self.source): + self.curChar = '\0' #EOF + else: + self.curChar = self.source[self.curPos] + + # Retorna o caractere seguinte (ainda não lido). + def peek(self): + if self.curPos+1 >= len(self.source): + return '\0' + else: + return self.source[self.curPos+1] + + # Token inválido encontrado, método usado para imprimir mensagem de erro e encerrar. + def abort(self, message): + sys.exit("Erro léxico! " + message) + + # Pular espaço em branco, exceto novas linhas, que são usadas como separadores. + def skipWhitespace(self): + while self.curChar == ' ' or self.curChar == '\n' or self.curChar == '\t' or self.curChar == '\r': + self.nextChar() + + # Pular comentários. + def skipComment(self): + if self.curChar=='#': + while self.curChar != '\n': + self.nextChar() + + # Return o próximo token. + def getToken(self): + self.skipWhitespace() + self.skipComment() + token = None + if self.curChar == '+': + token = Token(self.curChar, TokenType.PLUS) + elif self.curChar == '-': + token = Token(self.curChar, TokenType.MINUS) + elif self.curChar == '*': + token = Token(self.curChar, TokenType.ASTERISK) + elif self.curChar == '/': + token = Token(self.curChar, TokenType.SLASH) + elif self.curChar == '(': + token = Token(self.curChar, TokenType.L_PAREN) + elif self.curChar == ')': + token = Token(self.curChar, TokenType.R_PAREN) + elif self.curChar == ';': + token = Token(self.curChar, TokenType.SEMICOLON) + elif self.curChar == '\0': + token = Token(self.curChar, TokenType.EOF) + #se for = EQ, se for == EQEQ + elif self.curChar == '=': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.EQEQ) + else: + token = Token(self.curChar, TokenType.EQ) + elif self.curChar == '!': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.NOTEQ) + else: + self.abort("Esperava o símbolo de = e recebeu "+self.peek()) + elif self.curChar == '>': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.GTEQ) + else: + token = Token(self.curChar, TokenType.GT) + elif self.curChar == '<': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.LTEQ) + else: + token = Token(self.curChar, TokenType.LT) + elif self.curChar == '\"': + self.nextChar() + startPos = self.curPos + while self.curChar != '\"': + if self.curChar == '\\' or self.curChar == '\t' or self.curChar == '\r' or self.curChar == '%': + self.abort("Caractere ilegal dentro de uma string") + self.nextChar() + stringText = self.source[startPos : self.curPos] + token = Token(stringText, TokenType.STRING) + elif self.curChar.isdigit(): + startPos = self.curPos + while self.peek().isdigit(): + self.nextChar() + if self.peek() == '.': #decimais + self.nextChar() + if not self.peek().isdigit(): + self.abort("Caractere ilegal dentro de um número: "+ self.peek()) + while self.peek().isdigit(): + self.nextChar() + number = self.source[startPos : self.curPos + 1] + token = Token(number, TokenType.NUMBER) + elif self.curChar.isalpha(): + startPos = self.curPos + while self.peek().isalnum(): + self.nextChar() + word = self.source[startPos : self.curPos + 1] + keyword = Token.checkIfKeyword(word) + if keyword == None: + token = Token(word, TokenType.IDENT) + else: + token = Token(word, keyword) + else: + #Token desconhecido + self.abort("Token desconhecido: "+self.curChar) + + self.nextChar() + return token + +class Token: + def __init__(self, tokenText, tokenKind): + self.text = tokenText #lexema, a instância específica encontrada + self.kind = tokenKind # o tipo de token (TokenType) classificado + + @staticmethod + def checkIfKeyword(word): + for kind in TokenType: + if kind.name == word.upper() and kind.value > 100 and kind.value < 200: + return kind + return None + +class TokenType(enum.Enum): + EOF = -1 + NUMBER = 1 + IDENT = 2 + STRING = 3 + SEMICOLON = 4 + L_PAREN = 5 + R_PAREN = 6 + #PALAVRAS RESERVADAS + PRINT = 101 + TRUE = 102 + FALSE = 103 + #OPERADORES + EQ = 201 + PLUS = 202 + MINUS = 203 + ASTERISK = 204 + SLASH = 205 + EQEQ = 206 + NOTEQ = 207 + LT = 208 + LTEQ = 209 + GT = 210 + GTEQ = 211 \ No newline at end of file diff --git a/2023-11-23/ast/main.py b/2023-11-23/ast/main.py new file mode 100644 index 0000000..63d6d46 --- /dev/null +++ b/2023-11-23/ast/main.py @@ -0,0 +1,17 @@ +from lexer import * +from parse import * +import sys + +def main(): + if len(sys.argv) != 2: + sys.exit("Erro: Precisamos de um arquivo como argumento.") + with open(sys.argv[1], 'r') as inputFile: + input = inputFile.read() + + lexer = Lexer(input) + parser = Parser(lexer) + exps = parser.parse() + for exp in exps: + print(exp) + +main() \ No newline at end of file diff --git a/2023-11-23/ast/parse.py b/2023-11-23/ast/parse.py new file mode 100644 index 0000000..631d252 --- /dev/null +++ b/2023-11-23/ast/parse.py @@ -0,0 +1,101 @@ +import sys +from lexer import * +from astnodes import * + +class Parser: + def __init__(self, lexer): + self.lexer = lexer + self.curToken = None + self.peekToken = None + self.nextToken() + self.nextToken() + + #Retorna true se o token **atual** casa com kind + def checkToken(self, kind): + return kind == self.curToken.kind + + #Retorna true se o próximo token **(peek)** casa com kind + def checkPeek(self, kind): + return kind == self.peekToken.kind + + def match(self, kind): + if not self.checkToken(kind): + self.abort("Esperava por " + kind.name + ", apareceu " + self.curToken.kind.name) + self.nextToken() + + # Avançando com os ponteiros dos tokens (atual e peek) + def nextToken(self): + self.curToken = self.peekToken + self.peekToken = self.lexer.getToken() + + def abort(self, msg): + sys.exit("Erro sintático: "+msg) + + # S' ::= S$ + # S ::= (E;)* + # E ::= T (("+"|"-") T)* + # T ::= F (("*"|"/") F)* + # F ::= num | id | "(" E ")" + + # S' ::= S$ + def parse(self): + exps = self.S() + self.match(TokenType.EOF) + return exps + + + # S ::= (E;)* + def S(self): + explist = [] + while not(self.checkToken(TokenType.EOF)): + explist.append(self.E()) + self.match(TokenType.SEMICOLON) + return explist + + # E ::= T (("+"|"-") T)* + # E -> T + # E -> T + T + # E -> T - T + def E(self): + e = self.T() + while self.checkToken(TokenType.PLUS) or self.checkToken(TokenType.MINUS): + if self.checkToken(TokenType.PLUS): + self.match(TokenType.PLUS) + t = self.T() + e = SumExpr(e, t) + elif self.checkToken(TokenType.MINUS): + self.match(TokenType.MINUS) + t = self.T() + e = SubExpr(e, t) + return e + # T ::= F (("*"|"/") F)* + def T(self): + e = self.F() + while self.checkToken(TokenType.ASTERISK) or self.checkToken(TokenType.SLASH): + if self.checkToken(TokenType.ASTERISK): + self.match(TokenType.ASTERISK) + f = self.F() + e = MulExpr(e, f) + elif self.checkToken(TokenType.SLASH): + self.match(TokenType.SLASH) + f = self.F() + e = DivExpr(e, f) + return e + # F ::= num | id | "(" E ")" + def F(self): + e = None + if self.checkToken(TokenType.NUMBER): + valorTokenAtual = self.curToken.text + valorNumerico = int(valorTokenAtual) + e = NumExpr(valorNumerico) + self.match(TokenType.NUMBER) + elif self.checkToken(TokenType.IDENT): + e = IdExpr(self.curToken.text) + self.match(TokenType.IDENT) + elif self.checkToken(TokenType.L_PAREN): + self.match(TokenType.L_PAREN) + e = self.E() + self.match(TokenType.R_PAREN) + else: + self.abort("Token inesperado, esperava um número, um identificador, ou um abre parênteses, recebeu: " + self.curToken.text) + return e diff --git a/2023-11-28.md b/2023-11-28.md new file mode 100644 index 0000000..32893d7 --- /dev/null +++ b/2023-11-28.md @@ -0,0 +1,30 @@ +# IF688 - Teoria e Implementação de Linguagens Computacionais + +## Introdução a Análise Semântica e _Abstract Syntax Trees_ + +### Objetivo + +O objetivo desta aula é introduzir os conceitos fundamentais relacionados à extração de informação a partir de árvores sintáticas abstratas, usando o padrão de projeto _Visitor_, que permite introduzir diferentes interpretações de ASTs. + +### Questões para Discussão + +- Quais as diferentes formas de modularizar a implementação de operações que permitam extrair informações de ASTs? +- Como separar sintaxe de interpretação ao implementar ASTs e operações sobre ASTs? + +### Material usado em aula + +- [Slides (pdf)](https://drive.google.com/file/d/1aod-7wnQcyC3SQal8vdmguik3tRij2rA/view?usp=drive_web&authuser=0) +- Código desenvolvido em sala de aula + - [versão para o início da aula (.zip com código incompleto)](https://drive.google.com/file/d/1CRbR5nIYMkKeTnAGezQiI6_MCAIoMse2/view) + - versão do código ao final da aula (.zip com código escrito durante a aula + +### Vídeos + +- [Análise Semântica - Introdução a ASTs](https://www.youtube.com/watch?v=Wz4TSKOrBrM&list=PLHoVp5NAbKJYc5sSNRfOOjjxdp-WgJ8M4&index=5) + +### Links Relacionados + +- [The Visitor Pattern Explained](https://manski.net/2013/05/the-visitor-pattern-explained/) +- [Visitor Pattern](https://en.wikipedia.org/wiki/Visitor_pattern) +- [Visitor Design Pattern](https://sourcemaking.com/design_patterns/visitor) +- [Java Tutorials: Generics](https://docs.oracle.com/javase/tutorial/java/generics/index.html) \ No newline at end of file diff --git a/2023-11-28/ast/1.txt b/2023-11-28/ast/1.txt new file mode 100644 index 0000000..95cc64d --- /dev/null +++ b/2023-11-28/ast/1.txt @@ -0,0 +1,2 @@ +42+(57+22); +((12+3)+((4+5)+(7))); \ No newline at end of file diff --git a/2023-11-28/ast/2.txt b/2023-11-28/ast/2.txt new file mode 100644 index 0000000..562bc03 --- /dev/null +++ b/2023-11-28/ast/2.txt @@ -0,0 +1,7 @@ +22+4*10+(3+1); +15+(12); +((((((15))+1+2)+3))); +2*3; +4/2; +1+2; +1-1; \ No newline at end of file diff --git a/2023-11-28/ast/3.txt b/2023-11-28/ast/3.txt new file mode 100644 index 0000000..a6a2f46 --- /dev/null +++ b/2023-11-28/ast/3.txt @@ -0,0 +1,3 @@ +1 + 3 - 4 * 5; +1 + 3 - 4 * x; +w + y - z * x; \ No newline at end of file diff --git a/2023-11-28/ast/astnodes.py b/2023-11-28/ast/astnodes.py new file mode 100644 index 0000000..ac85efa --- /dev/null +++ b/2023-11-28/ast/astnodes.py @@ -0,0 +1,42 @@ +class Expr(object): + pass + +class NumExpr(Expr): + def __init__(self, valor): + self.valor = valor + def __str__(self): + return "NumExpr("+str(self.valor)+")" + +class IdExpr(Expr): + def __init__(self, nome): + self.nome = nome + def __str__(self): + return "IdExpr("+str(self.valor)+")" + +class SumExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "SumExpr("+str(self.esq)+","+str(self.dir)+")" + +class SubExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "SubExpr("+str(self.esq)+","+str(self.dir)+")" + +class MulExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "MulExpr("+str(self.esq)+","+str(self.dir)+")" + +class DivExpr(Expr): + def __init__(self, e_esq, e_dir): + self.esq = e_esq + self.dir = e_dir + def __str__(self): + return "DivExpr("+str(self.esq)+","+str(self.dir)+")" diff --git a/2023-11-28/ast/astvisitor.py b/2023-11-28/ast/astvisitor.py new file mode 100644 index 0000000..7b57097 --- /dev/null +++ b/2023-11-28/ast/astvisitor.py @@ -0,0 +1,9 @@ +class NodeVisitor(object): + def visit(self, node): + method_name = 'visit_' + type(node).__name__ + visitor = getattr(self, method_name, self.generic_visit) + return visitor(node) + + def generic_visit(self, node): + raise Exception('No visit_{} method'.format(type(node).__name__)) + diff --git a/2023-11-28/ast/lexer.py b/2023-11-28/ast/lexer.py new file mode 100644 index 0000000..c294a0c --- /dev/null +++ b/2023-11-28/ast/lexer.py @@ -0,0 +1,165 @@ +import enum +import sys + +class Lexer: + def __init__(self, input): + self.source = input + '\n' #código-fonte (entrada) + self.curChar = '' #caractere atual dentro do código-fonte + self.curPos = -1 + self.nextChar() + pass + + # Processa o proximo caractere + def nextChar(self): + self.curPos = self.curPos + 1 + if self.curPos >= len(self.source): + self.curChar = '\0' #EOF + else: + self.curChar = self.source[self.curPos] + + # Retorna o caractere seguinte (ainda não lido). + def peek(self): + if self.curPos+1 >= len(self.source): + return '\0' + else: + return self.source[self.curPos+1] + + # Token inválido encontrado, método usado para imprimir mensagem de erro e encerrar. + def abort(self, message): + sys.exit("Erro léxico! " + message) + + # Pular espaço em branco, exceto novas linhas, que são usadas como separadores. + def skipWhitespace(self): + while self.curChar == ' ' or self.curChar == '\n' or self.curChar == '\t' or self.curChar == '\r': + self.nextChar() + + # Pular comentários. + def skipComment(self): + if self.curChar=='#': + while self.curChar != '\n': + self.nextChar() + + # Return o próximo token. + def getToken(self): + self.skipWhitespace() + self.skipComment() + token = None + if self.curChar == '+': + token = Token(self.curChar, TokenType.PLUS) + elif self.curChar == '-': + token = Token(self.curChar, TokenType.MINUS) + elif self.curChar == '*': + token = Token(self.curChar, TokenType.ASTERISK) + elif self.curChar == '/': + token = Token(self.curChar, TokenType.SLASH) + elif self.curChar == '(': + token = Token(self.curChar, TokenType.L_PAREN) + elif self.curChar == ')': + token = Token(self.curChar, TokenType.R_PAREN) + elif self.curChar == ';': + token = Token(self.curChar, TokenType.SEMICOLON) + elif self.curChar == '\0': + token = Token(self.curChar, TokenType.EOF) + #se for = EQ, se for == EQEQ + elif self.curChar == '=': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.EQEQ) + else: + token = Token(self.curChar, TokenType.EQ) + elif self.curChar == '!': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.NOTEQ) + else: + self.abort("Esperava o símbolo de = e recebeu "+self.peek()) + elif self.curChar == '>': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.GTEQ) + else: + token = Token(self.curChar, TokenType.GT) + elif self.curChar == '<': + if self.peek() == '=': + c = self.curChar + self.nextChar() + token = Token(c + self.curChar, TokenType.LTEQ) + else: + token = Token(self.curChar, TokenType.LT) + elif self.curChar == '\"': + self.nextChar() + startPos = self.curPos + while self.curChar != '\"': + if self.curChar == '\\' or self.curChar == '\t' or self.curChar == '\r' or self.curChar == '%': + self.abort("Caractere ilegal dentro de uma string") + self.nextChar() + stringText = self.source[startPos : self.curPos] + token = Token(stringText, TokenType.STRING) + elif self.curChar.isdigit(): + startPos = self.curPos + while self.peek().isdigit(): + self.nextChar() + if self.peek() == '.': #decimais + self.nextChar() + if not self.peek().isdigit(): + self.abort("Caractere ilegal dentro de um número: "+ self.peek()) + while self.peek().isdigit(): + self.nextChar() + number = self.source[startPos : self.curPos + 1] + token = Token(number, TokenType.NUMBER) + elif self.curChar.isalpha(): + startPos = self.curPos + while self.peek().isalnum(): + self.nextChar() + word = self.source[startPos : self.curPos + 1] + keyword = Token.checkIfKeyword(word) + if keyword == None: + token = Token(word, TokenType.IDENT) + else: + token = Token(word, keyword) + else: + #Token desconhecido + self.abort("Token desconhecido: "+self.curChar) + + self.nextChar() + return token + +class Token: + def __init__(self, tokenText, tokenKind): + self.text = tokenText #lexema, a instância específica encontrada + self.kind = tokenKind # o tipo de token (TokenType) classificado + + @staticmethod + def checkIfKeyword(word): + for kind in TokenType: + if kind.name == word.upper() and kind.value > 100 and kind.value < 200: + return kind + return None + +class TokenType(enum.Enum): + EOF = -1 + NUMBER = 1 + IDENT = 2 + STRING = 3 + SEMICOLON = 4 + L_PAREN = 5 + R_PAREN = 6 + #PALAVRAS RESERVADAS + PRINT = 101 + TRUE = 102 + FALSE = 103 + #OPERADORES + EQ = 201 + PLUS = 202 + MINUS = 203 + ASTERISK = 204 + SLASH = 205 + EQEQ = 206 + NOTEQ = 207 + LT = 208 + LTEQ = 209 + GT = 210 + GTEQ = 211 \ No newline at end of file diff --git a/2023-11-28/ast/main.py b/2023-11-28/ast/main.py new file mode 100644 index 0000000..63d6d46 --- /dev/null +++ b/2023-11-28/ast/main.py @@ -0,0 +1,17 @@ +from lexer import * +from parse import * +import sys + +def main(): + if len(sys.argv) != 2: + sys.exit("Erro: Precisamos de um arquivo como argumento.") + with open(sys.argv[1], 'r') as inputFile: + input = inputFile.read() + + lexer = Lexer(input) + parser = Parser(lexer) + exps = parser.parse() + for exp in exps: + print(exp) + +main() \ No newline at end of file diff --git a/2023-11-28/ast/parse.py b/2023-11-28/ast/parse.py new file mode 100644 index 0000000..631d252 --- /dev/null +++ b/2023-11-28/ast/parse.py @@ -0,0 +1,101 @@ +import sys +from lexer import * +from astnodes import * + +class Parser: + def __init__(self, lexer): + self.lexer = lexer + self.curToken = None + self.peekToken = None + self.nextToken() + self.nextToken() + + #Retorna true se o token **atual** casa com kind + def checkToken(self, kind): + return kind == self.curToken.kind + + #Retorna true se o próximo token **(peek)** casa com kind + def checkPeek(self, kind): + return kind == self.peekToken.kind + + def match(self, kind): + if not self.checkToken(kind): + self.abort("Esperava por " + kind.name + ", apareceu " + self.curToken.kind.name) + self.nextToken() + + # Avançando com os ponteiros dos tokens (atual e peek) + def nextToken(self): + self.curToken = self.peekToken + self.peekToken = self.lexer.getToken() + + def abort(self, msg): + sys.exit("Erro sintático: "+msg) + + # S' ::= S$ + # S ::= (E;)* + # E ::= T (("+"|"-") T)* + # T ::= F (("*"|"/") F)* + # F ::= num | id | "(" E ")" + + # S' ::= S$ + def parse(self): + exps = self.S() + self.match(TokenType.EOF) + return exps + + + # S ::= (E;)* + def S(self): + explist = [] + while not(self.checkToken(TokenType.EOF)): + explist.append(self.E()) + self.match(TokenType.SEMICOLON) + return explist + + # E ::= T (("+"|"-") T)* + # E -> T + # E -> T + T + # E -> T - T + def E(self): + e = self.T() + while self.checkToken(TokenType.PLUS) or self.checkToken(TokenType.MINUS): + if self.checkToken(TokenType.PLUS): + self.match(TokenType.PLUS) + t = self.T() + e = SumExpr(e, t) + elif self.checkToken(TokenType.MINUS): + self.match(TokenType.MINUS) + t = self.T() + e = SubExpr(e, t) + return e + # T ::= F (("*"|"/") F)* + def T(self): + e = self.F() + while self.checkToken(TokenType.ASTERISK) or self.checkToken(TokenType.SLASH): + if self.checkToken(TokenType.ASTERISK): + self.match(TokenType.ASTERISK) + f = self.F() + e = MulExpr(e, f) + elif self.checkToken(TokenType.SLASH): + self.match(TokenType.SLASH) + f = self.F() + e = DivExpr(e, f) + return e + # F ::= num | id | "(" E ")" + def F(self): + e = None + if self.checkToken(TokenType.NUMBER): + valorTokenAtual = self.curToken.text + valorNumerico = int(valorTokenAtual) + e = NumExpr(valorNumerico) + self.match(TokenType.NUMBER) + elif self.checkToken(TokenType.IDENT): + e = IdExpr(self.curToken.text) + self.match(TokenType.IDENT) + elif self.checkToken(TokenType.L_PAREN): + self.match(TokenType.L_PAREN) + e = self.E() + self.match(TokenType.R_PAREN) + else: + self.abort("Token inesperado, esperava um número, um identificador, ou um abre parênteses, recebeu: " + self.curToken.text) + return e diff --git a/2023-11-28/basic/astnodes.py b/2023-11-28/basic/astnodes.py new file mode 100644 index 0000000..f2ba19b --- /dev/null +++ b/2023-11-28/basic/astnodes.py @@ -0,0 +1,11 @@ +class Stmt(object): + pass + +class Expr(object): + pass + +class ArithExpr(Expr): + pass + +class BooleanExpr(Expr): + pass \ No newline at end of file diff --git a/2023-11-28/basic/astvisitor.py b/2023-11-28/basic/astvisitor.py new file mode 100644 index 0000000..7b57097 --- /dev/null +++ b/2023-11-28/basic/astvisitor.py @@ -0,0 +1,9 @@ +class NodeVisitor(object): + def visit(self, node): + method_name = 'visit_' + type(node).__name__ + visitor = getattr(self, method_name, self.generic_visit) + return visitor(node) + + def generic_visit(self, node): + raise Exception('No visit_{} method'.format(type(node).__name__)) + diff --git a/2023-11-28/basic/basic.py b/2023-11-28/basic/basic.py new file mode 100644 index 0000000..0820702 --- /dev/null +++ b/2023-11-28/basic/basic.py @@ -0,0 +1,17 @@ +from lexer import * +from parse import * +import sys + +def main(): + if len(sys.argv) != 2: + sys.exit("Erro: Precisamos de um arquivo como argumento.") + with open(sys.argv[1], 'r') as inputFile: + input = inputFile.read() + + lexer = Lexer(input) + parser = Parser(lexer) + parser.parse() + print("Terminamos.") + + +main() \ No newline at end of file diff --git a/2023-11-28/basic/compare.basic b/2023-11-28/basic/compare.basic new file mode 100644 index 0000000..7a4461b --- /dev/null +++ b/2023-11-28/basic/compare.basic @@ -0,0 +1,11 @@ +PRINT "Type a number" +INPUT num +IF num > 100 THEN + PRINT "Maior que 100" +ENDIF +IF num < 100 THEN + PRINT "Menor que 100" +ENDIF +IF num == 100 THEN + PRINT "Digitou 100" +ENDIF \ No newline at end of file diff --git a/2023-11-28/basic/fib.basic b/2023-11-28/basic/fib.basic new file mode 100644 index 0000000..158c194 --- /dev/null +++ b/2023-11-28/basic/fib.basic @@ -0,0 +1,11 @@ +PRINT "How many fibonacci numbers do you want?" +INPUT nums +LET a = 0 +LET b = 1 +WHILE nums > 0 REPEAT + PRINT a + LET c = a + b + LET a = b + LET b = c + LET nums = nums - 1 +ENDWHILE \ No newline at end of file diff --git a/2023-11-28/basic/lexer.py b/2023-11-28/basic/lexer.py new file mode 100644 index 0000000..a4f8dc6 --- /dev/null +++ b/2023-11-28/basic/lexer.py @@ -0,0 +1,165 @@ +import enum +import sys + +class Lexer: + def __init__(self, input): + self.codigoFonte = input + '\n' + self.caractereAtual = '' + self.posicaoAtual = -1 + self.nextChar() + + # Processa o proximo caractere + def nextChar(self): + self.posicaoAtual += 1 + if self.posicaoAtual >= len(self.codigoFonte): + self.caractereAtual = '\0' #EOF + else: + self.caractereAtual = self.codigoFonte[self.posicaoAtual] + + # Retorna o caractere seguinte (ainda não lido). + def peek(self): + if self.posicaoAtual + 1 >= len(self.codigoFonte): + return '\0' + return self.codigoFonte[self.posicaoAtual+1] + + # Token inválido encontrado, método usado para imprimir mensagem de erro e encerrar. + def abort(self, message): + sys.exit("Erro léxico! "+message) + + # Pular espaço em branco, exceto novas linhas, que são usadas como separadores. + def skipWhitespace(self): + while self.caractereAtual == ' ' or self.caractereAtual == '\t' or self.caractereAtual == '\r': + self.nextChar() + + # Pular comentários. + def skipComment(self): + pass + + # Return o próximo token. + def getToken(self): + self.skipWhitespace() + token = None + + # Olha para o caractere atual e vê se dá pra decidir qual o token + if self.caractereAtual == '+': + token = Token(self.caractereAtual, TokenType.PLUS) #RETORNAR SIMBOLO DE ADICAO + elif self.caractereAtual == '-': + token = Token(self.caractereAtual, TokenType.MINUS) #RETORNAR SIMBOLO DE SUBTRACAO + elif self.caractereAtual == '*': + token = Token(self.caractereAtual, TokenType.ASTERISK) #RETORNAR SIMBOLO DE MULTIPLICACAO + elif self.caractereAtual == '/': + token = Token(self.caractereAtual, TokenType.SLASH) #RETORNAR SIMBOLO DE DIVISAO + elif self.caractereAtual == '\n': + token = Token(self.caractereAtual, TokenType.QUEBRA_LINHA) #RETORNAR SIMBOLO DE QUEBRA DE LINHA + elif self.caractereAtual == '\0': + token = Token(self.caractereAtual, TokenType.EOF) #RETORNAR EOF + elif self.caractereAtual == '=': + if self.peek() == '=': + c = self.caractereAtual + self.nextChar() + token = Token(c + self.caractereAtual, TokenType.EQEQ) # RETORNAR Token '==' + else: + token = Token(self.caractereAtual, TokenType.EQ) # RETORNAR Token '=' + elif self.caractereAtual == '>': + if self.peek() == '=': + c = self.caractereAtual + self.nextChar() + token = Token(c + self.caractereAtual, TokenType.GTEQ) # RETORNAR Token '>=' + else: + token = Token(self.caractereAtual, TokenType.GT) # RETORNAR Token '>' + elif self.caractereAtual == '<': + if self.peek() == '=': + c = self.caractereAtual + self.nextChar() + token = Token(c + self.caractereAtual, TokenType.LTEQ) # RETORNAR Token '<=' + else: + token = Token(self.caractereAtual, TokenType.LT) # RETORNAR Token '<' + elif self.caractereAtual == '!': + if self.peek() == '=': + c = self.caractereAtual + self.nextChar() + token = Token(c + self.caractereAtual, TokenType.NOTEQ) # RETORNAR Token '!=' + else: + self.abort("Esperava !=, recebeu !"+self.peek()) + elif self.caractereAtual == '\"': + self.nextChar() + posicaoInicial = self.posicaoAtual + while self.caractereAtual != '\"': + if self.caractereAtual == '\r' or self.caractereAtual == '\n' or self.caractereAtual == '\t' or self.caractereAtual == '\\' or self.caractereAtual == '%': + self.abort("Caractere ilegal dentro da string: " + self.caractereAtual) + self.nextChar() + textoString = self.codigoFonte[posicaoInicial : self.posicaoAtual] + token = Token(textoString, TokenType.STRING_LITERAL) + elif self.caractereAtual.isdigit(): + posicaoInicial = self.posicaoAtual + while self.peek().isdigit(): + self.nextChar() + if self.peek() == '.': + self.nextChar() + if not self.peek().isdigit(): + self.abort("Caractere ilegal dentro de um número: "+self.peek()) + while self.peek().isdigit(): + self.nextChar() + + textoNumero = self.codigoFonte[posicaoInicial : self.posicaoAtual + 1] + token = Token(textoNumero, TokenType.NUMERO) + elif self.caractereAtual.isalpha(): + posicaoInicial = self.posicaoAtual + while self.peek().isalnum(): + self.nextChar() + + textoToken = self.codigoFonte[posicaoInicial : self.posicaoAtual + 1] + tipoToken = Token.checkIfKeyword(textoToken) + if tipoToken == None: + token = Token(textoToken, TokenType.IDENTIFICADOR) + else: + token = Token(textoToken, tipoToken) + + else: + self.abort("Token desconhecido iniciado com o caractere: " + self.caractereAtual) + + self.nextChar() + return token + +class Token: + def __init__(self, lexema, tipo):#Token(23,NUMBER) + self.lexema = lexema + self.tipo = tipo + + @staticmethod + def checkIfKeyword(lexema): + for kind in TokenType: + if kind.name == lexema and kind.value >= 100 and kind.value <200: + return kind + return None + +class TokenType(enum.Enum): + EOF = -1 + QUEBRA_LINHA = 0 + NUMERO = 1 + IDENTIFICADOR = 2 + STRING_LITERAL = 3 + #PALAVRAS RESERVADAS + LABEL = 101 + GOTO = 102 + PRINT = 103 + INPUT = 104 + LET = 105 + IF = 106 + THEN = 107 + ENDIF = 108 + WHILE = 109 + REPEAT = 110 + ENDWHILE = 111 + #OPERADORES + EQ = 201 + PLUS = 202 + MINUS = 203 + ASTERISK = 204 + SLASH = 205 + EQEQ = 206 + NOTEQ = 207 + LT = 208 + LTEQ = 209 + GT = 210 + GTEQ = 211 \ No newline at end of file diff --git a/2023-11-28/basic/parse.py b/2023-11-28/basic/parse.py new file mode 100644 index 0000000..952b070 --- /dev/null +++ b/2023-11-28/basic/parse.py @@ -0,0 +1,134 @@ +import sys +from lexer import * + +class Parser: + def __init__(self, lexer): + self.lexer = lexer + self.tokenAtual = None + self.proximoToken = None + self.nextToken() + self.nextToken() + + #Retorna true se o Token **atual** casa com tipo de Token esperado + def checkToken(self, tipo): + return tipo == self.tokenAtual.tipo + + #Retorna true se o próximo Token **(peek)** casa com tipo de Token esperado + def checkPeek(self, tipo): + return tipo == self.proximoToken.tipo + + #Tenta fazer o casamento do Token atual. Se conseguir, avança para o próximo Token. Do contrário, gera mensagem de erro. + def match(self, tipo): + if not self.checkToken(tipo): + self.abort("Esperava por token do tipo " + tipo.name + ", mas apareceu " + self.tokenAtual.tipo.name) + else: + self.nextToken() + + # Avançando com os ponteiros dos tokens (atual e peek) + def nextToken(self): + self.tokenAtual = self.proximoToken + self.proximoToken = self.lexer.getToken() + + def abort(self, msg): + sys.exit("Erro sintático: "+msg) + + def parse(self): + self.program() + + # program ::= statement + def program(self): + while not self.checkToken(TokenType.EOF): + self.statement() + + # statement ::= + # PRINT expression nl | + # INPUT IDENTIFICADOR nl | + # LET IDENTIFICADOR "=" expression nl | + # WHILE expression REPEAT nl {statement} ENDWHILE nl | + # IF expression "THEN" nl {statement} ENDIF nl + def statement(self): + # PRINT expression nl | + if self.checkToken(TokenType.PRINT): + self.match(TokenType.PRINT) + self.expression() + # INPUT IDENTIFICADOR nl | + elif self.checkToken(TokenType.INPUT): + self.match(TokenType.INPUT) + self.match(TokenType.IDENTIFICADOR) + # LET IDENTIFICADOR "=" expression nl | + elif self.checkToken(TokenType.LET): + self.match(TokenType.LET) + self.match(TokenType.IDENTIFICADOR) + self.match(TokenType.EQ) + self.expression() + # WHILE expression REPEAT nl {statement} ENDWHILE nl | + elif self.checkToken(TokenType.WHILE): + self.match(TokenType.WHILE) + self.expression() + self.match(TokenType.REPEAT) + self.nl() + while not self.checkToken(TokenType.ENDWHILE): + self.statement() + self.match(TokenType.ENDWHILE) + # IF expression "THEN" nl {statement} ENDIF nl + elif self.checkToken(TokenType.IF): + self.match(TokenType.IF) + self.expression() + self.match(TokenType.THEN) + self.nl() + while not self.checkToken(TokenType.ENDIF): + self.statement() + self.match(TokenType.ENDIF) + else: + self.abort("Problema com " + self.tokenAtual.lexema + " (" + self.tokenAtual.tipo.name + ")") + self.nl() + + def nl(self): + self.match(TokenType.QUEBRA_LINHA) + while self.checkToken(TokenType.QUEBRA_LINHA): + self.match(TokenType.QUEBRA_LINHA) + + # expression ::== equality + def expression(self): + self.equality() + # equality ::== comparison ( ("==" | "!=" ) comparison)* + def equality(self): + self.comparison() + while self.checkToken(TokenType.EQEQ) or self.checkToken(TokenType.NOTEQ): + self.nextToken() + self.comparison() + # comparison ::== term ( ("<" | "<=" | ">" | ">=" ) term)* + def comparison(self): + self.term() + while self.checkToken(TokenType.LT) or self.checkToken(TokenType.LTEQ) or self.checkToken(TokenType.GT) or self.checkToken(TokenType.GTEQ): + self.nextToken() + self.term() + # term ::== factor {("-" | "+") factor} + def term(self): + self.factor() + while self.checkToken(TokenType.MINUS) or self.checkToken(TokenType.PLUS): + self.nextToken() + self.factor() + # factor ::== unary {("*" | "/") unary} + def factor(self): + self.unary() + while self.checkToken(TokenType.ASTERISK) or self.checkToken(TokenType.SLASH): + self.nextToken() + self.unary() + # unary ::== ["-" | "+" ] unary | primary + def unary(self): + if self.checkToken(TokenType.MINUS) or self.checkToken(TokenType.PLUS): + self.nextToken() + self.unary() + else: + self.primary() + # primary ::== NUM | ID | STRING + def primary(self): + if self.checkToken(TokenType.NUMERO): + self.match(TokenType.NUMERO) + elif self.checkToken(TokenType.IDENTIFICADOR): + self.match(TokenType.IDENTIFICADOR) + elif self.checkToken(TokenType.STRING_LITERAL): + self.match(TokenType.STRING_LITERAL) + else: + self.abort("Token inesperado, esperava por NUMERO, IDENTIFICADOR, ou STRING_LITERAL, apareceu: " + self.tokenAtual.tipo.name + " (" + self.tokenAtual.lexema + ")") \ No newline at end of file diff --git a/2023-11-28/basic/print.basic b/2023-11-28/basic/print.basic new file mode 100644 index 0000000..33ca8a7 --- /dev/null +++ b/2023-11-28/basic/print.basic @@ -0,0 +1,31 @@ +PRINT y>=0 + +INPUT x + +LET m = x+2*3 +LET b = x > 12 + 12 +LET s = "oi" + +PRINT a == b == c != d == e + +PRINT 1*y + +PRINT "w" + +IF x+2 THEN + + WHILE z == w REPEAT + PRINT z + IF w > 0 THEN + LET w = w-1 + ENDIF + LET z = z+1 + ENDWHILE + + PRINT x + + INPUT u + +ENDIF + +PRINT x+2 diff --git a/README.md b/README.md index 02b9aee..cc5c724 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,12 @@ Na disciplina, utilizaremos uma mistura de aulas tradicionais com exercícios e | 14.11.23 | Terça | [Análise Sintática Top-Down - Recursive Descent Parsing](2023-11-14.md) | | | 16.11.23 | Quinta | [Análise Sintática Bottom-Up - Intro e LR(0)](2023-11-16.md) | | | 21.11.23 | Terça | [Análise Sintática Bottom-Up - LR(1) Parsing](2023-11-21.md) | | -| 23.11.23 | Quinta | Análise Semântica | | -| 28.11.23 | Terça | Análise Semântica | | -| 30.11.23 | Quinta | APS - Exercícios | | +| 23.11.23 | Quinta | [Análise Semântica - Intro e ASTs](2023-11-23.md) | | +| 28.11.23 | Terça | [Análise Semântica - ASTs e Visitors](2023-11-28.md) | | +| 30.11.23 | Quinta | [Análise Semântica - Tipos e Escopo](2023-11-30.md) | | | 05.12.23 | Terça | APS - Exercícios | | | 07.12.23 | Quinta | APS - Exercícios | | -| 12.12.23 | Terça | Análise Semântica | | +| 12.12.23 | Terça | [Análise Semântica - Tabelas de Símbolos](#) | | | 14.12.23 | Quinta | Análise Semântica | | | 19.12.23 | Terça | **1 Exercício Escolar** | | | 21.12.23 | Quinta | APS - Exercícios | |