Pegjs: Implementieren Sie parametrisierbare Regeln

Erstellt am 25. Aug. 2011  ·  29Kommentare  ·  Quelle: pegjs/pegjs

Es wäre toll, Regeln mit Variablen parametrisieren zu können;

   = '\"' parse_contents '\"' ->
   / '\'' parse_contents('\'') '\'' ->
   / '+' parse_contents('+') '+' -> /* sure why not :) */

parse_contents(terminator='\"')
    = ('\\' terminator / !terminator .)+ -> return stuff
feature

Hilfreichster Kommentar

Vielen Dank für Ihre Zeit in der Erklärung

Wahrscheinlich habe ich in zehn Jahren noch eine Frage an Sie. Gute 2020er

Alle 29 Kommentare

Haben Sie einen konkreten Anwendungsfall, bei dem Ihnen dies viel Arbeit ersparen oder etwas derzeit Unmögliches möglich machen würde?

Es macht das Analysieren von Einrückungsebenen viel einfacher, indem Regeln aufgerufen werden, die die Ebene als Argument übergeben.

Außerdem ist es in einer reinen DRY-Logik, wenn Dinge wie "Zeug, das durch dieses Zeichen mit Escape-Sequenz begrenzt ist, so" gemacht wird, besser, etwas wie delimited('\'', '\\') aufzurufen, als nur die Regel (und ihre Aktionen!) dreimal auszuführen .

Ich hätte klarer sein sollen. Mit "spezifisch" suchte ich nach etwas wie "Ich habe an einer Grammatik der Sprache X gearbeitet und da sind 5 Regeln drin, die zu einer kombiniert werden könnten, hier sind sie:" Das heißt, ich wollte die reale Welt sehen Anwendungsfall und realer Code. Daraus kann ich besser abschätzen, in welchen Fällen diese Funktion sinnvoll wäre und für wie viele Personen.

Bitte nehmen Sie dies nicht, da ich per se gegen diese Funktion bin. Ich möchte im Allgemeinen keine Funktionen implementieren, die aufgrund der Komplexität und der Implementierungskosten nur für einen winzigen Bruchteil von Sprachen oder Entwicklern nützlich sind. Und in diesem Fall sind die Kosten relativ hoch.

Wenn ich nur einen Parser für Javascript schreibe, könnte ich string = delimited_by('\'') / delimited_by('\"') und später regexp = delimited_by('/') haben.

In letzter Zeit habe ich einen Parser für eine eigene Sprache geschrieben. Ich habe so etwas in einem PEG-Framework, das ich für Python geschrieben habe:

LeftAssociative(op, subrule): l:subrule rest:(op subrule)* -> a_recursive_function_that_reverses_rest_and_makes_bintrees(l, op, subrule)

Und ich kann dann schreiben:

...
exp_mult = LeftAssociative(/[\*\/%]/, exp_paren)
exp_add = LeftAssociative(/[\+-]/, exp_mult)

Da ich ungefähr so ​​viele Prioritätsebenen habe wie in C++ (all seine Operatoren plus ein paar mehr), lasse ich Sie sich vorstellen, wie nützlich in sein kann. Ich bin noch nicht fertig mit den Parsing-Ausdrücken, aber ich benutze es bereits 12 Mal.

Dies wäre großartig, wenn es mit einer „Import“-Funktion kombiniert würde

require(CommaSeparatedList.pegjs)
require(StringLiteral.pegjs)
require(IntegerLiteral.pegjs)

...

Function
 = name:FuncName "(" args:CommaSeparatedList(Literal)  ")" 

Hash
= "{"   props:CommaSeparatedList(Prop)   "}"

Prop
= Key ":" Literal

Literal =
  StringLiteral / IntegerLiteral

(Dies ist etwas komplizierter als die Anfrage von OP, aber es schien zu nahe zu sein, um einen eigenen Thread zu rechtfertigen.)

