Pegjs: Unterstützt das Parsen von einrückungsbasierten Sprachen

Erstellt am 16. Okt. 2013  ·  34Kommentare  ·  Quelle: pegjs/pegjs

Ich habe eine Reihe von einrückungsbasierten Sprachen wie CoffeeScript, Jade verwendet und möchte selbst DSLs erstellen.
Ich finde einige Tricks, die Einrückungen in pegjs durch die Suche beibehalten, und habe mich gefragt, ob es eine konsistente Lösung gibt:
http://stackoverflow.com/questions/11659095/parse-indentation-level-with-peg-js
http://stackoverflow.com/questions/4205442/peg-for-python-style-indentation
https://gist.github.com/jakubkulhan/3192844
https://groups.google.com/forum/#!searchin/pegjs/indent/pegjs/RkbAB4rPlfU/xxafrY5wGCEJ
Aber wird pegjs diese Funktion unterstützen?

feature

Hilfreichster Kommentar

Es ist sehr gefährlich, sich auf Nebeneffekte zu verlassen, die Sie in benutzerdefinierten Handlern hinzufügen, um einrückungsbasierte Grammatiken zu analysieren. Tu es einfach nicht. Pegjs müsste eine gewisse Fähigkeit hinzufügen, bedingte Zustände zu pushen und zu öffnen, um das Parsen von Einrückungen (und anderen kontextsensitiven Grammatiken) sicher zu machen.

Dies ist, was ich im Moment tue, und ich empfehle Ihnen Folgendes: Verarbeiten Sie die Eingabedatei vor und fügen Sie Ihre eigenen Einrückungs-/Ausrückungstoken ein. Ich verwende jeweils {{{{ und }}}}. Dann ist Ihre Grammatik kontextfrei und kann normal geparst werden. Es kann Ihre Zeilen-/Spaltenwerte durcheinander bringen, aber Sie können diese in einem Postprozessor korrigieren.

Alle 34 Kommentare

Es ist sehr gefährlich, sich auf Nebeneffekte zu verlassen, die Sie in benutzerdefinierten Handlern hinzufügen, um einrückungsbasierte Grammatiken zu analysieren. Tu es einfach nicht. Pegjs müsste eine gewisse Fähigkeit hinzufügen, bedingte Zustände zu pushen und zu öffnen, um das Parsen von Einrückungen (und anderen kontextsensitiven Grammatiken) sicher zu machen.

Dies ist, was ich im Moment tue, und ich empfehle Ihnen Folgendes: Verarbeiten Sie die Eingabedatei vor und fügen Sie Ihre eigenen Einrückungs-/Ausrückungstoken ein. Ich verwende jeweils {{{{ und }}}}. Dann ist Ihre Grammatik kontextfrei und kann normal geparst werden. Es kann Ihre Zeilen-/Spaltenwerte durcheinander bringen, aber Sie können diese in einem Postprozessor korrigieren.

Wenn Sie kein Javascript als Ziel verwenden müssen, unterstützt Pegasus , mein pegjs-Klon für C#, den Push-/Pop-Status. Hier ist ein Wiki-Artikel, wie Sie genau das tun können, was Sie wollen: https://github.com/otac0n/Pegasus/wiki/Significant-Whitespace-Parsing

Ich möchte vorschlagen, dass pegjs meine Syntax als Ausgangspunkt für das zustandsbasierte Parsing verwendet.

Die Fähigkeit, den Zustand sicher zu schieben und zu knallen, ist schön. Ich würde das verwenden, wenn es auf Javascript basiert. Es lohnt sich einfach nicht, eine CLR nur zum Parsen zu integrieren.

Das habe ich mir gedacht. Ich denke, in diesem Fall sollte ich wahrscheinlich versuchen, meine Verbesserungen in pegjs zurückzuportieren.

Das möchte ich jedoch nicht unbedingt tun, ohne ein Gespräch mit @dmajda zu führen.

