
今年の初めに、MongoDBのGUIである
MongoDB Compassに取り組んでいるチームに参加しました。 Intercomを介したCompassユーザー
は、MongoDBドライバーでサポート
されている、ユーザーにとって便利なプログラミング言語を使用してデータベースクエリを作成できるツールを要求しています。 つまり
、Mongo Shell言語を他の言語に、またはその逆に変換(コンパイル)する機能が必要でした。
この記事は、JavaScriptでコンパイラーを作成する際に役立つ実用的なガイドであり、コンパイラーを作成するための基本的な概念と原則を含む理論的なリソースでもあります。 最後に、執筆に使用されるすべての資料の完全なリストだけでなく、問題のより深い研究を目的とした追加の文献へのリンクもあります。 記事の情報は、サブジェクト領域の調査から始まり、その後、例として開発されているアプリケーションの機能を徐々に複雑にしていきます。 読みながら、あるステップから別のステップへの移行を把握していないように思える場合は、このプログラムの完全版を参照できます。これにより、ギャップを解消できます。内容
- 用語
- リサーチ
- コンパイラ作成
- おわりに
用語
コンパイラーは、高水準プログラミング言語のソースプログラムを別の言語の同等のプログラムに変換します。これは、コンパイラーの参加なしに実行できます。
レクサー-字句解析を実行するコンパイラー要素。
字句解析 (トークン化)-認識されたグループ(トークン、トークン)への入力文字列の解析解析のプロセス。
パーサー-結果を解析するコンパイラー要素。解析ツリーです。
解析-自然言語または形式言語のトークンの線形シーケンスを形式文法と比較するプロセス。
語彙素またはトークン-コンパイラにとって意味のあるプログラミング言語の有効な文字のシーケンス。
訪問者-子孫の処理を続行するツリーを操作するパターン。手動でメソッドを呼び出して子孫をバイパスする必要があります。
リスナーまたはウォーカー-すべての子孫を訪問するメソッドが自動的に呼び出されるときのツリーの操作パターン。 リスナーには、ノード訪問の開始時に呼び出されるメソッド(enterNode)と、ノード訪問後に呼び出されるメソッド(exitNode)があります。
解析ツリー-パーサーの結果を表す構造。 入力言語の構造の構文を反映し、操作の完全な相互接続を明確に含んでいます。
抽象構文ツリー (AST-抽象構文ツリー)は、プログラムのセマンティクスに影響を与えない構文規則のノードとエッジがないという点で、解析ツリーとは異なります。
Universal Abstract Syntax Tree (UAST)は、言語に依存しない注釈を使用したASTの正規化された形式です。
深さ優先探索
ツリートラバーサル(DFS)-グラフトラバーサルメソッドの1つ。 深さ検索戦略は、その名前が示すように、可能な限りグラフに「深く入り込む」ことです。
文法 (文法)-字句解析器および構文解析器を構築するための一連の指示。
ルートノード (ルート)-クロールを開始するツリーの最上位ノード。 これは祖先を持たない唯一のツリーノードですが、それ自体が他のすべてのツリーノードの祖先です。
リーフ、リーフ、またはターミナルノード (リーフ)-子孫を持たないノード。
親 -子を持つノード。 各ツリーノードには、0個以上の子孫ノードがあります。
リテラル-固定値またはデータ型の表現。
コードジェネレーターは、入力としてソースプログラムの中間表現を受け取り、ターゲット言語に変換します。
リサーチ
あるプログラミング言語を別のプログラミング言語に変換する問題を解決する方法は3つあります(ソースからソースへの変換)。
- 特定のプログラミング言語に既存のパーサーを使用します。
- 独自のパーサーを作成します。
- ツールまたはライブラリを使用してパーサーを生成します。
最初のオプションは良いですが、最も有名でサポートされている言語のみをカバーしています。 JavaScriptには、
Esprima 、
Falafel 、
UglifyJS 、
Jisonなどのパーサーがあります。 自分で何かを書く前に、既存のツールを調べる価値があります。おそらく、必要な機能を完全にカバーしているでしょう。
運が悪く、言語のパーサーが見つからない場合、または見つかったパーサーがすべてのニーズを満たしていない場合は、2番目のオプションに頼って自分で作成できます。
最初からコンパイラを書く方法を理解するための良い出発点は、
Super Tiny Compilerです。 ファイルからコメントを削除すると、最新のコンパイラーのすべての基本原則を含むコードは200行のみになります。
記事「
25行のJavaScriptでの単純なコンパイラーの実装」の著者は、JavaScriptコンパイラーを作成した経験を共有しています。 また、字句解析、解析、コード生成などの概念をカバーしています。
JavaScriptで単純なインタープリターを作成する方法は、例として単純な電卓を使用してインタープリターを作成するプロセスを調べる別のリソースです。
コンパイラーをゼロから作成するのは時間のかかるプロセスであり、コンパイルされた言語の構文上の機能を徹底的に予備調査する必要があります。 この場合、キーワードだけでなく、キーワードの相対的な位置も認識する必要があります。 ソースコードを分析するためのルールは明確であり、同じ初期条件下で出力で同じ結果を与える必要があります。
パーサーを生成するためのツールとライブラリがこれに役立ちます。 生のソースコードを取得し、トークンに分割し(字句解析)、トークンの線形シーケンスを正式な文法にマップし(解析)、新しいコードを構築できるツリー構造の構造にそれらを配置します。 それらの一部については、記事「
JavaScriptでの解析:ツールとライブラリ」で説明してい
ます 。