Ich baue einen R5RS-Schema-Parser mit Hilfe von PEG.js. Alles ist rosig, außer bei Quasi-Zitaten, die eine kontextbewusste Analyse erfordern. Es wäre nützlich, Regeln für die spontane Regelgenerierung aus Vorlagen parametrisieren zu können, um eine große Menge umständlicher Nachbearbeitung zu vermeiden. Eine vereinfachte Quasi-Zitat-Grammatik könnte beispielsweise so aussehen:

    quasiquotation = qq[1]
    qq[n] = "`" qq_template[n]
    qq_template[0] = expression
    qq_template[n] = simple_datum / list_qq_template[n] / unquotation[n]
    list_qq_template[n] = "(" qq_template[n]* ")" / qq[n+1]
    unquotation[n] = "," qq_template[n-1]

Ich bin daran interessiert, zur Entwicklung dieser Funktion beizutragen, wenn Interesse daran besteht, sie dem Tool hinzuzufügen.

Der Hauptgrund dafür wäre, kontextsensitive Grammatiken zu unterstützen, die, wenn ich mich nicht irre, die beliebtesten Sprachen sind (ich weiß mit Sicherheit, dass C und Python kontextspezifische Dinge haben). Laut Trevor Jim ist Haskell auch nicht kontextfrei und behauptet, dass die meisten Sprachen dies nicht sind:

http://trevorjim.com/haskell-is-not-context-free/
http://trevorjim.com/how-to-prove-that-a-programming-language-is-context-free/

Die Verwendung eines externen Zustands in einem Parser, der zurückverfolgen kann (wie PEG), ist gefährlich und kann zu Problemen führen, wie sie in diesem Parser zu sehen sind:

{   var countCs = 0;
}

start = ((x/y) ws*)* { return countCs }

x = "ab" c "d"
y = "a" bc "e"

c = "c" { countCs++; }
bc = "bc" { countCs++; }

ws = " " / "\n"

Das Obige gibt 2 anstelle der richtigen Antwort von 1 zurück. Probleme wie diese können schwer zu erklären sein, können heimtückische, schwer zu findende Fehler verursachen, und wenn sie gefunden werden, kann es sehr schwierig sein, sie überhaupt zu umgehen, geschweige denn, es elegant zu tun . Es ist mir unklar, wie ich dies überhaupt tun soll, ohne die von PEG zurückgegebenen Daten nachzubearbeiten. Wenn Ihr Parser selbst irgendwie die Zählung benötigt, hat er einfach Pech.

Derzeit ist die (gefährliche) Verwendung des externen Zustands die einzige Möglichkeit, kontextsensitive Grammatik zu analysieren. Mit parametrisierten Regeln könnte ein Parser dies analysieren, ohne einen ungültigen Zustand zu riskieren:

start = countCs:((x<0>/y<0>) ws*)* { return countCs.reduce(function(a,b){return a+b[0];}, 0); }

x<count> = "ab" theCount:c<count> "d" { return theCount; }
y<count> = "a" theCount:bc<count> "e" { return theCount; }

c<count> = "c" { return count++; }
bc<count> = "bc" { return count++; }

ws = " " / "\n"

David, Sie haben nach realen Situationen gefragt, und Pythons Syntax für Leerzeicheneinrückungen ist hier eindeutig ein Beispiel. Ich möchte in Lima (der Programmiersprache, die ich mit PEG erstelle) eine ähnliche Leerraum-Einrückungssyntax ausführen. Aber ich würde so etwas nicht implementieren wollen, wenn ich versehentlich einen ungültigen Zustand erzeugen könnte, der alles in die Luft jagt. Ich könnte jedes Parsing-Konstrukt benennen, das einen Kontext erfordert, wie z. B. x* y von C (ist es x mal y oder wird y als Zeiger auf einen x-typisierten Wert definiert?).

Beachten Sie, dass kontextsensitive Grammatiken notwendigerweise Informationen übergeben müssen, die von Unterausdrücken zurückgegeben werden, die bereits mit einer parametrisierten Regel übereinstimmen – andernfalls kann der Parser tatsächlich keinen Kontext verwenden. Hier ist ein echtes Beispiel für einen Zeichenfolgentyp, den ich für Lima in Betracht ziehe, der nur funktioniert, wenn parametrisiertes Parsing verfügbar ist und (als Variablen) auf Labels von zuvor übereinstimmenden Ausdrücken zugreifen kann:

