Heute veröffentlichen wir den zweiten Teil einer Übersetzung der JavaScript-Syntaxerweiterung mit Babel.

→ Schwindelerregender
erster TeilWie das Parsen funktioniert
Der Parser empfängt eine Liste von Token vom Code-Tokenisierungssystem und erstellt einen AST, indem er die Token einzeln untersucht. Um eine Entscheidung über die Verwendung von Token zu treffen und zu verstehen, welches Token als nächstes erwartet werden kann, verweist der Parser auf die Angabe der Grammatik der Sprache.
Die Grammatikspezifikation sieht ungefähr so aus:
... ExponentiationExpression -> UnaryExpression UpdateExpression ** ExponentiationExpression MultiplicativeExpression -> ExponentiationExpression MultiplicativeExpression ("*" or "/" or "%") ExponentiationExpression AdditiveExpression -> MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression ...
Es beschreibt die Priorität der Ausführung von Ausdrücken oder Anweisungen. Ein
AdditiveExpression
Ausdruck kann beispielsweise eines der folgenden Konstrukte darstellen:
- Ausdruck
MultiplicativeExpression
. - Ein
AdditiveExpression
Ausdruck, gefolgt von einem +
-Token-Operator, gefolgt von einem MultiplicativeExpression
Ausdruck. - Ein
AdditiveExpression
Ausdruck, gefolgt von einem " -
" -
Token, gefolgt von einem MultiplicativeExpression
Ausdruck.
Wenn wir also den Ausdruck
1 + 2 * 3
, sieht er folgendermaßen aus:
(AdditiveExpression "+" 1 (MultiplicativeExpression "*" 2 3))
Aber es wird nicht so sein:
(MultiplicativeExpression "*" (AdditiveExpression "+" 1 2) 3)
Das Programm wird unter Verwendung dieser Regeln in vom Parser ausgegebenen Code konvertiert:
class Parser {
Bitte beachten Sie, dass hier eine extrem vereinfachte Version dessen ist, was tatsächlich in Babel vorhanden ist. Ich hoffe jedoch, dass dieser Code es uns ermöglicht, die Essenz des Geschehens zu veranschaulichen.
Wie Sie sehen können, ist der Parser von Natur aus rekursiv. Es wechselt von den Designs mit der niedrigsten Priorität zu den Designs mit der höchsten Priorität. Beispielsweise ruft
parseMultiplicativeExpression
, und dieses Konstrukt ruft
parseExponentiationExpression
usw. auf. Dieser rekursive Prozess wird als
rekursives Abstiegsparsing bezeichnet .
Funktionen this.eat, this.match, this.next
Möglicherweise haben Sie bemerkt, dass in den vorherigen Beispielen einige Hilfsfunktionen verwendet wurden, z. B.
this.eat
,
this.match
,
this.next
und andere. Dies sind die internen Funktionen des Babel-Parsers. Diese Funktionen sind jedoch nicht nur für Babel verfügbar, sondern normalerweise auch in anderen Parsern vorhanden.
- Die Funktion
this.match
gibt einen booleschen Wert zurück, der angibt, ob das aktuelle Token die angegebene Bedingung erfüllt. - Die Funktion
this.next
sich in der Liste der Token vorwärts zum nächsten Token. - Die Funktion
this.eat
gibt dasselbe zurück wie die Funktion this.match
Wenn this.match
true
this.eat
führt this.eat
vor der Rückgabe von true
einen Aufruf von this.next
. - Mit der Funktion
this.lookahead
können Sie das nächste Token this.lookahead
ohne vorwärts zu gehen. this.lookahead
hilft Ihnen, eine Entscheidung über den aktuellen Knoten zu treffen.
Wenn Sie sich den von uns geänderten Parser-Code noch einmal ansehen, werden Sie feststellen, dass das Lesen viel einfacher geworden ist:
packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser { parseStatementContent() {
Ich weiß, dass ich mich nicht eingehend mit den Funktionen von Parsern befasst habe. Deshalb
hier und
da - ein paar nützliche Ressourcen zu diesem Thema. Ich habe viele davon gelernt und kann sie Ihnen empfehlen.
Vielleicht möchten Sie wissen, wie ich die in Babel AST Explorer erstellte Syntax visualisieren konnte, als ich das neue
curry
Attribut in AST zeigte.
Dies wurde möglich, weil ich im Babel AST Explorer eine neue Funktion hinzugefügt habe, mit der Sie Ihren eigenen Parser in dieses AST-Recherchetool laden können.
Wenn Sie dem Pfad
packages/babel-parser/lib
, finden Sie eine kompilierte Version des Parsers und eine Codezuordnung. Im
Babel AST Explorer
Bedienfeld sehen Sie die Schaltfläche zum Laden Ihres eigenen Parsers. Durch Herunterladen der
packages/babel-parser/lib/index.js
Sie den mit Ihrem eigenen Parser generierten AST visualisieren.
AST-VisualisierungUnser Plugin für Babel
Nachdem der Parser vollständig ist, schreiben wir ein Plugin für Babel.
Aber vielleicht haben Sie jetzt einige Zweifel, wie genau wir unseren eigenen Babel-Parser verwenden werden, insbesondere wenn man bedenkt, welchen Technologie-Stack wir zum Erstellen des Projekts verwenden.
Es gibt zwar nichts zu befürchten. Das Babel-Plugin kann Parser-Funktionen bereitstellen. Zugehörige
Dokumentationen finden Sie auf der Babel-Website.
babel-plugin-transformation-curry-function.js import customParser from './custom-parser'; export default function ourBabelPlugin() { return { parserOverride(code, opts) { return customParser.parse(code, opts); }, }; }
Da wir einen Fork des Babel-Parsers erstellt haben, funktionieren alle vorhandenen Parser-Funktionen sowie die integrierten Plugins weiterhin einwandfrei.
Nachdem wir diese Zweifel beseitigt haben, schauen wir uns an, wie eine Funktion so erstellt wird, dass sie das Curry unterstützt.
Wenn Sie die Erwartungen nicht erfüllen konnten und bereits versucht haben, unser Plug-In zu Ihrem Projekterstellungssystem hinzuzufügen, werden Sie möglicherweise feststellen, dass Funktionen, die das Currying unterstützen, zu regulären Funktionen kompiliert werden.
Dies geschieht, weil Babel nach dem Parsen und Transformieren des Codes
@babel/generator
, um Code aus dem transformierten AST zu generieren. Da
@babel/generator
nichts über das neue
curry
Attribut weiß, wird es einfach ignoriert.
Wenn eines Tages Funktionen, die das Currying unterstützen, in den JavaScript-Standard aufgenommen werden, möchten Sie möglicherweise eine PR durchführen, um hier neuen Code hinzuzufügen.
Damit die Funktion das Currying unterstützt, können Sie sie in ein Funktionscurrying höherer Ordnung einwickeln:
function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); }
Wenn Sie an den Funktionen der Implementierung des Mechanismus der Currying-Funktionen in JS interessiert sind, schauen Sie sich
dieses Material an.
Infolgedessen können wir eine Funktion transformieren, die das Currying unterstützt:
Im Moment werden wir uns nicht mit dem Mechanismus zum
Auslösen von Funktionen in JavaScript befassen, mit dem Sie die Funktion
foo
aufrufen können, bevor sie definiert ist.
So sieht der Transformationscode aus:
babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() { return {
Es wird für Sie viel einfacher sein, es herauszufinden, wenn Sie
dieses Material über Transformationen in Babel lesen.
Nun stehen wir vor der Frage, wie dieser Mechanismus Zugang zur
currying
Funktion erhalten kann. Hier können Sie einen von zwei Ansätzen verwenden.
▍Ansatz Nr. 1: Es kann davon ausgegangen werden, dass die Currying-Funktion im globalen Bereich deklariert ist
Wenn ja, ist die Arbeit bereits erledigt.
Wenn sich beim Ausführen des kompilierten Codes herausstellt, dass die
currying
Funktion nicht definiert ist, wird eine Fehlermeldung angezeigt, die wie
currying is not defined
aussieht: "
currying is not defined
". Es ist der Meldung "
regeneratorRuntime is not defined " sehr ähnlich.
Wenn jemand Ihr
babel-plugin-transformation-curry-function
, müssen Sie ihn möglicherweise darüber informieren, dass er die
currying
Polyfüllung installieren muss, um sicherzustellen, dass dieses Plugin ordnungsgemäß funktioniert.
▍ Ansatz 2: Sie können Babel / Helfer verwenden
Sie können
@babel/helpers
eine neue Hilfsfunktion hinzufügen. Es ist unwahrscheinlich, dass diese Entwicklung mit dem offiziellen
@babel/helpers
kombiniert wird. Infolgedessen müssen Sie einen Weg finden, um
@babel/core
den Speicherort Ihres
@babel/helpers
:
package.json { "resolutions": { "@babel/helpers": "7.6.0--your-custom-forked-version", }
Ich habe es selbst nicht versucht, aber ich glaube, dass dieser Mechanismus funktionieren wird. Wenn Sie es versuchen und auf Probleme stoßen, werde ich es gerne
besprechen .
@babel/helpers
neuen Hilfsfunktion zu
@babel/helpers
sehr einfach.
Wechseln Sie zunächst zur Datei packages / babel-helpers / src / helpers.js und fügen Sie einen neuen Eintrag hinzu:
helpers.currying = helper("7.6.0")` export default function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); } `;
Bei der Beschreibung einer Hilfsfunktion wird die erforderliche Version
@babel/core
angegeben. Einige Schwierigkeiten können hier durch den
export default
der
currying
Funktion verursacht werden.
Um eine
this.addHelper()
zu verwenden, rufen
this.addHelper()
einfach
this.addHelper()
:
Der Befehl
this.addHelper
bei Bedarf die
this.addHelper
oben in die Datei ein und gibt einen
Identifier
, der die implementierte Funktion angibt.
Anmerkungen
Ich bin schon seit einiger Zeit an der Arbeit an Babel beteiligt, musste jedoch noch keine Funktionen hinzufügen, um die neue JavaScript-Syntax für den Parser zu unterstützen. Ich habe hauptsächlich daran gearbeitet, Fehler zu beheben und die für die offiziellen Sprachfunktionen relevanten Funktionen zu verbessern.
Seit einiger Zeit beschäftigte ich mich jedoch mit der Idee, der Sprache neue Syntaxkonstrukte hinzuzufügen. Aus diesem Grund habe ich beschlossen, Material darüber zu schreiben und es auszuprobieren. Es ist unglaublich schön zu sehen, dass alles genau wie erwartet funktioniert.
Die Möglichkeit, die Syntax der von Ihnen verwendeten Sprache zu steuern, ist eine starke Inspirationsquelle. Dies ermöglicht es, durch die Implementierung einiger komplexer Konstruktionen weniger Code zu schreiben oder einfacheren Code als zuvor zu schreiben. Die Mechanismen zur Umwandlung von einfachem Code in komplexe Konstruktionen werden automatisiert und in die Kompilierungsphase übertragen. Dies erinnert daran, wie
async/await
die Probleme von Höllenrückrufen und langen Versprechungsketten löst.
Zusammenfassung
Hier haben wir darüber gesprochen, wie die Funktionen des Babel-Parsers geändert werden können. Wir haben unser eigenes Code-Transformations-Plugin geschrieben, kurz über
@babel/generator
und über das Erstellen von Hilfsfunktionen mit
@babel/helpers
. Informationen zur Transformation des Codes werden nur schematisch gegeben. Lesen Sie
hier mehr darüber.
Dabei haben wir einige Funktionen der Parser angesprochen. Wenn Sie sich für dieses Thema interessieren - dann
hier und
da - Ressourcen, die für Sie nützlich sind.
Die Reihenfolge der Aktionen, die wir ausgeführt haben, ist einem Teil des Prozesses sehr ähnlich, der ausgeführt wird, wenn eine neue JavaScript-Funktion an TC39 gesendet wird.
Auf der TC39-Repository-Seite finden Sie Informationen zu aktuellen Angeboten.
Hier finden Sie detailliertere Informationen zur Arbeit mit ähnlichen Angeboten. Wenn Sie eine neue JavaScript-Funktion vorschlagen, schreibt derjenige, der sie anbietet, normalerweise Polyfills oder bereitet durch Verzweigen von Babel eine Demonstration vor, die beweist, dass der Satz funktioniert. Wie Sie sehen, ist das Erstellen einer Abzweigung eines Parsers oder das Schreiben einer Polyfüllung nicht der schwierigste Teil des Prozesses zum Vorschlagen neuer JS-Funktionen. Es ist schwierig, den Themenbereich Innovation zu bestimmen, Optionen für seine Verwendung und Grenzfälle zu planen und zu überdenken. Es ist schwierig, die Meinungen und Vorschläge von Mitgliedern der JavaScript-Programmierer-Community zu sammeln Daher möchte ich all jenen meinen Dank aussprechen, die die Kraft finden, TC39 neue JavaScript-Funktionen anzubieten und damit diese Sprache zu entwickeln.
Hier ist eine Seite auf GitHub, auf der Sie
einen Überblick über das bekommen, was wir hier gemacht haben.
Liebe Leser! Wollten Sie schon immer die Syntax von JavaScript erweitern?