一見、問題の解決策はポケットの中にあるように思えるかもしれませんが、ソースコードを認識するようにパーサーに教えるためには、指示(文法)を書くためにより多くの工数を費やす必要があります。 また、コンパイラが複数のプログラミング言語をサポートする必要がある場合、このタスクは著しく複雑です。
この課題に直面した最初の開発者ではないことは明らかです。 IDEの作業はコード分析に関連しており、Babelは最新のJavaScriptをすべてのブラウザーでサポートされる標準に変換します。 これは、再利用できる文法がなければならないことを意味し、それによって作業を容易にするだけでなく、多くの潜在的に重大なエラーや不正確さを回避できます。
したがって、私たちの選択は、ほとんどすべてのプログラミング言語の文法が含まれているため、要件に最適なANTLRに基づいています。
別の方法は
Babelfishです 。これは、サポートされている言語のファイルを解析し、そのファイルからASTを抽出し、UASTに変換します。ノードはソース言語の構文にバインドされていません。 入力ではJavaScriptまたはC#を使用できますが、UASTレベルでは違いは見られません。 コンパイラの用語では、変換プロセスはASTをユニバーサル型にキャストするプロセスを担当します。

コンパイラの初心者は、特定のコードフラグメントと選択した言語に対応するパーサーに対して構文ツリーがどのようなものかを確認できるインターフェイスである
Astexplorerにも興味があるかもしれません。 デバッグや、ASTの構造の一般的なアイデアの形成に役立ちます。
コンパイラ作成
ANTLR (言語認識のための別のツール-言語を認識するための別のツール)は、Javaで書かれたパーサージェネレーターです。 彼は入力としてテキストを受け取り、それを
文法に基づいて分析し、それを組織化された構造に変換し、それを使用して抽象的な構文ツリーを作成することができます。 ANTLR 3もこのタスクを引き受けました-ASTによって生成されます。 ただし、ANTLR 4は
StringTemplatesを使用するためにこの機能を除外し、解析ツリーなどの概念でのみ動作します。
詳細については、
ドキュメントを参照するか、
The ANTLR Mega Tutorialの優れた記事を参照してください。パーサーとは何か、なぜそれが必要なのか、ANTLRの設定方法、便利なANTLR関数など、多数の例を紹介しています。
文法の作成について詳しくは、こちらをご覧ください。
あるプログラミング言語を別のプログラミング言語に変換するために、ANTLR 4とその文法の1つ、つまりECMAScript.g4を使用することにしました。 JavaScriptを選択したのは、その構文がMongo Shell言語に対応し、Compassアプリケーションの開発言語でもあるためです。 興味深い事実:LexerとParser C#を使用して解析ツリーを構築できますが、ECMAScript文法のノードを介してそれをバイパスできます。
この質問には、より詳細な調査が必要です。 自信を持って、すべてのコード構造がデフォルトで正しく認識されるとは限りません。 新しいメソッドとチェックを使用してバイパス機能を拡張する必要があります。 ただし、同じアプリケーション内で複数のパーサーをサポートする場合、ANTLRが優れたツールであることは既に明らかです。
ANTLRは、ツリーを操作するためのサポートファイルのリストを作成します。 これらのファイルの内容は、文法で指定されたルールに直接依存しています。 したがって、文法が変更された場合、これらのファイルを再生成する必要があります。 これは、コードの記述に直接使用しないでください。そうしないと、次の反復で変更が失われます。 クラスを作成し、ANTLRによって生成されたクラスから継承する必要があります。
ANTLRの結果として生成されたコードは、解析ツリーの作成に役立ちます。これは、新しいコードを生成するための基本的な手段です。 一番下の行は、子ノードを左から右に呼び出し(これがソーステキストの順序である場合)、それらが表す書式設定されたテキストを返すことです。
ノードがリテラルの場合、実際の値を返す必要があります。 結果を正確にしたい場合、これは思ったより難しいです。 この場合、精度を損なうことなく浮動小数点数を出力する機能と、さまざまな数値システムの数値を提供する必要があります。 文字列リテラルの場合、サポートする引用符のタイプを検討する価値があります。また、エスケープする必要がある文字のエスケープシーケンスを忘れないでください。 コードコメントをサポートしていますか? ユーザー入力形式(スペース、空白行)を維持しますか、それともテキストをより標準的で読みやすいフォームにしますか。 一方では、2番目のオプションはより専門的に見えますが、他方では、コンパイラーのユーザーは、結果として自分の入力と同じ形式を取得したいので、結果として満足しないかもしれません。 これらの問題に対する普遍的な解決策はありません。コンパイラーの範囲をより詳細に調査する必要があります。
ANTLRを使用してコンパイラーを作成する基本に集中するために、より単純な例を検討します。
ANTLRをインストールする
$ brew cask install java $ cd /usr/local/lib $ curl -O http://www.antlr.org/download/antlr-4.7.1-complete.jar $ export CLASSPATH=".:/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH" $ alias antlr4='java -Xmx500M -cp "/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH" org.antlr.v4.Tool' $ alias grun='java org.antlr.v4.gui.TestRig'
すべてのインストールが成功したことを確認するには、ターミナルに入力します。
$ java org.antlr.v4.Tool
ANTLRバージョンに関する情報とコマンドのヘルプが表示されます。

