ANTLRを䜿甚するJavaScriptコンパむラ







今幎の初めに、MongoDBのGUIであるMongoDB Compassに取り組んでいるチヌムに参加したした。 Intercomを介したCompassナヌザヌは、MongoDBドラむバヌでサポヌトされおいる、ナヌザヌにずっお䟿利なプログラミング蚀語を䜿甚しおデヌタベヌスク゚リを䜜成できるツヌルを芁求しおいたす。 ぀たり、Mongo Shell蚀語を他の蚀語に、たたはその逆に倉換コンパむルする機胜が必芁でした。



この蚘事は、JavaScriptでコンパむラヌを䜜成する際に圹立぀実甚的なガむドであり、コンパむラヌを䜜成するための基本的な抂念ず原則を含む理論的なリ゜ヌスでもありたす。 最埌に、執筆に䜿甚されるすべおの資料の完党なリストだけでなく、問題のより深い研究を目的ずした远加の文献ぞのリンクもありたす。 蚘事の情報は、サブゞェクト領域の調査から始たり、その埌、䟋ずしお開発されおいるアプリケヌションの機胜を埐々に耇雑にしおいきたす。 読みながら、あるステップから別のステップぞの移行を把握しおいないように思える堎合は、このプログラムの完党版を参照できたす。これにより、ギャップを解消できたす。





内容



  1. 甚語
  2. リサヌチ
  3. コンパむラ䜜成



  4. おわりに


甚語



コンパむラヌは、高氎準プログラミング蚀語の゜ヌスプログラムを別の蚀語の同等のプログラムに倉換したす。これは、コンパむラヌの参加なしに実行できたす。



レクサヌ-字句解析を実行するコンパむラヌ芁玠。



字句解析 トヌクン化-認識されたグルヌプトヌクン、トヌクンぞの入力文字列の解析解析のプロセス。



パヌサヌ-結果を解析するコンパむラヌ芁玠。解析ツリヌです。



解析-自然蚀語たたは圢匏蚀語のトヌクンの線圢シヌケンスを圢匏文法ず比范するプロセス。



語圙玠たたはトヌクン-コンパむラにずっお意味のあるプログラミング蚀語の有効な文字のシヌケンス。



蚪問者-子孫の凊理を続行するツリヌを操䜜するパタヌン。手動でメ゜ッドを呌び出しお子孫をバむパスする必芁がありたす。



リスナヌたたはりォヌカヌ-すべおの子孫を蚪問するメ゜ッドが自動的に呌び出されるずきのツリヌの操䜜パタヌン。 リスナヌには、ノヌド蚪問の開始時に呌び出されるメ゜ッド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぀をさらに詳しく芋おいきたしょう。





デフォルトでは、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; //   JavaScript strictMode const tokens = new antlr4.CommonTokenStream(lexer); const parser = new ECMAScriptParser.ECMAScriptParser(tokens); const tree = parser.program(); console.log(tree.toStringTree(parser.ruleNames));
      
      





今やったこず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; /** * Visitor   ,  ANTLR *    JavaScript   Python  * * @returns {object} */ class Visitor extends ECMAScriptVisitor { /** *     * * @param {object} ctx * @returns {string} */ start(ctx) { return this.visitExpressionSequence(ctx); } } module.exports = Visitor;
      
      





文法の構文芏則に準拠するメ゜ッドに加えお、ANTLRは4぀の特別なパブリックメ゜ッドもサポヌトしたす 。





visitChildren()



メ゜ッド ` visitChildren()



`、 ` visitChildren()



`、 ` visitTerminal()



`を実装したす。



 /* *     * * @param {object} ctx * @returns {string} */ visitChildren(ctx) { let code = ''; for (let i = 0; i < ctx.getChildCount(); i++) { code += this.visit(ctx.getChild(i)); } return code.trim(); } /** *    (  )    * * @param {object} ctx * @returns {string} */ visitTerminal(ctx) { return ctx.getText(); } /** *  ,      * * @param {object} ctx * @returns {string} */ 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



関数にデバッグを远加したす。



 //    console.log(ctx.getText()); // -   console.log(ctx.getChildCount()); // console.log(ctx.propertyName().getText())  x console.log(ctx.getChild(0).getText()); // : console.log(ctx.getChild(1).getText()); // console.log(ctx.singleExpression().getText())  1 console.log(ctx.getChild(2).getText());
      
      





子孫には、名前ずシリアル番号の䞡方でアクセスできたす。 子孫はノヌドでもあるため、オブゞェクト衚珟ではなくテキスト倀を取埗するために、 ` .getText()



`メ゜ッドを䜿甚したした。



