Estoy trabajando a través del algoritmo Shunting-yard , como lo describe wikipedia.
La descripción del algoritmo cuando se trata con operadores es la siguiente:
Si el token es un operador, o1, entonces:
mientras hay una ficha de operador, o2, en la parte superior de la pila de operadores, y
o1 is left-associative and its precedence is less than or equal to that of o2, or o1 is right associative, and has precedence less than that of o2,
luego saque o2 de la pila del operador, en la cola de salida;
empuje o1 en la pila del operador.
Sin embargo, dan el siguiente ejemplo:
Entrada:
sin max 2 3 / 3 * 3.1415
Cuando el algoritmo llega al /
token, la descripción de lo que debería suceder es la siguiente:
Token | Action | Output (in RPN) | Operator Stack
...
/ | Pop token to output | 2 3 max | / sin
...
Están apareciendo el símbolo de la función max
de la stack
y puesta en el queue
. Según su algoritmo, esto parecería significar que el token de función es un operador y tiene una precedencia menor que la del /
operador.
No hay explicación sobre si este es el caso o no. Entonces, para el Shunting-yard
algoritmo, ¿cuál es la precedencia de una función? ¿Son las funciones derecha o izquierda asociativas? ¿O es wikipedia simplemente incompleto / inexacto?
fuente
sin( max( 2 3) / 3 * 3.1415)
Hay dos casos diferentes a considerar, dependiendo de la sintaxis de su idioma. Si su idioma usa paréntesis para indicar la aplicación de funciones (por ejemplo
f(2+1)
), entonces la precedencia es irrelevante. La función debe insertarse en la pila y desplegarse después (para el ejemplo anterior, el resultado es2 1 + f
). Alternativamente, puede tratar la función como un valor y generarla de inmediato, y generar una operación de invocación de función después del paréntesis de cierre (que de lo contrario debería tratarse de la misma manera que cualquier otro paréntesis), por ejemplof 2 1 + $
, dónde$
está la operación de invocación de función.Sin embargo, si su lenguaje no utiliza paréntesis para indicar la invocación de la función, sino que coloca el argumento directamente después de la función sin ninguna puntuación especial (por ejemplo
f 2 + 1
), como aparentemente es el caso del ejemplo de Wikipedia, entonces las cosas son un poco más complicadas. Tenga en cuenta que la expresión que acabo de dar ás como ejemplo es ambigua: ¿se aplica f a 2 y 1 agregado al resultado, o agregamos 2 y 1 juntos y luego llamamos f con el resultado?De nuevo, hay dos enfoques. Simplemente puede empujar la función a la pila de operadores cuando la encuentre y asignarle cualquier precedencia que desee. Este es el enfoque más simple, y aparentemente es lo que ha hecho el ejemplo citado. Sin embargo, hay problemas prácticos. En primer lugar, ¿cómo identificas una función? Si tiene un conjunto finito, es fácil, pero si tiene funciones definidas por el usuario, esto significa que su analizador también necesita retroalimentar su entorno, lo que puede volverse desordenado rápidamente. ¿Y cómo manejas las funciones con múltiples argumentos?
Creo que para este estilo de sintaxis, el uso de funciones como valores que son más útiles para un operador de aplicación de funciones tiene mucho más sentido. Luego, simplemente puede inyectar el operador de la aplicación cada vez que lea un valor y lo último que leyó también fue un valor, por lo que no necesita ninguna forma especial de saber qué identificadores son funciones. También puede trabajar con expresiones que devuelven funciones (lo cual es difícil o imposible con el estilo de función como operación). Y esto significa que puede usar curry para manejar múltiples funciones de argumento, lo cual es una simplificación masiva sobre tratar de manejarlas directamente.
Lo único que debe decidir es cuál es la precedencia de la aplicación de funciones. La elección depende de usted, pero en todos los idiomas que he utilizado que funcionan de esta manera, ha sido el operador más vinculante en el lenguaje y ha sido correcto asociativo. (La única variación interesante es Haskell, que además de tener la versión fuertemente vinculante descrita, también tiene un sinónimo con el símbolo
$
que es el operador más débilmente vinculante en el lenguaje, permitiendo expresiones comof 2 + 1
aplicar f a 2 yf $ 2 + 1
aplicar a todo el resto de la expresión)fuente
Implementé las "funciones solicitadas en el patio de maniobras" después de leer el pensamiento original de Dijkstra (páginas 7-11 en el documento del compilador Algol 60, https://ir.cwi.nl/pub/9251 ), y necesito una solución sólida. hizo lo siguiente:
análisis:
Infix-to-postfix (patio de maniobras):
Funciona perfectamente en pruebas robustas y escenarios complicados. En mi aplicación (un expansor de expresiones que contiene argumentos de línea de comandos), soporto funciones de múltiples argumentos y un token "," de coma para separarlos, y estos fluyen a través de todo el proceso.
Los ejemplos se parecen a "sqrt (3 ^ 2 + 4 ^ 2)" que se convierte en "3 2 ^ 4 2 ^ + sqrt" y, en última instancia, "5", que es lo que el programa piensa que es el argumento. Es bignum, así que "" binomial (64, 32) / mcd (binomial (64, 32), binomial (63, 31)) "==> cosas grandes ==>" 2 "es útil". 123456 ^ 789 " tiene 40,173 dígitos y el tiempo muestra "evaluación = 0.000390 segundos" en mi MacBookPro, muy rápido.
También lo uso para expandir datos en tablas, y lo encuentro útil. De todos modos, este es mi consejo para manejar cuidadosamente las llamadas a funciones, los argumentos múltiples y el anidamiento profundo en un contexto de patio de maniobras de Dijkstra. Lo hice hoy por pensamiento independiente. No sé si puede haber mejores formas.
fuente