ANTLRを使用してNode.jsでプロジェクトを作成する
$ mkdir js-runtime $ cd js-runtime $ npm init
JavaScriptランタイムをインストールし
ます。これには、ANTL
4の npmパッケージantlr4-
JavaScriptターゲットが必要です。
$ npm i antlr4 --save
ECMAScript.g4文法をダウンロードします。これは後でANTLRにフィードします。
$ mkdir grammars $ curl --http1.1 https://github.com/antlr/grammars-v4/blob/master/ecmascript/ECMAScript.g4 --output grammars/ECMAScript.g4
ちなみに、 ANTLR Webサイトの[開発ツール]セクションには、Intellij、NetBeans、Eclipse、Visual Studio Code、jEditなどのIDEのプラグインへのリンクがあります。 カラーテーマ、セマンティックエラーチェック、ダイアグラムを使用した視覚化により、文法の記述とテストが容易になります。最後に、ANTLRを実行します。
$ java -Xmx500M -cp '/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH' org.antlr.v4.Tool -Dlanguage=JavaScript -lib grammars -o lib -visitor -Xexact-output-dir grammars/ECMAScript.g4
このスクリプトをpackage.jsonに追加して、常にアクセスできるようにします。 文法ファイルを変更した場合は、ANTLRを再起動して変更を適用する必要があります。

