miércoles, 13 de enero de 2010

Haciendo un intérprete de LISP (XII)

En la última parte mencionábamos que teníamos que introducir algunas funciones nativas que nos sacasen del REPL. Queremos salir del intérprete de nuestro LISP. Aquí diremos cómo hacerlo y también empezaremos a definir los nombres.

Saliendo del intérprete

La forma más sencilla es una función nativa que llame a exit().

Cell& FOL_Quit(Heap& heap, Cell& args, Cell& envir)
{
exit(0);
return args;
}

El retorno no es necesario estrictamente hablando. Lo que sí que es necesario es darle un nombre a esta función nativa. Lo haremos así, en la inicialización.

    env.Bind(h, L"quit", &FOL_Quit);

El resultado es que ahora podemos salir del intérprete escribiendo "(quit)".

Más nombres

Es completamente necesario que el usuario pueda dar nombres a nuevas funciones. Hasta ahora hemos permitido que las crease usando "lambda", pero eran funciones anónimas. Hay que ir un paso más allá y permitir nombrarlas. Lo haremos más general aún. Permitiremos que el usuario pueda darle un nombre a cualquier valor. Para eso introduciremos "setq" que no es más que otra función nativa.

Cell& FOL_SetQ(Heap& heap, Cell& args, Cell& envir)
{

"setq" va a requerir dos argumentos al menos. El primero será el nombre y el segundo una secuencia de expresiones cuyo resultado será el valor a asignar al nombre.


if(args.IsEmpty() || args.GetTail().IsEmpty())
throw std::runtime_error("SetQ needs two arguments at least");

Comprobemos que el primer argumento es un nombre. Es importante distinguir aquí que el primer argumento es un nombre, no una expresión cuyo resultado es un nombre. No vamos a evaluar ese primer argumento. Esa es la razón de que "setq" tenga la "q". Más sobre esto un poco más adelante.


if(!args.GetHead().IsName())
throw std::runtime_error("SetQ requires a name as fist argument");

El resto de argumentos sí se evalúa en secuencia y obtenemos un resultado.


Cell& r=heap.EvaluateInSequence(args.GetTail(), envir);

Es el resultado el que se vincula al nombre en el entorno actual. Esto es muy importante. Estamos vinculándolo en el entorno actual. No en el entorno global.


envir.GetEnvironment().Bind(heap, *args.GetHead().GetName(), r);

El retorno de "setq" no es significativo ya que lo que queremos es el efecto lateral de vincular el nombre con el resultado. Por sencillez, devolvemos el propio resultado. Devolver una CT_EMPTY es más portable pero habría que reservar otra celda en el montículo.


return r;
}

Finalmente, y como ocurre con las nativas, hemos de darle un nombre en el entorno global, justo antes de iniciar el REPL.

    env.Bind(h, L"setq", &FOL_SetQ);

Para probar "setq" definiremos la función "double". La sesión podría ser tal que así.

> (setq double (lambda (x) (add x x)))

<lambda 003A7FA8 <envir 003A6828> (x)
((add
x
x))>

> (double 3)

6

> (quit)

Quote

El hecho de que haya argumentos que debamos evaluar y otros que no es un inconveniente. Las lambda siempre evalúan sus argumentos. ¿Cómo podemos forzarlas para que no lo hagan? La respuesta es introducir una función nativa "quote" que, al ser evaluada, devuelve su único argumento sin evaluar.

Cell& FOL_Quote(Heap& heap, Cell& args, Cell& envir)
{
if(args.IsEmpty() || !args.GetTail().IsEmpty())
throw std::runtime_error("Quote needs one single argument");

return args.GetHead();
}

La añadimos también al entorno global con el nombre "quote".

    env.Bind(h, L"quote", &FOL_Quote);

El uso que tiene es bien sencillo.

> (quote (add 3 3))

(add
3
3)

> (add 3 3)

6

En el siguiente post introduciremos dos funciones nativas nuevas que nos van a permitir hurgar en los entresijos de nuestro intérprete. De esa forma, podremos ver cómo funciona interactivamente.

0 comentarios:

Publicar un comentario