Clem es un lenguaje de programación mínimo basado en pila con funciones de primera clase. Su objetivo es escribir un intérprete para el lenguaje Clem. Debe ejecutar correctamente todos los ejemplos incluidos en la implementación de referencia, que está disponible aquí .
- Como de costumbre, se aplican las lagunas estándar .
- La entrada más pequeña por conteo de bytes gana.
El lenguaje clem
Clem es un lenguaje de programación basado en pila con funciones de primera clase. La mejor manera de aprender Clem es ejecutar el clem
intérprete sin argumentos. Comenzará en modo interactivo, permitiéndole jugar con los comandos disponibles. Para ejecutar los programas de ejemplo, escriba clem example.clm
donde ejemplo es el nombre del programa. Este breve tutorial debería ser suficiente para comenzar.
Hay dos clases principales de funciones. Funciones atómicas y funciones compuestas. Las funciones compuestas son listas compuestas de otras funciones compuestas y funciones atómicas. Tenga en cuenta que una función compuesta no puede contenerse a sí misma.
Funciones atómicas
El primer tipo de función atómica es la constante . Una constante es simplemente un valor entero. Por ejemplo, -10. Cuando el intérprete encuentra una constante , la empuja a la pila. Corre clem
ahora. Escriba -10
en el indicador. Debería ver
> -10
001: (-10)
>
El valor 001
describe la posición de la función en la pila y (-10)
es la constante que acaba de ingresar. Ahora ingrese +11
en el indicador. Debería ver
> +11
002: (-10)
001: (11)
>
Observe que se (-10)
ha movido a la segunda posición en la pila y (11)
ahora ocupa la primera. Esta es la naturaleza de una pila! Notarás que -
también es el comando de decremento. Cada vez -
o +
preceder a un número, denotan el signo de ese número y no el comando correspondiente. Todas las demás funciones atómicas son comandos . Hay 14 en total:
@ Rotate the top three functions on the stack
# Pop the function on top of the stack and push it twice
$ Swap the top two functions on top of the stack
% Pop the function on top of the stack and throw it away
/ Pop a compound function. Split off the first function, push what's left,
then push the first function.
. Pop two functions, concatenate them and push the result
+ Pop a function. If its a constant then increment it. Push it
- Pop a function. If its a constant then decrement it. Push it
< Get a character from STDIN and push it to the stack. Pushes -1 on EOF.
> Pop a function and print its ASCII character if its a constant
c Pop a function and print its value if its a constant
w Pop a function from the stack. Peek at the top of the stack. While it is
a non-zero constant, execute the function.
Al escribir un comando en el indicador, se ejecutará el comando. Escriba #
en el indicador (el comando duplicado). Debería ver
> #
003: (-10)
002: (11)
001: (11)
>
Observe que el (11) se ha duplicado. Ahora escriba %
en el indicador (el comando soltar). Debería ver
> %
002: (-10)
001: (11)
>
Para enviar un comando a la pila, simplemente enciérrelo entre paréntesis. Escriba (-)
en el indicador. Esto empujará al operador de disminución a la pila. Debería ver
> (-)
003: (-10)
002: (11)
001: (-)
>
Funciones compuestas
También puede encerrar múltiples funciones atómicas entre paréntesis para formar una función compuesta. Cuando ingresa una función compuesta en el indicador, se empuja a la pila. Escriba ($+$)
en el indicador. Debería ver
> ($+$)
004: (-10)
003: (11)
002: (-)
001: ($ + $)
>
Técnicamente, todo en la pila es una función compuesta. Sin embargo, algunas de las funciones compuestas en la pila consisten en una sola función atómica (en cuyo caso, las consideraremos funciones atómicas por conveniencia). Al manipular funciones compuestas en la pila, el .
comando (concatenación) suele ser útil. Escribe .
ahora. Debería ver
> .
003: (-10)
002: (11)
001: (- $ + $)
>
Observe que las funciones primera y segunda en la pila se concatenaron, y que la segunda función en la pila viene primero en la lista resultante. Para ejecutar una función que está en la pila (ya sea atómica o compuesta), debemos emitir el w
comando (while). El w
comando mostrará la primera función en la pila y la ejecutará repetidamente siempre que la segunda función en la pila sea una constante distinta de cero. Intenta predecir qué sucederá si escribimos w
. Ahora, escribe w
. Debería ver
> w
002: (1)
001: (0)
>
¿Es eso lo que esperabas? Se agregaron los dos números que se encuentran en la parte superior de la pila y su suma permanece. Vamos a intentarlo de nuevo. Primero, soltaremos el cero y empujaremos un 10 escribiendo %10
. Debería ver
> %10
002: (1)
001: (10)
>
Ahora escribiremos toda la función en una sola toma, pero agregaremos un extra %
al final para eliminar el cero. Escriba (-$+$)w%
en el indicador. Debería ver
> (-$+$)w%
001: (11)
>
(Tenga en cuenta que este algoritmo solo funciona si la primera constante en la pila es positiva).
Instrumentos de cuerda
Las cuerdas también están presentes. En su mayoría son azúcares sintácticos, pero pueden ser bastante útiles. Cuando el intérprete encuentra una cadena, empuja a cada personaje del último al primero en la pila. Escriba %
para descartar el 11 del ejemplo anterior. Ahora, escriba 0 10 "Hi!"
en el indicador. El 0
insertará un terminador NULL y la 10
insertará un carácter de nueva línea. Debería ver
> 0 10 "Hi!"
005: (0)
004: (10)
003: (33)
002: (105)
001: (72)
>
Escriba (>)w
para imprimir caracteres de la pila hasta que encontremos el terminador NULL. Debería ver
> (>)w
Hi!
001: (0)
>
Conclusiones
Esperemos que esto sea suficiente para comenzar con el intérprete. El diseño del lenguaje debe ser relativamente sencillo. Avíseme si hay algo terriblemente confuso :) Algunas cosas se han dejado intencionalmente vagas: los valores deben estar firmados y al menos 16 bits, la pila debe ser lo suficientemente grande como para ejecutar todos los programas de referencia, etc. Muchos detalles no se han tallado aquí porque una especificación de lenguaje completo sería prohibitivamente grande para publicar (y aún no he escrito uno: P). En caso de duda, imite la implementación de referencia.