literalStringWithExplicitLength = "string[" n:number ":" characters<n> "]"
number = n:[0-9]* {return parseInt(n.join(''));}
characters<n> = c:. { // base case
  if(n>0) return null; // don't match base case unless n is 0
  else return c;
}
/ c:. cs:characters<n-1> {
  ret c+cs
}

Dies wäre in der Lage, eine Zeichenfolge wie string[10:abcdefghij] zu analysieren. Sie können das nicht mit schönem reinem PEG.js machen, so wie es aussieht. Du hast etwas Schreckliches getan wie:

{ var literalStringLengthLeft=undefined;
}
literalStringWithExplicitLength = "string[" n:specialNumber ":" characters "]"
specialNumber = n:number {
  literalStringLengthLeft = n;
  return n;
}
number = n:[0-9]* {return parseInt(n.join(''));}
characters = c:character cs:characters? {
  return c + cs
}
character = c:. {
  if(literalStringLengthLeft > 0) {
    literalStringLengthLeft--;
    return c;
  } else {
    literalStringLengthLeft = undefined;
    return null; // doesn't match
  }
}

Viele, viele Protokolle haben diese Art von Parsing-Bedarf – zum Beispiel haben IPv4-Pakete ein Feld, das ihre Gesamtlänge beschreibt. Sie benötigen diesen Kontext, um den Rest des Pakets richtig zu analysieren. Gleiches gilt für IPv6, UDP und wahrscheinlich jedes andere paketbasierte Protokoll. Die meisten Protokolle, die TCP verwenden, werden auch so etwas benötigen, da man in der Lage sein muss, mehrere völlig separate Objekte mit demselben konzeptionellen Zeichenstrom zu übertragen.

Wie auch immer, ich hoffe, ich habe einige gute Beispiele und Gründe gegeben, warum ich denke, dass dies nicht nur ein nettes Feature ist, nicht nur ein leistungsstarkes Feature, sondern wirklich ein wesentliches Feature, das vielen Parsern fehlt (einschließlich im Moment PEG.js ).

Pegasus (ein Projekt, das den größten Teil seiner Syntax mit peg.js teilt) löst dies, indem es einen #STATE{} -Ausdruck hat, der die Fähigkeit erhält, ein Zustandswörterbuch zu mutieren. Dieses Zustandswörterbuch wird zurückverfolgt, wenn Regeln zurückverfolgt werden. Dies ermöglicht es, signifikantes Whitespace-Parsing zu unterstützen (Einzelheiten finden Sie im Wiki-Eintrag über Significant Whitespace ).

Außerdem kann durch Zurückverfolgen des Zustands zusammen mit dem Parsing-Cursor eine Speicherung auch für zustandsbehaftete Regeln erreicht werden.

Peg.js könnte das gleiche tun, denke ich.

Wie verwaltet Pegasus den Zurückverfolgungsstatus, wenn Regeln zurückverfolgt werden? Ich kann mir vorstellen, dass Sie eine Momentaufnahme des gesamten geänderten Programmstatus behalten und wiederherstellen könnten, aber das wäre teuer. Ich könnte mir vorstellen, nur eine Momentaufnahme der geänderten Variablen zu behalten, aber das würde entweder erfordern, dass der Benutzer sie angibt, was die Erstellung von Parsern komplizierter machen würde, oder der Parser müsste irgendwie den gesamten geänderten Zustand in einem Teil des Codes herausfinden. Keines davon klingt ideal, also wie macht es Pegasus?

Theoretisch könnte der Parser ungültig ausgeführte Aktionen vermeiden, wenn A. Aktionen in Closures eingereiht und erst ausgeführt werden, wenn der Parser vollständig abgeschlossen ist, und B. weil sie ausgeführt werden, nachdem der Parser abgeschlossen ist, sie eine Regelübereinstimmung nicht abbrechen könnten. Vielleicht wäre dieses Schema optimaler als das Zustands-Backtracking in Pegasus?

Auch das Beheben des Problems des ungültigen Zustands ist in der Tat sehr nett, aber es löst nicht das Problem der Ausdrückbarkeit, das ich im Zusammenhang mit einem Zeichenfolgenliteral wie string[10:abcdefghij] angesprochen habe, aber ich bin definitiv daran interessiert, wie es funktioniert

