動機
- JSで素のWebGLをいじってゲームを作ろうとしていた。
- GLSLで静的型付け言語の良さを再認識する。
- JSからWebGLをいじってると、型変換のオーバーヘッドがちょっと気になってくる。
- JSでのベクトルと行列の演算処理を書くのは面倒くさい。しかしGLSLのベクトルと行列の演算機能はよくできてて、JSもこんな感じでベクトル演算を書ければ楽なのになあと思う。
- WebAssemblyができてメジャーブラウザで使えるようになる。これはまあLLVMのWeb版とも言える仕様。つまり言語が作れる!
- 調べるともうすでにemscriptenがサポート、AssemblyScriptができていた!
- これ使えばいいかなと思うが、emscriptenはサポートコードがでかい。さらにAssemblyScriptも試したが、ちょっと私のニーズには合わなさそう。
- でまあGLSLチックなベクトル・行列演算機能を持つC風の言語を作ることにした。
何で作っているか
- node.jsとpratt parserのコードをベースに実装している。
- wasm出力はbinaryen.js(emscriptenでwasmにコンパイルして)を使用している。
- とりあえずなにがしかのコンパイルはWebページ上でできるようになっている。
- 検証用ウェブページ
リポジトリ
仕様の概要(現時点で考えてること)
- 静的型付け
- 自由文脈形式
- C風かつwasmを強く意識した構文
- そのままwasmにコンパイルされ、余計なサポートコードを極力吐かない
- ユーザー定義型(カスタム型)が定義・使用可能
- classなどは持たないかもしれない
- ガベージコレクションは行わない
- wasmの線形メモリに破壊的にアクセスできる
- ヒープ/フリーストアのようなメモリ管理機構は持たない。ただしライブラリで書けるレベルの構文は用意する。
- ベクトル・行列演算機能をGLSLのようにビルトインで持つ
- コルーチンを言語機能として持つ
仕様詳細(まとめ中)
進捗状況
- 2018年3月ころから実装し始めたが、まだまだ道半ばといった感じ。。
実装状況のレポーティング
- まだまだ実装中
- 実装状況はTwitterでつぶやき、それをTogetterでまとめている
コンパイルのサンプル
- まだまだこの程度しかコンパイルできません。。
その1
ソース
// 関数 日本語使用可能
i32 𩸽(i32 a,i32 b){
return a * b;
};
// メイン
export i32 main(){
i32 b = 0;
for(i32 c = 0;c < 4;++c) {
b = b + 1;
}
return 𩸽(b,b);// 4
};
動くサンプル
コンパイル結果
(module
(type $𩸽 (func (param i32 i32) (result i32)))
(type $main (func (result i32)))
(memory $0 1 1)
(export "main" (func $main))
(func $𩸽 (; 0 ;) (type $𩸽) (param $0 i32) (param $1 i32) (result i32)
(return
(i32.mul
(get_local $0)
(get_local $1)
)
)
)
(func $main (; 1 ;) (type $main) (result i32)
(local $0 i32)
(local $1 i32)
(set_local $0
(i32.const 0)
)
(block
(block $for0
(set_local $1
(i32.const 0)
)
(loop $loop1
(br_if $for0
(i32.eqz
(i32.lt_s
(get_local $1)
(i32.const 4)
)
)
)
(block
(set_local $0
(i32.add
(get_local $0)
(i32.const 1)
)
)
)
(set_local $1
(i32.add
(get_local $1)
(i32.const 1)
)
)
(br $loop1)
)
)
)
(return
(call $𩸽
(get_local $0)
(get_local $0)
)
)
)
)
その2:ユーザー定義型(カスタム型)
ソース
type Bar {
public:
i32 barA = 3;
i32 barB = 4;
};
type Foo {
public:
i32 a = 1;
i32 b = 2;
Bar c;
};
export i32 main(){
Foo foo,foo1;
foo.a = 2;
foo1 = foo;
foo.a = 10;
return foo.a * foo1.a;
};
動くサンプル(コンパイル結果)
その3:ポインタ
ソースコード
export i32 main(){
i32 p = 0;// メモリオフセット0をポインタにセット
*p = 32.0f;// floatの値を保存
u32 a = *p;// float値をu32値として取り出し
return a;
};
動くサンプル(コンパイル結果)
その4:型エイリアス
ソースコード
export i32 main(){
// type エイリアス
type& T = i32;
type& T1 = T;
T a = 1;
T1& b = a;
++a;
b += 2;
return a;// 4
};
動くサンプル(コンパイル結果)
その5:const変数
ソースコード
const WIDTH = 320;
const HEIGHT = 240;
export i32 main(){
i32 a = WIDTH * HEIGHT;
return a;// 76800
};
動くサンプル(コンパイル結果)
その6:reinterpret cast
ソースコード
export f64 main(){
i64 a = 0x3fc4 0000 0000 0000xl;// f64を整数値として代入
f64 b = (^f64)a;// reinterpret cast
return b;// 0.15625
};
動くサンプル(コンパイル結果)
その7:キャスト
ソースコード
// キャストの実装(ネイティブ型同士のみ)
export i32 main(){
i32 b = 10;
f64 c = (f64)b + 10.0lf;
return (i32)c;
};
動くサンプル(コンパイル結果)
その8:数値リテラル
ソースコード
export i32 main(){
i32 a = 0x1 00x;// 16進リテラル(スペースで値が区切れる)
,b = 0b1 0000 0000 b;// 2進数リテラル
if( a == b){
return 1;
}
return 0;
};