2010-05-06 6 views
6

En su FAQ, Bjarne Stroustrup dice:¿Cuál es el preprocesador C de longitudes/límites como herramienta de creación de idiomas? ¿Dónde puedo aprender más sobre esto?

Para construir [Cfront, el primer compilador de C++ ], primera vez que utiliza C para escribir una "C con clases" -aC preprocesador. "C with Classes" era un dialecto C que se convirtió en el antecesor inmediato de C++ ... Escribí la primera versión de Cfront en "C with Classes".

Cuando leí esto, se despertó mi interés por el C preprocesador. Había visto sus capacidades macro como adecuadas para simplificar expresiones comunes, pero no había pensado en su capacidad para agregar significativamente a la sintaxis y la semántica en el nivel que imagino que llevaría clases a C.

así que ahora tengo algunas preguntas sobre mi mente:

  1. ¿Hay otros ejemplos de este enfoque a bootstrapping un lenguaje fuera de C?

  2. ¿La fuente del trabajo original de Stroustrup está disponible en cualquier lugar?

  3. ¿Dónde podría obtener más información sobre los detalles de la utilización de esta técnica?

  4. ¿Cuáles son las longitudes/límites de ese enfoque? ¿Podría uno, por ejemplo, crear un conjunto de macros de preprocesador que permitan a alguien escribir en algo significativamente parecido a Lisp/Scheme?

+1

Echa un vistazo a la siguiente pregunta acerca de cómo escribir código orientado a objetos en C http://stackoverflow.com/questions/351733/ can-you-write-object-oriented-code-in-c .... porque algunas de las implementaciones de C oop hacen un uso intensivo del preprocesador IIRC. –

+1

C con clases era un compilador que apuntaba al código C, que es un [hábito común] (http://programmers.stackexchange.com/a/257873/40065). Mencionar el preprocesador C es confuso. –

Respuesta

4

Para un ejemplo de la clase de engendro de un "lenguaje" se pueden crear mediante el preprocesador de C, echar un vistazo a este archivo de cabecera:

http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/mac.h

Es a partir del código fuente del original Unix shell escrito por Steve Bourne y su objetivo es convertir C en un Algol como lenguaje. Aquí está un ejemplo de lo que es una pieza de código se parece al usarlo:

http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/args.c

Eso parece un poco extraño, pero todavía es C. Puede parecer un idioma diferente, sino porque se implementa en el preprocesador, no hay soporte sintáctico para ello, por ejemplo

WHILE foo 
DO 
    SWITCH 
    .... 
    ENDSW 
OD 

es todo muy fino y compila bien, pero también lo hace

WHILE foo 
DO 
    SWITCH 
    .... 
    OD 
ENDSW 
+0

Ambos enlaces parecen muertos. –

+1

Sí, tienes razón. Veré si puedo encontrar algunos enlaces actualizados – JeremyP

12

Tenga en cuenta que es BS no diciendo que utiliza el preprocesador C (CPP) para crear C con las clases -se no lo hicieron. Escribió su propio preprocesador usando C. Y Cfront era un verdadero compilador, no un preprocesador. El preprocesador de C es, de hecho, profundamente inadecuado para el desarrollo del lenguaje, ya que no tiene capacidades de análisis alguno.

+2

Pocas habilidades de análisis. Las listas de parámetros macro requieren un poco de procesamiento que sube al nivel de análisis sintáctico. Para manejar 'FOO ((x, y, z), a, b," yo, yo y yo ")' tiene que hacer una profundidad de paréntesis. – nategoose

+0

@nategoose Por supuesto, puede analizar macros de preprocesador C: ¡para eso está diseñado! Pero para escribir cosas como C With Classes, tiene que poder analizar el código C With Classes, y no puede hacer eso. –

+0

Estoy bastante seguro de que CPP es Turing-Complete. Parece que eso haría posible tal análisis, si no es fácil. –

4

El preprocesador C no es lo que estás buscando. No puede agregar sintaxis y semántica como lo hizo Cfront.

Cfront era un compilador real que traducía C con clases, más tarde C++, a C. Era un preprocesador solo en que se ejecutaba antes del compilador de C. Usé un programa llamado f2c una vez para traducir el código FORTRAN 77 al código C. Funcionó según el mismo principio.

Existen lenguajes como Common Lisp con suficiente poder macro para agregar nueva sintaxis y semántica, e idiomas como Forth, donde el sistema es lo suficientemente flexible para acomodar cambios, pero eso no funcionará para la mayoría de los idiomas.

1

Parece que su preprocesador "C with Classes" -to-C era no lo mismo que el preprocesador estándar C, ya que habla específicamente de escribir este preprocesador.

El preprocesador C es muy limitado. Se puede usar para hacer abreviaturas para expresiones comunes, pero eso es todo. Cuando intenta definir nuevos constructos de lenguaje con él, rápidamente se vuelve más incómodo y frágil.

2

Creo que Objective-C comenzó de la misma manera. Fue un preprocesador que construyó un código C que luego se pasó a un compilador de C. Pero no era el preprocesador THE C en el sentido de #define FOO, se ejecutó como un paso adicional antes o después del preprocesador C estándar. El resultado de cualquier cantidad de pasos de preprocesador puede enviarse al compilador de C.

3

Como han mencionado otros, C++ no se ha creado usando el preprocesador C (CPP).

Dicho esto, puede hacer insano cosas con el CPP y la recursividad; Soy bastante seguro que es Turing completo. Las bibliotecas que estoy a punto de vincular para usar mucho de trucos feos para obtener un comportamiento interesante. Aunque puedes construir un tipo de elegancia en la parte superior, muchos podrían considerarlo Turing Tarpit.

Para un más suave introducción a esto, intente Cloak.

Para ir más profundo, mira

  • Boost - "multiplataforma", pero más feo para ella; parte de C++ biblioteca populares
  • Chaos - seguimiento por tipo Boost-PP, pero sólo es compatible con herramientas C99 conformes, ergo más elegantes
  • Order - de lo que puedo decir, un lenguaje Lisp, inspirado por el Caos , construido en CPP puro

Por ej. con Order or Chaos, puede escribir un generador de secuencia de fibonacci recursivo en CPP puro.

+0

Chaos también admite preprocesadores compatibles con C89. –

0

Sugiero que empiece con GCC Macros documentation que proporciona bastante información interesante sobre la implementación de GCC del preprocesador C.

Clay Bridges en su respuesta proporciona un par de ejemplos del uso del Preprocesador C. El del lenguaje de la Orden es interesante con varios ejemplos. El autor de Order menciona un problema con el que se encontró, las implementaciones de C Preprocessor pueden no implementar completamente estándares más recientes.

En general, usar el preprocesador C para desarrollar un tipo de lenguaje bastardo, como lo que hizo Steve Bourne al escribir el Bourne Shell para Unix es una actividad que consideraría un motivo idóneo para la reproducción seguida de múltiples sesiones de inmersión en el agua.

Lo más importante que debe recordar sobre el preprocesador C es que está manipulando tokens de texto. Entonces, el preprocesador C permitirá bastante retoques con la sintaxis. Por ejemplo, la siguiente macro, que compila con Visual Studio 2005 sin errores, muestra la manipulación de texto no intuitiva posible.

#define TESTOP(a,x,y,op,z) a (x) op (y); z 

void f(void) 
{ 
    int i = 0, j = 5; 

    TESTOP(,i,j,+=,); 
    TESTOP(,i,(j + 2),+=,); 
    TESTOP({,i,(j + 2),+=,}); 
} 

Sin embargo sí es necesario para entender y solucionar algunas de las limitaciones del preprocesador C al empujar los límites. Vea el GCC topic Macro Pitfalls para algunos de los problemas a considerar.

Y puede utilizar el preprocesador C como un preprocesador general de macro y texto que se dirige a alguna herramienta que no sea el compilador de C. Por ejemplo, el imake utility for build automation anterior usaba el preprocesador C para proporcionar una amplia facilidad macro.

Donde he visto que el preprocesador C más utilizado fue en la simplificación de código complejo y declaraciones.

Un caso que he visto fue el uso del preprocesador C para proporcionar un lenguaje de máquina de estados que se utilizó para crear las estructuras de datos y los datos para describir una máquina de estados. Las estructuras de datos resultantes se usaron luego como argumento para una función de máquina de estado. Esto permitió que se escriban múltiples procedimientos de máquina de estados diferentes en el lenguaje definido del preprocesador C con el procesamiento de la máquina de estado realizado por una sola función.

Microsoft, en sus Microsoft Foundation Classes (MFC), usó el preprocesador C para ocultar bastantes de los detalles de mensajería de MFC. Una vez que te acostumbras, algo como lo siguiente es razonablemente fácil de leer. Como el IDE de Visual Studio tenía herramientas para generar y modificar el código con las macros, el programador se mostró bastante directo.

BEGIN_MESSAGE_MAP(CFrameworkWndDoc, CWindowDocument) 
    //{{AFX_MSG_MAP(CFrameworkWndDoc) 
    ON_WM_CHAR() 
    ON_WM_TIMER() 
    ON_MESSAGE(WU_EVS_DFLT_LOAD, OnDefaultWinLoad) 
    ON_MESSAGE(WU_EVS_POPUP_WINDOW, OnPopupWindowByName) 
    ON_MESSAGE(WU_EVS_POPDOWN_WINDOW, OnPopdownWindowByName) 
    ON_MESSAGE(WM_APP_CONNENGINE_MSG_RCVD, OnConnEngineMsgRcvd) 
    ON_MESSAGE(WM_APP_XMLMSG_MSG_RCVD, OnXmlMsgRcvd) 
    ON_MESSAGE(WM_APP_BIOMETRIC_MSG_RCVD, OnBiometricMsgRcvd) 
    ON_MESSAGE(WM_APP_SHUTDOWN_MSG, OnShutdownMsgRcvd) 
    ON_MESSAGE(WM_POWERBROADCAST, OnPowerMsgRcvd) 
    ON_MESSAGE(WM_APP_SHOW_HIDE_GROUP, OnShowHideGroupMsgRcvd) 
    //}}AFX_MSG_MAP 
END_MESSAGE_MAP() 

sobre todo cuando ves lo que las macros aspecto usado como:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \ 
     PTM_WARNING_DISABLE \ 
     const AFX_MSGMAP* theClass::GetMessageMap() const \ 
      { return GetThisMessageMap(); } \ 
     const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \ 
     { \ 
      typedef theClass ThisClass;      \ 
      typedef baseClass TheBaseClass;     \ 
      static const AFX_MSGMAP_ENTRY _messageEntries[] = \ 
      { 

    #define END_MESSAGE_MAP() \ 
      {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ 
     }; \ 
      static const AFX_MSGMAP messageMap = \ 
      { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ 
      return &messageMap; \ 
     }         \ 
     PTM_WARNING_RESTORE 

// for Windows messages 
#define ON_MESSAGE(message, memberFxn) \ 
    { message, 0, 0, 0, AfxSig_lwl, \ 
     (AFX_PMSG)(AFX_PMSGW) \ 
     (static_cast< LRESULT (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM) > \ 
     (memberFxn)) }, 

#define ON_WM_TIMER() \ 
    { WM_TIMER, 0, 0, 0, AfxSig_vw, \ 
     (AFX_PMSG)(AFX_PMSGW) \ 
     (static_cast< void (AFX_MSG_CALL CWnd::*)(UINT_PTR) > (&ThisClass :: OnTimer)) }, 
Cuestiones relacionadas