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:

11 Días
6 Hrs
3 Min
44 Seg

Extensión del lexer: condicionales, operaciones y booleanos

9/58
Recursos

Aportes 8

Preguntas 2

Ordenar por:

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

Reto resuelto! 😄
.
Primero escribo el test expectando los tokens:

def test_one_character_operator(self) -> None:

        source: str = "=+"
        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.ASSIGN, "="),
            Token(TokenType.PLUS, "+"),
            Token(TokenType.MINUS, "-"),
            Token(TokenType.DIVISION, "/"),
            Token(TokenType.MULTIPLICATION, "*"),
            Token(TokenType.LT, "<"),
            Token(TokenType.GT, ">"),
            Token(TokenType.NEGATION, "!"),
        ]

        self.assertEquals(tokens, expected_tokens)

Después, en la lista de token types se agregan los nuevos tokens:

DIVISION = auto()
GT = auto() # Gretater Than (>)
MINUS = auto() # Resta
MULTIPLICATION = auto()
NEGATION = auto() # Negación (!)

Después se añaden las condiciones al lexer para que pueda retornar los tokens correctos (hay que escapar la multiplicación):

        elif match(r"^-$", self._character):
            token = Token(TokenType.MINUS, self._character)

        elif match(r"^/$", self._character):
            token = Token(TokenType.DIVISION, self._character)

        elif match(r"^\*$", self._character):
            token = Token(TokenType.MULTIPLICATION, self._character)

        elif match(r"^<$", self._character):
            token = Token(TokenType.LT, self._character)

        elif match(r"^>$", self._character):
            token = Token(TokenType.GT, self._character)

        elif match(r"^!$", self._character):
            token = Token(TokenType.NEGATION, self._character)

¡Y listo! 😄
.
https://github.com/RetaxMaster/lpp/commit/b935aa10e833565492fea8935a68e4dd87067c66

Quise incluir el NOT pero lo habíamos agregado como un token inválido al inicio, entonces no fue posible y tuve que corregir.

```python def test_control_statement(self) -> None: source: str =''' si(5<10){ regresa verdadero; }si_no{ regresa falso; } ''' lexer: Lexer=Lexer(source) tokens:List[Token] ```
ok

Esta es mi solucion en Deno con TypeScript:
test:

  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.ASSIGN, '='),
      new Token(TokenType.PLUS, '+'),
      new Token(TokenType.MINUS, '-'),
      new Token(TokenType.DIVISION, '/'),
      new Token(TokenType.MULTIPLICATION, '*'),
      new Token(TokenType.LT, '<'),
      new Token(TokenType.RT, '>'),
      new Token(TokenType.NEGATION, '!'),
    ]

    assertEquals(tokens, expected_tokens)
  })

tokens:

export enum TokenType {
  ASSIGN,
  COMMA,
  EOF,
  FUNCTION,
  IDENT,
  ILLEGAL,
  NUMBER,
  LBRACE,
  LET,
  LPAREN,
  PLUS,
  RBRACE,
  RPAREN,
  SEMICOLON,
  RETURN,
  IF,
 ``` LT,
  RT,
  TRUE,
  FALSE,
  ELSE,
  MINUS,
  DIVISION,
  MULTIPLICATION,
  NEGATION
}

export const validCharacters: { [key: string]: TokenType } = {
  ',': TokenType.COMMA,
  ';': TokenType.SEMICOLON,
  '{': TokenType.LBRACE,
  '}': TokenType.RBRACE,
  '(': TokenType.LPAREN,
  ')': TokenType.RPAREN,
  '': TokenType.EOF,
  
  //operators
  '+': TokenType.PLUS,
  '=': TokenType.ASSIGN,
  '<': TokenType.LT,
  '>': TokenType.RT,
  '*': TokenType.MULTIPLICATION,
  '-': TokenType.MINUS,
  '/': TokenType.DIVISION,
  '!': TokenType.NEGATION
}

lexer:

import { Token, TokenType, validCharacters, lookupTokenType } from "./token.ts"

export class Lexer {
    private source: string
    private character: string
    private readPosition: number
    private position: number

    constructor(source: string) {
        this.source = source
        this.character = ''
        this.readPosition = 0
        this.position = 0

        this.readCharacter()
    }

    nextToken(): Token {
        this.skipWhiteSpace()
        let tokenType: TokenType = TokenType.EOF
        let token: Token = new Token(tokenType, '')

        if (this.isValidCharacter(this.character)) {
            tokenType = validCharacters[this.character]
            token = new Token(tokenType, this.character)
        }
        else if (this.isLetter(this.character)) {
            const literal = this.readIdentifier()
            const tokenType = lookupTokenType(literal)
            return new Token(tokenType, literal)
        }
        else if (this.isNumber(this.character)) {
            const literal = this.readNumber()
            return new Token(TokenType.NUMBER, literal)
        }
        else {
            token = new Token(TokenType.ILLEGAL, this.character)
        }

        this.readCharacter()
        return token
    }

    private readIdentifier(): string {
        const initialPosition = this.position

        while (this.isLetter(this.character)) {
            this.readCharacter()
        }

        return this.source.slice(initialPosition, this.position)
    }

    private readCharacter(): void {
        if (this.readPosition >= this.source.length) this.character = ''
        else this.character = this.source[this.readPosition]

        this.position = this.readPosition
        this.readPosition += 1
    }

    private readNumber(): string {
        const initialPosition = this.position

        while (this.isNumber(this.character)) {
            this.readCharacter()
        }

        return this.source.slice(initialPosition, this.position)
    }

    private isLetter(character: string): boolean {
        return /^[a-zA-Z_&]$/.test(character)
    }

    private isValidCharacter(character: string): boolean {
        return Object.keys(validCharacters).includes(character)
    }

    private isNumber(character: string): boolean {
        return /^[0-9.]$/.test(character)
    }

    private skipWhiteSpace(): void {
        while (/^[\s\t]$/.test(this.character)) {
            this.readCharacter()
        }
    }
}

Definición de palabras reservadas para compradores y boleanos

Aquí mi solución:

def test_one_character_operator(self) -> None: # Test para reconocer los operadores de un caracter
        source: str = '=+/-*<>!'
        lexer: Lexer = Lexer(source) # Inicializamos el Lexer pasandole como argumento el source
        tokens: List[Token] = [] # Se genera la lista de tokens que el lexer debería de devolver
        for i in range(len(source)): # Se hace un loop a lo largo de Lexer y en cada loop se llama a next_token para ir populando o añadiendo cada Token a la lista de tokens
            tokens.append(lexer.next_token())

        expected_tokens: List[Token] = [
            Token(TokenType.ASSIGN, '='),
            Token(TokenType.PLUS, '+'),
            Token(TokenType.DIV, '/'),
            Token(TokenType.MINUS, '-'),
            Token(TokenType.MULT, '*'),
            Token(TokenType.LT, '<'),
            Token(TokenType.GT, '>'),
            Token(TokenType.EXC, '!'),
        ]
        self.assertEquals(tokens, expected_tokens)
@unique # Se le añade el decorador unique pora saber que los tokens son unicos, un decorador recibe como parametro una funcion, le añade cosas, la ejecuta y retorna a esta misma función pero ya modificada, por eso se dice que retorna una función diferente
class TokenType(Enum):
    # Es buena practica ponerlos en orden alfabetico
    # auto es que no interesa el valor del enum
    ASSIGN = auto()
    COMMA = auto()
    ELSE = auto()
    EOF = auto()
    EXC = auto()
    DIV = auto()
    FALSE = auto()
    FUNCTION = auto()
    GT = auto()
    IDENT = auto()
    IF = auto()
    ILLEGAL = auto()
    INT = auto()
    LBRACE = auto()
    LET = auto()
    LPAREN = auto()
    LT = auto()
    MINUS = auto()
    MULT = auto()
    PLUS = auto()
    RBRACE = auto()
    RETURN = auto()
    RPAREN = auto()
    SEMICOLON = auto()
    TRUE = auto()

Y la implementación en el lexer

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
        """
        self._skip_whitespace() # Se ignoraran los espacios en blanco siempre al empezar un nuevo token

        if match(r'^=$', self._character):
            token = Token(TokenType.ASSIGN, self._character)
        elif match(r'^\+$', self._character):  # Se escapa por tener un significado especial en las expresiones regulares
            token = Token(TokenType.PLUS, self._character)
        elif match(r'^$', self._character):
            token = Token(TokenType.EOF, self._character)
        elif match(r'^\($', self._character): # Se escapa por tener un significado especial en las expresiones regulares
            token = Token(TokenType.LPAREN, self._character)
        elif match(r'^\)$', self._character): # Se escapa por tener un significado especial en las expresiones regulares
            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.LT, self._character)
        elif match(r'^/$', self._character):
            token = Token(TokenType.DIV, self._character)
        elif match(r'^-$', self._character):
            token = Token(TokenType.MINUS, self._character)
        elif match(r'^\*$', self._character): # Se escapa por tener un significado especial en las expresiones regulares
            token = Token(TokenType.MULT, self._character)
        elif match(r'^>$', self._character):
            token = Token(TokenType.GT, self._character)
        elif match(r'^!$', self._character):
            token = Token(TokenType.EXC, self._character)
        # Funciones auxiliares, en lugar de generar una expresion regular se generan funciones auxiliares
        elif self._is_letter(self._character): # Ahora si nos encontramos frente a un caracter lo que se quiere es generar una literal, donde se genera una funcion y luego se conoce que tipo de Token es
            literal = self._read_identifier()
            # Ahora como podemos tener un identifier o keyword se genera esta funcion
            token_type = lookup_token_type(literal) # Esta funcion se genera en token.py

            return Token(token_type, literal) # Se regresa lo que mande la funcion lookup y la literal
        # Se necesita saber si estamos frente a un numero
        elif self._is_number(self._character):
            literal = self._read_number()

            return Token(TokenType.INT, literal)
        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 invertida
        """
        # Escapar: hacer que el caracter 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

Este curso está muy bueno!

Aquí se me había olvidado poner los tokens en la lista y estuve media hora viendo que pasaba xd

# lexer_test.py

def test_one_character_operator(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.ASSIGN, '='),
                Token(TokenType.PLUS, '+'),
                Token(TokenType.MINUS, "-"),
                Token(TokenType.DIVISION, "/"),
                Token(TokenType.MULTIPLICATION, "*"),
                Token(TokenType.LT, "<"),
                Token(TokenType.GT, ">"),
                Token(TokenType.NEGATION, "!"),
            ]
# lexer.py

	# 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.MINUS, self._character)
        # Token '*'
        elif match(r'^\*$', self._character):
            token = Token(TokenType.MULTIPLICATION, self._character)
        # Token '/'
        elif match(r'^\/$', self._character):
            token = Token(TokenType.DIVISION, self._character)
        # Token '<'
        elif match(r'^<$', self._character):
            token = Token(TokenType.LT, self._character)
        # Token '>'
        elif match(r'^>$', self._character):
            token = Token(TokenType.GT, self._character)
            # Token '!'
        elif match(r'^!$', self._character):
            token = Token(TokenType.NEGATION, self._character)
DIVISION = auto() # /
GT = auto() # >
MINUS = auto() # -
MULTIPLICATION = auto() # *
NEGATION = auto() # !