Der Status des gesamten Programms wird nicht zurückverfolgt. Es unterhält ein unveränderliches Wörterbuch des Zustands. Es speichert das aktuelle Zustandswörterbuch zusammen mit dem Cursor, und immer wenn der Cursor zurückverfolgt wird, wird das Zustandswörterbuch mit ihm zurückverfolgt. Das Wörterbuch ist außerhalb von #STATE{} -Aktionen unveränderlich und wird unmittelbar vor jeder Statusänderung KOPIERT.

Es gibt eine kleine Leistungseinbuße für das Setzen einer zusätzlichen Variablen jedes Mal, wenn Sie den Cursor bewegen, aber dies wird bei weitem durch die Fähigkeit, sich zustandsbehaftete Regeln zu merken, aufgewogen. Außerdem führt dies nicht zu Unmengen an Speicherzuweisung, da die Unveränderlichkeit des Zustandswörterbuchs es ermöglicht, es gemeinsam zu nutzen, bis es mutiert wird. Wenn Sie beispielsweise in Ihrem Parser keinen Zustand haben, gibt es nur eine Zuweisung: ein einzelnes (leeres) Zustandswörterbuch.

JavaScript hat (meines Wissens) nicht die Fähigkeit, ein Objekt unveränderlich zu machen, aber das war hauptsächlich eine Sicherheitsfunktion. Peg.js müsste nur ein Zustandswörterbuch kopieren, bevor es jeden #STATE{} -Codeblock verarbeitet, und das zurückverfolgen, wenn der Cursor zurückverfolgt wird.

Oh ok, also muss der Benutzer im Grunde angeben, welchen Zustand er ändert. Das ist ziemlich toll. Aber ich glaube immer noch nicht, dass es wirklich die gleichen Vorteile abdeckt wie die Parametrisierung. Es hört sich so an, als wäre es wahrscheinlich für andere Dinge nützlich.

Ich habe gerade einen Fork geschrieben, der eine Umgebung bereitstellt, auf die über die Variable env zugegriffen werden kann: https://github.com/tebbi/pegjs
Dies ist dasselbe wie das oben vorgeschlagene Objekt #STATE{} .
Es ist ein schneller Hack, der eine (paket-)globale Variable verwendet, die wiederhergestellt wird, wenn eine Parsing-Funktion verlassen wird. Das Kopieren von env erfolgt mit Object.create().

Hier ist eine Beispielgrammatik, die sie verwendet, um durch Leerzeichen definierte Blöcke a la Python zu analysieren:

{
  env.indLevel = -1
}

block =
  empty
  ind:ws* &{
    if (ind.length <= env.indLevel) return false;
    env.indLevel = ind.length;
    return true;
  }
  first:statement? rest:indStatement*
  {
    if (first) rest.unshift(first);
    return rest;
  }

indStatement =
  "\n" empty ind:ws* &{ return env.indLevel === ind.length; }
  stm:statement
  {return stm; }

statement =
    id:identifier ws* ":" ws* "\n"
    bl:block { return [id, bl]; }
  / identifier

identifier = s:[a-z]* { return s.join(""); }

empty = (ws* "\n")*

ws = [ \t\r]

Hier ist eine Beispieleingabe für den resultierenden Parser:

b:
   c
   d:
       e
   f
g

Ich habe den Eindruck, dass PEG.js keinerlei Parameter für Regeln unterstützt - was überraschend ist. Diese Funktion ist mir sehr wichtig.

Was ich brauche, ist einfacher als die Anforderung des OP - das OP möchte die Grammatik selbst abhängig vom Parameter ändern, aber ich muss mindestens nur eine Ganzzahl an eine Regel übergeben. Grundsätzlich möchte ich eine LLLPG -Regel übersetzen, die so aussieht (wobei PrefixExpr ein Ausdruck mit hoher Priorität ist, z. B. ein Präfixausdruck wie -x oder ein Bezeichner ...):