@otac0n Es ist schön. Ich schreibe kein C#. JavaScript ist für mich viel besser.

Einrückungsbasierte Sprachen sind wichtig. Ich möchte mir die Vereinfachung ihrer Analyse nach 1.0.0 ansehen.

Ich denke, dieses Problem lässt sich am besten lösen, indem man den Zustand im Allgemeinen erlaubt, genau wie Pegasus und wie in #285 vorgeschlagen. Hier ist eine Idee (das Folgende ist die bedeutende Whitespace-Grammatik von Pegasus, die in pegjs übersetzt und mit meiner Syntaxidee hinzugefügt wurde):

{var indentation = 0}

program
  = s:statements eof { return s }

statements
  = line+

line
  = INDENTATION s:statement { return s }

statement
  = s:simpleStatement eol { return s }
  / "if" _ n:name _? ":" eol INDENT !"bar " s:statements UNDENT {
      return { condition: n, statements: s }
    }
  / "def" _ n:name _? ":" eol INDENT s:statements UNDENT {
      return { name: n, statements: s }
    }

simpleStatement
  = a:name _? "=" _? b:name { return { lValue: a, expression: b } }

name
  = [a-zA-Z] [a-zA-Z0-9]* { return text() }

_ = [ \t]+

eol = _? comment? ("\r\n" / "\n\r" / "\r" / "\n" / eof)

comment = "//" [^\r\n]*

eof = !.

INDENTATION
  = spaces:" "* &{ return spaces.length == indentation }

INDENT
  = #STATE{indentation}{ indentation += 4 }

UNDENT
  = #STATE{indentation}{ indentation -= 4 }

Beachten Sie die #STATE{indentation} Blöcke unten (offensichtlich von Pegasus inspiriert). Ich nenne diese Zustandsblöcke. Die Idee ist, eine Zustandssperre vor Aktionen zuzulassen. Hier ist ein komplizierterer Zustandsblock:

#STATE{a, b, arr: {arr.slice()}, obj: {shallowCopy(obj)}, c}

Es ist eine Abkürzung für:

#STATE{a: {a}, b: {b}, arr: {arr.slice()}, obj: {shallowCopy(obj)}, c: {c}}

Mit anderen Worten, nachdem die Abkürzungserweiterung angewendet wurde, ist der Inhalt eines Zustandsblocks eine Liste von identifier ":" "{" code "}" . Das Hinzufügen eines Zustandsblocks vor einer Aktion teilt pegjs mit, dass diese Aktion die aufgelisteten Bezeichner ändert, und wenn die Regel zurückverfolgt wird, sollten diese Bezeichner auf den Code zwischen den geschweiften Klammern zurückgesetzt werden.

Hier sind die kompilierten Funktionen für INDENT und UNDENT aus der obigen Grammatik, wobei das Zurücksetzen der Variablen indentation hinzugefügt wurde:

    function peg$parseINDENT() {
      var s0, s1, t0;

      s0 = peg$currPos;
      t0 = indentation;
      s1 = [];
      if (s1 !== peg$FAILED) {
        peg$reportedPos = s0;
        s1 = peg$c41();
      } else {
        indentation = t0;
      }
      s0 = s1;

      return s0;
    }

    function peg$parseUNDENT() {
      var s0, s1, t0;

      s0 = peg$currPos;
      t0 = indentation;
      s1 = [];
      if (s1 !== peg$FAILED) {
        peg$reportedPos = s0;
        s1 = peg$c42();
      } else {
        indentation = t0;
      }
      s0 = s1;

      return s0;
    }

Und hier ist ein bisschen, wie der „komplizierte Zustandsblock“ von oben kompiliert werden könnte:

s0 = peg$currPos;
t0 = a;
t1 = b;
t2 = arr.slice();
t3 = shallowCopy(obj);
t4 = c;
// ...
if (s1 !== peg$FAILED) {
  // ...
} else {
  peg$currPos = s0;
  a = t0;
  b = t1;
  arr = t2;
  obj = t3;
  c = t4;
}

