una tipos parametros llamadas lenguaje las invoca implementan funciones funcion ejemplos como características c parsing casting function-call

tipos - ¿Cómo distingue un analizador C entre una conversión de tipo y una llamada a función en general?



tipos de funciones en c (3)

De lo que estás hablando es de una "gramática sin contexto", donde puedes analizar todo sin tener que recordar qué es un tipo y qué es una variable (o, en general, usar atributos semánticos asociados con un identificador). C, desafortunadamente, no está libre de contexto, entonces no tienes ese lujo.

Intento escribir un analizador C para mi propia educación. Sé que podría usar herramientas como YACC para simplificar el proceso, pero quiero aprender tanto como sea posible de la experiencia, así que estoy empezando desde cero.

Mi pregunta es cómo debo manejar una línea como esta:

doSomethingWith((foo)(bar));

Podría ser que (foo)(bar) es un tipo de molde, como en:

typedef int foo; void doSomethingWith(foo aFoo) { ... } int main() { float bar = 23.6; doSomethingWith((foo)(bar)); return 0; }

O bien, podría ser que (foo)(bar) es una llamada a función, como en:

int foo(int bar) { return bar; } void doSomethingWith(int anInt) { ... } int main() { int bar = 10; doSomethingWith((foo)(bar)); return 0; }

Me parece que el analizador no puede determinar con cuál de los dos casos se trata solo mirando la línea doSomethingWith((foo)(bar)); Esto me molesta, porque esperaba poder separar la etapa de análisis de la etapa de "interpretación" donde realmente se determina que la línea typedef int foo; significa que foo ahora es un tipo válido. En mi escenario imaginado, Type a = b + c * d analizaría muy bien, incluso si Type, a, b, c y d no están definidos en ninguna parte, y los problemas solo surgirán más tarde, cuando en realidad intenten "resolver" los identificadores

Entonces, mi pregunta es: ¿cómo lidian los analizadores C "reales" con esto? ¿La separación entre las dos etapas que esperaba era solo un deseo ingenuo, o me estoy perdiendo algo?


Históricamente, typedefs era una adición relativamente tardía a C. Antes de que se agregaran al lenguaje, los nombres de los tipos consistían en palabras clave ( int , char , double , struct , etc.) y signos de puntuación ( * , [] , () ) y por lo tanto, fueron fáciles de reconocer inequívocamente. Un identificador nunca podría ser un nombre de tipo, por lo que un identificador entre paréntesis seguido de una expresión no podría ser una expresión moldeada.

Typedefs hizo posible que un identificador definido por el usuario sea un nombre de tipo, lo que ensucia bastante la gramática.

Eche un vistazo a la sintaxis del tipo-especificador en el estándar C (usaré la versión C90 ya que es un poco más simple):

tipo-especificador:
vacío
carbonizarse
corto
En t
largo
flotador
doble
firmado
no firmado
struct-or-union-specifier
enum-specifier
typedef-name

Todos menos el último se pueden reconocer fácilmente porque o bien son palabras clave o bien comienzan con una palabra clave. Pero un typedef-name es solo un identificador.

Cuando un compilador de C procesa una declaración typedef , debe, en efecto, introducir el nombre typedef como una palabra clave nueva. Lo que significa que, a diferencia de un lenguaje con una gramática libre de contexto, debe haber comentarios de la tabla de símbolos al analizador.

E incluso eso es una simplificación excesiva. Un nombre typedef aún se puede redefinir, ya sea como otro typedef o como algo más, en un ámbito interno:

{ typedef int foo; /* foo is a typedef name */ { int foo; /* foo is now an ordinary identifier, an object name */ } /* And now foo is a typedef name again */ }

Por lo tanto, un nombre typedef es efectivamente una palabra clave definida por el usuario si se usa en un contexto donde un nombre de tipo es válido, pero sigue siendo un identificador ordinario si se vuelve a declarar.

TL; DR: El análisis C es difícil.


Prácticamente ningún lenguaje moderno está libre de contexto (por ejemplo, puede tener el significado de una frase determinada completamente localmente).

El dinero inteligente es construir un analizador sin contexto y resolver dependencias de contexto más adelante, aislando las dos tareas.

Por lo tanto, la pregunta de "¿cómo sabe el analizador el tipo emitido desde la llamada a la función?" Se convierte en un tema no tópico; la única razón por la que existe es que la gente insiste en enredar el análisis en bruto con el nombre y la resolución de tipo.

Para un modelo más limpio, considere usar un analizador GLR. Vea esta respuesta SO para obtener más detalles, usando el problema de resolver qué

x*y;

significa en C, el mismo problema para OP, si aún no ha tropezado con él.