Ich arbeite an dem von Wikipedia beschriebenen Shunting-Yard-Algorithmus .
Die Beschreibung des Algorithmus im Umgang mit Operatoren lautet wie folgt:
Wenn das Token ein Operator o1 ist, dann:
während sich oben auf dem Operatorstapel ein Operator-Token o2 befindet, und beides
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,
Stellen Sie dann o2 vom Operator-Stack in die Ausgabewarteschlange.
Schieben Sie o1 auf den Bedienerstapel.
Sie geben jedoch das folgende Beispiel:
Eingang:
sin max 2 3 / 3 * 3.1415
Wenn der Algorithmus das /
Token trifft , lautet die Beschreibung dessen, was passieren soll, wie folgt:
Token | Action | Output (in RPN) | Operator Stack
...
/ | Pop token to output | 2 3 max | / sin
...
Sie knallen den Funktionstoken max
vom stack
und setzen ihn in den queue
. Nach ihrem Algorithmus scheint dies zu bedeuten, dass das Funktionstoken sowohl ein Operator ist als auch eine geringere Priorität als der /
Operator hat.
Es gibt keine Erklärung, ob dies der Fall ist oder nicht. Shunting-yard
Welche Priorität hat eine Funktion für den Algorithmus? Sind Funktionen rechts oder links assoziativ? Oder ist Wikipedia nur unvollständig / ungenau?
quelle
sin( max( 2 3) / 3 * 3.1415)
Abhängig von Ihrer Sprachsyntax sind zwei verschiedene Fälle zu berücksichtigen. Wenn Ihre Sprache Klammern verwendet, um die Funktionsanwendung anzuzeigen (z. B.
f(2+1)
), ist der Vorrang irrelevant. Die Funktion sollte auf den Stapel geschoben und danach herausgesprungen werden (für das obige Beispiel ist das Ergebnis2 1 + f
). Alternativ können Sie die Funktion als Wert behandeln und sofort ausgeben und eine Funktionsaufrufoperation nach der geschlossenen Klammer ausgeben (die ansonsten wie jede andere Klammer behandelt werden sollte), z. B.f 2 1 + $
wo$
sich die Funktionsaufrufoperation befindet.Wenn Ihre Sprache jedoch keine Klammern verwendet, um den Funktionsaufruf anzuzeigen, sondern das Argument direkt nach der Funktion ohne spezielle Interpunktion (z. B.
f 2 + 1
) platziert, wie dies anscheinend im Beispiel von Wikipedia der Fall ist, sind die Dinge etwas komplizierter. Beachten Sie, dass der Ausdruck, den ich gerade als Beispiel gegeben habe, nicht eindeutig ist: Wird f auf 2 und 1 angewendet, die zum Ergebnis hinzugefügt werden, oder addieren wir 2 und 1 zusammen und rufen dann f mit dem Ergebnis auf?Auch hier gibt es zwei Ansätze. Sie können die Funktion einfach auf den Operator-Stack übertragen, wenn Sie darauf stoßen, und ihr die gewünschte Priorität zuweisen. Dies ist der einfachste Ansatz und anscheinend das, was das zitierte Beispiel getan hat. Es gibt jedoch praktische Probleme. Wie identifizieren Sie zunächst eine Funktion? Wenn Sie eine endliche Menge haben, ist es einfach, aber wenn Sie benutzerdefinierte Funktionen haben, bedeutet dies, dass Ihr Parser auch Rückmeldungen in Ihre Umgebung benötigt, was schnell chaotisch werden kann. Und wie gehen Sie mit Funktionen mit mehreren Argumenten um?
Meiner Meinung nach ist es für diesen Syntaxstil viel sinnvoller, Funktionen als Werte zu verwenden, die von einem Funktionsanwendungsoperator handlicher sind. Dann können Sie den Anwendungsoperator einfach einfügen, wenn Sie einen Wert lesen, und das letzte, was Sie gelesen haben, war auch ein Wert, sodass Sie keine spezielle Methode benötigen, um festzustellen, welche Bezeichner Funktionen sind. Sie können auch mit Ausdrücken arbeiten, die Funktionen zurückgeben (was mit dem Funktionsstil schwierig oder unmöglich ist). Dies bedeutet, dass Sie Currying verwenden können, um mehrere Argumentfunktionen zu verarbeiten. Dies ist eine massive Vereinfachung gegenüber dem Versuch, sie direkt zu verarbeiten.
Das einzige, was Sie dann entscheiden müssen, ist, welche Priorität die Funktionsanwendung hat. Die Wahl liegt bei Ihnen, aber in jeder Sprache, die ich verwendet habe und die so funktioniert, war es der am stärksten bindende Operator in der Sprache und war richtig assoziativ. (Die einzige interessante Variante ist Haskell, das neben der beschriebenen stark bindenden Version auch ein Synonym für das Symbol hat,
$
das der am schwächsten bindende Operator in der Sprache ist und Ausdrücke wief 2 + 1
f bis 2f $ 2 + 1
anwenden und anwenden lässt es auf den gesamten Rest des Ausdrucks)quelle
Ich implementierte die nach "Funktionen im Rangierbahnhof" angeforderten Funktionen, nachdem ich Dijkstras ursprüngliche Überlegungen gelesen hatte (Seiten 7-11 im Algol 60-Compilerpapier, https://ir.cwi.nl/pub/9251 ) und eine robuste Lösung benötigte hat folgendes getan:
Parsing:
Infix-to-Postfix (Rangierbahnhof):
Funktioniert perfekt in robusten Tests und komplizierten Szenarien. In meiner Anwendung (ein Expander für Befehlszeilenargumente, die Ausdrücke enthalten) unterstütze ich Funktionen mit mehreren Argumenten und ein Komma-Token, um sie zu trennen, und diese fließen durch den gesamten Prozess.
Beispiele sehen aus wie "sqrt (3 ^ 2 + 4 ^ 2)", was zu "3 2 ^ 4 2 ^ + sqrt" und letztendlich zu "5" wird, was das Programm für das Argument hält. Es ist bignum, also ist Binomial (64, 32) / gcd (Binomial (64, 32), Binomial (63, 31)) ==> große Dinge ==> "2" hilfreich. 123456 ^ 789 ist 40.173 Stellen und das Timing zeigt "Auswertung = 0,000390 Sek." auf meinem MacBookPro, so schnell.
Ich benutze dies auch, um Daten in Tabellen zu erweitern und finde das praktisch. Wie auch immer, dies ist mein Tipp auf dem Weg, Funktionsaufrufe, mehrere Argumente und tiefes Verschachteln in einem Dijkstra-Rangierbahn-Kontext sorgfältig zu behandeln. Habe es heute gerade aus unabhängigem Denken gemacht. Ich weiß nicht, ob es bessere Wege gibt.
quelle