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.
fuente
Respuestas:
Haskell,
931921875esto aún no está totalmente golfizado, pero probablemente nunca lo será. Aún así, ya es más corto que todas las demás soluciones.
Voy a jugar al golf más pronto.No tengo ganas de jugar al golf más que esto.probablemente tiene algunos errores sutiles porque no jugué con la implementación de referencia C.
Esta solución utiliza el tipo
StateT [String] IO ()
para almacenar un programa clem "ejecutable". la mayoría del programa es un analizador que analiza el "programa ejecutable".para ejecutar este uso
r "<insert clem program here>"
.fuente
Python,
16841281 caracteresTengo todas las cosas básicas de golf hechas. Ejecuta todos los programas de ejemplo y coincide con la salida carácter por carácter.
Prueba :
Reúna clemint.py , clemtest_data.py , clemtest.py y un
clem
binario compilado en un directorio y ejecúteloclemtest.py
.Expansión :
La versión más despiadada es esta . Sigue junto con ese.
S
Es la pila principal. Cada elemento de la pila es una lista de 3, una de:Para las constantes,
f
es una función que empuja la constante a la pila. Para atmoics,f
es una función que ejecuta una de las operaciones (por ejemplo-
,+
). Para los compuestos,fs
es una lista de artículos.xec
ejecuta un elemento Si es una constante o una atómica, solo ejecuta la función. Si es un compuesto, si aún no ha habido recursividad, ejecuta cada función. Así ejecución(10 20 - 30)
ejecutará cada una de las funciones10
,20
,-
y30
, dejando10 19 30
en la pila. Si ha habido recursividad, simplemente empuja la función compuesta a la pila. Por ejemplo, al ejecutar(10 20 (3 4) 30)
, el resultado debería ser10 20 (3 4) 30
, no10 20 3 4 30
.Anidar fue un poco complicado. ¿Qué haces mientras lees
(1 (2 (3 4)))
? La solución es tener una pila de pilas. En cada nivel de anidación, se empuja una nueva pila en la pila de pilas, y todas las operaciones de inserción van a esta pila. Además, si ha habido anidamiento, las funciones atómicas se empujan en lugar de ejecutarse. Entonces, si ve10 20 (- 30) 40
,10
se empuja, entonces20
, se crea una nueva pila,-
y30
se empuja a la nueva pila, y se)
sale de la nueva pila, la convierte en un elemento y la empuja hacia la pila un nivel hacia abajo.endnest()
manijas)
. Fue un poco complicado ya que hay un caso especial en el que solo se ha presionado un elemento y estamos volviendo a la pila principal. Es decir,(10)
debe empujar la constante10
, no es un compuesto con una constante, porque entonces-
y+
no funciona. No estoy seguro de si esto tiene principios, pero es la forma en que funciona ...Mi intérprete es un procesador carácter por carácter, no crea tokens, por lo que los números, las cadenas y los comentarios son algo molestos de tratar. Hay una pila separada
N
, para un número procesado actualmente, y cada vez que se procesa un carácter que no es un número, tengo que llamarendnum()
para ver si primero debo completar ese número y ponerlo en la pila. Las variables booleanas realizan un seguimiento de si estamos en una cadena o un comentario; Cuando se cierra una cuerda, empuja todas las entrañas de la pila. Los números negativos también requieren un manejo especial.Eso es todo para el resumen. El resto estaba poniendo en práctica todos los muebles empotrados, y asegurándose de hacer copias de profundidad en
+
,-
, y#
.fuente
C 837
Gracias a @ceilingcat por encontrar una versión mucho mejor (y más corta)
Esto trata todo como cadenas simples: todos los elementos de la pila son cadenas, incluso las constantes son cadenas.
Pruébalo en línea!
Una versión menos golfizada de mi original (a diferencia de la versión golfizada, esta imprime la pila cuando termina si no está vacía y toma un parámetro -e para que pueda especificar el script en la línea de comando en lugar de leerlo desde un archivo):
fuente