Creación de un lenguaje: tokens

¡Continuamos nuestra serie sobre cómo crear un lenguaje de programación! En esta ocasión, vamos a ver que son los tokens. Estas unidades mínimas en el código fuente son fundamentales para comprender la estructura de un programa. Descubre cómo se clasifican y representan los tokens en nuestro nuevo lenguaje. ¡No te lo pierdas!

Creación de un lenguaje: tokens

Continuando con nuestra serie de posts sobre la creación de un lenguaje de programación, en esta entrega nos adentraremos en el concepto de tokens. Un token es la unidad básica en el código fuente de un programa Codexivo. Representa una parte indivisible del código, como una palabra clave, un identificador, un operador o un literal.

Un token en Codexivo consta de dos componentes principales:

  1. Tipo de Token (TokenType): Indica la categoría o clasificación del token. Por ejemplo, TokenType.IDENT para identificadores, TokenType.KEYWORD para palabras clave, TokenType.OPERATOR para operadores, TokenType.LITERAL para literales, etc. El tipo de token proporciona información sobre la naturaleza del token y cómo debe ser interpretado.
  2. Literal: Es el valor textual asociado al token. Representa la representación exacta del elemento léxico en el código fuente. Por ejemplo, para un token TokenType.IDENT que representa un identificador, el literal podría ser el nombre del identificador ("x", "foo", etc.). Para un token TokenType.LITERAL que representa un valor literal, el literal podría ser un número ("123", "3.14", etc.) o el texto entre comillas de una cadena ("Hola", "mundo", etc.).

Además de estos componentes esenciales, un token en Codexivo puede incluir información adicional dependiendo del contexto y las necesidades del analizador (parser), como la ubicación en el código fuente (línea y columna) para fines de diagnóstico de errores y resaltado de sintaxis.

En resumen, un token en Codexivo está compuesto por un tipo de token que indica su categoría o clasificación, y un literal que representa su valor textual. También puede contener detalles adicionales, como la ubicación en el código fuente, según sea necesario.

En el caso de Codexivo, hemos definido los siguientes TokenTypes que incluyen operadores básicos, y hemos simplificado otros, como los números. En algunos lenguajes, los números se separan en diferentes tipos, como enteros, flotantes y dobles. Sin embargo, en Codexivo utilizamos el TokenType.NUM para manejar tanto enteros como flotantes sin distinción, similar a lenguajes como JavaScript.

export enum TokenType {
  AND = "AND",
  ASSIGN = "ASSIGN",
  ASTERISK = "ASTERISK",
  BANG = "BANG",
  COMMA = "COMMA",
  DO = "DO",
  ELSE = "ELSE",
  EOF = "EOF",
  EQ = "EQ",
  FALSE = "FALSE",
  FOR = "FOR",
  FUNCTION = "FUNCTION",
  GT = "GT",
  GT_EQ = "GT_EQ",
  IDENT = "IDENT",
  IF = "IF",
  ILLEGAL = "ILLEGAL",
  LBRACE = "LBRACE",
  LET = "LET",
  LPAREN = "LPAREN",
  LT = "LT",
  LT_EQ = "LT_EQ",
  MINUS = "MINUS",
  NEQ = "NEQ",
  NOT = "NOT",
  NUM = "NUM",
  OR = "OR",
  PLUS = "PLUS",
  PLUS_EQ = "PLUS_EQ",
  RBRACE = "RBRACE",
  RETURN = "RETURN",
  RPAREN = "RPAREN",
  SEMICOLON = "SEMICOLON",
  SLASH = "SLASH",
  TRUE = "TRUE",
  WHILE = "WHILE",
}

Una vez definidos los TokenTypes, es importante establecer los literales para los símbolos y las palabras clave que se utilizarán en Codexivo. Basándonos en JavaScript, hemos considerado los siguientes símbolos:

const tokenPatterns = {
  "=": TokenType.ASSIGN,
  "==": TokenType.EQ,
  "+": TokenType.PLUS,
  "+=": TokenType.PLUS_EQ,
  "-": TokenType.MINUS,
  "*": TokenType.ASTERISK,
  "/": TokenType.SLASH,
  "<": TokenType.LT,
  "<=": TokenType.LT_EQ,
  ">": TokenType.GT,
  ">=": TokenType.GT_EQ,
  "!": TokenType.BANG,
  ",": TokenType.COMMA,
  ";": TokenType.SEMICOLON,
  "(": TokenType.LPAREN,
  ")": TokenType.RPAREN,
  "{": TokenType.LBRACE,
  "}": TokenType.RBRACE,
  "": TokenType.EOF,
};

Y las palabras clave serian:

const keywords: { [key: string]: TokenType } = {
  falso: TokenType.FALSE,
  procedimiento: TokenType.FUNCTION,
  regresa: TokenType.RETURN,
  si: TokenType.IF,
  si_no: TokenType.ELSE,
  pero_si: TokenType.ELSEIF,
  variable: TokenType.LET,
  verdadero: TokenType.TRUE,
  mientras: TokenType.WHILE,
  hacer: TokenType.DO,
  hasta_que: TokenType.WHILE,
  y: TokenType.AND,
  o: TokenType.OR,
  para: TokenType.FOR,
  no: TokenType.NOT,
};

y de esta forma tenemos todos los tokens que necesitamos para esta primera versión, estos tokens serán usados por el lexer para identificar los tokens en el código fuente como el siguiente:

// Definición de variables
variable n = 10;
variable m = 20;
variable q = "hola mundo";

// Definición de procedimientos
procedimiento suma(a, b) {
  regresa a + b;
}

procedimiento resta(a, b) {
  regresa a - b;
}

procedimiento multiplicacion(a, b) {
  regresa a * b;
}

procedimiento division(a, b) {
  regresa a / b;
}

// Llamadas a procedimientos
suma(n, m);

resta(n, m);

// Bucle "para" con condición y cuerpo de bucle
para (variable i = 0; i < 10; i = i + 1) {
    multiplicacion(n, m);
}
// Bucle "hacer-mientras" con condición y cuerpo de bucle
hacer {
  division(n, m);
} hasta_que (n > 0);

Con el código de ejemplo anterior, podemos ver cómo Codexivo está tomando forma poco a poco. Hemos definido los TokenTypes, los símbolos y las palabras clave que formarán la base del lenguaje. Además, hemos utilizado una variedad de tokens en el código de ejemplo para ilustrar su funcionamiento.

A medida que avancemos en esta serie de posts, exploraremos más aspectos de Codexivo, como el analizador sintáctico (parser) y el árbol de sintaxis abstracta (AST). Estos elementos nos permitirán comprender y ejecutar el código escrito en Codexivo.

¡Es emocionante ver cómo tu lenguaje de programación está evolucionando! Continúa siguiendo esta serie de posts para descubrir más sobre el proceso de creación de un lenguaje de programación y los conceptos clave de Codexivo.

Posts Relacionados

Talvez te interese ver alguno de estos posts.