YaPを作成するというトピックは、約6か月間悩まされてきました。 私はCoffeeScript 、 TypeScript 、 ELM 、 それらの何千人もを「殺す」という目標を自分自身に設定しませんでした。キッチンとそれらがどのように書かれているかを理解したかっただけです。
不愉快なことに、これらの言語のほとんどはJison ( JavaScriptの Bison)を使用しており、本質的にJisonはあなたのためにすべてを行い、設定したルールに従ってASTを収集するため、「理解」するのに私のタスクには適合しませんでした(Jisonとしてあなたのために仕事の大部分をしますが、今ではそうではないような素晴らしいツールです。
最終的に、私は試行錯誤(または、記事を読んだり、リバースエンジニアリング)で、ソーステキストをトークンに分割してからJSコードに変換するまで、独自の本格的なプログラミング言語を書く方法を学びました。
このガイドはJavaScriptに結び付けられておらず、開発速度と読みやすさの理由だけで選択されているため、「lisp」/「python」/「完全に新しい構文」を使い慣れた言語で書くことができます。
また、コンパイラー(この場合はトランスレーター)を作成する前に、言語を作成するプロセスは、 ASM / JVMビットコード / LLVMビットコード / などでコンパイルされた言語を作成するプロセスと変わらないため、このガイドはJavaScriptで翻訳された言語の作成に限定されません。
この(および後続の記事)で作成されるすべてのコードは Githubにあります。 タグは、便宜上、記事の始まりと終わりを示します。
理論のビット
ウィキペディアに入ることなく、ソースコードを最終的なJSコードに変換するプロセスは次のように進みます。
source code -(Lexer)-> tokens -(Parser)-> AST -(Compiler)-> js code
ここで何が起こっていますか:
1)レクサー
プログラムのソースコードはトークンに分割されます。 簡単な方法では、これはソーステキストでキーワード、リテラル、文字、識別子などを見つけることです。
つまり これからの出力( CoffeeScript ):
a = true if a console.log('Hello, lexer')
これを取得します(略記):
[IDENTIFIER:"a"] [ASSIGN:"="] [BOOLEAN:"true"] [NEWLINE:"\n"] [NEWLINE:"\n"] [KEYWORD:"if"] [IDENTIFIER:"a"] [NEWLINE:"\n"] [INDENT:" "] [IDENTIFIER:"console"] [DOT:"."] [IDENTIFIER:"log"] [ROUND_BRAKET_START:"("] [STRING:"'Hello, lexer'"] [ROUND_BRAKET_END:")"] [NEWLINE:"\n"] [OUTDENT:""] [EOF:"EOF"]
CoffeeScriptはインデントされ、ブラケット{
および}
による明示的なブロック割り当てがないため、ブロックはインデントされ( INDENT
およびOUTDENT
)、本質的にブラケットを置き換えます。
2)パーサー
パーサーは、トークン(トークン)のASTを作成します。 配列全体を走査し、tipiトークンまたはそのシーケンスに基づいて適切なパターンを再帰的に選択します。
パラグラフ1で受け取ったトークンから、パーサーはこのツリー(短縮レコード)のようなものを構成します。
{ type: 'ROOT', // nodes: [{ type: 'VARIABLE', // a = true id: { type: 'IDENTIFIER', value: 'a' }, init: { type: 'LITERAL', value: true } }, { type: 'IF_STATEMENT', // test: { type: 'IDENTIFIER', value: 'a' }, consequent: { type: 'BLOCK_STATEMENT', nodes: [{ type: 'EXPRESSION_STATEMENT', // console.log expression: { type: 'CALL_EXPRESSION', callee: { type: 'MEMBER_EXPRESSION', object: { type: 'IDENTIFIER', value: 'console' }, property: { type: 'IDENTIFIER', value: 'log' } }, arguments: [{ type: 'LITERAL', value: 'Hello, lexer' }] } }] } }] }
ツリーのボリュームを恐れないでください。実際には、ツリーは再帰的に生成され、その作成は困難を引き起こしません。
3)コンパイラー
ASTに従って最終コードを構築します。 この項目は、バイトコードまたはランタイムでのコンパイルで置き換えることができますが、この一連の記事のフレームワーク内で、別のプログラミング言語へのトランスレーターの実装を検討します。
コンパイラー(トランスレーターを読む)は、抽象構文ツリーをJavaScriptコードに変換します。
var a = true; if (a) { console.log('Hello, lexer'); }
以上です。 ほとんどのコンパイラは、この原則に基づいて動作します(わずかな変更があります。時には、ソーステキストを文字のストリームにストリーミングするプロセスを追加します。
ハブラン
したがって、理論を理解したら、次の構文を持つ独自のプログラミング言語を作成する必要があります(蒸気を避けるために、 Ruby 、 Python 、 CoffeeScriptを混合します)。
#!/bin/habrlang # Hello habrlang def hello <- "world" end console.log(hello())
次の章では、翻訳者のすべての主要なクラスを実装し、 HabrlangのコメントをJavaScriptで翻訳する方法を彼に教えます 。
Githubレポ : https : //github.com/SuperPaintman/habrlang