Escapar: hacer que el caractér sea tomado cómo texto plano en lugar de su significado por defecto en la expreción regular.
Introducción al desarrollo de intérpretes y lenguajes de programación
Aprende a desarrollar lenguajes de programación con intérpretes
Desarrolla LPP o Lenguaje de Programación Platzi
Construcción del lexer o tokenizador
¿Qué es análisis léxico? Funcionamiento del lexer y tokens
Estructura y definición de tokens en Python
Lectura de caracteres y tokens
Tokens ilegales, operadores de un solo carácter y delimitadores
Reconocimiento y diferenciación entre letras y números
Declaración y ejecución de funciones
Extensión del lexer: condicionales, operaciones y booleanos
Operadores de dos caracteres
Primera versión del REPL con tokens
Construcción del parser o analizador sintáctico
¿Qué es un parser y AST?
Estructura y definición de nodos del AST en Python
Parseo del programa o nodo principal
Parseo de assignment statements
Parseo de let statements
Parseo de errores
Parseo del return statement
Técnicas de parsing y pratt parsing
Pruebas del AST
Implementación del pratt parser
Parseo de Identifiers: testing
Parseo de Identifiers: implementación
Parseo de enteros
Prefix operators: negación y negativos
Infix operators y orden de las operaciones: testing
Infix operators y orden de las operaciones: implementación
Parseo de booleanos
Desafío: testing de infix operators y booleanos
Parseo de expresiones agrupadas
Parseo de condicionales: testing y AST
Parseo de condicionales: implementación
Parseo de declaración de funciones: testing
Parseo de declaración de funciones: AST e implementación
Parseo de llamadas a funciones: testing y AST
Parseo de llamadas a funciones: implementación
Completando los TODOs o pendientes del lexer
Segunda versión del REPL con AST
Evaluación o análisis semántico
Significado de símbolos
Estrategias de evaluación para intérpretes de software
Representación de objetos
Evaluación de expresiones: enteros
Evaluación de expresiones: booleanos y nulos
Evaluación de expresiones: prefix
Evaluación de expresiones: infix
Evaluación de condicionales
Evaluación del return statement
Manejo de errores
Ambiente
Bindings
Evaluación de funciones
Llamadas a funciones
Mejora del intérprete
Implementación de strings
Operaciones con strings
Built-in functions: objeto y tests
Built-in functions: evaluación
Siguientes pasos
Retos para expandir tu intérprete
Continúa con el Curso de Creación de Compiladores de Software
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
No se trata de lo que quieres comprar, sino de quién quieres ser. Invierte en tu educación con el precio especial
Antes: $249
Paga en 4 cuotas sin intereses
Termina en:
Aportes 13
Preguntas 1
Escapar: hacer que el caractér sea tomado cómo texto plano en lugar de su significado por defecto en la expreción regular.
Como programadores debemos siempre optimizar el código para que sea fácil de leer y darle mantenimiento, por eso modifiqué la función next_token para que no hay a necesidad de hacer un elif cada vez que agregue un nuevo token.
.
Con esta función, solo agregamos los token válidos a un diccionario, donde la expresión regular es la clave y el el tipo de token es el valor. Luego, un loop evaluará las expresiones y si pasa correctamente, le asignará el tipo que e fue asociado en el diccionario. Si ninguna expresión regular “machea”, entonces le asigna TokenType.ILEGAL
def next_token(self) -> Token:
token_dict = {
"^=$": TokenType.ASSIGN,
"^\+$": TokenType.PLUS,
"^\($": TokenType.LPAREN,
"^\)$": TokenType.RPAREN,
"^{$": TokenType.LBRACE,
"^}$": TokenType.RBRACE,
"^,$": TokenType.COMMA,
"^;$": TokenType.SEMICOLON,
"^$": TokenType.EOF,
}
token = None
for regex, token_type in token_dict.items():
if match(regex, self._character):
token = Token(token_type, self._character)
break
if token is None:
token = Token(TokenType.ILLEGAL, self._character)
self._read_character()
return token
Decidí crear otra función para obtener los tipos de token en mi Lexer
class Lexer:
def __init__(self, source: str) -> None:
self._source: str = source
self._character: str = ''
self._read_position: int = 0
self._position: int = 0
self._read_character()
def next_token(self) -> Token:
token_type = self._get_token_type()
token = Token(token_type, self._character)
self._read_character()
return token
def _get_token_type(self):
if match(r'^=$', self._character):
token_type = TokenType.ASSIGN
elif match(r'^\+$', self._character):
token_type = TokenType.PLUS
elif match(r'^$', self._character):
token_type = TokenType.EOF
elif match(r'^\($', self._character):
token_type = TokenType.LPAREN
elif match(r'^\)$', self._character):
token_type = TokenType.RPAREN
elif match(r'^\{$', self._character):
token_type = TokenType.LBRACE
elif match(r'^}$', self._character):
token_type = TokenType.RBRACE
elif match(r'^,$', self._character):
token_type = TokenType.COMMA
elif match(r'^;$', self._character):
token_type = TokenType.SEMICOLON
else:
token_type = TokenType.ILLEGAL
return token_type
def _read_character(self) -> None:
if self._read_position >= len(self._source):
self._character = ''
else:
self._character = self._source[self._read_position]
self._position = self._read_position
self._read_position += 1
Reto conseguido:
.
Mi función de test (lo único que hace es espectar sus tokens y los tipos):
def test_delimiters(self) -> None:
source: str = "(){},;"
lexer: Lexer = Lexer(source)
tokens: List[Token] = []
for i in range(len(source)):
tokens.append(lexer.next_token())
expected_tokens: List[Token] = [
Token(TokenType.LPAREN, "("),
Token(TokenType.RPAREN, ")"),
Token(TokenType.LBRACE, "{"),
Token(TokenType.RBRACE, "}"),
Token(TokenType.COMMA, ","),
Token(TokenType.SEMICOLON, ";"),
]
self.assertEquals(tokens, expected_tokens)
.
Mi función next_token
, realmente al poner demasiados if/else se vuelve un poco complejo de leer así que igual se puede llegar a hacer algo para ello 🤔
def next_token(self) -> Token:
if match(r"^=$", self._character):
token = Token(TokenType.ASSIGN, self._character)
elif match(r"^\+$", self._character):
token = Token(TokenType.PLUS, self._character)
elif match(r"^\($", self._character):
token = Token(TokenType.LPAREN, self._character)
elif match(r"^\)$", self._character):
token = Token(TokenType.RPAREN, self._character)
elif match(r"^{$", self._character):
token = Token(TokenType.LBRACE, self._character)
elif match(r"^}$", self._character):
token = Token(TokenType.RBRACE, self._character)
elif match(r"^,$", self._character):
token = Token(TokenType.COMMA, self._character)
elif match(r"^;$", self._character):
token = Token(TokenType.SEMICOLON, self._character)
elif match(r"^$", self._character):
token = Token(TokenType.EOF, self._character)
else:
token = Token(TokenType.ILLEGAL, self._character)
self._read_character()
return token
Yo estoy haciendo el proyecto con Deno y TypeScript.
Tests:
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts"
import { Token, TokenType } from '../src/token.ts'
import { Lexer } from '../src/lexer.ts'
Deno.test("lexer test", async (t) => {
await t.step("test illegal", () => {
const source = "!¿@"
const lexer = new Lexer(source)
const tokens: Token[] = []
for (let i = 0; i < source.length; i++) {
tokens.push(lexer.nextToken())
}
const expected_tokens = [
new Token(TokenType.ILLEGAL, '!'),
new Token(TokenType.ILLEGAL, '¿'),
new Token(TokenType.ILLEGAL, '@'),
]
assertEquals(tokens, expected_tokens)
})
await t.step('test one character operator', () => {
const source = '+='
const lexer = new Lexer(source)
const tokens: Token[] = []
for (let i = 0; i < source.length; i++) {
tokens.push(lexer.nextToken())
}
const expected_tokens = [
new Token(TokenType.PLUS, '+'),
new Token(TokenType.ASSIGN, '='),
]
assertEquals(tokens, expected_tokens)
})
})
Tokens:
export enum TokenType {
ASSIGN,
COMMA,
EOF,
FUNCTION,
IDENT,
ILLEGAL,
INT,
LBRACE,
LET,
LPAREN,
PLUS,
RBRACE,
RPAREN,
SEMICOLON,
}
export class Token {
tokenType: TokenType
literal: string
constructor(tokenType: TokenType, literal: string) {
this.tokenType = tokenType
this.literal = literal
}
public toString(): string {
return `Type: ${this.tokenType}, Literal: ${this.literal}`
}
}
Lexer:
import { Token, TokenType } from "./token.ts"
export class Lexer {
private source: string
private chraacter: string
private readPosition: number
private position: number
private TOKENS: { [key: string]: TokenType } = {
'=': TokenType.ASSIGN,
',': TokenType.COMMA,
';': TokenType.SEMICOLON,
'+': TokenType.PLUS,
'{': TokenType.LBRACE,
'}': TokenType.RBRACE,
'(': TokenType.LPAREN,
')': TokenType.RPAREN,
'let': TokenType.LET,
'fn': TokenType.FUNCTION,
'': TokenType.EOF,
}
constructor(source: string) {
this.source = source
this.chraacter = ''
this.readPosition = 0
this.position = 0
this.readCharacter()
}
nextToken(): Token {
const tokenType: TokenType = this.TOKENS[this.chraacter] ?? TokenType.ILLEGAL
const token = new Token(tokenType, this.chraacter)
this.readCharacter()
return token
}
private readCharacter(): void {
if (this.readPosition >= this.source.length) this.chraacter = ''
else this.chraacter = this.source[this.readPosition]
this.position = this.readPosition
this.readPosition++
}
}
He aquí mi solución:
# Challenge
def test_delimiters(self) -> None:
source = '(){},;'
lexer: Lexer = Lexer(source)
tokens: List[Token] = []
for i in range(len(source)):
tokens.append(lexer.next_token())
expected_tokens: List[Token] = [
Token(TokenType.LPAREN, '('),
Token(TokenType.RPAREN, ')'),
Token(TokenType.LBRACE, '{'),
Token(TokenType.RBRACE, '}'),
Token(TokenType.COMMA, ','),
Token(TokenType.SEMICOLON, ';'),
]
self.assertEquals(tokens, expected_tokens) # Se revisa que los tokens que aviente el lexer sean especificamente los tokens que tenemos en expected tokens
Condicionales en next_token:
def next_token(self) -> Token:
# Se va a revisar con expresiones regulares
"""
En Python las expresiones regulares empiezan con cadenas row
# Comience al principio de la cadena, encuentre un igual y que termine en esto, se quiere un igual desde el principio hasta el final
"""
if match(r'^=$', self._character):
token = Token(TokenType.ASSIGN, self._character)
elif match(r'^\+$', self._character):
token = Token(TokenType.PLUS, self._character)
elif match(r'^\($', self._character):
token = Token(TokenType.LPAREN, self._character)
elif match(r'^\)$', self._character):
token = Token(TokenType.RPAREN, self._character)
elif match(r'^{$', self._character):
token = Token(TokenType.LBRACE, self._character)
elif match(r'^}$', self._character):
token = Token(TokenType.RBRACE, self._character)
elif match(r'^,$', self._character):
token = Token(TokenType.COMMA, self._character)
elif match(r'^;$', self._character):
token = Token(TokenType.SEMICOLON, self._character)
elif match(r'^$', self._character):
token = Token(TokenType.EOF, self._character)
else: # Si no reconoce entonces dirá que es un Token ilegal
token = Token(TokenType.ILLEGAL, self._character)
"""
Se tiene que escapar especificamente el caracter de suma porque suma significa algo especifico en las expresiones regulares, significa que haga match por lo menos una o mas veces pero aqui no interesa la funcionalidad sino especificamente el caracter, se escapa con la diagonal
"""
# Escapar: hacer que el caractér sea tomado cómo texto plano en lugar de su significado por defecto en la expreción regular.
# Se necesita correrlo despues de que se genere el token y antes de regresarlo
self._read_character()
return token # Se regresa el token
Implemente una clase Tokens para ahorrarnos tantas condiciones, aunque creo que habrá que refactorizarlo en las próximas clases.
class Tokens(object):
TOKENS = {
'=': TokenType.ASSIGN,
',': TokenType.COMMA,
';': TokenType.SEMICOLON,
'+': TokenType.PLUS,
'{': TokenType.LBRACE,
'}': TokenType.RBRACE,
'(': TokenType.LPARENT,
')': TokenType.RPARENT,
'let': TokenType.LET,
'fn': TokenType.FUNCTION,
'': TokenType.EOF,
}
@classmethod
def exists(cls, value: str) -> TokenType:
if value in cls.TOKENS:
return cls.TOKENS[value]
return TokenType.ILLEGAL
En la u nos dejaron las practicas de trabajo pero no podemos utilizar la libreria ‘re’. a puro if else y for. pero las pruebas unitarias estan en todo!
Definición del reto: test_delimiters
Yo tuve un problema a la hora de definir la validación de los tokens de paréntesis, ya que para las expresiones regulares al igual que con el signo suma el paréntesis es considerado un carácter especial. Por lo que se soluciona de la misma forma que con al suma:
Test, la unica diferencia con la que veníamos haciendo es que se separo la generación de la lista de token a una función aparte para que la lectura del test sea un poco más simple
def test_delimeters(self) -> None:
source = '(){},;'
tokens = self._load_tokens(source)
expected_tokens: List[Token] = [
Token(TokenType.LPAREN, '('),
Token(TokenType.RPAREN, ')'),
Token(TokenType.LBRANCE, '{'),
Token(TokenType.RBRACE, '}'),
Token(TokenType.COMMA, ','),
Token(TokenType.SEMICOLON, ';'),
]
self.assertEqual(tokens, expected_tokens)
def _load_tokens(self, source: str) -> List[Token]:
lexer: Lexer = Lexer(source)
tokens: List[Token] = []
for i in range(len(source)):
tokens.append(lexer.next_token())
return tokens
Lexer
def next_token(self) -> Token:
token = self._character_to_token()
self._read_caracter()
return token
def _character_to_token(self) -> Token:
if match(r'^=$', self._character):
token = Token(TokenType.ASSIGN, self._character)
elif match(r'^\+$', self._character):
token = Token(TokenType.PLUS, self._character)
elif match(r'^$', self._character):
token = Token(TokenType.EOF, self._character)
elif match(r'^\($', self._character):
token = Token(TokenType.LPAREN, self._character)
elif match(r'^\)$', self._character):
token = Token(TokenType.RPAREN, self._character)
elif match(r'^\{$', self._character):
token = Token(TokenType.LBRANCE, self._character)
elif match(r'^}$', self._character):
token = Token(TokenType.RBRACE, self._character)
elif match(r'^,$', self._character):
token = Token(TokenType.COMMA, self._character)
elif match(r'^;$', self._character):
token = Token(TokenType.SEMICOLON, self._character)
else:
token = Token(TokenType.ILLEGAL, self._character)
return token
# lexer_test.py
def test_delimeters(self) -> None:
source: str = '(){},;'
lexer: Lexer = Lexer(source)
tokens: List[Token] = []
for i in range(len(source)):
tokens.append(lexer.next_token())
expected_tokens: List[Token] = [
Token(TokenType.LPAREN, '('),
Token(TokenType.RPAREN, ')'),
Token(TokenType.LBRACE, '{'),
Token(TokenType.RBRACE, '}'),
Token(TokenType.COMMA, ','),
Token(TokenType.SEMICOLON, ';'),
]
self.assertEquals(tokens, expected_tokens)
# lexer.py
def next_token(self) -> Token:
# Token '='
if match(r'^=$', self._character):
token = Token(TokenType.ASSIGN, self._character)
# Token '+'
elif match(r'^\+$', self._character):
token = Token(TokenType.PLUS, self._character)
# Token ''
elif match(r'^$', self._character):
token = Token(TokenType.EOF, self._character)
# Token '('
elif match(r'^\($', self._character):
token = Token(TokenType.LPAREN, self._character)
# Token ')'
elif match(r'^\)$', self._character):
token = Token(TokenType.RPAREN, self._character)
# Token '{'
elif match(r'^\{$', self._character):
token = Token(TokenType.LBRACE, self._character)
# Token '}'
elif match(r'^\}$', self._character):
token = Token(TokenType.RBRACE, self._character)
# Token ','
elif match(r'^,$', self._character):
token = Token(TokenType.COMMA, self._character)
# Token ';'
elif match(r'^\;$', self._character):
token = Token(TokenType.SEMICOLON, self._character)
# Illegal Token
else:
token = Token(TokenType.ILLEGAL, self._character)
self._read_character()
return token
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?