Tuesday, July 17, 2007

forth en lisp

estaba aburrido en mi clase de física y me puse a escribir algo que se me ocurrió el otro día: hacer que lisp pueda usarse tipo forth.

forth, por si no lo saben, es un lenguaje stack-based. podriamos decir que carece de sintaxis: es una sucesion de palabras que se lee de izquierda a derecha, y se comunican por una pila o stack. algo así:

2 3 + dup +

Las palabras son "2", "3", "+" y "dup".
"2" y "3" ponen, respectivamente un 2 y un 3 en la pila.
"+" toma los dos primeros elementos de la pila, los suma, y lo pone encima de la pila.
"dup" mira el primer elemento de la pila, y lo vuelve a poner encima de la pila.

El programa se ejecuta así

inicio -- pila: (vacía)
2 -- pila: 2
3 -- pila: 3 2
+ -- pila: 5
dup -- pila: 5 5
+ -- pila: 10

Este estilo de programacion carece de sintaxis, y es muy legible para algunos. Para mi es un quilombo, pero no importa, quise hacer un forth en lisp.

La idea es simple: represento la pila con una lista. Defino funciones comunes de lisp, que reciban la pila/lista, y devuelvan la lista modificada. Luego, para combinar las funciones, las compongo.

En realidad, forth y sus derivados suelen tener otra pila auxiliar, y aca no hay. Otra opcion sería usar dos pilas. En lugar de como hice acá, usar &rest y demases, quiza convenga pasar la lista como un parametro cualquiera, y direcamente pasamos dos listas, o hacer alguna tramoya rara con connect (se podria hacer).

La idea aqui presentada podria expandirse, pero francamente no tengo ganas.

El codigo:

(defun dup (f &rest rest)
    (cons f (cons f rest)))
(defun swap (a b &rest rest)
    (cons b (cons a rest)))
(defun fdrop (a &rest rest)
    rest)
(defmacro if0 (test &body b)
    `(if (= ,test 0) ,@b))
(defun fconnect (f &rest list)
    (if0 (length list)
        f
        (lcompose (apply #'fconnect list) f)))
(defun lcompose (f g)
    (lambda (&rest args)
        (apply f (apply g args))))
        
(defmacro lisp-forth (lispf forthf comb length)
    `(defun ,forthf (&rest list)
        (,comb (apply #',lispf 
                (
take ,length list)) 
                  (drop ,length list))))
(defun take (len list)
    (loop for i from 1 to len
          for e in list
          collect e))
(defun drop (len list)
    (if0 len
         list
         (drop (- len 1) (cdr list))))

(defmacro connect (&rest list)
    `(fconnect ,@(mapcar #'asfunction list)))

(defun asfunction (e)
    (cond ((symbolp e) `(function ,e))
          ((or (numberp e) (eq (car e) 'quote)) 
                `(push-identity ,e))))
(defun push-identity  (e)
    (lambda (&rest list) (cons e list)))
          
(defmacro define-forth (f &rest definition)
    `(defun ,f (&rest args)
        (apply (connect ,@definition) args)))
        
(lisp-forth + f+ cons 2)
(lisp-forth - f- cons 2)
(define-forth lolfun dup f+ 2 f+ 3 swap f-)

(defun lolfun2 (n) ((+ n n 2) 3))

explico rápidamente que hace cada cosa:

dup, swap, y fdrop, hacen lo mismo que en forth.
if0 es una simple comparacion con 0 que uso a veces.
fconnect es una funcion que recibe una lista de funciones estilo forth, y las compone recursivamente. lcompose es como una composicion de funciones normal, pero la diferencia es que lo que devuelve la segunda funcion no es el parametro unico de la primera funcion, sino que es aplicado (funcall vs. apply).

lisp-forth es una macro para definir funciones estilo forth a partir de funciones lisp. damos el nombre de la funcion lisp, el nombre de la funcion que queremos definir, con que vamos a combinar el resultado de la funcion comun con el resultado de la pila, y cuantos elementos de la pila le vamos a pasar como parametro.

connect es una macro que agrega los #' a las funciones y cambia los numeros y cosas quoteadas (simbolos, listas, etc) por funciones que colocan esos objetos en la pila (funciones armadas con push-identity), mediante la funcion asfunction (asfunction podria tener mas opciones pero por ahora, esas).

define-forth es, finalmente, lo que nos permite armar funciones tipo forth en base a otras funciones tipo forth.

espero que les haya gustado, mis dos o tres lectores que seguramente no saben lisp y no van a entender este post (posible excepcion: CC)!

2 comments:

Solfegieto said...

Ehh...
No entendí.

ZippoLag said...

mhmh, no se lisp..
pero puedo llegarme a dar cuenta de algo:
limaste jodido.
y/o sos groso.