@[LL(1)]
rule Expr(context::Precedence)::LNode @{
    {prec::Precedence;}
    e:PrefixExpr(context)
    greedy
    (   // Infix operator
        &{context.CanParse(prec=InfixPrecedenceOf(LT($LI)))}
        t:(NormalOp|BQString|Dot|Assignment)
        rhs:Expr(prec)
        { ... }
    |   // Method_calls(with arguments), indexers[with indexes], generic!arguments
        &{context.CanParse(P.Primary)}
        e=FinishPrimaryExpr(e)
    |   // Suffix operator
        ...
    )*
    {return e;}
};
// Helper rule that parses one of the syntactically special primary expressions
@[private] rule FinishPrimaryExpr(e::LNode)::LNode @{
(   // call(function)
    "(" list:ExprList(ref endMarker) ")"
    { ... }
    |   // ! operator (generic #of)
        "!" ...
    |   // Indexer / square brackets
        {var args = (new VList!LNode { e });}
        "[" args=ExprList(args) "]"
        { ... }
    )
    {return e;}
};

Meine Sprache hat 25 Vorrangstufen, und mit diesen Regeln habe ich fast alle reduziert, damit sie von einer einzigen Regel verarbeitet werden (Sie können sich Precedence als einen Wrapper um ein paar ganze Zahlen vorstellen, die den Vorrang von an beschreiben Operator). Darüber hinaus hat meine Sprache eine unendliche Anzahl von Operatoren (im Grunde jede Folge von Satzzeichen) und die Priorität eines Operators wird entschieden, nachdem er analysiert wurde. Obwohl es _technisch_ möglich wäre, die Sprache auf die übliche Weise zu parsen, mit einer separaten Regel für jede der 25 Arten von Operatoren, wäre dies eine schreckliche Methode.

Außerdem können Sie hier sehen, dass die innere Regel FinishPrimaryExpr einen Syntaxbaum erstellt, der einen Parameter enthält, der von der einschließenden Regel übergeben wird.

Also ... gibt es eine Möglichkeit, Parameter an eine PEG.js-Regel zu übergeben?

+1! In meinem Fall möchte ich einfach einen Parser für eine Syntax generieren, bei der einige Trennzeichen global konfigurierbar sind. In diesem Fall kann ich dies erreichen, indem ich die Trennzeichen-Literale durch Match-Everything-Ausdrücke in Kombination mit einem Prädikat ersetze, aber es wäre viel eleganter (und auch effizienter), wenn Match-Everything einfach durch eine Variable ersetzt werden könnte.

+1, gibt es eine Chance, dies in absehbarer Zeit umzusetzen?

Ein weiterer Anwendungsfall. Dies ist aus Ihrem Beispiel javascript.pegjs :

(...)

RelationalExpression
  = head:ShiftExpression
    tail:(__ RelationalOperator __ ShiftExpression)*
    { return buildBinaryExpression(head, tail); }

RelationalOperator
  = "<="
  / ">="
  / $("<" !"<")
  / $(">" !">")
  / $InstanceofToken
  / $InToken

RelationalExpressionNoIn
  = head:ShiftExpression
    tail:(__ RelationalOperatorNoIn __ ShiftExpression)*
    { return buildBinaryExpression(head, tail); }

RelationalOperatorNoIn
  = "<="
  / ">="
  / $("<" !"<")
  / $(">" !">")
  / $InstanceofToken

(...)

  (...)
  / ForToken __
    "(" __
    init:(ExpressionNoIn __)? ";" __
    test:(Expression __)? ";" __
    update:(Expression __)?
    ")" __
    body:Statement
  (...)

(...)

All diese ...NoIn -Regeln (und es gibt VIELE davon) sind einfach wegen der for in -Anweisung erforderlich. Wäre ein viel besserer Ansatz dafür nicht so etwas wie:

(...)

RelationalExpression<allowIn>
  = head:ShiftExpression
    tail:(__ RelationalOperator<allowIn> __ ShiftExpression)*
    { return buildBinaryExpression(head, tail); }

RelationalOperator<allowIn>
  = "<="
  / ">="
  / $("<" !"<")
  / $(">" !">")
  / $InstanceofToken
  / &{ return allowIn; } InToken
    {return "in";}

(...)

  (...)
  / ForToken __
    "(" __
    init:(Expression<false> __)? ";" __
    test:(Expression<true> __)? ";" __
    update:(Expression<true> __)?
    ")" __
    body:Statement
  (...)

