新しいプログラミング言語 Mash

数年間、私は独自のプログラミング言語の開発に挑戦しました。 私の意見では、可能な限り最もシンプルで、完全に機能し、便利な言語を作成したかったのです。

この記事では、私の作業の主な段階に焦点を当て、まず、作成された言語の概念と、現在取り組んでいるその最初の実装について説明したいと思います。

あらかじめ言っておきますが、私はプロジェクト全体を Free Pascal で書きました。 その上のプログラムは膨大な数のプラットフォーム用にアセンブルでき、コンパイラー自体は非常に最適化されたバイナリを生成します (プロジェクトのすべてのコンポーネントを O2 フラグで収集します)。

言語ランタイム

まず第一に、将来のアプリケーションを私の言語で実行するために作成する必要があった仮想マシンについて話す価値があります。 おそらく、それが最も簡単な方法だったため、スタック アーキテクチャを実装することにしました。 これを行う方法に関する通常の記事がロシア語で見つからなかったので、英語の教材に慣れた後、自分の自転車の設計と作成に着手しました。 次に、この問題に関する私の「先進的な」アイデアと展開を紹介します。

スタックの実装

明らかに、VM の最上位にはスタックがあります。 私の実装ではブロック単位で動作します。 本質的に、これはポインタとスタックの最上位のインデックスを格納する変数の単純な配列です。
初期化されると、256 要素の配列が作成されます。 より多くのポインターがスタックにプッシュされると、そのサイズは次の 256 要素分増加します。 したがって、スタックから要素を削除するときに、そのサイズが調整されます。

VM はいくつかのスタックを使用します。

  1. メインスタック。
  2. リターンポイントを格納するためのスタック。
  3. ガベージ コレクター スタック。
  4. Try/catch/finally ブロック ハンドラー スタック。

定数と変数

これはシンプルです。 定数は別個の小さなコードで処理され、静的アドレスを介して将来のアプリケーションで使用できます。 変数は特定のサイズのポインターの配列であり、そのセルへのアクセスはインデックスによって実行されます。 静的アドレス。 変数はスタックの一番上にプッシュすることも、そこから読み取ることもできます。 実は、なぜなら、 私たちの変数は基本的に VM メモリ内の値へのポインタを格納しますが、言語は暗黙的なポインタを扱うことによって支配されます。

ガベージコレクター

私の VM では半自動です。 それらの。 ガベージ コレクターをいつ呼び出すかは開発者自身が決定します。 Python、Perl、Ruby、Lua などの通常のポインター カウンターを使用した場合は機能しません。 これはマーカー システムを通じて実装されます。 それらの。 変数に一時的な値を割り当てる場合、この値へのポインタがガベージ コレクタのスタックに追加されます。 今後、コレクターは、すでに準備されているポインターのリストを迅速に実行します。

try/catch/finally ブロックの処理

他の現代言語と同様に、例外処理は重要なコンポーネントです。 VM コアは try..catch ブロックでラップされており、例外に関する情報をスタックにプッシュすることで例外をキャッチした後、コードの実行に戻ることができます。 アプリケーション コードでは、コードの try/catch/finally ブロックを定義し、catch (例外ハンドラー) とfinally/end (ブロックの終わり) でエントリ ポイントを指定できます。

マルチスレッド化

VM レベルでサポートされます。 シンプルで使いやすいです。 割り込みシステムなしで動作するため、コードは複数のスレッドでそれぞれ数倍高速に実行される必要があります。

VM用の外部ライブラリ

これなしではどうしようもありません。 VM は、他の言語での実装方法と同様に、インポートをサポートしています。 コードの一部を Mash で記述し、コードの一部をネイティブ言語で記述して、それらを XNUMX つにリンクできます。

高レベルのマッシュ言語から VM のバイトコードへのトランスレータ

中級言語

複雑な言語から VM コードへのトランスレーターをすばやく作成するために、最初に中間言語を開発しました。 結果は、ここで特に考える意味もない、アセンブラ的なひどい光景でした。 このレベルでは、トランスレータはほとんどの定数と変数を処理し、それらの静的アドレスとエントリ ポイントのアドレスを計算することだけを述べておきます。

トランスレータのアーキテクチャ

実装に最適なアーキテクチャを選択しませんでした。 トランスレータは、他のトランスレータとは異なり、コード ツリーを構築しません。 彼は構造の始まりに目を向けます。 それらの。 解析されるコード部分が「while <condition>:」のように見える場合、これが while ループ構造であり、while ループ構造として処理する必要があることは明らかです。 複雑なスイッチケースのようなもの。

このアーキテクチャ上のソリューションのおかげで、トランスレータはそれほど高速ではないことが判明しました。 ただし、その変更は大幅に容易になりました。 コーヒーが冷めるよりも早く、必要な構造を追加しました。 完全な OOP サポートは XNUMX 週間以内に実装されました。

コードの最適化

もちろん、ここではもっと良く実装できたかもしれません (実装される予定ですが、後で気がつき次第実装されます)。 これまでのところ、オプティマイザは、未使用のコード、定数、およびアセンブリからのインポートを削除する方法のみを知っています。 また、同じ値を持つ複数の定数は XNUMX つに置き換えられます。 それだけです。

言語マッシュ

言語の基本概念

主なアイデアは、可能な限り最も機能的でシンプルな言語を開発することでした。 開発はその課題に力強く取り組んでいると思います。

コードブロック、プロシージャ、関数

言語内のすべての構造はコロンで始まります。 : オペレーターによって閉じられます end.

プロシージャと関数は、それぞれ proc と func として宣言されます。 引数は括弧内にリストされています。 すべては他のほとんどの言語と同じです。

オペレーター return 関数や演算子から値を返すことができます 破る プロシージャ/関数を終了できます (ループの外側にある場合)。

サンプルコード:

...

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

proc main():
  println(summ(inputln(), inputln()))
end

サポートされているデザイン

  • ループ: for..end、while..end、until..end
  • 条件: if..[else..]end、switch..[case..end..][else..]end
  • メソッド: proc <name>():... end、func <name>():... end
  • ラベルとジャンプ: <名前>:、ジャンプ <名前>
  • Enum 列挙型と定数配列。

変数

変換者はそれらを自動的に決定することも、開発者がそれらを定義する前に var を記述するかどうかも決定できます。

コード例:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

グローバル変数とローカル変数がサポートされています。

OOP

さて、最もおいしい話題に来ました。 Mash は、すべてのオブジェクト指向プログラミング パラダイムをサポートします。 それらの。 クラス、継承、ポリモーフィズム (動的を含む)、動的自動リフレクションおよびイントロスペクション (完全)。

早速ですが、コード例を示すだけにしておきます。

単純なクラスとその操作:

uses <bf>
uses <crt>

class MyClass:
  var a, b
  proc Create, Free
  func Summ
end

proc MyClass::Create(a, b):
  $a = new(a)
  $b = new(b)
end

proc MyClass::Free():
  Free($a, $b)
  $rem()
end

func MyClass::Summ():
  return $a + $b
end

proc main():
  x ?= new MyClass(10, 20)
  println(x->Summ())
  x->Free()
end

出力は 30 です。

継承とポリモーフィズム:

uses <bf>
uses <crt>

class MyClass:
  var a, b
  proc Create, Free
  func Summ
end

proc MyClass::Create(a, b):
  $a = new(a)
  $b = new(b)
end

proc MyClass::Free():
  Free($a, $b)
  $rem()
end

func MyClass::Summ():
  return $a + $b
end

class MyNewClass(MyClass):
  func Summ
end

func MyNewClass::Summ():
  return ($a + $b) * 2
end

proc main():
  x ?= new MyNewClass(10, 20)
  println(x->Summ())
  x->Free()
end

出力は 60 です。

動的ポリモーフィズムについてはどうですか? はい、これは反省です!:

uses <bf>
uses <crt>

class MyClass:
  var a, b
  proc Create, Free
  func Summ
end

proc MyClass::Create(a, b):
  $a = new(a)
  $b = new(b)
end

proc MyClass::Free():
  Free($a, $b)
  $rem()
end

func MyClass::Summ():
  return $a + $b
end

class MyNewClass(MyClass):
  func Summ
end

func MyNewClass::Summ():
  return ($a + $b) * 2
end

proc main():
  x ?= new MyClass(10, 20)
  x->Summ ?= MyNewClass::Summ
  println(x->Summ())
  x->Free()
end

出力は 60 です。

ここで、単純な値とクラスを内省してみましょう。

uses <bf>
uses <crt>

class MyClass:
  var a, b
end

proc main():
  x ?= new MyClass
  println(BoolToStr(x->type == MyClass))
  x->rem()
  println(BoolToStr(typeof(3.14) == typeReal))
end

出力: true、true。

代入演算子と明示的ポインタについて

?= 演算子は、変数にメモリ内の値へのポインタを割り当てるために使用されます。
= 演算子は、変数からのポインターを使用してメモリ内の値を変更します。
ここで、明示的なポインタについて少し説明します。 それらを言語に追加して存在するようにしました。
@<variable> — 変数への明示的なポインタを取得します。
?<variable> — ポインタによって変数を取得します。
@= — 変数への明示的なポインタによって値を変数に割り当てます。

サンプルコード:

uses <bf>
uses <crt>

proc main():
  var a = 10, b
  b ?= @a
  PrintLn(b)
  b ?= ?b
  PrintLn(b)
  b++
  PrintLn(a)
  InputLn()
end

出力: 何らかの数値、10、11。

試してみてください...[捕まえて...][ついに...]終了

サンプルコード:

uses <bf>
uses <crt>

proc main():
  println("Start")
  try:
    println("Trying to do something...")
    a ?= 10 / 0
  catch:
    println(getError())
  finally:
    println("Finally")
  end
  println("End")
  inputln()
end

将来の計画

私はずっと GraalVM と Truffle を探し続けています。 私の実行環境には JIT コンパイラがないため、パフォーマンスの点では、現時点では Python としか競合できません。 GraalVM や LLVM をベースにした JIT コンパイルを実装できればと思っています。

リポジトリ

開発内容を試したり、自分でプロジェクトをフォローしたりすることができます。

Сайт
GitHub 上のリポジトリ

もしよろしければ最後まで読んでいただきありがとうございます。

出所: habr.com

コメントを追加します