Fase 2: Implemente lexer.py
🎯 Objetivo
Implementar uma classe Lexer que:
- Carrega código Minerês de arquivo ou string
- Usa o
Automatopara reconhecer tokens individuais - Rastreia posição (linha/coluna) para cada token
- Retorna lista de
Tokenobjects
📖 Conceitos
O que faz o Lexer?
Entrada (arquivo): Processamento: Saída (tokens):
-------------- -------- -----------
"certin" ┐
"c_nao" ├─→ Lexer (usa Automato) ──→ [(CERTIN, line=1, col=1),
"2 uai" │ (C_NAO, line=2, col=1),
│ (INTEGER_LITERAL, line=3, col=1),
┘ (UAI, line=3, col=3)]
Diferença Automato vs Lexer
- Automato: Reconhece UM token de cada vez (entrada arbitrária)
- Lexer: Tokeniza FLUXO de entrada, rastreando posição
💻 Estrutura esperada
Arquivo: src/mineres_compilador/lexer.py
Passo 1: Imports
"""
Módulo Lexer - Análise lexical de programas Minerês.
Responsabilidades:
- Ler arquivo/string de entrada
- Usar automato para reconhecer tokens
- Rastrear linha/coluna
- Tratar identificadores vs palavras-chave
"""
from typing import List, Optional, Generator
from pathlib import Path
from .automato import Automato
from .token import Token
from .tokentype import TokenType, ALL_WORD_TOKENS
Passo 2: Classe Lexer - Estrutura
class Lexer:
"""
Analisador léxico para Minerês.
Atributos:
- automato: Automato carregado
- input_str: Código a analisar
- posicao: Índice atual na entrada
- linha: Número da linha current
- coluna: Número da coluna current
- tokens: Lista de tokens reconhecidos
"""
def __init__(self, caminho_automato: str = "automatos/automato.txt"):
"""Inicializa lexer com automato."""
self.automato = Automato()
self.automato.carregar_do_arquivo(caminho_automato)
self.input_str = ""
self.posicao = 0
self.linha = 1
self.coluna = 1
self.tokens: List[Token] = []
Passo 3: Carregar entrada
def carregar_arquivo(self, caminho: str) -> None:
"""
Carrega arquivo de entrada.
Args:
caminho: Caminho ao arquivo .mineires
TODO:
- Abrir arquivo
- Ler conteúdo completo
- Resetar posição/linha/coluna/tokens
"""
# Exemplo:
# path = Path(caminho)
# with open(path, 'r', encoding='utf-8') as f:
# self.input_str = f.read()
# Resetar estado
def carregar_string(self, conteudo: str) -> None:
"""Carrega entrada a partir de string."""
# TODO: Similar a carregar_arquivo, mas sem arquivo
# self.input_str = conteudo
# Resetar estado
Passo 4: Método principal analisar()
Este método tokeniza entrada completa:
def analisar(self) -> List[Token]:
"""
Realiza análise lexical completa.
Algoritmo:
1. Enquanto houver entrada (self.posicao < len(self.input_str)):
a. Se caractere é whitespace (espaço, tab):
- Pule (mas rastreie coluna)
b. Se caractere é newline:
- Increment linha, resete coluna
c. Senão:
- Reconheça um token via _reconhecer_proximo_token()
- Adicione a self.tokens
Returns:
self.tokens (lista de tokens reconhecidos)
"""
self.tokens = []
# TODO: Implemente while loop acima
Whitespace handling:
if self.input_str[self.posicao] == ' ':
self.posicao += 1
self.coluna += 1
continue
Passo 5: Reconhecer um token
O método mais importante:
def _reconhecer_proximo_token(self) -> Optional[Token]:
"""
Reconhece um token a partir da posição atual.
Algoritmo:
1. Guarde posição inicial (linha_inicio, coluna_inicio)
2. Extraia substring restante: self.input_str[self.posicao:]
3. Use self.automato.reconhecer() para tentar reconhecer
4. Se aceito:
a. Extraia lexeme (substring de comprimento reconhecido)
b. Se token_type é 'IDENTIFIER':
- Verifique se é palavra-chave em ALL_WORD_TOKENS
- Se sim, mude token_type para a palavra-chave
c. Senão, mappe token_type_nome para TokenType enum
5. Se não aceito:
- Considere caractere como erro (UNKNOWN)
6. Atualize self.posicao
7. Atualize linha/coluna (considerando newlines no token)
8. Retorne Token(...) ou None se erro
Exemplo com entrada "certin":
- posicao=0, linha=1, coluna=1
- automato.reconhecer("certin...") → (True, 'CERTIN', 6)
- lexeme = "certin"
- TokenType.CERTIN
- posicao += 6
- Retorna: Token(type=TokenType.CERTIN, lexeme="certin", line=1, column=1)
"""
# TODO: Implemente algoritmo
Pseudo-código:
linha_inicio = self.linha
coluna_inicio = self.coluna
restante = self.input_str[self.posicao:]
aceito, token_type_nome, comprimento = self.automato.reconhecer(restante)
if aceito and comprimento > 0:
lexeme = restante[:comprimento]
# Mapeie token_type_nome para TokenType enum
# Verifique se é IDENTIFIER que pode ser palavra-chave
else:
# Caractere não reconhecido
lexeme = restante[0] if restante else ""
token_type = TokenType.UNKNOWN
# Atualize posição
self.posicao += len(lexeme)
# Atualize linha/coluna
for char in lexeme:
if char == '\n':
self.linha += 1
self.coluna = 1
else:
self.coluna += 1
return Token(type=token_type, lexeme=lexeme, line=linha_inicio, column=coluna_inicio)
Passo 6: Gerador (opcional)
Para eficiência em arquivos grandes:
def gerar_tokens(self) -> Generator[Token, None, None]:
"""
Gerador que produz tokens um a um.
Uso:
lexer.carregar_arquivo("programa.mineires")
for token in lexer.gerar_tokens():
print(token)
"""
# TODO: Similar a analisar(), mas não armazena em lista
# Em vez disso: yield token
⚠️ Detalhes importantes
1. Mapeamento de Identificador → Palavra-chave
if token_type_nome == "IDENTIFIER":
# Verificar se é palavra reservada
token_mapeado = ALL_WORD_TOKENS.get(lexeme.lower())
if token_mapeado:
token_type = token_mapeado # É palavra-chave
else:
token_type = TokenType.IDENTIFIER # É identificador normal
2. Rastreamento de linha/coluna
for char in lexeme:
if char == '\n':
self.linha += 1
self.coluna = 1
else:
self.coluna += 1
3. TokenType enum lookup
try:
token_type = TokenType[token_type_nome] # Ex: TokenType["CERTIN"]
except KeyError:
token_type = TokenType.UNKNOWN
✅ Checklist
- Classe
Lexercom inicialização carregar_arquivo()funcionacarregar_string()funcionaanalisar()implementa loop principal_reconhecer_proximo_token()reconhece um token- Whitespace/newline tratados corretamente
- Identificadores vs palavras-chave diferenciados
- TokenType mapeado de nome para enum
- Rastreamento de linha/coluna correto
- Retorna lista de Token objects
🧪 Teste
from src.mineres_compilador.lexer import Lexer
lexer = Lexer()
lexer.carregar_string("certin")
tokens = lexer.analisar()
assert len(tokens) == 1
assert tokens[0].type.value == "CERTIN"
assert tokens[0].lexeme == "certin"
assert tokens[0].line == 1
assert tokens[0].column == 1
print("✅ Lexer funciona!")
Ou teste com arquivo:
lexer.carregar_arquivo("entradas/entrada_exemplo_main.mineires.txt")
tokens = lexer.analisar()
print(f"Total de tokens: {len(tokens)}")
for token in tokens[:5]:
print(f" {token.type.value:20} {token.lexeme:15} L{token.line}:C{token.column}")
📚 Próximo passo
Quando lexer funcionar, avance para Fase 3: Main.py