Was halten Sie von dieser Idee, in der Lage zu sein:

  • Teilen Sie pegjs mit, welche zustandsbehafteten Variablen durch eine Aktion geändert werden.
  • Geben Sie den Code an, der zum Speichern dieser Variablen erforderlich ist, wenn sie zurückgesetzt werden müssen. (Einschließlich der Abkürzungssyntax für den einfachen Fall, in dem die Variable ein primitiver Wert ist.)

Und was haltet ihr von der Syntax?

Bearbeiten: Hier ist die vorgeschlagene Syntaxgrammatik (nur zum Spaß):

diff --git a/src/parser.pegjs b/src/parser.pegjs
index 08f6c4f..09e079f 100644
--- a/src/parser.pegjs
+++ b/src/parser.pegjs
@@ -116,12 +116,31 @@ ChoiceExpression
     }

 ActionExpression
-  = expression:SequenceExpression code:(__ CodeBlock)? {
+  = expression:SequenceExpression code:((__ StateBlock)? __ CodeBlock)? {
       return code !== null
-        ? { type: "action", expression: expression, code: code[1] }
+        ? {
+            type:       "action",
+            expression: expression,
+            code:       code[2],
+            stateVars:  (code[0] !== null ? code[0][1] : [])
+          }
         : expression;
     }

+StateBlock "state block"
+  = "#STATE{" __ first:StateBlockItem rest:(__ "," __ StateBlockItem)* __ "}" {
+      return buildList(first, rest, 3);
+    }
+
+StateBlockItem
+  = varName:Identifier expression:(__ ":" __ CodeBlock)? {
+      return {
+        type:       "stateVar",
+        name:       varName,
+        expression: expression !== null ? expression[3] : varName
+      };
+    }
+
 SequenceExpression
   = first:LabeledExpression rest:(__ LabeledExpression)* {
       return rest.length > 0

Hallo Leute,
Nur zur Verdeutlichung: Liege ich richtig, dass es besser ist, PEG.js (mit Workarounds vom Anfang dieses Problems) nicht mit einrückungsbasierten Sprachen zu verwenden, bis dieses Problem geschlossen ist?
Vielen Dank.

Ich meine, es gibt Problemumgehungen zum Analysieren von Einrückungen, aber die Kommentare sagen, dass diese Problemumgehungen in einigen bestimmten Fällen fehlschlagen.

Lassen Sie mich die Situation klarstellen: Das Parsen von einrückungsbasierten Sprachen in PEG.js ist möglich. Es gibt verschiedene oben erwähnte Lösungen und ich habe gerade eine andere erstellt, um ein „Gefühl“ dafür zu bekommen (es ist eine Grammatik einer einfachen Sprache mit zwei Anweisungen, von denen eine eingerückte Unteranweisungen enthalten kann – ähnlich wie zB if in Python).

Allen Lösungen ist gemeinsam, dass sie den Einrückungsstatus manuell verfolgen müssen (weil PEG.js dies nicht kann). Dies bedeutet, dass es zwei Einschränkungen gibt:

  1. Sie können die Grammatik mit Caching nicht sicher kompilieren (da der Parser zwischengespeicherte Ergebnisse verwenden könnte, anstatt zustandsmanipulierenden Code auszuführen).
  2. Sie können nicht über Einrückungsebenen hinweg zurückverfolgen (da es derzeit keine Möglichkeit gibt, den Status beim Zurückverfolgen aufzulösen). Mit anderen Worten, Sie können eine Sprache nicht parsen, in der es zwei gültige Konstrukte gibt, die erst nach einer Änderung der Zeilenvorschub- und Einrückungsebene eindeutig gemacht werden können.

Einschränkung 1 kann in einigen Fällen zu Leistungsproblemen führen, aber ich glaube nicht, dass es viele Sprachen gibt, für die Einschränkung 2 ein Problem darstellen würde.

Ich bin mit diesem Zustand bis 1.0.0 in Ordnung und ich habe vor, irgendwann danach wieder auf dieses Thema zurückzukommen. Die erste Verbesserungsstufe könnte darin bestehen, Einschränkung 2 durch eine explizitere Zustandsverfolgung (wie oben vorgeschlagen) zu beseitigen oder einen Backtracking-Hook bereitzustellen (damit man den Zustand korrekt ausrollen kann). Die zweite Ebene könnte darin bestehen, die Notwendigkeit zu beseitigen, den Einrückungsstatus manuell zu verfolgen, indem eine deklarative Möglichkeit dafür bereitgestellt wird. Dies könnte bei Einschränkung 1 helfen.

H, ich habe einen (winzigen, hackigen) Patch für PEG.js geschrieben, der richtiges Backtracking unterstützt, wie ich hier erklärt habe: https://github.com/pegjs/pegjs/issues/45

Entschuldigung für die Beule

Ich habe gerade versucht, CSON- und YAML-Parser für eine Sprache zu erstellen, die ich entwerfe, und als ich nach Möglichkeiten suchte, einen einrückungsbasierten Parser mit PEG.js zu erstellen, kam ich auf eine einfache Methode, die:

1) verlässt sich nicht auf Push/Pop-Zustände
2) Durchsetzung von Einzugsebenen über Code innerhalb von Aktionen

