Introducción al desarrollo de intérpretes y lenguajes de programación

1

Aprende a desarrollar lenguajes de programación con intérpretes

2

Desarrolla LPP o Lenguaje de Programación Platzi

Construcción del lexer o tokenizador

3

¿Qué es análisis léxico? Funcionamiento del lexer y tokens

4

Estructura y definición de tokens en Python

5

Lectura de caracteres y tokens

6

Tokens ilegales, operadores de un solo carácter y delimitadores

7

Reconocimiento y diferenciación entre letras y números

8

Declaración y ejecución de funciones

9

Extensión del lexer: condicionales, operaciones y booleanos

10

Operadores de dos caracteres

11

Primera versión del REPL con tokens

Construcción del parser o analizador sintáctico

12

¿Qué es un parser y AST?

13

Estructura y definición de nodos del AST en Python

14

Parseo del programa o nodo principal

15

Parseo de assignment statements

16

Parseo de let statements

17

Parseo de errores

18

Parseo del return statement

19

Técnicas de parsing y pratt parsing

20

Pruebas del AST

21

Implementación del pratt parser

22

Parseo de Identifiers: testing

23

Parseo de Identifiers: implementación

24

Parseo de enteros

25

Prefix operators: negación y negativos

26

Infix operators y orden de las operaciones: testing

27

Infix operators y orden de las operaciones: implementación

28

Parseo de booleanos

29

Desafío: testing de infix operators y booleanos

30

Parseo de expresiones agrupadas

31

Parseo de condicionales: testing y AST

32

Parseo de condicionales: implementación

33

Parseo de declaración de funciones: testing

34

Parseo de declaración de funciones: AST e implementación

35

Parseo de llamadas a funciones: testing y AST

36

Parseo de llamadas a funciones: implementación

37

Completando los TODOs o pendientes del lexer

38

Segunda versión del REPL con AST

Evaluación o análisis semántico

39

Significado de símbolos

40

Estrategias de evaluación para intérpretes de software

41

Representación de objetos

42

Evaluación de expresiones: enteros

43

Evaluación de expresiones: booleanos y nulos

44

Evaluación de expresiones: prefix

45

Evaluación de expresiones: infix

46

Evaluación de condicionales

47

Evaluación del return statement

48

Manejo de errores

49

Ambiente

50

Bindings

51

Evaluación de funciones

52

Llamadas a funciones

Mejora del intérprete

53

Implementación de strings

54

Operaciones con strings

55

Built-in functions: objeto y tests

56

Built-in functions: evaluación

Siguientes pasos

57

Retos para expandir tu intérprete

58

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

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

12 Días
13 Hrs
54 Min
14 Seg

Tokens ilegales, operadores de un solo carácter y delimitadores

6/58
Recursos

Aportes 13

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

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++
    }
}
que buen material

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