2011-12-08 11 views
8

Tengo un lenguaje con una sintaxis similar a C++. El lexer y el analizador están en su lugar y producen el AST correcto. Para la parte más grande, el back-end también está hecho.Escribiendo un compilador: ¿cómo hacer que funcionen plantillas simples?

El sistema básico que el compilador usa para crear tipos es muy simple: todos los tipos se consideran incorporados y todas las instancias son globales. Así que solo hay un mapa simple que coincide con el nombre de un tipo con un método que crea una variable que es básicamente un tipo genérico como boost :: any. Otro mapa con el nombre de la variable como llave y la variable como valor sirve como el ámbito global:

std::map< std::string, std::function< Variable() > typeList; 

    //register some types 
typeList[ "X" ] = Variable::Create<X>; 
typeList[ "Y" ] = CreateInstanceOfY; 
.... 

Cuando el compilador obtiene el nodo AST para una inicialización como X myVar; hace básicamente

std::map< std::string, Variable > globalScope; 
globalScope[ "myVar" ] = typeList[ "X" ](); 

Cuando miVar se utiliza más adelante se puede acceder mediante un simple despacho tipo como

X& x = myVar.GetReference<X>(); 

Ahora me gustaría extender esta un poco y utilizar plantillas simples. Supongamos que hay un tipo de "matriz" que se implementa utilizando un vector. Yo podría registrar todo como

typeList[ "array<X>" ] = Variable::Create< std::vector<X> >; 

pero eso no es muy manejable, ya que tendría que repetirse para todas las combinaciones. Lo ideal sería que iba a necesitar una funcionalidad que permite a escribir algo como esto:

typeList.CreateTemplateVariable("array", "X") 

que luego crear una instancia de variable que internamente tiene un std :: vector < X>. Intenté mucho, pero no puedo entender cómo hacerlo. Quizás comencé el camino equivocado con el mapeo simple de tipos y esa es la razón por la cual no puedo entenderlo.

Entonces la pregunta es simple: ¿es posible hacer esto? ¿Y cómo?

+0

cuando se crea el código C++ de AST, ES de todos modos un compilador. – belgther

+0

Un pequeño detalle sobre la terminología: un compilador traduce el código de un formato a otro.Un intérprete ejecuta algún código. –

+0

@JoachimPileborg gracias por señalar eso ... Realmente no soy bueno con la terminología. – stijn

Respuesta

2

No estoy seguro de que tengo el problema de la derecha, pero si usted tiene tipos M paramétricas (vector<>, list<>, ...) y los N tipos simples (int, double, ...), lo que se necesita MxN implementaciones reales si quieres admitir todas las combinaciones. Todas estas implementaciones deben conocerse en tiempo de compilación (o, en principio, puede invocar el compilador de C++ sobre la marcha). ¿De verdad quieres esto?

Una solución alternativa podría ser el uso de contenedores no tipados en su lugar. Por ejemplo, vector<Object*> almacenando punteros que pueden convertirse posteriormente al tipo requerido, por ejemplo con un dynamic_cast. De esta forma solo necesitaría implementaciones M para tipos paramétricos, y puede resolver "array" a vector, y "X" a X de forma independiente.

+0

tiene razón sobre el M * N, sin embargo, sería mejor tener algún sistema dinámico que también sea compatible, o se pueda extender a, plantillas anidadas y múltiples parámetros de plantilla. Voy a ver si el enfoque sin mecanografía me lleva a algún lugar, parece interesante. – stijn

+0

@stijn, aún puede tener todo escrito en su idioma, tener plantillas y todo lo demás. El problema surge cuando intenta mapear tipos de su lenguaje en C++ uno a uno, como lo que está tratando de hacer para "array " -> vector . No tiene que hacerlo, sin embargo, cualquier cosa que parezca escrita en su idioma no tiene que ser tipeado en la implementación, tan pronto como su compilador haga una comprobación de tipo. –

1

En general, la forma en que hace los tipos de plantillas es como usted describe, pero en lugar de crear todas las combinaciones posibles con anticipación, las crea a pedido. Lo que podría tener una rutina getType como:

std::function< Variable() > getType(std::string name) { 
    auto rv = typeList[name]; 
    if (rv) return rv; 
    auto template_start = name.find('<'); 
    if (template_start != string::npos) { 
     auto template_end = name.rfind('>'); 
     std::string arg = name.substr(template_start+1, template_end); 
     std::string base = name.substr(0, template_start); 
     typeList[name] = rv = InstantiateTemplate(base, arg); 
     return rv; } 
    throw UnknownTypeError(name); 
} 

Esta es llamada para cualquier tipo mencionado en el programa, la creación de las instancias de plantilla necesarios en la demanda.

+0

gracias por la entrada, pero la mayor parte de su función es en realidad lo que el analizador ya hace; además, lo hace recursivamente, por lo que mi analizador maneja las plantillas anidadas con argumentos ilimitados como array >> y crea un nodo AST llamado TypeSpecifier para él. Mi pregunta es cómo escribir la funcionalidad 'InstantiateTemplate', es decir, cómo transformar el TypeSpecifier en una instancia real de un tipo. – stijn

Cuestiones relacionadas