libフォルダーに移動し、ANTLRがファイルのリストを作成したことを確認します。 それらのうちの3つをさらに詳しく見ていきましょう。
- ECMAScriptLexer.jsは、文法で指定された規則に従って、ソースコードの文字ストリームをトークンストリームに分割します。
- ECMAScriptParser.jsは、トークンストリームから、解析ツリーと呼ばれる抽象的な接続されたツリーのような構造を構築します。
- ECMAScriptVisitor.jsは、構築されたツリーを走査します。 理論的には、子孫の深さを再帰的に走査して、このツリーを独立して処理できます。 ただし、多数のノードタイプとそれらの処理の複雑なロジックを使用する場合は、訪問者が行うように、独自のメソッドを使用して各ノードタイプにアクセスすることをお勧めします。
デフォルトでは、ANTLRは* Visitor.jsを作成しないことに注意してください。 ANTLRでツリーをトラバースする標準的な方法はリスナーです。 リスナーの代わりに訪問者を生成して使用する場合は、スクリプトで行ったように、 '
-visitor
'フラグを使用して明示的に指定する必要があります。
$ java -Xmx500M -cp '/usr/local/lib/antlr-4.7.1-complete.jar:$CLASSPATH' org.antlr.v4.Tool -Dlanguage=JavaScript -lib grammars -o lib -visitor -Xexact-output-dir grammars/ECMAScript.g4
ただし、両方の方法の作業の本質と結果は非常に似ていますが、Visitorを使用するとコードがよりきれいに見え、変換プロセスをより詳細に制御できます。 ツリーのノードにアクセスする順序と、ノードにアクセスするかどうかを設定できます。 クロール中に既存のサイトを変更することもでき、訪問したサイトに関する情報を保存する必要はありません。 記事
Antlr4-訪問者とリスナーのパターンは、このトピックをより詳細に、例とともに説明しました。
ソースコード分析と構文ツリー構築
最後にプログラミングを始めましょう。 ANTLRとJavaScriptの組み合わせで検索すると、文字通りどこにでも似たようなコードの例が見つかります。
const antlr4 = require('antlr4'); const ECMAScriptLexer = require('./lib/ECMAScriptLexer.js'); const ECMAScriptParser = require('./lib/ECMAScriptParser.js'); const input = '{x: 1}'; const chars = new antlr4.InputStream(input); const lexer = new ECMAScriptLexer.ECMAScriptLexer(chars); lexer.strictMode = false;
今やったこと:ANTLRが生成したレクサーとパーサーを使用して、ソース文字列をLISP形式(ANTLR 4の標準ツリー出力形式)でツリービューに表示しました。 文法によると、ECMAScriptツリーのルートは「
program
ルールであるため、この例ではトラバーサルの開始点として選択しました。 ただし、これはこのノードが常に最初であることを意味するものではありません。 元の文字列 `
{x: 1}
`の場合、 `
expressionSequence
クロールを開始することは完全に公平です。
const tree = parser.expressionSequence();

`
.toStringTree()
`フォーマットを削除すると、ツリーオブジェクトの内部構造を見ることができます。
従来、あるプログラミング言語を別のプログラミング言語に変換するプロセス全体は、3つの段階に分けることができます。
- ソースコード分析。
- 構文ツリーの構築。
- 新しいコードを生成します。
ご覧のとおり、ANTLRのおかげで作業が大幅に簡素化され、数行のコードでいくつかの手順がカバーされました。 もちろん、それらに戻り、文法を更新し、ツリーを変換しますが、それでも良い基盤はすでに構築されています。 リポジトリからダウンロードされた文法も劣るか、エラーが発生する可能性があります。 しかし、おそらく、彼らは自分で文法を一から書き始めた場合にのみ、あなたが犯したであろう間違いをすでに修正しているでしょう。 ポイントはこれです。他の開発者が書いたコードを盲目的に信頼するべきではありませんが、既存のルールを改善するために同じルールを書く時間を節約できます。 おそらく、次世代の若いANTLRは、より理想的なバージョンの文法をすでにダウンロードしているのでしょう。
コード生成
プロジェクトに新しいディレクトリ `
codegeneration
作成し、その中に新しいPythonGenerator.jsファイルを作成します。
$ mkdir codegeneration $ cd codegeneration $ touch PythonGenerator.js
ご想像のとおり、例として、JavaScriptからPythonに何かを変換しています。
生成されたECMAScriptVisitor.jsファイルには、膨大なメソッドのリストが含まれています。各メソッドは、対応するノードにアクセスすると、ツリーウォーク中に自動的に呼び出されます。 そしてその瞬間、現在のノードを変更できます。 これを行うには、ECMAScriptVisitorから継承し、必要なメソッドをオーバーライドするクラスを作成します。
const ECMAScriptVisitor = require('../lib/ECMAScriptVisitor').ECMAScriptVisitor; class Visitor extends ECMAScriptVisitor { start(ctx) { return this.visitExpressionSequence(ctx); } } module.exports = Visitor;
文法の構文規則に準拠するメソッドに加えて、ANTLRは
4つの特別なパブリックメソッドもサポートし
ます 。
- visit()は、ツリーのトラバースを担当します。
- visitChildren()はノードの横断を担当します。
- visitTerminal()は、トークンのバイパスを担当します。
- visitErrorNode()は、無効なトークンをバイパスします。
visitChildren()
メソッド `
visitChildren()
`、 `
visitChildren()
`、 `
visitTerminal()
`を実装します。
visitChildren(ctx) { let code = ''; for (let i = 0; i < ctx.getChildCount(); i++) { code += this.visit(ctx.getChild(i)); } return code.trim(); } visitTerminal(ctx) { return ctx.getText(); } visitPropertyExpressionAssignment(ctx) { const key = this.visit(ctx.propertyName()); const value = this.visit(ctx.singleExpression()); return `'${key}': ${value}`; }
関数 `
visitPropertyExpressionAssignment
は、値`
visitPropertyExpressionAssignment
`
visitPropertyExpressionAssignment
に割り当てるノードに
visitPropertyExpressionAssignment
ます。 Pythonでは、オプションのJavaScriptとは異なり、オブジェクトの文字列パラメーターは引用符で囲む必要があります。 この場合、これはJavaScriptコードのフラグメントをPythonコードに変換するために必要な唯一の変更です。

index.jsでPythonGeneratorへの呼び出しを追加します。
console.log('JavaScript input:'); console.log(input); console.log('Python output:'); const output = new PythonGenerator().start(tree); console.log(output);
プログラムを実行した結果、コンパイラがタスクを正常に完了し、JavaScriptオブジェクトをPythonオブジェクトに変換したことがわかります。

私たちは親から子孫へと木を横断し始め、徐々にその葉へと降りていきます。 次に、逆の順序で1レベル上の書式設定された値を置換します。したがって、ツリーノードのチェーン全体を、新しいプログラミング言語の構文に対応するテキスト表現に置き換えます。

`
visitPropertyExpressionAssignment
関数にデバッグを追加します。
子孫には、名前とシリアル番号の両方でアクセスできます。 子孫はノードでもあるため、オブジェクト表現ではなくテキスト値を取得するために、 `
.getText()
`メソッドを使用しました。
ECMAScript.g4を補足し、コンパイラに `
Number
キーワードを認識するように教えます。


ビジターを再生成して、文法に加えられた変更を引き出します。
$ npm run compile
PythonGenerator.jsファイルに戻り、新しいメソッドのリストを追加します。
visitNewExpression(ctx) { return this.visit(ctx.singleExpression()); } visitNumberExpression(ctx) { const argumentList = ctx.arguments().argumentList();
Pythonはコンストラクタを呼び出すときに `
visitNewExpression
キーワードを使用しないため、それを削除するか、
visitNewExpression
ノードで最初の子を削除してツリーの走査を続行します。 次に、キーワード `
Number
を`
int
に置き換えます。これはPythonの同等の機能です。 `
Number
は式(式)であるため、
.arguments()
`メソッドを介して引数にアクセスできます。

