私はプログラミング言語を書いた。 ここでは、あまりにも、どのようにすることができます。

By William W Wold

過去6ヶ月にわたって、私はPineconeと呼ばれるプログラミング言語に取り組んできました。 私はまだ成熟したとは言いませんが、

  • 変数
  • 関数
  • ユーザー定義構造

興味がある場合は、Pineconeのランディングペー私は専門家ではありません。

私は専門家ではありません。 私がこのプロジェクトを始めたとき、私は何をしていたのか見当もつかなかったし、私はまだしていません。私は言語作成に関するクラスをゼロにし、オンラインでそれについて少しだけ読んで、私が与えられたアドバイスの多くに従わなかった。そして、まだ、私はまだ完全に新しい言語を作りました。

そして、私はまだ完全に新しい言語を作りました。 そして、それは動作します。 だから私は正しい何かをしている必要があります。

この記事では、私はフードの下に潜って、ソースコードを魔法に変えるために使用するパイプラインPinecone(および他のプログラミング言語)をお見せします。また、私が行ったトレードオフのいくつかと、なぜ私が行った決定をしたのかについても触れます。

これは決してプログラミング言語を書く上での完全なチュートリアルではありませんが、言語開発に興味があるなら、それは良い出発点です。

Getting Started

「どこから始めるのか全くわからない」ということは、他の開発者に私が言語を書いていることを伝えるときによく聞くことです。 それがあなたの反応である場合、私は今、新しい言語を開始するときに行われるいくつかの最初の決定と取られるステップを通過します。

コンパイルと解釈

言語には、コンパイルと解釈の二つの主要なタイプがあります:

  • コンパイラは、プログラムが行うすべてのことを把握し、それを”マシンコード”(コンピュータが本当に速く実行できる形式)に変換し、後で実行するこ
  • インタプリタは、ソースコードを行ごとにステップ実行し、それが何をしているのかを把握します。

技術的にはどの言語もコンパイルまたは解釈することができますが、通常は特定の言語に対してどちらか一方がより理にかなっています。 一般的に、解釈はより柔軟になる傾向があり、コンパイルはより高いパフォーマンスを持つ傾向があります。 しかし、これは非常に複雑なトピックの表面を傷つけるだけです。

私は非常にパフォーマンスを重視し、私は高性能とシンプル指向の両方のプログラミング言語の欠如を見たので、私はPineconeのためにコンパイルされ