Mir war aufgefallen, dass eine der beiden oben genannten Lösungen den generierten Parsern tatsächlich Leistungsprobleme hinzufügt. Außerdem meiner Meinung nach:

1) Sich auf die Zustandsangaben zu verlassen, fügt nicht nur eine hässliche PEG.js-Syntax hinzu, sondern kann auch beeinflussen, welche Art von Parsern generiert werden kann, da sie die aktionsbasierte Zustandshandhabung unterstützen müssten.
2) Manchmal führt das Hinzufügen von Code in Aktionen zu einer sprachabhängigen Regel, und für einige Entwickler bedeutet dies, dass sie keine Plugins verwenden können, um Parser für andere Sprachen wie C oder PHP zu generieren, ohne auf weitere Plugins zurückzugreifen, um Aktionen für Regeln zu handhaben, was nur bedeutet ein größeres Build-System, nur um 1 oder 2 Änderungen zu unterstützen.

Nach einer Weile begann ich damit, meine eigene Variante des PEG.js-Parsers zu erstellen und dachte: Warum nicht einfach die Präfixoperatoren Inkrement ("++") und Dekrement ("--") verwenden (__++ Ausdruck__ und __-- Ausdruck__ ), um die Ergebnisse von Match-Ausdrücken (__expression *__ oder __expression +__) zu verarbeiten.

Im Folgenden ist ein Beispiel Grammatik basiert auf @dmajda ‚s Einfache intentation-basierte Sprache , neu geschrieben das neue zu verwenden __ ++ expression__ und __-- expression__ statt __ & {Prädikat} __:

Start
  = Statements

Statements
  = Statement*

Statement
  = Indent* statement:(S / I) { return statement; }

S
  = "S" EOS {
      return "S";
    }

I
  = "I" EOL ++Indent statements:Statements --Indent { return statements; }
  / "I" EOS { return []; }

Indent "indent"
  = "\t"
 / !__ "  "

__ "white space"
 = " \t"
 / " "

EOS
  = EOL
  / EOF

EOL
  = "\n"

EOF
  = !.

Viel angenehmer für das Auge, nicht wahr? Leichter zu verstehen, sowohl für Menschen als auch für Software.

Wie funktioniert es? einfach:

1) Indent* teilt dem Parser mit, dass wir 0 oder mehr von dem wollen, was Indent zurückgibt
2) ++Indent weist den Parser an, die erforderliche Mindestanzahl von Übereinstimmungen für Indent zu erhöhen
3) Jedes Mal, wenn der Parser die Übereinstimmungen für Indent zurückgibt, erwartet er zuerst __1 weitere__ Übereinstimmungen als zuvor, andernfalls wird _peg$SyntaxError_ geworfen.
4) --Indent weist den Parser an, die erforderliche Mindestanzahl von Übereinstimmungen für Indent zu verringern
5) Jetzt, wann immer der Parser nach Indent sucht und die Übereinstimmungen zurückgibt, erwartet er __1 weniger__ Übereinstimmung als vorher, andernfalls wird _peg$SyntaxError_ geworfen.

Diese Lösung ist der beste Weg, um Unterstützung für 'Significant Whitespace Parsing' hinzuzufügen, ohne PEG.js-Grammatiken eine hässliche Syntax hinzuzufügen oder Generatoren von Drittanbietern zu blockieren.

Hier sind die geänderten Regeln, um Unterstützung für das Parsen in _src/parser.pegjs_ hinzuzufügen:

{
  const OPS_TO_PREFIXED_TYPES = {
    "$": "text",
    "&": "simple_and",
    "!": "simple_not",
    "++": "increment_match",
    "--": "decrement_match"
  };
}

PrefixedOperator
  = "$"
  / "&"
  / "!"
  / "++"
  / "--"

SuffixedOperator
  = "?"
  / "*"
  / "+" !"+"

Gehe ich richtig davon aus, dass wir zur Unterstützung der Compiler-/Generatorseite Folgendes tun müssen:

1) Fügen Sie einen Compiler-Pass hinzu, der sicherstellt, dass __++ expression__ oder __-- expression__ nur für __expression *__ oder __expression +__ verwendet werden, wobei __expression__ vom Typ sein muss: choice, sequence oder rule_ref
2) Fügen Sie im generierten Parser eine Cache-basierte Prüfung für __expression *__ oder __expression +__ hinzu, die bestätigt, dass die erforderliche Mindestübereinstimmung erfüllt ist, bevor die Übereinstimmungen zurückgegeben werden
3) fügen Sie optional eine Hilfsmethode für generierte Parser hinzu, um diese zu implementieren, die die Anzahl von Übereinstimmungen zurückgibt, die für eine gegebene Regel erforderlich sind, z. nMatches( name: String ): Number

@futagoza , das ist sauber und clever. Ich mag das. Ich arbeite an einem Parser, der den Zustand verarbeitet, aber der einzige Zustand, den wir wirklich brauchen, sind Einrückungsebenen. Ich kann diese Idee verwenden und Ihnen Anerkennung dafür geben. Das Verfolgen der Einrückungsebene erfordert immer noch den Push-/Popping-Zustand und kann daher einige Optimierungen verhindern, aber die Semantik davon ist sehr schön.

Wenn Sie einer Grammatik Operatoren hinzufügen, empfehle ich, auch den Präfixoperator @ hinzuzufügen. Sein Zweck besteht darin, einfach ein einzelnes Regelergebnis aus einer Sequenz zu extrahieren. Dadurch wird die Beispielgrammatik noch sauberer. Keine trivialen { return x } Aktionen mehr.

Start
  = Statements

Statements
  = Statement*

Statement
  = Indent* @(S / I)

S
  = "S" EOS {
      return "S";
    }

I
  = "I" EOL ++Indent <strong i="8">@Statements</strong> --Indent
  / "I" EOS { return []; }

Indent "indent"
  = "\t"
 / !__ "  "

__ "white space"
 = " \t"
 / " "

EOS
  = EOL
  / EOF

EOL
  = "\n"

EOF
  = !.

@kodyjking was

@futagoza Haben Sie einen Fork/Branch mit aktiviertem Einrückungspatch und einer kleinen Beispielgrammatik?

Ich arbeite an dieser Gabel/Zweig- Einrückung

@krinye "Ich empfehle, auch den @ hinzuzufügen. Sein Zweck besteht darin, einfach ein einzelnes Regelergebnis aus einer Sequenz zu extrahieren."