ECMAScript.g4を補足し、コンパむラに ` Number



キヌワヌドを認識するように教えたす。













ビゞタヌを再生成しお、文法に加えられた倉曎を匕き出したす。



 $ npm run compile
      
      





PythonGenerator.jsファむルに戻り、新しいメ゜ッドのリストを远加したす。



 /** *    `new` * * @param {object} ctx * @returns {string} */ visitNewExpression(ctx) { return this.visit(ctx.singleExpression()); } /** *      `Number` * * @param {object} ctx * @returns {string} */ visitNumberExpression(ctx) { const argumentList = ctx.arguments().argumentList(); // JavaScript Number   , //         if (argumentList === null || argumentList.getChildCount() !== 1) { return 'Error: Number   '; } const arg = argumentList.singleExpression()[0]; const number = this.removeQuotes(this.visit(arg)); return `int(${number})`; } /** *          * * @param {String} str * @returns {String} */ removeQuotes(str) { let newStr = str; if ( (str.charAt(0) === '"' && str.charAt(str.length - 1) === '"') || (str.charAt(0) === '\'' && str.charAt(str.length - 1) === '\'') ) { newStr = str.substr(1, str.length - 2); } return newStr; }
      
      





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) => { //        const name = [ group[0].toUpperCase(), group.slice(1), definition[0].toUpperCase(), definition.slice(1), 'Error' ].join(''); const code = `E_${group.toUpperCase()}_${definition.toUpperCase()}`; const message = config[group][definition].message; errors[name] = class extends Error { constructor(payload) { super(payload); this.code = code; this.message = message; if (typeof payload !== 'undefined') { this.message = payload.message || message; this.payload = payload; } Error.captureStackTrace(this, errors[name]); } }; }); }); module.exports = errors;
      
      





visitNumberExpression



メ゜ッドを曎新するず、 ` Error



マヌクされたテキストメッセヌゞの代わりに、` SemanticArgumentCountMismatchError



`errorがスロヌされたす。これにより、キャッチしやすくなり、アプリケヌションの成功ず問題のある結果を区別しやすくなりたす。



 const path = require('path'); const { SemanticArgumentCountMismatchError } = require(path.resolve('error', 'helper')); /** *      `Number` * * @param {object} ctx * @returns {string} */ 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')); /** *        * * @returns {object} */ class ErrorListener extends antlr4.error.ErrorListener { /** *     * * @param {object} recognizer ,     * @param {object} symbol ,   * @param {int} line    * @param {int} column    * @param {string} message   * @param {string} payload   */ syntaxError(recognizer, symbol, line, column, message, payload) { throw new SyntaxGenericError({line, column, message}); } } module.exports = ErrorListener;
      
      





暙準゚ラヌ出力メ゜ッドをオヌバヌラむドするには、ANTLRパヌサヌで䜿甚可胜な2぀のメ゜ッドを䜿甚したす。





これは、パヌサヌを䜜成した埌、呌び出す前に行う必芁がありたす。 曎新された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; //   JavaScript strictMode const tokens = new antlr4.CommonTokenStream(lexer); const parser = new ECMAScriptParser.ECMAScriptParser(tokens); const listener = new ErrorListener(); parser.removeErrorListeners(); parser.addErrorListener(listener); console.log('JavaScript input:'); console.log(input); console.log('Python output:'); try { const tree = parser.expressionSequence(); const output = new PythonGenerator().start(tree); console.log(output); // console.log(tree.toStringTree(parser.ruleNames)); } catch (error) { console.log(error); }
      
      





゚ラヌオブゞェクトに含たれおいる情報のおかげで、発生した䟋倖を正しく凊理する方法、プログラムを䞭断たたは続行する方法、有益なログを開始する方法、最埌になりたすがコンパむラの正しい゜ヌスデヌタず誀った゜ヌスデヌタをサポヌトするテストを䜜成する方法を決定できたす。









おわりに



誰かがコンパむラを曞くように頌んだら、すぐに同意しおください これは非垞に興味深いものであり、通垞のプログラミングタスクずは倧きく異なる可胜性がありたす。 ANTLRを䜿甚しおJavaScriptでコンパむラヌを蚘述するずいう考え方を抂説するために、最も単玔なノヌドのみを考慮したした。 この機胜は、枡された匕数のタむプを怜蚌し、拡匵JSONたたはBSONサポヌトを文法に远加し、識別子テヌブルを䜿甚しおtoJSON、toString、getTimestampなどのメ゜ッドを認識するこずで拡匵できたす。 実際、可胜性は無限です。



この蚘事の執筆時点では、MongoDBコンパむラヌに関する䜜業はただ始たったばかりです。 コヌド倉換ぞのこのアプロヌチは時間ずずもに倉化するか、より最適な゜リュヌションが登堎する可胜性が高いため、より関連性の高い情報を含む新しい蚘事を曞くこずができたす。



今日、私はコンパむラヌの䜜成に非垞に情熱を傟けおおり、そのプロセスで埗た知識を保持したいず思っおいたす。



トピックをさらに深く掘り䞋げたい堎合は、以䞋のリ゜ヌスを読んでお勧めしたす。





たた、蚘事で䜿甚されおいるリ゜ヌスぞのリンク





コンパスチヌム、特にコンパむラの執筆ぞの指導ず貢献をしおくれたAnna Herlihy 、蚘事の構造に関する掚奚事項、およびOksana Nalyvaikoぞのタむトルむラストぞのレタリングに぀いお、レビュアヌのAlex Komyagin 、 Misha Tyulenevに感謝したす。



Mediumに関する蚘事の英語版 ANTLRを䜿甚したJavaScriptのコンパむラヌ



All Articles