miércoles, 25 de noviembre de 2009

Haciendo un intérprete de LISP (III)

En esta tercera entrada voy a explicar las operaciones sobre las celdas.

Selectores de una celda

Como ya dijimos, aunque la celda sea una clase C++, no vamos a usar polimorfismo. Usaremos la aproximación del C con switches. Eso significa que el primer selector que necesitamos es

CELL_TYPE CellType(void)const { return cell_type; }

Y también sería interesante alguna forma de ver los contenidos de la celda.

void Print(std::wostream& o, std::wstring const& indent=L"", int depth=10)const;

El primer argumento es el stream de salida que vamos a usar para imprimir. Luego la indentación que queremos y finalmente, debido a que podemos hacer ciclos referenciando celdas, la máxima profundidad de representación. Nada de esto es muy importante por ahora.

Otros selectores más interesantes son los que preguntan por un tipo concreto.

 bool IsEmpty(void)const
{
return cell_type==CT_EMPTY;
}

bool IsPair(void)const
{
return cell_type==CT_PAIR;
}

bool IsName(void)const
{
return cell_type==CT_NAME;
}

bool IsNative(void)const
{
return cell_type==CT_NATIVE;
}


Luego, los que obtienen los campos relevantes de cada tipo de celda:


 Cell& GetHead(void)const
{
if(cell_type!=CT_PAIR)
throw std::logic_error("Only pairs have head");

return *value.head;
}

Cell& GetTail(void)const
{
if(cell_type!=CT_PAIR)
throw std::logic_error("Only pairs have tail");

return *value.tail;
}

INTEGER GetInteger(void)const
{
if(cell_type!=CT_INTEGER)
throw std::logic_error("Only integers have an integer value");

return value.integer;
}

REAL GetReal(void)const
{
if(cell_type!=CT_REAL)
throw std::logic_error("Only reals have a real value");

return value.real;
}

BOOLEAN GetBoolean(void)const
{
if(cell_type!=CT_BOOLEAN)
throw std::logic_error("Only booleans have a boolean value");

return value.boolean;
}

STRING GetString(void)const
{
if(cell_type!=CT_STRING)
throw std::logic_error("Only strings have a string value");

return value.string_idx;
}

STRING GetName(void)const
{
if(cell_type!=CT_NAME)
throw std::logic_error("Only names have a name value");

return value.string_idx;
}

Environment& GetEnvironment(void)const
{
if(cell_type!=CT_ENVIR)
throw std::logic_error("Only environments have an environment value");

return *value.environment;
}

Lambda& GetLambda(void)const
{
if(cell_type!=CT_LAMBDA)
throw std::logic_error("Only lambdas have an lambda value");

return *value.lambda;
}

NATIVE GetNative(void)const
{
if(cell_type!=CT_NATIVE)
throw std::logic_error("Only natives have a native value");

return value.native;
}

Finalmente, añadimos un predicado que nos dice si una cadena de pares es una lista (termina en CT_EMPTY) o no.




bool IsList(void)const
{
Cell const* c=this;
while(c->IsPair())
c=c->value.tail;
return c->IsEmpty();
}

De esta manera, cualquier cosa que queramos de la celda no requiere el conocimiento del interior de la celda. Separamos implementación de interfaz.

El siguiente paso es la modificación de las celdas.

Operaciones sobre celdas

La operación principal va a ser cambiar la celda a un tipo y con unos miembros específicos. Las celdas van a ser inmutables. Eso significa que si una celda no era CT_UNUSED, cambiarla es un error.



Cell& SetPair(Cell& head, Cell& tail)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_PAIR;
value.head=&head;
value.tail=&tail;
return *this;
}

Cell& SetInteger(INTEGER integer)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_INTEGER;
value.integer=integer;
return *this;
}

Cell& SetReal(REAL real)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_REAL;
value.real=real;
return *this;
}

Cell& SetBoolean(BOOLEAN boolean)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_BOOLEAN;
value.boolean=boolean;
return *this;
}

Cell& SetString(STRING string)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_STRING;
value.string_idx=string;
return *this;
}

Cell& SetName(STRING name)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_NAME;
value.string_idx=name;
return *this;
}

Cell& SetEnvironment(Cell* parent);

Cell& SetLambda(Lambda& ol)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_LAMBDA;
value.lambda=&ol;
return *this;
}

Cell& SetNative(NATIVE native)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_NATIVE;
value.native=native;
return *this;
}

Excepto SetEnvironment() el resto de operaciones son simples. El caso de SetEnvironment() es distinto porque esa celda debe guardar un entorno. El entorno es la relación entre nombres (cadenas) y un valor. Necesitamos un mapa para eso. Meteremos el mapa en una clase Environment y crearemos la celda así.



Cell& Cell::SetEnvironment(Cell* parent)
{
if(cell_type!=CT_UNUSED)
throw std::logic_error("Cells are immutable");

cell_type=CT_ENVIR;
value.environment=new Environment(parent);
return *this;
}

Constructores y destructores

El que una celda de entorno contenga un objeto de la clase Environment significa que en algún lado habrá que destruir ese objeto creado. De hecho, también hay que destruir los objetos Lambda (que ya veremos cómo y dónde se crean). De este modo, nuestro constructor y destructor queda así.



Cell(void) : cell_type(CT_UNUSED) {}
~Cell(void);

Luego, el cuerpo del destructor será


Cell::~Cell(void)
{
switch(cell_type)
{
case CT_ENVIR:
delete value.environment;
break;

case CT_LAMBDA:
delete value.lambda;
break;
}
}

En resumen:

  • Tenemos dos formas de identificar las celdas. Con el CellType() y con predicados del tipo IsName(). Estos últimos sólo son una abreviatura de cosas como CellType()==Cell::CT_NAME.
  • Tenemos varios selectores para obtener los miembros de las celdas: GetTail(), GetName(), etc.
  • Tenemos varias operaciones para hacer la celda de un tipo: SetPair(), SetLambda(), etc. Sólo se pueden usar si la celda es de tipo CT_UNUSED.
  • Finalmente, tenemos un constructor que inicializa la celda a CT_UNUSED y un destructor que elimina los objetos Environment o Lambda que podamos haber asignado a una de estas celdas.


0 comentarios:

Publicar un comentario