Um zu versuchen, ein PEG in JavaScript zu implementieren, das ältere Browser nicht von Stapelüberläufen abstürzen lässt, möchte ich eine syntaktische Ausdruckgrammatik erstellen, die eine Zeichenfolge auf nichtrekursive Weise analysiert. Wie machst Du das? Es fühlt sich geistesabwesend an.Wie schreibt man eine rekursive Funktion mit einem nicht rekursiven Stack?
Sagen Sie bitte eine Struktur wie dieses:
- A
grammar
viele Ausdrücke hat - Ein
expression
hat vielematchers
- A
matcher
hat vieletokens
(oder was auch immer ist ein besseres Wort dafür) - Ein
token
kann entweder auf einen anderenexpression
zeigen oder eine primitive Zeichenfolge oder ein regulärer Ausdruck sein. Wenn es also auf einen anderen Ausdruck zeigt, startet die Rekursion.
So sagen Sie die Hierarchie wie folgt definieren:
var grammar = new Grammar('math');
var expression = grammar.expression;
expression('math')
.match(':number', ':operator', ':number', function(left, operator, right){
switch (operator) {
case '+': return left + right;
case '-': return left - right;
case '*': return left * right;
case '/': return left/right;
}
});
expression('number')
.match(/\d+/, parseInt);
expression('operator')
.match('+')
.match('-')
.match('*')
.match('/');
var val = grammar.parse('6*8'); // 42
Wenn Sie grammar.parse
nennen, ist es an der Wurzel Ausdruck beginnt (die den gleichen Namen wie es hat, so „Mathematik“). Dann durchläuft es jeden Matcher, dann jedes Token, und wenn das Token ein Ausdruck ist, rekursiv. Im Grunde ist dies (der Parser wird der Überblick über die Offset/Position der Zeichenfolge es die Muster gegen einstimmt; dies ist nur Pseudo-Code):
function parse(str, expression, position) {
var result = [];
expression.matchers.forEach(function(matcher){
matcher.tokens.forEach(function(token){
var val;
if (token.expression) {
val = parse(str, token.expression, position);
} else {
val = token.parse(str, position);
}
if (val) result.push(val);
});
});
return result;
}
parse('6*8', grammar.root, 0);
Also für einen einfachen Ausdruck wie 6*8
gibt es sehr wenig Rekursion, aber man kann schnell zu einem komplexen Ausdruck mit vielen Ebenen der Verschachtelung. Plus multipliziere die Verschachtelung mit all diesen verschachtelten for-Schleifen, und der Stapel wird groß (ich verwende nicht forEach
, ich benutze for-Schleifen, aber in der for-Schleife ruft er die meiste Zeit eine Funktion auf, so dass er hübsch wird Ziemlich das gleiche).
Die Frage ist, Wie können Sie dies "abflachen"? Statt Rekursion zu tun, wie machen Sie das so im Wesentlichen es ist dies wie:
while (token = stack.pop()) {
val = token.parse(val);
if (val) result.push(val);
}
Ich bin nicht für die Details, wie suche eine Lösung für dieses spezifische PEG Problem zu implementieren, ich bin nur mehr suchen die allgemeine Art und Weise, wie Sie den rekursiven Zustand nicht rekursiv verfolgen.
Es scheint, als ob Sie die Tiefe jedes Objekttyps getrennt in einer Art Pass-Through-Objekt verfolgen müssen (wie 'neuer Kontext (Grammatik)'). Also würde es Eigenschaften 'expressionStack',' matcherStack' und 'tokenStack' haben, und die Indizes für jede verfolgen. Aber dann scheint das kompliziert, also nicht sicher, ob das der Weg ist ... –
Die allgemeinste Lösung ist ein Quell-zu-Quell-Compiler von Javascript zu Javascript, der das Eingabeprogramm in einen Fortsetzungsmodus mit einem Trampolin umwandelt. Ich habe keine Ahnung, ob jemand so ein Biest geschrieben hat. –
Das scheint auch komplex :). Dies ist, was ich bisher habe, aber (a) es scheint zu komplex zu sein, bis zu dem Punkt, wo es schwer zu verstehen ist, und (b) es macht es schwieriger, Randfälle zu behandeln. https://github.com/parsejs/grammar/blob/master/lib/context.js –