(...)

Dies fühlt sich sehr ähnlich an, wie beispielsweise die JavaScript-Grammatik angegeben wird: https://tc39.github.io/ecma262/#prod -IterationStatement (beachten Sie die ~In )

Eine Sprache, die ich gerade entwickle, hat genau dieses Problem: Ich möchte einige Regeln nur an bestimmten Stellen deaktivieren/aktivieren. Ich würde gerne davon absehen, jede betroffene Regel zu duplizieren, wie Sie es für die JavaScript-Grammatik getan haben.

Gibt es eine alternative Möglichkeit, dies zu erreichen, ohne die Regeln zu duplizieren?

+1, gibt es eine Chance, dies in absehbarer Zeit umzusetzen?

Diesem Problem ist ein Meilenstein zugewiesen (nach 1.0.0). Die aktuelle Version von PEG.js ist 0.10.0. Offensichtlich werden Probleme nach 1.0.0 nach der Veröffentlichung von 1.0.0 behandelt, was gemäß der Roadmap irgendwann nach der Veröffentlichung von 0.11.0 geschehen wird.

Dies sollte Ihre Frage beantworten. Der beste Weg, den gesamten Prozess zu beschleunigen, besteht darin, bei Problemen zu helfen, die auf 0.11.0 und 1.0.0 abzielen.

Gibt es eine alternative Möglichkeit, dies zu erreichen, ohne die Regeln zu duplizieren?

Eine Möglichkeit besteht darin, den Zustand manuell zu verfolgen und dann semantische Prädikate zu verwenden. Aber dieser Ansatz hat Probleme mit dem Backtracking und ich würde ihn nicht empfehlen (mit anderen Worten, wenn ich die Wahl zwischen Regelduplizierung und manueller Zustandsverfolgung hätte, würde ich Duplizierung wählen).

Es gibt zwei Arten von Argumenten, die an Parser übergeben werden können:

  1. Werte. Grammatiken für Sprachen wie Python, Nim und Haskell (und auf andere Weise auch Scheme) hängen von der "Tiefe" des Ausdrucks innerhalb des Baums ab. Korrekte Grammatik zu schreiben erfordert, diesen Kontext irgendwie zu bestehen.
  2. Parser. leftAssociative(element, separator) , escapedString(quote) und withPosition(parser) sind gute Beispiele dafür.

Es sollte eine Möglichkeit geben, irgendwie zu markieren, ob das Argument ein Parser oder ein Wert ist. Als ich versuchte, den richtigen Ansatz zu finden, verwendete ich schließlich globale Variablen für den Kontext, und das ist offensichtlich eine Sackgasse. Hat jemand irgendwelche Ideen dazu?

Wie sieht es mit Makros aus ?

Gegeben:

Add <Expression, Add>
  = left:Expression _ '+' _ right:Add
    { return { type: 'add', left, right } }
  / Expression

Wann:

  = Add <MyExpression, MyAdd>

MyExpression
  = [0-9]+

Dann:

  = left:MyExpression _ '+' _ right:MyAdd
    { return { type: 'add', left, right } }
  / MyExpression

MyExpression
  = [0-9]+

Dadurch können wir Regeln von unten nach oben erstellen :smirk:

Ich stimme zu, ich empfehle Entwicklern, diese Funktion hinzuzufügen :)

Ich brauche diese Funktion wirklich für eine aktualisierte JavaScript-Grammatik, die ich schreibe, also steht sie ganz oben auf meiner Wunschliste. Werde es mal ausprobieren und sehen wie es klappt.

@samvv Ich bin auf einem ganz anderen Weg darauf gestoßen und habe noch nicht den ganzen Thread gelesen.
Allerdings zeige ich in #572, von dem ich hier referenziert habe, eine Technik, mit der man parametrisierte Regeln simulieren kann.

Das heißt im Wesentlichen: Return- Funktionen sind Zwischen-Parse-Ergebnisse.

Dieser "Trick" ist auf keinen Fall meine Erfindung und wahrscheinlich ziemlich klobig für Ihren Zweck, denke ich. Aber es könnte ein Workaround für dich sein. Ich meine bis "post v1.0" ... :)

