miércoles, 27 de enero de 2010

Haciendo un intérprete de LISP (XV)

En esta entrega del intérprete LISP vamos a tratar la entrada/salida someramente. Nos dedicaremos a ver únicamente la rutina de lectura de expresiones y la de escritura de valores.

La rutina de lectura

Tampoco vamos a adentrarnos mucho en la rutina de lectura. De hecho, se puede usar una PEG fácilmente para las expresiones S. Tomado, y simplificado, de aquí.

Atom = {Char}+ 
<s-expression> ::= <quote>? Atom / <quote>? '('  <s-expression>* ')' 
<quote> ::= [']

Realmente lo interesante aquí es que los literales y los identificadores se pueden agrupar como átomos en la gramática y, luego, distinguirlos en la parte semántica.

La rutina de escritura

A diferencia de la rutina de lectura, la escritura debe servir para cualquier valor. Por ejemplo, una función lambda. Además, hemos de intentar formatear algo la salida para que la lectura sea agradable. Es lo que se denomina el prettyprinting.

No vamos a calentarnos mucho la cabeza. Usaremos una indentación para destacar el grado de anidamiento que tengan los valores. Todo eso se puede incluir en una misma rutina de la siguiente forma:

void Cell::Print(std::wostream& o, std::wstring const& indent, int depth)const
{
if(this==NULL)
throw std::logic_error("Null cell");

switch(cell_type)
{
case CT_UNUSED:
o << L"[unused]";
break;

case CT_EMPTY:
o << L"()";
break;

case CT_PAIR:
if(IsList())
{
if(depth>0)
{
o << L"(";
Cell const* c=this;
c->value.head->Print(o, indent+L" ", depth-1);
c=c->value.tail;
while(c->IsPair())
{
o << L"\n " << indent;
c->value.head->Print(o, indent+L" ", depth-1);
c=c->value.tail;
}
o << L")";
}
else
o << L"[list]";
}
else
{
if(depth>0)
{
o << L"[\n";
value.head->Print(o, indent+L" ");
o << L"\n";
value.tail->Print(o, indent+L" ");
o << L"]";
}
else
o << L"[pair]";
}
break;

case CT_INTEGER:
o << value.integer;
break;

case CT_REAL:
o << value.real;
break;

case CT_STRING:
o << L"\"" << *value.string_idx << L"\"";
break;

case CT_BOOLEAN:
o << (value.boolean? L"[true]" : L"[false]");
break;

case CT_NAME:
o << *value.string_idx;
break;

case CT_ENVIR:
o << L"[envir " << (void*)this;
if(depth>0)
{
o << L"\n";
value.environment->Print(o, indent+L" ", depth-1);
}
o << L"]";
break;

case CT_LAMBDA:
if(depth>0)
{
o << L"[lambda " << (void*)this << L" ";
value.lambda->Print(o, indent+L" ", depth-1);
o << L"]";
}
else
o << L"[lambda " << (void*)this << L"]";
break;

case CT_NATIVE:
o << L"[native " << (void*)this << L"]";
break;
}
}

En las celdas lambda y entorno delegamos al propio objeto, pero las rutinas de impresión son muy similares.

Con esto hemos acabado la parte inexcusable del LISP. A partir de aquí dedicaremos las siguientes entradas a extensiones y modificaciones. Empezaremos por la más necesaria: la recolección de basura, ya que hasta ahora creábamos celdas pero no las destruíamos.

0 comentarios:

Publicar un comentario