Könnte einer von euch einen Blick darauf werfen und einen Kommentar oder eine PR mit dem Fix machen. Vielen Dank :)

Readme: Gabeländerungen

Ich debuggte ... inkrement_match-not-found

Ah, habe den Vorbehalt nicht bemerkt:

Gehe ich richtig davon aus, dass wir zur Unterstützung der Compiler-/Generatorseite Folgendes tun müssen:

  • Fügen Sie einen Compiler-Pass hinzu, der sicherstellt, dass ++-Ausdruck oder ---Ausdruck nur für Ausdruck * oder Ausdruck + verwendet werden, wobei Ausdruck folgende Typen haben muss: choice, sequence oder rule_ref
  • Fügen Sie im generierten Parser eine Cache-basierte Prüfung für Ausdruck * oder Ausdruck + hinzu, die bestätigt, dass die erforderliche Mindestübereinstimmung erfüllt ist, bevor die Übereinstimmungen zurückgegeben werden
  • Fügen Sie optional eine Hilfsmethode für generierte Parser hinzu, um die Anzahl der für eine bestimmte Regel erforderlichen Übereinstimmungen zurückzugeben, z. nMatches( name: String ): Zahl

Nur so habe ich versucht, dies in visitor.js hinzuzufügen

      increment_match: visitExpression,
      decrement_match: visitExpression,

Jetzt bekomme ich Invalid opcode: undefined.

@kristianmandrup In Bezug auf den @-Operator zum Extrahieren einzelner Werte aus Sequenzen habe ich eine Abzweigung mit genau dieser Funktion zu PegJS hinzugefügt, die hier verfügbar ist:

https://github.com/krisnye/pegjs

Es ist eine ziemlich einfache Ergänzung.

@krisnye +1 für die grammatikbasierte Implementierung, schön und einfach. Wenn es Ihnen nichts ausmacht, werde ich dies zu meiner Variante von PEG.js hinzufügen 😄

@kristianmandrup sehe dich für meinen Vorschlag einsetzen

@futagoza Bitte tun.

Ich habe die Einrückungslogik mit einem Kollegen besprochen und wir schlagen die folgenden syntaktischen Elemente vor

// inkrementiert die benannte Zustandsvariable
Kennung++
// dekrementiert die benannte Zustandsvariable
Kennung--

// konstanten Betrag oder Zustandsvariable wiederholen (mit Standardwert Null, wenn Variable noch nicht inkrementiert)
Regel{Ganzzahl | Kennung}
// Min/Max mit Konstanten oder Zustandsvariablen wiederholen
Regel{Ganzzahl | Bezeichner, ganze Zahl | Kennung}

Der Parser, an dem wir arbeiten, kann mit beliebigen Zuständen umgehen, aber ehrlich gesagt, das oben Genannte ist alles, was für das Parsing von Einrückungen benötigt wird.

Vielen Dank, Jungs! Wenn es so einfach ist, warum nicht eine "dedizierte" Gabel machen, bei der die Dinge, die Sie erwähnen, "einfach funktionieren" ;)
Danke schön!

@krisnye

  1. Die Verwendung von __identifier++__ kann leicht zu einem chaotischen Fehler führen, wenn der Entwickler __identifier+__ meinte, deshalb habe ich mich für __++identifier__ und aus Konsistenzgründen für __--identifier__ entschieden.
  2. Wie in einem anderen Thema zu Bereichen erwähnt, kann rule{ STATE_REPEAT / RANGE } mit rule{ ACTION } verwechselt werden, insbesondere wenn Sie einen Syntax-Highlighter für PEG.js erstellen, daher wurde dieser Ansatz von @dmajda abgelehnt