@meisl Cool, danke für den Tipp! Werde es ausprobieren, wenn ich etwas Zeit finde.

@samvv Ooh, ah ... Ich fürchte, ich habe etwas ziemlich Wichtiges übersehen:

Es macht einen ziemlichen Unterschied, ob Sie die parametrisierte Regel wollen

  • nur Werte erzeugen können, die vom Parameter abhängen
  • oder (auch) seine Parsing-Entscheidungen vom Parameter abhängig machen

Was ich vorgeschlagen habe, hilft nur bei Ersterem - während Letzteres das eigentliche Problem des OP ist ...
Das tut mir leid.

Es gibt jedoch auch für letzteres eine Problemumgehung, wenn auch noch klobiger.
Und der Teil "abhängige Entscheidungen" hat nichts mit der Rückgabe von Funktionen zu tun ...

Ich hänge ein Beispiel an, das Sie unter https://pegjs.org/online ausprobieren können

Die Grundidee ist: Verwenden Sie den globalen Zustand, um sich an den aktuellen "Terminator" zu erinnern. Das ist zugegebenermaßen ein ziemlicher Hack und wiederholt sich.
Aber: Hinzufügen eines weiteren Trennzeichens, sagen wir | würde nur bedeuten, eine weitere Alternative zu str hinzuzufügen:

  / (t:'|' {term = t}) c:conts t:.&{ return isT(t) }  { return c }

die sich von den anderen nur durch das eine Zeichen | unterscheidet


{
  var term;
  function isT(ch) { return ch === term }
  function isE(ch) { return ch === '\\' }
}
start = str*

str
  = (t:'\"' {term = t}) c:conts t:.&{ return isT(t) }  { return c }
  / (t:'\'' {term = t}) c:conts t:.&{ return isT(t) }  { return c }

conts
  = c:(
        '\\' t:.&{ return isT(t) || isE(t) } { return t }
      /      t:.!{ return isT(t)           } { return t }
    )*
    { return c.join('') }

... bei Eingängen

  • "abc" -> ["abc"]
  • "a\"bc" -> ["a\"bc"]
  • "a\\bc" -> ["a\bc"]
  • "a\\b\"c"'a\\b\'' -> ["a\b\"c", "a\b'c"]

ps: das ist wirklich NICHTs was man mit der hand schreiben will, ich weiß. Aber hey, stellen Sie sich vor, es würde für Sie generiert ... Ich denke, im Prinzip ist das so .

@ceymard - Mir ist klar, dass es zehn Jahre später ist, aber ich bin neugierig, wie sich das von # 36 unterscheidet

Wow, es hat eine Weile gedauert, bis ich mich daran erinnerte. 10 Jahre !

In diesem PR nehmen Regeln Argumente und können parametrisiert werden. Dies soll von der Grammatik selbst verwendet werden, um zu vermeiden, dass ähnliche, aber unterschiedliche Regeln wiederholt werden.

In #36 werden die Regeln außerhalb der Grammatik selbst spezifiziert. Die Grammatik ist also selbst parametrisiert.

Ich denke, der Umfang ist unterschiedlich, obwohl man argumentieren könnte, dass eine Grammatik selbst eine Regel ist und dies daher dasselbe Problem ist. Ich denke jedoch, dass es nicht so ist, da #36 wahrscheinlich einige geringfügige API-Änderungen bedeuten würde, während diese PR dies nicht tun würde.

Also, um die C++-artige Terminologie auf eine zutiefst falsche Weise zu missbrauchen, sind erstere Template-Statics, während letztere Konstruktoraufrufe sind?

Ich denke, diese Analogie funktioniert einigermaßen, ja.

Vielen Dank für Ihre Zeit in der Erklärung

Wahrscheinlich habe ich in zehn Jahren noch eine Frage an Sie. Gute 2020er

Es wäre wirklich nützlich, um die Redundanz meiner Parser-Definition zu entfernen. Ich habe eine benutzerdefinierte Grammatik, die absichtlich sehr entspannt ist, und einige Regeln müssen in etwas anderen Kontexten angewendet werden.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen