miércoles, 23 de febrero de 2011

miniSL parte 10 - Extracción de valores

Si imprimir los valores de una celda fue fácil, obtenerlos va a ser aún más fácil. Podríamos leer directamente los campos de una celda, pero es más conveniente encapsular las operaciones más comunes en una única función miembro de CELL que lance una excepción si algo va mal.
int GetInteger()const
{
 if(type==INT_LIT)
  return int_val;
 throw L"Integer expected";
}

bool GetBoolean()const
{
 if(type==BOOL_VAL)
  return bool_val;
 throw L"Boolean expected";
}

STRING const& GetString()const
{
 if(type==STRING_LIT)
  return *string_val;
 throw L"String expected";
}

El único caso no trivial aparece cuando queremos explorar o modificar los entornos. Recordemos que cada celda de entorno tiene una tabla de símbolos (un ENVIR_TABLE que era un typedef de std::map<CELL*, CELL*&gt;) en el campo envir_table y un entorno padre en el campo parent_envir. Estas complejidades son necesarias para código con una forma similar a esta:
> define(a, 5)
[]
> define(f, lambda([x],[x+a]))
[]
> f(3)
8

Cuando se definen a y f, se hace en el entorno global. Cuando se llama a f(3) se crea un nuevo entorno local donde x se vincula a 3. ¿Pero cuánto vale a en el entorno local? ¡No está definida en el entorno local, está definida en el entorno global! La solución es crear un enlace, el del entorno padre, de la forma indicada en el diagrama de abajo.



De esta manera están disponibles tanto la a como la propia f dentro del cuerpo de la f. De todo esto, lo único que necesitamos por ahora son dos funciones en la clase Script. La primera hace la búsqueda de un nombre que se realiza siempre de la misma forma. Primero, buscamos en el entorno actual y, si no se encuentra, repetimos recursivamente buscando en el entorno padre.
CELL* Script::FindName(CELL& name, CELL& envir)
{
 //Try to find the name in current environment
 ENVIR_TABLE::const_iterator i=envir.envir_table->find(&name);
 if(i!=envir.envir_table->end())
  return i->second;

 //Failed, try parent
 if(envir.parent_envir!=NULL)
  return FindName(name, *envir.parent_envir);

 //Failed also, not found
 return NULL;
}

La segunda, para cambiar un nombre, hace casi lo mismo: buscamos el nombre en el entorno actual cambiándolo si lo encontramos y, si no lo encontramos, llamamos recursivamente la función en el entorno padre. Si no hay entorno padre es que el nombre no estaba definido.
void Script::ChangeName(CELL& name, CELL& envir, CELL& value)
{
 //Try to find the name in current environment
 ENVIR_TABLE::iterator i=envir.envir_table->find(&name);
 if(i!=envir.envir_table->end())
 {
  (*envir.envir_table)[&name]=&value;
  return;
 }

 //Failed, try parent
 if(envir.parent_envir!=NULL)
  return ChangeName(name, *envir.parent_envir, value);

 //Failed also, not found
 throw L"Name to be changed is undefined";
}

También necesitaremos la operación de definición de un nombre en el entorno actual como hace define, sin buscar en los entornos padre, pero coincide exactamente con la operación básica de inserción en std::map así que, aunque sea algo incorrecto al romper la abstracción sobre la implementación, usaremos directamente la clase std::map.

Hasta ahora sólo hemos explicado cómo leer una celda. En la siguiente entrada hablaremos de la creación de celdas.

0 comentarios:

Publicar un comentario en la entrada