@kristianmandrup _(OFF TOPIC)_ Ich bin gut darin, Funktionen zu entwerfen und manchmal Implementierungen vorzunehmen, aber schrecklich darin, sie zu testen und zu vergleichen, also mache ich normalerweise Arbeitsvarianten (ohne Tests oder Benchmarks) in privaten Nicht-Repo-Verzeichnissen auf meinem PC , und dann vergiss sie 🤣. Für meine aktuelle Variante von PEG.js (genannt ePEG.js 😝, eine erweiterte Neufassung von PEG.js) füge ich die hier erwähnten Dinge sowie andere Funktionen (Bereiche, Importe, Vorlagen usw.) hinzu, also füge ich hinzu Tests und Benchmarks, aber ich arbeite derzeit auch an einem C++-Projekt, das meine Zeit in Anspruch nimmt, daher gibt es dazu keine ETA.

@futagoza Danke Kumpel :) Betrachtet man die Funktionserweiterungen, erwähnt aber keine Einrückungsunterstützung. Ist das enthalten, aber undokumentiert oder kommt die Straße runter?

Jemand anderes aus dieser Liste wies mich auf andere Parser-Builder/Generator-Lösungen hin, die ich ebenfalls untersuchen könnte. Halte mich auf dem Laufenden! Danke schön!

@kristianmandrup Soweit ich das beurteilen kann, ist es nicht enthalten, aber sagte vor 3 Jahren, dass er sich nach der Veröffentlichung von PEG.js v1 damit befassen wird, aber soweit ich das beurteilen kann, wird dies nicht in weiteren 2 Jahren sein, es sei denn, er plant dies veröffentlichen Sie weitere Nebenversionen von PEG.js v0 (_0.12_, _0.13_, _etc_)

Ich meinte, ob Sie die Einrückungsunterstützung bereits in ePEG oder in die Roadmap aufgenommen hatten?

@kristianmandrup oh 😆, das steht auf der Roadmap. Ich habe das ePEG.js-Repo eine Weile nicht aktualisiert und erst kürzlich beschlossen, es in eine vollständige Neufassung von PEG.js anstelle eines Plugins umzuwandeln.

@futagoza stimmte den ++/-- als Voroperationen zu, und ich habe die Aktionssyntax vergessen, wir haben sie in [min,max] geändert

So

++identifier
--identifier
rule[integer | identifier]
rule[integer | identifier, integer | identifier]

@krisnye [ ... ] wird für Zeichenklassen verwendet, siehe https://github.com/pegjs/pegjs#characters

Was ich in ePEG.js mache, ist das Hinzufügen von Bereichen (auch auf der Roadmap), um das zu erreichen, was Sie meiner Meinung nach beschreiben:

space = [ \t]*
rule = range|expression
  • __Ausdruck__ kann einer der folgenden sein: __++Leerzeichen__, __--Leerzeichen__ oder __Leerzeichen__
  • __Bereich__ kann sein __ min.. __, __ min..max __, __ ..max __ oder __ exact __
  • __min__, __max__ oder __exact__ kann nur eine __Ganzzahl ohne Vorzeichen__ sein
  • Die Verwendung eines __Bereichs__ mit einem __Ausdruck__ (zB 2|Ausdruck) kann es uns ermöglichen, die Gesamtmenge von __Ausdruck__ festzulegen, die erforderlich ist, damit __rule__ erfolgreich geparst wird.
  • Wenn Sie den Bereich __exact__ mit __++expression__ oder __--expression__ (zB 3|++expression) verwenden, können wir den __integer__-Betrag für __++__ oder __--__ festlegen, was standardmäßig __1__ ist.
  • die Verwendung von entweder __min__ oder __max__ range mit __++expression__ oder __--expression__ löst einen Syntaxfehler aus.

Ich verwende keine Zustandsvariablen, da dies nur mit Regelbezeichnern verwirrend wäre.

