3 puntos por GN⁺ 2024-05-03 | 1 comentarios | Compartir por WhatsApp

Introducción al lenguaje de programación Cognition

Planteamiento del problema

  • Los programadores de Lisp sostienen que, con código S-expresión y un sistema de macros funcional, se puede construir metaprogramación y sistemas generalizados
  • Pero Lisp tiene un problema fundamental
    • Dado que la metaprogramación y la programación no son lo mismo, Lisp siempre tiene una sintaxis estricta (ciertos caracteres para paréntesis o para look-ahead)
    • El paréntesis de apertura le indica a Lisp que siga leyendo hasta encontrar el paréntesis de cierre
    • Esto hace que los paréntesis de apertura y cierre sean inmodificables dentro del lenguaje (conceptualmente no, pero en algunas implementaciones sí)
    • Lo más importante es que, sin procesamiento de cadenas, es imposible cambiar después el orden en que esos tokens se distinguen
  • Otros lenguajes también tienen otras formas de decidir qué leer a continuación al ver ciertos tokens
    • A ese proceso se le llama sintaxis (syntax)
  • Cognition se diferencia al usar una antisintaxis (antisyntax) con notación postfija completa
    • Se parece a un lenguaje de programación concatenativo, pero también tiene dos problemas principales
      1. La introducción de caracteres de corchete de apertura/cierre (en realidad, notación prefija)
      2. El carácter de comillas para cadenas
    • Esto los vuelve poco adecuados como lenguaje general
    • También aparece el mismo problema en una implementación de sintaxis C de Lisp (uso excesivo de caracteres de escape, necesidad de espacios en blanco para separar inicio y fin de ciertos tokens)
  • Racket tiene un sistema de macros, pero no es dinámico en runtime y usa preprocesamiento

Introducción a Cognition

  • Proyecto en el que llevé trabajando varios meses junto con Matthew Hinton
  • El objetivo es crear uno de los sistemas de sintaxis más generalizados usando notación postfija completa
  • Puede requerir conocimientos previos de gramática, tokenización y parsing, pero se intenta explicar de forma clara
  • Repositorio: https://github.com/metacrank/cognition

Cognition baremetal

  • Baremetal Cognition se parece a Brainfuck, pero permite metaprogramación seria
  • Ejemplo de código de bootstrap:
ldfgldftgldfdtgldf dfiff1 crank f
  • Los espacios y saltos de línea son importantes
    • En la segunda línea hay un espacio detrás de df
    • En la tercera línea hay un espacio
  • Esto permite introducir dos conceptos nuevos: delimitador (delimiter) e ignorado (ignore)

Tokenización

  • El delimitador (delimiter) permite al tokenizador distinguir dónde empieza y termina un token
  • La lista de tokenizadores de un solo carácter es pública, por lo que puede modificarse y leerse dentro de Cognition
  • El carácter ignorado (ignored character) se omite por completo por el tokenizador en la primera etapa de cada ciclo read-eval-print
    • Es decir, se salta el conjunto de caracteres ignorados al iniciar la recolección de tokens
  • Por defecto todos los caracteres son delimitadores y no hay caracteres ignorados
  • Con las listas de delimitadores e ignorados se puede alternar lista negra/lista blanca para caracteres dados (ofreciendo concisión y practicidad)

Falias

  • Un Falias es una lista de palabras que se ejecuta al ser colocada en la pila
  • Todos los Falias ejecutan la cima de la pila (equivalente al eval de Stem)
  • f es el Falias predeterminado y ejecuta d en la cima de la pila sin colocarlo en ella
  • d cambia la lista de delimitadores al valor de cadena de la palabra (es decir, excluye solo el carácter l de los delimitadores)
  • En el entorno predeterminado no se ejecuta ninguna palabra salvo los Falias especiales

Precauciones sobre delimitadores

  • Hay una regla interesante para los delimitadores
    • Si un carácter no se ignora en el bucle de tokenización, el carácter delimitador se incluye como parte del token actual y continúa
    • Esto es opuesto a un singlet (incluye su propio carácter y se salta para finalizar la recolección del token)
  • También se puede establecer si una lista es blanca o negra
    • Puedes poner en lista negra o blanca las listas de delimitadores, singlets y caracteres ignorados
    • Por defecto no hay delimitadores en lista negra, singlets en lista blanca ni caracteres ignorados en lista blanca
  • Todos los demás caracteres se recopilan como parte del token actual y se siguen agregando nuevos caracteres hasta que el bucle se detiene por una regla de delimitador o singlet

Continuación del código bootstrap

ldf
  • Se hace que l sea un carácter no delimitador
gldftgldfdtgldf  dfiff1 crank f
  • Como d es un delimitador, se coloca gl en la pila y se llama al Falias f, haciendo que gl se convierta en un carácter no delimitador
  • tgl se coloca en la pila y pasa a ser un carácter no delimitador por df
  • dtgl se coloca en la pila y, por \ndf, el salto de línea (\n) se convierte en el único carácter no delimitador (el salto de línea está incluido en el código real)
  • Por la regla de delimitadores, se colocan en la pila el espacio y \n (incluido el espacio de la tercera línea)
  • Se tokeniza otra palabra \ \n
  • La pila actual es la siguiente (de abajo hacia arriba): 3. dtgl 2. [carácter de espacio]\n
    1. [carácter de espacio]\n
  • df configura \ \n como carácter no delimitador
  • if configura \ \n como carácter ignorado al inicio de la tokenización
  • f ejecuta dtgl y almacena el cambio de lista blanca/negra de delimitadores en la dflag
  • Ahora todos los caracteres que no son delimitadores se convierten en delimitadores, y todos los delimitadores se convierten en caracteres no delimitadores
  • Por último, espacio y salto de línea pasan a ser delimitadores de token y se ignoran al comienzo de los tokens
  • Luego se tokeniza 1 y se coloca en la pila; después se tokeniza la palabra crank y se ejecuta con f (1 se trata como número en este caso, pero en Cognition todo es una palabra)
  • ¡Secuencia de bootstrap completa! Lo que crank hace se explica en la siguiente sección

Resumen del bootstrap

  • Cognition permite cambiar la forma en que se tokeniza de forma dinámica y programática
    • Algo imposible en otros lenguajes
    • Puedes programar el tokenizador de un lenguaje externo dentro de Cognition y tokenizar como quieras
  • Esto es posible porque usa notación postfija y no hace look-ahead
    • No hace falta analizar sintácticamente uno o más tokens antes de evaluar una expresión
  • Con Falias se puede ejecutar una palabra sin palabras prefijadas ni ejecución de palabras predeterminadas

Crank

  • El sistema metacrank permite establecer la forma por defecto de ejecutar tokens al colocarlos en la pila
  • La palabra crank recibe un número como argumento y ejecuta la cima de la pila cada vez que se colocan n palabras en la pila
  • Código de ejemplo (crank 1 configurado):
5 crank 2
crank 2 crank 
1 crank unglue swap quote prepose def
  • En un entorno con crank 1, se puede detener el uso de f durante la evaluación de tokens
    • Se evalúa 1 por cada token tokenizado
    • Dado que se programó una sintaxis separada por espacios y saltos de línea, el código se puede interpretar de forma intuitiva
  • El código intenta evaluar 5 al comienzo (como no es builtin, se evalúa a sí mismo)
  • crank quedó configurado para ejecutarse cada 5 palabras colocadas en la pila
  • 2crank, 2, crank y 1 se colocan en la pila (crank no se ejecuta porque está configurado con crank 5): 4. 2crank 3. 2 2. crank
    1. 1
  • crank se ejecuta al ser la quinta palabra (con crank 1 configurado)
  • unglue es un builtin que toma el valor de la palabra de la cima de la pila (usado por 1)
    • Es decir, toma el puntero de función asociado al builtin crank
  • La pila queda así: 3. 2crank 2. 2
    1. [CLIB]
    • CLIB es el puntero de función que apunta al builtin crank
  • Ejecución de swap: 3. 2crank 2. [CLIB]
    1. 2
  • Ejecución de quote (builtin que cita la cima de la pila): 3. 2crank 2. [CLIB]
    1. [2]
  • Ejecución de prepose (parecido a compose en Stem, pero se coloca en algo llamado VMACRO): 2. 2crank
    1. ([2] [CLIB])
  • Llamada a def
    • Coloca 2 y define la palabra 2crank para llamar al puntero de función del builtin crank
  • Falta explicar qué es VMACRO, además de la diferencia entre la pila de Cognition y la de Stem

Diferencias con Stem

  • En la pila de Stem se puede colocar una palabra directamente en la pila
  • En Cognition, al poner una palabra en la pila esta se guarda en un contenedor en lugar de evaluarse
    • En Stem, palabras como compose trabajan sobre una palabra (o un contenedor con una sola palabra) y otro contenedor
    • Esto vuelve más consistente la API de Cognition
  • La palabra cd también usa este concepto

Macros

  • Otra diferencia entre la cita de Stem y el contenedor de Cognition
  • Al evaluar una macro, todo lo que hay dentro de la macro se evalúa y se ignora crank
  • Cuando una palabra se enlaza, al evaluar esa palabra

1 comentarios

 
GN⁺ 2024-05-03
Comentarios de Hacker News

Algunos comentarios destacados:

  • La explicación del propio proyecto Cognition en el texto aparece demasiado tarde, en la sección introductoria. Sería mejor presentar primero la información más importante para ahorrar tiempo al lector.
  • Ya existen otros enfoques, como la configuración de la capa reader en Racket, que amplían la sintaxis y mantienen la interoperabilidad. Queda la duda de si el enfoque de Cognition es fundamentalmente "mejor".
  • En Common Lisp también se puede modificar libremente la sintaxis mediante reader macro, macro y compiler macro. La metaprogramación se centra más en la semántica que en la sintaxis.
  • La capacidad de Cognition de definir, redefinir y entrar o salir de la estructura sintáctica en tiempo de ejecución es hermosa e interesante. Abre la posibilidad de construir una máquina verdaderamente "pensante".
  • La sintaxis sirve para dar estructura, así que eliminar la sintaxis misma es contradictorio. Una sintaxis demasiado concisa puede perjudicar la legibilidad y la comprensión.
  • El estilo de redacción del propio documento es bastante prolijo y con tono satírico, lo que lo vuelve difícil de leer. Pero aborda temas muy profundos.