Cómo parsear un archivo Slice (el lenguaje de especificación de interfaces de ICE) para obtener toda la información semántica que contiene utilizando libSlice (el Parser proporcionado por ZeroC)
Los archivos slice todos sabemos lo que son: Archivos que proporcionan una descripción de las clases, módulos, interfaces y operaciones que implementará un sistema distribuido basado en ICE.
La gente de ZeroC proporciona el código fuente de su middleware, pero éste no está muy bien documentado (siendo generosos), así que la manera de parsear un archivo slice y obtener toda la semántica que se necesitaba, ha pasado por hacer un poco de ingenería inversa del código de una de las utilidades que da ZeroC (en concreto slice2cpp). Así que para evitar tener que repetir el trabajo, aquí dejo un poco documentado el funcionamiento del parser para Slice que la gente de ZeroC montó.
Antes de empezar, no estaría de mas conocer un poco el patrón de programación visitor, ya que se utilizará para acceder a la información proporcionada por el parser. También se supone que se tienen nociones básicas de C++.
Yo he utilizado ice-3.3-beta así que tampoco estará de mas que además de tener ice instalado, se instalen los fuentes:
javieralso@avalon:~$ apt-get source zeroc-ice33
Para parsear un archivo slice, necesitaremos utilizar la librería Slice/Parser proporcionada por ZeroC. Un ejemplo de función que llama al parser sería la siguiente:
#include <IceUtil/OutputUtil.h> #include <IceUtil/Options.h> #include <Slice/Parser.h> #include <vector> #include "OurParser.h" #include "OurVisitor.h" OurParser::OurParser(const string& path, const string& filename) { vector<string> dummyArgs; _pp = new Preprocessor(path,filename,dummyArgs); FILE* cppHandle = _pp->preprocess(false); UnitPtr u = Unit::createUnit(false, true, true, true); int parseStatus = u->parse("", cppHandle, false); OurVisitor visitor; u->visit(&visitor, cppHandle); } OurParser::~OurParser() {} int main(int argc, char* argv[]) { OurParser* sp = new OurParser("", argv<a href="#fn877750352495f6c4bc4603">1</a>); return 0; }
¿y como funciona esto? Bueno, la clase OurParser será quien realice todo el trabajo. Básicamente, su cometido consiste en crear una instancia del preprocesador pasándole el archivo a parsear y un conjunto de opciones (en nuestro caso, como no queremos pasarle nada, se le pasa un vector de cadenas vacío, dummyArgs). Después de instanciar el preprocesador, se preprocesa el archivo:
vector<string> dummyArgs; _pp = new Preprocessor(path,filename,dummyArgs); FILE* cppHandle = _pp->preprocess(false);
Después se instancia el parser propiamente dicho, y se parsea el Slice
UnitPtr u = Unit::createUnit(false, true, true, true); int parseStatus = u->parse("", cppHandle, false);
u es quien realiza todo el trabajo de parseado. Las opciones que se pasan cuando se crea se utilizan entre otras cosas para propósitos de depuración y las que se dan en el ejemplo son válidas en general. Si se quiere, se puede jugar con los valores a ver qué pasa o directamente echar un ojo al código de slice2cpp donde están un poco mas comentadas.
la variable parseStatus almacena el resultado del parseo.
Una vez que se ha terminado de parsear el archivo, ya se cuenta con toda la información semántica, así que solo nos resta obtener dicha información y procesarla a nuestro antojo. Como se ha intentando que el parser sea lo mas general posible, de modo que pueda ser reutilizado para lo que sea necesario (vamos, que haya buena separación entre frontend y backend), se ha hecho uso del patron de diseño visitor que se comentó anteriormente. En nuestro caso creamos una clase que contendrá una serie de métodos que serán llamados en determinados momentos, como por ejemplo cuando se inicie la declaración de un módulo, su finalización, cuando se declare una función, sus parámetros, metadatos, se defina una estructura de datos o una clase, etc….
En nuestro caso, después del parseo, “visitamos” al parser para pedirle toda la información:
OurVisitor visitor; u->visit(&visitor, cppHandle);
visitor es una instancia de nuestra clase visitante, que implementará los métodos necesarios para procesar la información.
Ésta clase viene a ser algo como ésto:
#include <Slice/Parser.h> using namespace Slice; class OurVisitor: public ParserVisitor { public: OurVisitor(SymbolTable&); ~OurVisitor(); virtual bool visitModuleStart(const ModulePtr&); virtual void visitModuleEnd(const ModulePtr&); virtual void visitClassDecl(const ClassDeclPtr&); virtual bool visitClassDefStart(const ClassDefPtr&); virtual void visitClassDefEnd(const ClassDefPtr&); virtual bool visitExceptionStart(const ExceptionPtr&); virtual void visitExceptionEnd(const ExceptionPtr&); virtual bool visitStructStart(const StructPtr&); virtual void visitStructEnd(const StructPtr&); virtual void visitOperation(const OperationPtr&); virtual void visitParamDecl(const ParamDeclPtr&); virtual void visitDataMember(const DataMemberPtr&); virtual void visitSequence(const SequencePtr&); virtual void visitDictionary(const DictionaryPtr&); virtual void visitEnum(const EnumPtr&); virtual void visitConst(const ConstPtr&); }
Bueno, como se puede ver, nuestro “visitante” tiene que heredar de la clase ParserVisitor, además, debe implementar todos los métodos que se ven. Éstos métodos serán llamados por el parser y será en ellos en los que nosotros definamos el procesado que queremos hacer de la información.
…
bool OurVisitor::visitModuleStart(const ModulePtr& p) { cout "Nombre del modulo: " << p->name() << endl; cout "Ambito de declaracion: " << p->scope() <<endl; return true; }
Cuando se abandona la declaración de un módulo, se invoca a visitModuleEnd, pasandole exactamente la misma información que a su homólogo de inicio de módulo.
bool OurVisitor::visitClassDefStart(const ClassDefPtr& p) { cout "Nombre del interfaz: " << p->name() << endl; cout "Ambito de declaracion: " << p->scope() <<endl; return true; }
El procedimiento encargado de obtener la información de las operaciones es VisitOperation y un ejemplo de implementación que simplemente imprime información sería el siguiente:
void OurVisitor::visitOperation(const OperationPtr& p) { size_t strIndex; string metadataName, metadataValue; StringList operationMetaData; cout "Nombre: " << p->name() << endl; cout "Ambito de declaracion: " << p->scope() << endl; TypePtr ret = p->returnType(); string retS = returnTypeToString(ret, "", p->getMetaData()); cout << "Tipo de retorno: " << retS << endl; if(p->mode() Operation::Idempotent || p->mode() Operation::Nonmutating){ // Idempotent operation cout << "Funcion idempotente" << endl; } // Looking for metadata. operationMetaData = p->getMetaData(); cout << "Metadatos:" << endl; for(StringList::iterator it = operationMetaData.begin(); it != operationMetaData.end(); it++) { cout << *it << endl; } } // Looking for parameters. ParamDeclList paramList = p->parameters(); cout << "Parametros:" << endl; for(ParamDeclList::const_iterator q = paramList.begin();\ q != paramList.end(); ++q){ cout << "Nombre: " << (*q)->name(); StringList metaData = (*q)->getMetaData(); if (!(*q)->isOutParam()){ cout << "Parametro de salida" << endl; typeString = inputTypeToString((*q)->type(), false, metaData); cout << "Tipo del parametro: " << typeString << endl; } }
string retS = returnTypeToString(ret, "", p->getMetaData());
)? Bueno, pues tendremos que sobreescribir las funciones de las que hablé antes para que nos devuelva lo que nosotros queramos. Yo en mi caso, modifiqué las tres. Aquí muestro mi outputTypeToString:string Slice::outputTypeToString(const TypePtr& type, bool useWstring, const StringList& metaData) { static const char* outputBuiltinTable[] = { "Byte", "bool", "Short", "Int", "Long", "Float", "Double", "string", "ObjectPtr", "ObjectPrx", "LocalObjectPtr" }; BuiltinPtr builtin = BuiltinPtr::dynamicCast(type); if(builtin) { if(builtin->kind() Builtin::KindString) { string strType = findMetaData(metaData, true); if(strType != "string" && (useWstring || strType "wstring")) { if(featureProfile IceE) { return "Wstring&"; } else { return "wstring&"; } } } return outputBuiltinTable[builtin->kind()]; } ClassDeclPtr cl = ClassDeclPtr::dynamicCast(type); if(cl) { return fixKwd(cl->scoped() + "Ptr"); } StructPtr st = StructPtr::dynamicCast(type); if(st) { if(findMetaData(st->getMetaData(), false) "class") { return fixKwd(st->scoped() + "Ptr"); } return fixKwd(st->scoped()); } ProxyPtr proxy = ProxyPtr::dynamicCast(type); if(proxy) { return fixKwd(proxy->_class()->scoped() + "Prx"); } SequencePtr seq = SequencePtr::dynamicCast(type); if(seq) { string seqType = findMetaData(metaData, false); if(!seqType.empty()) { return seqType; } else { return fixKwd(seq->scoped()); } } ContainedPtr contained = ContainedPtr::dynamicCast(type); if(contained) { return fixKwd(contained->scoped()); } return "???"; }
Comentarios
esto ...
… me lo tengo que imprimir xD …
—
“Computer Science is no more about computers than astronomy is about telescopes.”
– Edsger W. Dijkstra