Mit einer Kombination aus __range__, __++__, __--__ oder __ @__ möchte ich PEG-Grammatikdateien erstellen, deren Ergebnis weniger auf Regeln __action__ angewiesen ist, was die Entwicklungszeit von Whitespace (z -basiert, ASCII Art, etc) Sprachen, da sich die Sprachdesigner und/oder -implementierer keine Sorgen machen müssen, ob sie versuchen zu bestätigen, ob die richtige Menge an Whitespace geparst wurde.
Dies sollte es Plugin-Entwicklern auch ermöglichen, Parser-Generatoren zu erstellen, die _ohne Angst_ vor der Sprache unserer __action__ optimieren können (standardmäßig JavaScript, aber mit Plugins kann es in CoffeeScript, PHP usw. geändert werden).

Es scheint also heute noch nicht möglich zu sein, Python mit PEG.js out of the box zu parsen, oder?

Wenn nicht, kommt das bald? Gibt es eine Reihe von Aufgaben, die erforderlich sind, um diese Arbeit zu machen, zu denen die Menschen beitragen könnten?

Ich habe ein Projekt, bei dem ich gerne einen Python AST in JS erhalten, modifizieren und dann mit derselben Formatierung wieder in den Quellcode konvertieren möchte Fahrplan.

@mindjuice Noch nicht, es ist für Post 1.0 geplant.

Wenn Sie nicht warten können, haben mein Neffe und ich einen in TypeScript geschriebenen Parser erstellt, der dieselbe Syntax verwendet und Einrückungen verarbeitet. Es ist noch nicht dokumentiert, aber es ist ziemlich einfach. Es wird noch daran gearbeitet, da wir es als Parser für ein neues Sprachdesign verwenden.

https://github.com/krisnye/pegs

Sie können auch einen anderen Parser-Generator mit Unterstützung wie Chevrotain ausprobieren

Beispiel für Python-Einrückung

Ich glaube, es ist sehr gut möglich, Python-ähnliche Einrückungen mit PEGjs zu analysieren.
Das folgende Beispiel verwendet nur auf vier Leerzeichen basierende Einrückung, kann jedoch erweitert werden, um beliebig angeordnete Tabulatoren und andere Leerzeichen abzudecken.
Tatsächlich hat die Sprache, an der ich arbeite, eine etwas kompliziertere Einrückungsgeschichte als Python, und diese Grammatik funktioniert gut dafür.

{
    let prevIndentCount = 0;
    function print(...s) { console.log(...s); }
}

Indent 'indent'
    = i:("    "+) { 
        let currentIndentCount = i.toString().replace(/,/g, "").length;
        if (currentIndentCount === prevIndentCount + 4) { 
            // DEBUG //
            print("=== Indent ===");
            print("    current:"+currentIndentCount); 
            print("    previous:"+prevIndentCount);
            print("    lineNumber:"+location().start.line); 
            // DEBUG //
            prevIndentCount += 4;
            return "[indent]";
        }
        error("error: expected a 4-space indentation here!")
    } // 4 spaces 

Samedent 'samedent'
    = s:("    "+ / "") &{
        let currentIndentCount = s.toString().replace(/,/g, "").length;
        if (currentIndentCount === prevIndentCount) {
            print("=== Samedent ===");
            return true;
        }
        return false;
    }

Dedent 'dedent'
    = d:("    "+ / "") {
        let currentIndentCount = d.toString().replace(/,/g, "").length;
        if (currentIndentCount < prevIndentCount) {
            // DEBUG //
            print("=== Dedent ===");
            print("    current:"+currentIndentCount); 
            print("    previous:"+prevIndentCount);
            print("    lineNumber:"+location().start.line); 
            // DEBUG //
            prevIndentCount -= 4;
            return "[dedent]";
        }
        error("error: expected a 4-space dedentation here!");
    }

Mit der obigen Grammatik können Sie eine eingerückte Blockregel wie folgt erstellen:

FunctionDeclaration 
    = 'function' _ Identifier _ FunctionParameterSection _ ":" _ FunctionBody

FunctionBody
    = Newline Indent FunctionSourceCode (Newline Samedent FunctionSourceCode)* Dedent 
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen