Cプログラミング言語の無限にポータブルな代替品。

「無料」を覚えている人にとって、オークは本質的にそのプロジェクトのより堅牢で高レベルのバージョンです。オークの目標は、フロントエンドではできるだけ高レベルであることですが、バックエンドではできるだけ小さくて低レベルであることです。
私は仕事を探している大学の新鮮な高校の卒業生であり、新入生です。あなたが私のプロジェクトを楽しんでいるなら、私にコーヒーを買って私をサポートすることを検討してください!
オークの非常識な携帯性の鍵は、信じられないほどコンパクトなバックエンドの実装です。オークのバックエンドのコードは、100行未満のCで表現できます。このような小さな実装は、中間表現の小さな命令セットのためにのみ可能です。 OakのIRは、 17の異なる指示で構成されています。それはBrainfuckと同等です!
オークのバックエンドは非常に単純に機能します。すべての命令はメモリテープで動作します。このテープは、基本的に二重精度のフロートの静的な配列です。
let x : num = 5.25 ; ... let p : & num = & x ; `beginning of heap`
| | |
v v v
[ 0 , 0 , 0 , 5.25 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , ... ]
^
|
`current location of the stack pointer`変数が関数で定義されている場合、仮想マシンの現在のベースポインターに対して静的位置が与えられます。したがって、関数が呼び出されると、関数の変数のためのスペースがスタックに割り当てられ、ベースポインターがこの新しいスペースを使用するように増加します。次に、コンパイラは変数を、コードの残りの部分でベースポインターオフセットに追加したアドレスに置き換えます!
さらに、メモリテープはスタックとヒープとして機能します。すべてのプログラムの変数のスペースが割り当てられた後、スタックに使用されるメモリが開始されます。スタックはプログラム全体でデータとともに成長して縮小します。たとえば、2つの数値が合計された場合、それらはスタックからポップアウトされ、結果に置き換えられます。同様に、ヒープはプログラム全体で成長して縮小します。ただし、ヒープは動的に割り当てられたデータに使用されます。コンパイル時に不明なメモリフットプリントを使用した情報。
オークのバックエンドが根本的にどのように動作するかを理解したので、ここに完全な命令セットがあります!
| 命令 | 副作用 |
|---|---|
push(n: f64); | 数字をスタックに押し込みます。 |
add(); | スタックから2つの数字をポップし、合計を押します。 |
subtract(); | スタックから2つの数字をポップします。 2番目から最初のものを減算し、結果をプッシュします。 |
multiply(); | スタックから2つの数字をポップし、製品をプッシュします。 |
divide(); | スタックから2つの数字をポップします。 2番目を最初のもので除算し、結果をプッシュします。 |
sign(); | スタックの数字をポップします。ゼロが大きい場合または等しい場合は、 1押します。そうでなければ-1を押します。 |
allocate(); | スタックの数字をポップし、ヒープ上のその数の遊離セルへのポインターを返します。 |
free(); | スタックの数字をポップし、この数字がメモリを指す場所に移動します。スタックから別の数字をポップし、この場所にある多くのセルをメモリ内で解放します。 |
store(size: i32); | スタックの数字をポップし、この数字がメモリを指す場所に移動します。次に、スタックからポップsize数値を開きます。これらの数値をこの場所に逆順に保存します。 |
load(size: i32); | スタックの数字をポップし、この数字がメモリを指す場所に移動します。次に、連続したメモリセルのsize数をスタックに押し込みます。 |
call(fn: i32); | IT's Compilerが割り当てられたIDによってユーザー定義関数を呼び出します。 |
call_foreign_fn(name: String); | ソースの名前で外国の機能を呼び出します。 |
begin_while(); | しばらくループを開始します。反復ごとに、スタックの数字をポップします。数値がゼロでない場合は、ループを続行します。 |
end_while(); | しばらくのループの終わりをマークします。 |
load_base_ptr(); | 確立されたスタックフレームのベースポインターをロードします。これは、常にスタックポインター以下に等しくなります。変数は、各関数のベースポインターに対して保存されます。したがって、 x: numおよびy: num 、 xを定義する関数は、 base_ptr + 1に保存され、 y base_ptr + 2に保存される場合があります。これにより、関数は、静的メモリの位置を使用するのではなく、動的に、必要に応じてメモリに変数を保存できます。 |
establish_stack_frame(arg_size: i32, local_scope_size: i32); | スタックからarg_size数のセルをポップして、それらを保管します。次に、 load_base_ptrを呼び出して、この機能が終了したら親スタックフレームを再開します。 local_scope_size数のゼロをスタックに押して、関数の変数のためのスペースを作ります。最後に、当初注文されたように、保存された引数セルをスタックに戻します。 |
end_stack_frame(return_size: i32, local_scope_size: i32); | スタックからreturn_sizeのセルの数をポップして、それらを保管します。次に、スタックフレームのメモリを破棄するために、スタックからオフのセルの数をPop local_scope_size 。スタックの値をポップし、ベースポインターに保存して、親スタックフレームを再開します。最後に、当初注文されたように、保存されたリターンバリューセルをスタックに戻します。 |
これらの指示のみを使用して、OakはCが提供できるよりもさらに高いレベルの抽象化を実装できます!!!それはあまり聞こえないかもしれませんが、この小さな言語にとっては非常に強力です。
オークの構文は、Rustプログラミング言語に大きく触発されています。
関数はfnキーワードで宣言され、 returnセマンティクスを除き、錆関数と構文的に同一です。さらに、ユーザー定義の種類と定数は、それぞれtypeとconstキーワードで宣言されます。
Rustの外側の属性と同様に、Oakは多くのコンパイルタイムフラグを紹介します。これらのいくつかは、他のオーク機能とともに以下に示されています。

では、オークコンパイラはどの程度正確に機能しますか?
構造をその関数に平らにします
putnumln(*bday.day) putnumln(*Date::day(&bday))になります。これは非常に簡単なプロセスです。すべての操作の種類のサイズを計算します
// `3` is the size of the structure on the stack
fn Date :: new ( month : 1 , day : 1 , year : 1 ) -> 3 {
month ; day ; year
}
// self is a pointer to an item of size `3`
fn Date :: day ( self : & 3 ) -> & 1 { self + 1 }
fn main ( ) -> 0 {
let bday : 3 = Date :: new ( 5 , 14 , 2002 ) ;
}プログラムのメモリフットプリントを静的に計算します
オークの表現とステートメントを同等のIR命令に変換します
メソッド呼び出しが有効な場合、さまざまな状況があります。方法は、常に議論として構造へのポインターを取ります。ただし、メソッドを呼び出すオブジェクトは、ポインターである必要はありません。たとえば、次のコードが有効です。BDAY let bday: Date = Date::new(); bday.print(); 。可変bdayはポインターではありませんが、メソッド.print()を使用できます。これがその理由です。
コンパイラがフラット化されたメソッドコールを見るとき、「インスタンス式」をポインターに変換する方法を見つける必要があります。変数の場合、これは簡単です:参照を追加するだけです!たとえば、すでにポインターである表現は、さらに簡単です。何もしないでください!ただし、他の種類の表現については、もう少し冗長です。コンパイラは、式を保存するために隠された変数にスニークし、インスタンス式として変数を使用してメソッド呼び出しを再度コンパイルします。かなりクールですよね?
ターゲットのIR命令を組み立てます
Targetという名前の特性があります。 Target特性を使用して、IRの各言語の指示を実行する場合、Oakは新しいプログラミングまたはアセンブリ言語まで自動的にコンパイルできます。はい、それは見た目と同じくらい簡単です! ユーザーがインターネットにアクセスできないライブラリやファイルのドキュメントを読むことができるようにするために、Oakはdoc Subcommandを提供します。これにより、著者はコードにドキュメント属性を追加して、他のユーザーがソースをふるいにかけてコメントを読むことなく、コードまたはAPIを理解できるようにすることができます。
これがコードの例です。
# [ std ]
# [ header ( "This file tests Oak's doc subcommand." ) ]
# [ doc ( "This constant is a constant." ) ]
const CONSTANT = 3 ;
// No doc attribute
const TEST = CONSTANT + 5 ;
# [ doc ( "This structure represents a given date in time.
A Date object has three members:
|Member|Value|
|-|-|
|`month: num` | The month component of the date |
|`day: num` | The day component of the date |
|`year: num` | The year component of the date |" ) ]
struct Date {
let month : num , day : num , year : num ;
# [ doc ( "The constructor used to create a date." ) ]
fn new ( month : num , day : num , year : num ) -> Date {
return [ month , day , year ] ;
}
# [ doc ( "Print the date object to STDOUT" ) ]
fn print ( self : & Date ) {
putnum ( self ->month ) ; putchar ( '/' ) ;
putnum ( self ->day ) ; putchar ( '/' ) ;
putnumln ( self ->year ) ;
}
}
# [ doc ( "This function takes a number `n` and returns `n * n`, or `n` squared." ) ]
fn square ( n : num ) -> num {
return n * n
}
fn main ( ) {
let d = Date :: new ( 5 , 14 , 2002 ) ;
d . print ( ) ;
}また、フォーマットされたドキュメントを端末に印刷するためのdoc Subcommandの使用例を示します。

現在の開発ビルドを取得するには、リポジトリをクローンしてインストールします。
git clone https://github.com/adam-mcdaniel/oakc
cd oakc
cargo install -f --path . 現在のリリースビルドを取得するには、crates.ioからインストールしてください。
# Also works for updating oakc
cargo install -f oakc次に、OAKファイルをOAKCバイナリでコンパイルできます。
oak c examples/hello_world.ok -c
main.exeCバックエンド-C99をサポートするGCCコンパイラ
Go BackEnd -Golang 1.14コンパイラ
TypeScript BackEnd -TypeScript 3.9コンパイラ