Creo que entiendo el objetivo de un AST, y he construido un par de estructuras de árbol antes, pero nunca un AST. Estoy bastante confundido porque los nodos son texto y no número, por lo que no puedo pensar en una buena manera de ingresar un token / cadena mientras estoy analizando algún código.
Por ejemplo, cuando miré los diagramas de AST, la variable y su valor eran nodos hoja a un signo igual. Esto tiene mucho sentido para mí, pero ¿cómo haría para implementar esto? Supongo que puedo hacerlo caso por caso, de modo que cuando tropiezo con un "=" lo uso como un nodo y agrego el valor analizado antes del "=" como la hoja. Simplemente parece incorrecto, porque probablemente tendría que hacer casos para toneladas y toneladas de cosas, dependiendo de la sintaxis.
Y luego me encontré con otro problema, ¿cómo se atraviesa el árbol? ¿Voy todo el camino por la altura, y regreso a un nodo cuando toco el fondo, y hago lo mismo para su vecino?
He visto toneladas de diagramas en AST, pero no pude encontrar un ejemplo bastante simple de uno en el código, lo que probablemente ayudaría.
fuente
Respuestas:
La respuesta corta es que usas pilas. Este es un buen ejemplo, pero lo aplicaré a un AST.
Para su información, este es el algoritmo de Edsger Dijkstra Shunting-Yard .
En este caso, usaré una pila de operadores y una pila de expresiones. Como los números se consideran expresiones en la mayoría de los idiomas, usaré la pila de expresiones para almacenarlos.
(Por favor, sé amable con mi código. Sé que no es robusto; se supone que es un pseudocódigo).
De todos modos, como puede ver en el código, las expresiones arbitrarias pueden ser operandos para otras expresiones. Si tiene la siguiente entrada:
el código que escribí produciría este AST:
Y luego, cuando desea producir el código para ese AST, realiza un recorrido del árbol de pedidos posteriores . Cuando visita un nodo hoja (con un número), genera una constante porque el compilador necesita conocer los valores de los operandos. Cuando visita un nodo con un operador, genera la instrucción adecuada del operador. Por ejemplo, el operador '+' le da una instrucción "agregar".
fuente
Hay una diferencia significativa entre cómo se representa típicamente un AST en la prueba (un árbol con números / variables en los nodos de hoja y símbolos en los nodos interiores) y cómo se implementa realmente.
La implementación típica de un AST (en un lenguaje OO) hace un uso intensivo del polimorfismo. Los nodos en el AST generalmente se implementan con una variedad de clases, todas derivadas de una
ASTNode
clase común . Para cada construcción sintáctica en el lenguaje que está procesando, habrá una clase para representar esa construcción en el AST, comoConstantNode
(para constantes, como0x10
o42
),VariableNode
(para nombres de variables),AssignmentNode
(para operaciones de asignación),ExpressionNode
(para genéricos expresiones), etc.Cada tipo de nodo específico especifica si ese nodo tiene hijos, cuántos y posiblemente de qué tipo. A
ConstantNode
normalmente no tendrá hijos,AssignmentNode
tendrá dos y unaExpressionBlockNode
puede tener cualquier número de hijos.El analizador crea el AST, que sabe qué construcción acaba de analizar, por lo que puede construir el tipo de nodo AST correcto.
Al atravesar el AST, el polimorfismo de los nodos entra realmente en juego. La base
ASTNode
define las operaciones que se pueden realizar en los nodos, y cada tipo de nodo específico implementa esas operaciones de la manera específica para esa construcción de lenguaje en particular.fuente
Construir el AST a partir del texto fuente es un análisis "simplemente" . Cómo exactamente se hace depende del lenguaje formal analizado y la implementación. Puede usar generadores de analizadores sintéticos como menhir (para Ocaml) , GNU
bison
conflex
o ANTLR, etc. etc. A menudo se hace "manualmente" codificando un analizador de descenso recursivo (vea esta respuesta explicando por qué). El aspecto contextual del análisis a menudo se realiza en otros lugares (tablas de símbolos, atributos, ...).Sin embargo, en la práctica, los AST son mucho más complejos de lo que crees. Por ejemplo, en un compilador como GCC, el AST mantiene la información de ubicación de origen y alguna información de tipeo. Lea sobre Generic Trees en GCC y mire dentro de su gcc / tree.def . Por cierto, mire también dentro de GCC MELT (que he diseñado e implementado), es relevante para su pregunta.
fuente
--My comment #1 print("Hello, ".."world.")
transforma en `[{" type ":" - "," content ":" My comment # 1 "}, {" type ":" call "," name ":" print "," argumentos ": [[{" type ":" str "," action ":" .. "," content ":" Hola ",}, {" type ":" str "," content ": "mundo." }]]}] `¡Creo que es mucho más simple en JS que en cualquier otro idioma!Sé que esta pregunta tiene más de 4 años, pero creo que debería agregar una respuesta más detallada.
Los árboles de sintaxis abstracta se crean de manera diferente a otros árboles; la afirmación más verdadera en este caso es que los nodos del árbol de sintaxis tienen una cantidad variada de nodos según sea necesario.
Un ejemplo son las expresiones binarias como
1 + 2
Una expresión simple como esa crearía un solo nodo raíz que contiene un nodo derecho e izquierdo que contiene los datos sobre los números. En lenguaje C, se vería algo así como¿Tu pregunta también fue cómo atravesar? Recorrer en este caso se llama Nodos de visita . Visitar cada nodo requiere que use cada tipo de nodo para determinar cómo evaluar los datos de cada nodo de sintaxis.
Aquí hay otro ejemplo de eso en C donde simplemente imprimo el contenido de cada nodo:
Observe cómo la función visita recursivamente cada nodo de acuerdo con el tipo de nodo con el que estamos tratando.
¡Agreguemos un ejemplo más complejo, una
if
construcción de declaración! Recuerde que las declaraciones if también pueden tener una cláusula else opcional. Agreguemos la instrucción if-else a nuestra estructura de nodo original. Recuerde que las declaraciones if también pueden tener declaraciones if, por lo que puede ocurrir una especie de recursión dentro de nuestro sistema de nodos. Las otras declaraciones son opcionales, por lo que elelsestmt
campo puede ser NULL, lo que la función recursiva del visitante puede ignorar.de nuevo en nuestra función de impresión de visitante de nodo llamada
AST_PrintNode
, podemos acomodar laif
construcción AST de la declaración agregando este código C:¡Tan sencillo como eso! En conclusión, el árbol de sintaxis no es mucho más que un árbol de una unión etiquetada del árbol y sus datos.
fuente