LLPML (Low Level Programming Markup Language) 七誌
概要 ネイティブコンパイラ 出力は PE(Win32) 形式で Wine でも実行可 将来的には OS( カーネル ) の記述も視野 開発環境をソースと一緒に配布 バンドルされたコンパイラを実行するだけ 環境構築不要で気軽にコンパイル 独自の Andromeda 言語 LLPML という名前は XML を使用していた名残
ネイティブコンパイラ EXE/DLL(PE 形式バイナリ ) の解析が発端 .NET のアセンブリから CIL 抽出のため PE 解析 ネイティブ DLL を自前 PE ローダでリロケート PE リンカはすぐに完成 ローダを作ったときに PE の構造を把握 リンカは CompilerLib として単体でも提供 後はネイティブコードを生成するだけ 最適化を考慮しなければそれほど難しくない
環境構築不要 Visual Studio MS アレルギーで敬遠されがち Cygwin/MinGW コマンドラインを覚えないといけない gcc バンドル 気軽に真似出来る配布形態ではない LLPML コンパイラが 300KB のため手軽にバンドル
なぜ独自言語か? 作りやすい所からつまみ食い いきなり既存言語に準拠するのは面倒 C/C++/C# 実装までのつなぎ? 文法を規定せずに AST を直接処理してみたかった AST は言語仕様と不可分のため無理 実態は C/C++/C# モデルの劣化コピー AST の直接表記として当初は XML を使用
Andromeda 言語 外見は JavaScript 似 C 言語のコンセプトに付加的要素 オブジェクト指向 クロージャ(値束縛・非参照) 前方参照によりプロトタイプ宣言が不要 型推論のある静的型付言語 構造体 ( スタック ) とクラス ( ヒープ ) を区別 クラスには参照カウントが働く
main main 関数を書かないスクリプトライクな書き方が可能(どちらでも良い) function main { printfln("Hello, World!"); } printfln("Hello, World!");
型推論 型推論のある静的型付言語 //  代入 var a = new Test; // var a : Test = new Test;  と同等 //  ラムダ式 var add = \(x, y) => x + y; //  関数の戻り値 function sub(x, y) { return x – y; }
クロージャ 高階関数でクロージャを返せる int a = 2; function test(b) { return \x => (a + b) * x; } var cl = test(3); // a がグローバル変数のときだけ反映 a = 4; printfln("cl(5) = %d", cl(5));
XML 当初 AST の直接表記として XML を採用 独自言語の作成に抵抗があった スキーマ名が LLPML しかし XML だろうと独自言語は独自言語 冗長 ヴィジュアル言語のバックエンドを想定 自分自身の記述に到達できず破綻 XML 形式は放棄して独自言語に移行
LLPML/XML の例 冗長すぎて手に負えない <if>    <cond>      <equal><var name=&quot;s&quot; />0</equal>    </cond>    <block>      <let><var name=&quot;e10&quot; />0</let>    </block> </if> if (s == 0) e10 = 0;
前方参照 パースは一度でコード生成時に名前解決 パース時に参照解決しない(仮の動的型) コード生成時に参照解決する(静的型) コード生成時に型推論される(静的型) // パース時に構文から Test をクラスと判断 // コード生成時に t の型が決定される var t = new Test; // パース時に構文からメンバの呼び出しと判断 t.test(); // 定義しなければコード生成時にエラー class Test { function test(){} } 前方 後方
サンク サンクとは実行時に生成するラッパ 実行時コード生成 (JIT) の一種 引数を値束縛したサンクを delegate とする var add = \(x, y) => x + y; //  ラムダ式 var add_2 = \x => add(2, x); //  カリー化 //  引数を束縛したサンクを生成 var add_2d = delegate(2, add); //  変数はサンク生成時の値が束縛される int a = 2; var add_a = delegate(a, add);
メンバ関数ポインタ インスタンスメンバへの関数ポインタ 第 1 引数に this を束縛したサンク class Test { function test(){} } var f = Test.test; //  静的関数ポインタ printfln(&quot;f: %s&quot;, typeof(f).Name); var t = new Test; var d = t.test; // delegate(t, Test.test) printfln(&quot;d: %s&quot;, typeof(d).Name); //  実行結果 // f: function(var:Test) // d: delegate()
値束縛型クロージャ 変数の値をサンクに埋め込むため GC 不要 参照型クロージャと挙動が異なる function test() { int a = 2; return \ => printfln(&quot;%d&quot;, ++a); } var d = test(); //  サンクに値束縛されるため何度呼んでも同値 d(); d(); d(); //  実行結果 : 3 3 3
インスタンスのヘッダ RTTI や参照カウントなど実行時情報 C 言語との互換性を考慮 ヘッダはポインタの前、配列は NULL 終端 Win32API に string を LPCWSTR として渡せる 型情報 ヘッダ (32bit×4) ポインタ (NULL) データ 配列またはクラス 参照数 サイズ 個数 終端 Pascal 配列 配列のみ
今後 LLPML(C#) を Andromeda 自身で書き直す 当初の目標だったが中止 C# に揺り戻し GUI アプリが作りやすい Windows Mobile アプリが作りやすい unsafe を使えば VM なしでカーネル記述可 ? C# のネイティブコンパイラを開発予定 自分自身の書き直しに必要な工程が減る

LLPML

  • 1.
    LLPML (Low LevelProgramming Markup Language) 七誌
  • 2.
    概要 ネイティブコンパイラ 出力はPE(Win32) 形式で Wine でも実行可 将来的には OS( カーネル ) の記述も視野 開発環境をソースと一緒に配布 バンドルされたコンパイラを実行するだけ 環境構築不要で気軽にコンパイル 独自の Andromeda 言語 LLPML という名前は XML を使用していた名残
  • 3.
    ネイティブコンパイラ EXE/DLL(PE 形式バイナリ) の解析が発端 .NET のアセンブリから CIL 抽出のため PE 解析 ネイティブ DLL を自前 PE ローダでリロケート PE リンカはすぐに完成 ローダを作ったときに PE の構造を把握 リンカは CompilerLib として単体でも提供 後はネイティブコードを生成するだけ 最適化を考慮しなければそれほど難しくない
  • 4.
    環境構築不要 Visual StudioMS アレルギーで敬遠されがち Cygwin/MinGW コマンドラインを覚えないといけない gcc バンドル 気軽に真似出来る配布形態ではない LLPML コンパイラが 300KB のため手軽にバンドル
  • 5.
    なぜ独自言語か? 作りやすい所からつまみ食い いきなり既存言語に準拠するのは面倒C/C++/C# 実装までのつなぎ? 文法を規定せずに AST を直接処理してみたかった AST は言語仕様と不可分のため無理 実態は C/C++/C# モデルの劣化コピー AST の直接表記として当初は XML を使用
  • 6.
    Andromeda 言語 外見はJavaScript 似 C 言語のコンセプトに付加的要素 オブジェクト指向 クロージャ(値束縛・非参照) 前方参照によりプロトタイプ宣言が不要 型推論のある静的型付言語 構造体 ( スタック ) とクラス ( ヒープ ) を区別 クラスには参照カウントが働く
  • 7.
    main main 関数を書かないスクリプトライクな書き方が可能(どちらでも良い)function main { printfln(&quot;Hello, World!&quot;); } printfln(&quot;Hello, World!&quot;);
  • 8.
    型推論 型推論のある静的型付言語 // 代入 var a = new Test; // var a : Test = new Test; と同等 // ラムダ式 var add = \(x, y) => x + y; // 関数の戻り値 function sub(x, y) { return x – y; }
  • 9.
    クロージャ 高階関数でクロージャを返せる inta = 2; function test(b) { return \x => (a + b) * x; } var cl = test(3); // a がグローバル変数のときだけ反映 a = 4; printfln(&quot;cl(5) = %d&quot;, cl(5));
  • 10.
    XML 当初 ASTの直接表記として XML を採用 独自言語の作成に抵抗があった スキーマ名が LLPML しかし XML だろうと独自言語は独自言語 冗長 ヴィジュアル言語のバックエンドを想定 自分自身の記述に到達できず破綻 XML 形式は放棄して独自言語に移行
  • 11.
    LLPML/XML の例 冗長すぎて手に負えない<if>    <cond>      <equal><var name=&quot;s&quot; />0</equal>    </cond>    <block>      <let><var name=&quot;e10&quot; />0</let>    </block> </if> if (s == 0) e10 = 0;
  • 12.
    前方参照 パースは一度でコード生成時に名前解決 パース時に参照解決しない(仮の動的型)コード生成時に参照解決する(静的型) コード生成時に型推論される(静的型) // パース時に構文から Test をクラスと判断 // コード生成時に t の型が決定される var t = new Test; // パース時に構文からメンバの呼び出しと判断 t.test(); // 定義しなければコード生成時にエラー class Test { function test(){} } 前方 後方
  • 13.
    サンク サンクとは実行時に生成するラッパ 実行時コード生成(JIT) の一種 引数を値束縛したサンクを delegate とする var add = \(x, y) => x + y; // ラムダ式 var add_2 = \x => add(2, x); // カリー化 // 引数を束縛したサンクを生成 var add_2d = delegate(2, add); // 変数はサンク生成時の値が束縛される int a = 2; var add_a = delegate(a, add);
  • 14.
    メンバ関数ポインタ インスタンスメンバへの関数ポインタ 第1 引数に this を束縛したサンク class Test { function test(){} } var f = Test.test; // 静的関数ポインタ printfln(&quot;f: %s&quot;, typeof(f).Name); var t = new Test; var d = t.test; // delegate(t, Test.test) printfln(&quot;d: %s&quot;, typeof(d).Name); // 実行結果 // f: function(var:Test) // d: delegate()
  • 15.
    値束縛型クロージャ 変数の値をサンクに埋め込むため GC不要 参照型クロージャと挙動が異なる function test() { int a = 2; return \ => printfln(&quot;%d&quot;, ++a); } var d = test(); // サンクに値束縛されるため何度呼んでも同値 d(); d(); d(); // 実行結果 : 3 3 3
  • 16.
    インスタンスのヘッダ RTTI や参照カウントなど実行時情報C 言語との互換性を考慮 ヘッダはポインタの前、配列は NULL 終端 Win32API に string を LPCWSTR として渡せる 型情報 ヘッダ (32bit×4) ポインタ (NULL) データ 配列またはクラス 参照数 サイズ 個数 終端 Pascal 配列 配列のみ
  • 17.
    今後 LLPML(C#) をAndromeda 自身で書き直す 当初の目標だったが中止 C# に揺り戻し GUI アプリが作りやすい Windows Mobile アプリが作りやすい unsafe を使えば VM なしでカーネル記述可 ? C# のネイティブコンパイラを開発予定 自分自身の書き直しに必要な工程が減る