プログラミング言語の作成、パート2:プログラムの中間プレゼンテーション

画像



はじめに



私の次の記事を読むために立ち寄ったすべての人への挨拶。



繰り返しますが、以前の研究に基づいたプログラミング言語の作成について説明します。その結果については、この投稿で説明します。



最初の部分(リンク: habr.com/post/435202 )では、将来のアプリケーションを将来の言語で実行する言語VMの設計と作成の段階について説明しました。

この記事では、VMで直接実行するための抽象バイトコードにアセンブルされる中間プログラミング言語を作成する主な段階について説明する予定です。



プロジェクトのウェブサイトとそのリポジトリへのリンクをすぐに提供しても害はないと思います。



サイト

リポジトリ



すべてのコードはFPCで記述されていることをすぐに言わなければならず、その例を示します。



それで、私たちは悟りを始めます。



なぜ中間YaPに降伏したのですか?



プログラムを高水準言語から、限られた命令セットで構成される実行可能なバイトコードにすぐに変換することは非常に簡単であるため、プロジェクトに中間言語を追加することで、桁違いに単純化する方がよいことを理解する価値があります。 一連のオペコードを使用して、数式、構造、およびクラスをすぐに提示するよりも、コードを徐々に簡素化することをお勧めします。 ちなみに、これはほとんどのサードパーティの翻訳者とコンパイラが動作する方法です。



以前の記事で、言語VMを実装する方法について書きました。 次に、アセンブラーに似た言語と、さらにトランスレーターを記述するための機能を実装する必要があります。 これらの段階で、将来のプロジェクトの基礎を築きます。 基礎が優れているほど、建物が急勾配であることを理解する価値があります。



この奇跡を実現するための第一歩を踏み出す



まず、目標を設定する価値があります。 実際に何を書きますか? 最終コードにはどのような特性があり、どのような特性がありますか?



プロジェクトのこの部分を構成する主な機能部分のリストを作成できます。





上の図は、プリミティブトランスレーターによってVMのコードに変換される中間言語のコードのフラグメントを示しています。これについては、後で説明します。



それで、目標が設定されました。実装に進みましょう。



簡単なアセンブラーを書く



アセンブラとは何ですか?



実際、これはテキスト記述ではなくオペコードの置換を実行するプログラムです。



次のコードを検討してください。



push 0 push 1 add peek 2 pop
      
      





アセンブラコードを処理した後、VMの実行可能コードを取得します。



命令は単音節と二音節であることがわかります。 スタックされたVM用のこれ以上の複雑な指示はありません。



文字列からトークンを抽出できるコードが必要です(文字列の中に文字列がある可能性があることを考慮します)。



私たちはそれを書きます:



 function Tk(s: string; w: word): string; begin Result := ''; while (length(s) > 0) and (w > 0) do begin if s[1] = '"' then begin Delete(s, 1, 1); Result := copy(s, 1, pos('"', s) - 1); Delete(s, 1, pos('"', s)); s := trim(s); end else if Pos(' ', s) > 0 then begin Result := copy(s, 1, pos(' ', s) - 1); Delete(s, 1, pos(' ', s)); s := trim(s); end else begin Result := s; s := ''; end; Dec(w); end; end;
      
      





さて、各ステートメントにswitch-case構造のようなものを実装する必要があり、簡単なアセンブラの準備ができました。



変数



VMには、変数をサポートするためのポインターの配列があり、それに応じて静的なアドレス指定があることを思い出してください。 つまり、変数を操作する機能はTStringListとして表すことができます。TStringListでは、文字列は変数の名前であり、インデックスは静的アドレスです。 このリスト内の変数名の重複は受け入れられないことを理解する必要があります。 必要なコードを想像したり、自分で書いたりできると思います。



完成した実装を確認したい場合は、/ lang / u_variables.pasを歓迎します。



定数



ここでの原則は変数の場合と同じですが、1つだけあります。 最適化するには、定数の名前ではなく、値にバインドすることをお勧めします。 つまり 各定数値にはTStringListを含めることができます。TStringListは、この値を持つ定数の名前を格納するのに役立ちます。

定数の場合は、データ型を指定する必要があります。したがって、言語に追加するには、小さなパーサーを作成する必要があります。



実装:/lang/u_consts.pas



メソッドエントリポイント



コードブロッキングの実装、さまざまなデザインのサポートなど。 この機能のサポートは、アセンブラレベルで実装する必要があります。



コード例を考えてみましょう:



 Summ: peek 0 pop peek 1 pop push 0 new peek 2 mov push 2 push 0 add jr
      
      





上記はSummメソッドの翻訳例です:



 func Summ(a, b): return a + b end
      
      





エントリポイントにはオペコードがないことを理解してください。 Summメソッドのエントリポイントとは何ですか? この素数は、次のオペコードエントリポイントのオフセットです。 (オペコードのオフセットは、実行可能な抽象バイトコードの先頭に対するオペコードの番号です)。 ここで、タスクに直面しています。コンパイル段階でこのオフセットを計算し、オプションとして、この数値としてSumm定数を宣言する必要があります。



これを行うには、各オペレーターに特定の重量カウンターを作成します。 「pop」などの単純な単音節演算子があります。 それらは1バイトを占有します。 より複雑なもの、たとえば「プッシュ123」があります-それらは、オペコード用に1バイト、符号なしint型用に4バイトを占有します。



エントリポイントアセンブラのサポートを追加するコードの本質:



  1. カウンターがあります。i= 0としましょう。
  2. 「プッシュ123」型の構造がある場合はコードを実行し、単純なオペコードが1の場合は5を追加します。エントリポイントがある場合はコードから削除し、カウンター値とエントリポイントの名前で対応する定数を宣言します。


その他の機能



たとえば、これは処理前の単純なコード変換です。



まとめ



小さなアセンブラを実装しました。 それに基づいてより複雑なトランスレータを実装するために必要になります。 これで、VM用の小さなプログラムを作成できます。 したがって、他の記事では、より複雑な翻訳者を作成するプロセスについて説明します。



最後まで読んでくれてありがとう。



何かはっきりしないことがあれば、私はあなたのコメントを待っています。



All Articles