これは、多くの言語設計上の決定が影響を受けるため、早期に行う重要な決定でした(たとえば、静的型付けはコンパイルされた言語には大きな利

Pineconeはコンパイルを念頭に置いて設計されていますが、しばらくの間実行する唯一の方法である完全に機能するインタプリタがあります。 これにはいくつかの理由がありますが、これについては後で説明します。私はそれが少しメタであることを知っていますが、プログラミング言語自体はプログラムであるため、言語で書く必要があります。

言語の選択

私はそれが少しメタであることを知っていますが、プログラミング言語自体はプログラムであるため、あなたはそれを言語で書く必要があります。 私はそのパフォーマンスと大規模な機能セットのためにC++を選びました。 また、私は実際にC++で働くことを楽しんでいます。

解釈された言語を書いている場合、コンパイルされた言語(C、C++、Swiftなど)で書くのは理にかなっています。コンパイルする予定がある場合は、より遅い言語(PythonやJavaScriptなど)の方が受け入れられます。 コンパイル時は悪いかもしれませんが、私の意見では、それは悪い実行時と同じくらい大きな問題ではありません。

高レベル設計

プログラミング言語は、一般的にパイプラインとして構造化されています。 つまり、いくつかの段階があります。 各ステージには、特定の、明確に定義された方法でフォーマットされたデータがあります。 また、各ステージから次のステージにデータを変換する機能も備えています。

最初のステージは、入力ソースファイル全体を含む文字列です。 最終段階は実行できるものです。 これは、Pineconeパイプラインを段階的に通過するにつれて、すべて明らかになります。H3>

ほとんどのプログラミング言語の最初のステップは、文字列、またはトークン化です。 ‘Lex’は字句解析の略で、テキストの束をトークンに分割するための非常に派手な言葉です。 ‘Tokenizer’という言葉はもっと理にかなっていますが、’lexer’は私がとにかくそれを使用すると言うのはとても楽しいです。

トークン

トークンは言語の小さな単位です。 トークンは、変数名または関数名(別名識別子)、演算子または数値である可能性があります。

レクサーのタスク

レクサーは、ソースコードのファイル全体を含む文字列を取り込み、すべてのトークンを含むリストを吐き出すことになっています。

パイプラインの将来の段階では、元のソースコードを参照しないため、レクサーは必要なすべての情報を生成する必要があります。 この比較的厳密なパイプライン形式の理由は、レクサーがコメントを削除したり、何かが数値または識別子であるかどうかを検出したりするなどのタス そのロジックをレクサー内でロックしておきたいので、残りの言語を書くときにこれらのルールについて考える必要がないので、このタイプの構文を一箇所で変更することができます。

Flex

私が言語を始めた日、私が最初に書いたのは単純な字句解析でした。 その後すぐに、私はおそらく字句をより簡単にし、バグの少ないツールについて学び始めました。

そのようなツールの主なものは、レクサーを生成するプログラムであるFlexです。 あなたはそれに言語の文法を記述するための特別な構文を持つファイルを与えます。 それから、文字列をlexesして目的の出力を生成するCプログラムを生成します。

私の決定

私は当分の間書いた字句解析を維持することにしました。 結局、私はFlexを使用することの大きな利点を見ませんでしたが、少なくとも依存関係を追加してビルドプロセスを複雑にするのを正当化するには十私のレクサーはわずか数百行の長さであり、めったに私に問題を与えません。

私のレクサーは数百行の長さです。

私自身のレクサーをローリングすることで、複数のファイルを編集せずに言語に演算子を追加する機能など、より柔軟性が得られます。p>

解析

パイプラインの第二段階はパーサーです。 パーサーは、トークンのリストをノードのツリーに変換します。 このタイプのデータを格納するために使用されるツリーは、抽象構文ツリー、またはASTとして知られています。 少なくともPineconeでは、ASTにはタイプや識別子に関する情報はありません。 それは単純に構造化されたトークンです。

Parser Duties

パーサーは、レクサーが生成するトークンの順序付きリストに構造体を追加します。 あいまいさを止めるには、パーサーは括弧と操作の順序を考慮する必要があります。 単に演算子を解析することはそれほど難しくありませんが、言語構造が追加されるにつれて、解析は非常に複雑になる可能性があります。

Bison

ここでも、サードパーティのライブラリを含むようにする決定がありました。 主な構文解析ライブラリはBisonです。 BisonはFlexとよく似ています。 文法情報を格納するカスタム形式のファイルを作成すると、Bisonはそれを使用して解析を行うCプログラムを生成します。 私はバイソンを使用することを選択しませんでした。

なぜカスタムが優れているのか

レクサーでは、自分のコードを使用するという決定はかなり明白でした。 レクサーは、私自身の’左パッド’を書いていないのと同じくらい愚かに感じた私自身を書いていないような些細なプログラムです。

パーサーでは、それは別の問題です。 私のPineconeパーサーは現在750行の長さであり、最初の二つはゴミだったので、私はそれらの三つを書いてきました。私はもともといくつかの理由で私の決定を下しましたが、それは完全にスムーズに行っていませんが、それらのほとんどは真実を保持しています。

主なものは次のとおりです。

  • ワークフローでのコンテキスト切り替えを最小限に抑える:C++とPinecone間のコンテキスト切り替えは、Bisonの文法をスローすることなく十分に悪い文法
  • ビルドをシンプルに保つ:文法が変更されるたびに、ビルドの前にBisonを実行する必要があります。 これは自動化することができますが、ビルドシステムを切り替えるときには苦痛になります。
  • 私はクールなたわごとを構築するのが好きです:私はそれが簡単だと思ったので、私はPineconeを作っていませんでしたが、私はそれを自分で行うことがで カスタムパーサーは些細なことではないかもしれませんが、それは完全に実行可能です。最初は、私が実行可能な道を進んでいるかどうかは完全にはわかりませんでしたが、Walter Bright(c++の初期バージョンの開発者であり、D言語の作成者)がこのト:

    アクションツリー

    私たちは今、共通の、普遍的な用語の領域を残している、または少なくと 私の理解から、私が「アクションツリー」と呼ぶものは、LLVMのIR(中間表現)に最も似ています。

    アクションツリーと抽象構文ツリーの間には微妙だが非常に重要な違いがあります。 それらの間に違いがあるはずであることを理解するのにかなりの時間がかかりました(これはパーサーの書き換えの必要性に貢献しました)。

    アクションツリー vs AST

    簡単に言えば、アクションツリーはコンテキストを持つASTです。 そのコンテキストは、関数が返す型や、変数が使用される2つの場所が実際には同じ変数を使用しているという情報です。 このコンテキストを把握して覚えておく必要があるため、アクションツリーを生成するコードには、多くの名前空間ルックアップテーブルやその他のthingamabobsが必

    アクションツリーの実行

    アクションツリーを取得したら、コードを実行するのは簡単です。 各アクションノードには、いくつかの入力を受け取り、アクションが必要なこと(サブアクションを呼び出すことも含む)を行い、アクションの出力を返す関数”execute”があります。 これは、アクションの通訳です。

    コンパイルオプション

    “しかし、待ってください!”と言っているのを聞いたが、パインコーネはコンパイルになっていないのだろうか?「はい、そうです。 しかし、コンパイルは解釈よりも困難です。 いくつかの可能なアプローチがあります。

    私自身のコンパイラをビルド

    これは最初は私にとって良いアイデアのように聞こえました。 私は物事を自分で作るのが大好きで、私は組み立てが得意な言い訳をしていました。

    残念ながら、移植可能なコンパイラを書くことは、各言語要素のマシンコードを書くほど簡単ではありません。 アーキテクチャとオペレーティングシステムの数のために、クロスプラットフォームコンパイラバックエンドを書くことは非現実的です。Swift、Rust、Clangの背後にあるチームでさえ、すべてを自分で気にしたくないので、代わりにすべてを使用します…

    LLVM

    LLVMはコンパイラツールのコレクシ これは基本的にあなたの言語をコンパイルされた実行可能バイナリに変えるライブラリです。 それは完璧な選択のように見えたので、私はすぐに飛び込んだ。 悲しいことに、私は水の深さを確認しなかったし、私はすぐに溺死した。

    LLVMは、アセンブリ言語ではありませんが、巨大な複雑なライブラリです。 使用することは不可能ではなく、彼らは良いチュートリアルを持っていますが、Pineconeコンパイラを完全に実装する準備が整う前に、いくつかの練習をしな私はコンパイルされたPineconeのいくつかの並べ替えを望んでいたし、私はそれを速く望んでいたので、私は私が仕事をすることができる知っていた一つの私はPineconeをc++transpilerに書き、GCCで出力ソースを自動的にコンパイルする機能を追加しました。 これは現在、ほぼすべてのPineconeプログラムで機能します(ただし、それを破るエッジケースはいくつかあります)。 これは、特に移植性やスケーラブルなソリューションではありませんが、それは当分の間動作します。Pineconeを開発し続けると仮定すると、遅かれ早かれLLVMコンパイルのサポートが得られます。 私はそれにどれだけ取り組んでいるのか疑問に思いますが、トランスパイラは決して完全に安定しておらず、LLVMの利点は数多くあります。 それは私がLLVMでいくつかのサンプルプロジェクトを作り、それのこつを得る時間があるときの問題です。

    それまでは、インタプリタは些細なプログラムに最適であり、C++transpilingはより多くのパフォーマンスを必要とするほとんどのものに適しています。

    結論

    私はあなたのためにプログラミング言語を少し神秘的にしたことを願っています。 あなたが自分で作りたいのであれば、私はそれを強くお勧めします。 把握するための実装の詳細はたくさんありますが、ここでの概要はあなたを軌道に乗せるのに十分でなければなりません。ここでは、始めるための私の高いレベルのアドバイスがあります(覚えておいて、私は本当に私がやっていることを知らないので、塩の粒でそれを取る):

    • 解釈された言語は、一般的に簡単に設計、構築、学習されます。 私はあなたがそれがあなたがしたいことだと知っていれば、コンパイルされたものを書くことからあなたを落胆させていませんが、あなたがフェンス
    • レクサーとパーサーに関しては、あなたが望むものは何でもしてください。 あなた自身の執筆のためのそして反対の有効な議論がある。 結局のところ、あなたのデザインを考えて、すべてを賢明な方法で実装するなら、それは本当に重要ではありません。
    • 私が終わったパイプラインから学びます。 私が今持っているパイプラインの設計には、多くの試行錯誤がありました。 私はAst、場所で行動の木に変わるAst、および他の恐ろしいアイデアを排除しようとしました。 このパイプラインは機能するので、本当に良いアイデアがない限り、変更しないでください。
    • 複雑な汎用言語を実装する時間や動機がない場合は、Brainfuckなどの難解な言語を実装してみてください。 これらの通訳者は、数百行ほどの短いものにすることができます。

    私はPineconeの開発に関してはほとんど後悔していません。 私は道に沿っていくつかの悪い選択をしましたが、私はそのような間違いの影響を受けるコードのほとんどを書き直しました。今のところ、Pineconeは十分に機能し、簡単に改善できる状態にあります。

    今のところ、Pineconeは十分に機能し、簡単に改善できる状態にあります。

    Pineconeを書くことは私のための非常に教育的で楽しい経験であり、それはちょうど始まったばかりです。

コメントを残す

メールアドレスが公開されることはありません。