同様に、ECMAScriptVisitor.jsにリストされているすべてのメソッドを実行し、すべてのJavaScriptリテラル、シンボル、ルールなどをPython(または他のプログラミング言語)の同等物に変換できます。
エラー処理
ANTLRはデフォルトで、文法で指定された構文に準拠しているかどうかの入力を検証します。 不一致が発生した場合、コンソールには発生した問題に関する情報が提供されますが、ANTLRは認識できる文字列を返します。 ソース文字列 `{x:2}`からコロンを削除すると、ANTLRは認識されないノードを `undefined`に置き換えます。

この動作に影響を与えることができ、破線の代わりに詳細なエラー情報を出力します。 まず、アプリケーションのルートに新しいモジュールを作成します。このモジュールは、カスタムエラータイプを生成します。
$ mkdir error $ cd error $ touch helper.js $ touch config.json
これはコンパイラに関するトピックの範囲を超えているため、このモジュールの実装の機能については説明しません。 以下の完成バージョンをコピーするか、アプリケーションのインフラストラクチャにより適した独自のバージョンを作成できます。
アプリケーションで使用するすべてのタイプのエラーは、config.jsonに示されています。
{ "syntax": {"generic": {"message": "Something went wrong"}}, "semantic": { "argumentCountMismatch": { "message": "Argument count mismatch" } } }
次に、error.jsは構成からリストを循環し、その中の各レコードに対して、Errorから継承された個別のクラスを作成します。
const config = require('./config.json'); const errors = {}; Object.keys(config).forEach((group) => { Object.keys(config[group]).forEach((definition) => {
visitNumberExpression
メソッドを更新すると、 `
Error
マークされたテキストメッセージの代わりに、`
SemanticArgumentCountMismatchError
`errorがスローされます。これにより、キャッチしやすくなり、アプリケーションの成功と問題のある結果を区別しやすくなります。
const path = require('path'); const { SemanticArgumentCountMismatchError } = require(path.resolve('error', 'helper')); visitNumberExpression(ctx) { const argumentList = ctx.arguments().argumentList(); if (argumentList === null || argumentList.getChildCount() !== 1) { throw new SemanticArgumentCountMismatchError(); } const arg = argumentList.singleExpression()[0]; const number = this.removeQuotes(this.visit(arg)); return `int(${number})`; }
ここで、ANTLRの作業に直接関連するエラー、つまりコードの解析中に発生するエラーに対処しましょう。 コード生成ディレクトリで、新しいファイルErrorListener.jsを作成し、その中に `antlr4.error.ErrorListener`から継承したクラスを作成します。
const antlr4 = require('antlr4'); const path = require('path'); const {SyntaxGenericError} = require(path.resolve('error', 'helper')); class ErrorListener extends antlr4.error.ErrorListener { syntaxError(recognizer, symbol, line, column, message, payload) { throw new SyntaxGenericError({line, column, message}); } } module.exports = ErrorListener;
標準エラー出力メソッドをオーバーライドするには、ANTLRパーサーで使用可能な2つのメソッドを使用します。
- parser.removeErrorListeners()は、標準のConsoleErrorListenerを削除します。
- parser.addErrorListener()は、カスタムErrorListenerを追加します。
これは、パーサーを作成した後、呼び出す前に行う必要があります。 更新されたindex.jsの完全なコードは次のようになります。
const antlr4 = require('antlr4'); const ECMAScriptLexer = require('./lib/ECMAScriptLexer.js'); const ECMAScriptParser = require('./lib/ECMAScriptParser.js'); const PythonGenerator = require('./codegeneration/PythonGenerator.js'); const ErrorListener = require('./codegeneration/ErrorListener.js'); const input = '{x 2}'; const chars = new antlr4.InputStream(input); const lexer = new ECMAScriptLexer.ECMAScriptLexer(chars); lexer.strictMode = false;
エラーオブジェクトに含まれている情報のおかげで、発生した例外を正しく処理する方法、プログラムを中断または続行する方法、有益なログを開始する方法、最後になりますがコンパイラの正しいソースデータと誤ったソースデータをサポートするテストを作成する方法を決定できます。

おわりに
誰かがコンパイラを書くように頼んだら、すぐに同意してください! これは非常に興味深いものであり、通常のプログラミングタスクとは大きく異なる可能性があります。 ANTLRを使用してJavaScriptでコンパイラーを記述するという考え方を概説するために、最も単純なノードのみを考慮しました。 この機能は、渡された引数のタイプを検証し、拡張JSONまたはBSONサポートを文法に追加し、識別子テーブルを使用してtoJSON()、toString()、getTimestamp()などのメソッドを認識することで拡張できます。 実際、可能性は無限です。
この記事の執筆時点では、MongoDBコンパイラーに関する作業はまだ始まったばかりです。 コード変換へのこのアプローチは時間とともに変化するか、より最適なソリューションが登場する可能性が高いため、より関連性の高い情報を含む新しい記事を書くことができます。
今日、私はコンパイラーの作成に非常に情熱を傾けており、そのプロセスで得た知識を保持したいと思っています。
トピックをさらに深く掘り下げたい場合は、以下のリソースを読んでお勧めします。
また、記事で使用されているリソースへのリンク:
コンパスチーム、特にコンパイラの執筆への指導と貢献をしてくれた
Anna Herlihy 、記事の構造に関する推奨事項、および
Oksana Nalyvaikoへのタイトルイラストへのレタリングについて、レビュアーの
Alex Komyagin 、
Misha Tyulenevに
感謝します。
Mediumに関する記事の英語版:
ANTLRを使用したJavaScriptのコンパイラー