¿Cómo definen y guardan los lenguajes de programación funciones / métodos? Estoy creando un lenguaje de programación interpretado en Ruby, y estoy tratando de descubrir cómo implementar la declaración de función.
Mi primera idea es guardar el contenido de la declaración en un mapa. Por ejemplo, si hice algo como
def a() {
callSomething();
x += 5;
}
Luego agregaría una entrada en mi mapa:
{
'a' => 'callSomething(); x += 5;'
}
El problema con esto es que se volvería recursivo, porque tendría que llamar a mi parse
método en la cadena, que luego volvería a llamar parse
cuando se encontrara doSomething
, y finalmente me quedaría sin espacio de pila.
Entonces, ¿cómo manejan esto los idiomas interpretados?
programming-languages
language-design
functions
methods
Perilla de la puerta
fuente
fuente
Respuestas:
¿Sería correcto asumir que su función "analizar" no solo analiza el código sino que también lo ejecuta al mismo tiempo? Si desea hacerlo de esa manera, en lugar de almacenar el contenido de una función en su mapa, almacene la ubicación de la función.
Pero hay una mejor manera. Requiere un poco más de esfuerzo por adelantado, pero produce resultados mucho mejores a medida que aumenta la complejidad: use un árbol de sintaxis abstracta.
La idea básica es que solo analiza el código una vez, nunca. Luego tiene un conjunto de tipos de datos que representan operaciones y valores, y crea un árbol de ellos, así:
se convierte en:
(Esto es solo una representación de texto de la estructura de un AST hipotético. El árbol real probablemente no estaría en forma de texto). De todos modos, analiza su código en un AST, y luego ejecuta su intérprete directamente sobre el AST, o use un segundo pase ("generación de código") para convertir el AST en alguna forma de salida.
En el caso de su idioma, lo que probablemente haría es tener un mapa que asigne nombres de funciones a AST de funciones, en lugar de nombres de funciones a cadenas de funciones.
fuente
(((((((((((((((( x )))))))))))))))))
. En realidad, las pilas pueden ser mucho más grandes, y la complejidad gramatical del código real es bastante limitada. Ciertamente si ese código tiene que ser legible por humanos.No deberías llamar a parse al ver
callSomething()
(supongo que quisiste decir encallSomething
lugar dedoSomething
). La diferencia entrea
ycallSomething
es que uno es una definición de método mientras que el otro es una llamada a método.Cuando vea una nueva definición, querrá hacer verificaciones relacionadas para asegurarse de que puede agregar esa definición, por lo tanto:
Suponiendo que estas verificaciones pasan, puede agregarlo a su mapa y comenzar a verificar el contenido de ese método.
Cuando encuentre una llamada de método como
callSomething()
, debe realizar las siguientes comprobaciones:callSomething
en su mapa?Si encuentra que
callSomething()
está bien, entonces, en este punto, lo que desearía hacer realmente depende de cómo desee abordarlo. Estrictamente hablando, una vez que sepa que tal llamada está bien en este momento, solo puede guardar el nombre del método y los argumentos sin entrar en más detalles. Cuando ejecute su programa, invocará el método con los argumentos que debería tener en tiempo de ejecución.Si desea ir más allá, puede guardar no solo la cadena sino también un enlace al método real. Esto sería más eficiente, pero si tiene que administrar la memoria, puede ser confuso. Te recomendaría que simplemente te aferres a la cuerda al principio. Más tarde puedes intentar optimizar.
Tenga en cuenta que todo esto supone que ha eliminado su programa, lo que significa que ha reconocido todos los tokens en su programa y sabe cuáles son . Eso no quiere decir que sepa si tienen sentido juntos todavía, que es la fase de análisis. Si aún no sabe cuáles son los tokens, le sugiero que primero se concentre en obtener esa información primero.
¡Espero que eso ayude! ¡Bienvenido a Programmers SE!
fuente
Al leer tu publicación, noté dos preguntas en tu pregunta. El más importante es cómo analizar. Hay muchos tipos de programas de análisis (por ejemplo recursiva analizador de descenso , LR analizadores , de Packrat analizadores ) y generadores de analizadores sintácticos (por ejemplo GNU bisontes , antlr ) se puede utilizar para atravesar un programa textual "recursiva" dada una gramática (explícito o implícito).
La segunda pregunta es sobre el formato de almacenamiento para funciones. Cuando no está haciendo una traducción dirigida por la sintaxis , crea una representación intermedia de su programa, que puede ser un árbol de sintaxis abstracta , o algún lenguaje intermedio personalizado, para poder seguir procesándolo (compilar, transformar, ejecutar, escribir en un archivo, etc.)
fuente
Desde un punto de vista genérico, la definición de una función es poco más que una etiqueta o marcador en el código. La mayoría de los demás operadores de bucle, alcance y condicional son similares; son sustitutos de un comando básico de "salto" o "goto" en los niveles inferiores de abstracción. Una llamada de función básicamente se reduce a los siguientes comandos de computadora de bajo nivel:
Una declaración de "devolución" o similar hará lo siguiente:
Por lo tanto, las funciones son simplemente abstracciones en una especificación de lenguaje de nivel superior, que permiten a los humanos organizar el código de una manera más fácil de mantener e intuitiva. Cuando se compila en un lenguaje ensamblador o intermedio (JIL, MSIL, ILX), y definitivamente cuando se procesa como código de máquina, casi todas esas abstracciones desaparecen.
fuente