2018年2月26日月曜日

interfaceを動的に継承する方法

どうも、こんにちは、はざまです。今回は需要があるかどうかわかりませんが久しぶりに単独の技術ネタを書こうかなと思います。
先日の記事で私が自作言語を作っていることは周知のことかと思いますが、そのコードを書いている際にある問題に出くわしました。Interfaceの実装問題です。C#にはTypeBuilderというクラスがあり、これを使えば、型(interfaceもclassもstructも)を定義できるはずなのですが、なぜかinterfaceを実装するclassを定義しようとしてもうまくいかない。MSDNのTypeBuilder.AddInterfaceImplemetationメソッドの例の通りに書いているのにうまくいかない、なんでだろうと1週間以上試行錯誤を経てたどり着いた結果が、生成したクラスがobjectを継承していないことでした。つまり、C#でinterfaceを継承した型を動的に生成する最小のコードは、以下のような感じになるでしょう。
using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Test
{
    class Main
    {
        public static void Main(string[] args)
        {
            var name = new AssemblyName("test");
            var asm_builder = Thread.GetDomain().DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave, "./");
            var file_name = "test.exe";
            var mod_builder = asm_builder.DefineDynamicModule(file_name);
            var interface_builder = mod_builder.DefineType("IInterface", TypeAttributes.Abstract | TypeAttributes.Interface);
            interface_builder.DefineMethod("DoSomeBehavior", MethodAttributes.Public | MethodAttributes.Abstract | MethodAttributes.Virtual, typeof(int), null);
            var interface_type = interface_builder.CreateType();

            var type_builder = mod_builder.DefineType("TestClass", TypeAttributes.NotPublic | TypeAttributes.Class, typeof(object), new []{interface_type});
            var ctor = type_builder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                CallingConventions.Standard, Enumerable.Empty<Type>().ToArray());
            var il_generator = ctor.GetILGenerator();
            il_generator.Emit(OpCodes.Ret);

            type_builder.AddInterfaceImplementation(interface_type);
            var method_builder = type_builder.DefineMethod("DoSomeBehavior", MethodAttributes.Public | MethodAttributes.Virtual, typeof(int), Enumerable.Empty<Type>().ToArray());
            var il_generator2 = method_builder.GetILGenerator();
            il_generator2.Emit(OpCodes.Ldc_I4_0);
            il_generator2.Emit(OpCodes.Ret);

            var class_type = type_builder.CreateType();
            var instance = Activator.CreateInstance(class_type);

            asm_builder.Save(file_name);
            var method = class_type.GetMethod("DoSomeBehavior");
            var return_value = method.Invoke(instance, null);
            Console.WriteLine(return_value);
        }
    }
}

注意点としては、classにobjectを継承させる以外にも、interfaceはabstractにすること、interfaceのメソッドは、abstractかつvirtual、publicで宣言すること、classのメソッドは、publicかつvirtualで宣言することが挙げられます。今回は、フィールドを宣言したり、アクセスしたりしないので簡潔になっていますが、フィールドにメソッド内でアクセスしようとすると途端に複雑になり、TypeBuilder単独では達成できなくなったりしますが、それはまた別の話です。そこに関しては、他の方が記事にしているので、当方では記事にするつもりはありません。

2018年2月17日土曜日

オレオレ言語、Expressoについて・・・骨格解説編

こんにちは、はざまです。今回も前回に引き続き、自作言語Expressoの解説をしていこうと思います。

以下、まずはいくつか特筆すべきと思われるExpressoの機能を紹介します。
まず、Expressoでは、組み込み型でvectorやdictionaryがサポートされています。これらを生成するリテラルが用意されていますし、多少コンパイルでも特別扱い(実体は、それぞれSystem.Collections.Generic.ListとSystem.Collections.Generic.Dictionaryですが)されます。ただ、今の所は、実装上の問題により、パターンマッチの対象にはなっていないのですが……
他に組み込み型関連では、intseqという型があります。intseqとは"integer sequence"の略で、これはPythonで言うところのxrange型やRustのRange型と同様、整数列を生成するジェネレータです。残念ながら、int(32ビットの整数)の範囲の整数しか扱えないのですが、いわゆるCのような旧式のfor文が存在しないExpressoにおいて、カウントアップなどを行う際に多用される型です。vectorやarrayなどに作用して、整数列にマッチする要素だけを取り出すiterator(.NET用語だと、enumerator)も生成できます(sliceと呼ばれる)。
Expressoには、Rustにも存在するmatch文があります。これは、最近はやりのパターンマッチを行う文法要素で、各種オブジェクトの分解や、リテラル値とのマッチングを行います。tupleパターンとのマッチ程度しか想定していませんが、一応変数宣言(let文var文)でも、パターンが使用できるようになりました。
一つ書き忘れてましたが、クロージャは、関数やメソッドに直接渡すなど、すぐにその引数の型を推論できる状態であれば、型を省略することができます。将来的にEnumerable拡張のメソッドを呼べるようになった際にメソッドチェーンを書きやすくするための実装ですが、拡張メソッドを導入するかどうか悩んでいます。型を定義する度に、全型を走査しなければならなくなりそうで、ちょっと微妙なんですよね・・・

いくつか特筆に値する機能を見たところで、皆様も気になっているかもしれないExpressoの原理について解説しましょう。まず、現状、コンパイラは純C#製です。レキサ、パーサー、アナライザ、コード生成、全部C#で完結しています。吐かれるバイナリは、C#のコンパイル後の表現であるIL形式ですし、パーサージェネレータもC#のものを使用しています。この部分もC#を選んだ理由の一つに挙げられるでしょう(式木と呼ばれるデータ構造を生成するだけで実行可能なコードが生成できる)。いずれ、セルフホスティングしてコンパイラ自体をExpressoで実装したいところなのですが、パーサーとアナライザの切り離しをどうするか、パーサーはC#のものを使用するとして、現状、パーサーとアナライザは三位一体なので、そうするとあとはコード生成部分程度しかExpressoで書ける部分がなくなり、結局今のままと大して変わらないのではないかなどの問題があり、まだ実現していません。また、Expresso化するにあたって、組み込みのオブジェクト(intseq,slice)の実装をどうするかという問題もあります。intseq型は、ExpressoIntegerSequenceという型をC#で定義しているのですが、C#の機能を利用してEnumeratorを生成しやすくしているので、Expressoに置き換えるなら、それを自分で実装しなくてはならなくなります(コンパイラが自動で行う変換なので、それを知っていれば大した問題ではないかもしれません。yield式を使用するので、状態を保持するステートマシンみたいなものを自分で書かなければならない)。
とまあ、問題はあるものの、コード生成が楽だったり、パーサージェネレータが存在したり、標準でクロスプラットフォームで動くので、C#はオレオレ言語作りに結構向いている環境と言えるかもしれません。まあ、今から言語作りをしたい方にはいきなり言語作りするのではなく、まずはLISPのインタープリタあたりを実装するところから始められることをお勧めしますが。上で出したレキサ、パーサー、インタープリタ、それぞれの機能を具体的にイメージできるようになります。
次に文法についてですが、現状明文化していないので、Cocoのパーサー定義を見ていただくのが一番早いかと思います。中には、パーサー定義を作ったものの、機能の実装をしていなくて動かないものもありますが(具体的にいうとcomprehension, interfaceなどです)。大雑把に文法を把握したいのなら、ExpressoTest/sources配下のファイルが参考になるでしょう。こちらも仮で書いただけの定義があったりして、パースもできないものが含まれていたりしますが、概要を知りたいだけなら十分と思われます。
ドキュメントについては、Rustの公式解説本のようなものを英語でmarkdown形式で書いています。日本語で書き直すのは面倒なので、しないかもしれません。こちらは、Expresso/Documentation/配下に存在します。
まだ、書きたいことはあるような気がしますが、今回の記事はこの程度で、どうしても書いておきたいことができたら、また記事にしようと思います。では( ̄^ ̄)ゞ

2018年2月15日木曜日

オレオレ言語、Expressoについて・・・導入編

こちらでは、ご無沙汰しています、はざまです。
突然ですが、プログラマをされている皆様が生涯で何としても作り上げたいプログラムはなんでしょうか?これは私の願望も多分に含まれているのですが、恐らく一定数の方が、自作言語と答えるのではないでしょうか?

というわけで今回の記事は、自作言語のExpresso(エクスプレッソ)の紹介をします。4,5年前から開発しているオレオレ言語なんですが、最近、try,catchなども実装し、それなりに使える言語になってきたので、α版として公開することにしようかと思った次第です。とは言っても、専用のサイトはまだ用意しませんが。
Expressoという名前は、ExpressiveとEspressoからの造語です。表現力豊かに、かつエスプレッソ(コーヒー)一杯飲む間にでも開発できるような簡潔な言語を目指すという意味を込めています。また、言語のスローガンとして"Easy for beginners, elegant for enthusiasts"という標語も掲げています。「初学者には簡単に、熱狂者には華麗に」という意味ですね。この標語にはあえて、eで始まる単語を多用しています。これは「e(いい)を探す言語」というダジャレです。
Expressoは、オレオレ言語でありながら、普及、実用化させるならPascalのような教育用言語の地位を目指しています。そのために、先ほどの標語のような目標を掲げているわけです。つまり、初学者には簡単に書ける言語、しかしながら、習熟者にとっても、書きやすい言語を目指すということです。
なぜ、教育用言語を開発するのか不思議に思っているの方もいるかもしれないので、解説しておくと、私は大学時代にPascalという言語でプログラミングを学んだのですが、Wikipediaには教育用と書いてあるんですよ。200x年にしては時代錯誤的な構文とまだ設計に慣れていなかったせいもあって無事単位を落とし、翌年Cに変わったカリキュラムで再履したんですが、その現状を変えたいと思ったのがきっかけです。まあ、スピードとかを売りにしてもRustに敵うわけがないという本音もあります。

言語仕様としては、まだα版と銘打ってることもあって、かなりガバガバなところが多いんですが、基本は静的型付け、オブジェクト指向を基本とするマルチパラダイム言語になっています。一番強く影響を受けている言語が、Rustなので、Rustで採用されている仕様が結構入っています。
現状、Expressoは、.NET環境上でのみ動く言語になっています。理由は、.NETだと比較的ランタイム環境を整えるのも楽ですし、クロスプラットフォームで動くのが大きいです。まあ、私が、いちばん好きな言語がC#だからというのもありますが……

伝統的なHello worldプログラムの解説をする前に、導入方法を紹介しましょう。現状、専用のサイトがないので、Githubのリポジトリからcloneして導入していただく形になります。こちらからcloneしてください。cloneしたら、git submodule update --initを実行してください。依存リポジトリの解決が行われます(といっても、一つしかありませんが)。そして、cloneしたディレクトリ/ExpressoTest/配下にtest_executablesというディレクトリを作成してください。ここにテストで使用するバイナリが吐かれる設定になっているので、これがないとテストが実行できません。gitにこのディレクトリを追加できるのなら、追加したいところではあります。
2018/4/7 追記: それからソリューションをVisual Studioなどで開いて、InteroperabilityTestプロジェクトをビルドし、出力されたDLLファイルを/ExpressoTest/sources/for_unit_testsディレクトリに移動してください。
そうしたら、Mac,Linuxユーザの方なら、あとはメインのソリューションファイルをIDEで開けば、ビルドして実行できるはずです(NuGetを使って一部の依存プロジェクトをダウンロードするようになったので、自動で手元にないプロジェクトをダウンロードするよう、IDEの設定を変える必要があるかもしれません)。Windowsユーザの方は、Cocoという依存プログラムを拾ってこなければなりません。あと、パーサ定義をシェルスクリプトで自動生成しているので、その代わりのバッチファイルも書かなきゃダメですね、多分。
Coco/R for C#からCoco.exeを選択してバイナリを拾ってきたら、cloneしたディレクトリ/Expresso/配下に配置してください。Expressoが言語のコアを担うプロジェクトです。バッチファイルは、cloneしたディレクトリ/Expresso/parserCompile.shというシェルスクリプトを参考に作成していただきたいのですが、Coco.exeに渡すオプションは自由に変更してください。バッチファイルを作成したら、Expressoプロジェクト設定でビルド前に自動実行するように設定すれば、いちいち手動でパーサを生成する必要がなくなります。
長々と書きましたが、Windowsでの動作確認は不十分な(動きはするもののテストは通らない程度までしか確認していない)ので、動くことは保証しません。手軽に使いたいなら、Mac+Visual StudioかLinux+Xamarin Studioあたりの環境をお勧めします(昔は、後者、今は前者の開発環境で作ってます)(2018/4/7 追記: 現状、Windowsでは.NET自体の実装が異なるようでEmitterTestsが動きません)(2018/4/8 追記: Windowsでも、大方のEmitterTestsを動かせるようになりました。ですが、動かないものは割とどうしようもなさそうなエラーで失敗してます・・・特にOOPのはずなのに、インターフェイスが動かないのは痛すぎる・・・)。

さて、ここまでで肝心のコンパイラは動かせるようになったはずなので、伝統的なHello worldプログラムに移ります。Hello worldプログラムは、以下のように書きます。
module main;
def main()
{
    println("Hello, world!");
}

中身の解説をする前に実行してみましょう。ExpressoConsoleプロジェクトをビルドし、MacかLinuxならmono exsc.exe hello_world.exs -o ./ -e hello_worldなどとしてまずコンパイルします。その後、カレントディレクトリにExpresso.dllとExpressoRuntime.dllをコピーしてください。正式リリースする頃には、この部分はなんとかすると思いますが、とりあえず今は、ランタイム環境が必要です。そして、できあがったmain.exeをmono hello_world.exeとして実行すると、Hello, world!と画面上に出力されるはずです。Expressoプログラムの実行に成功しました!

Expressoでは、基本的に1ファイル1モジュール構成を採用しています。ここは、Python譲りですね。各モジュールは、明示的に名前付けすることを義務付けられています。プログラムのエントリーポイントは、mainモジュールのmain関数からになります。今のところ、main関数は、引数も戻り値もなしの仕様になっています(Cのように文字列配列の引数を定義したり、intを戻り値にしても動きますが、単純に無視されます)(2018/4/11 追記: main関数がargs引数を取り、intを返せるようになりました。monoを使って実行するからです)。
ご覧の通り、関数、メソッドはdefキーワードで定義します。PythonやRubyで採用されている構文だったと思いますが、Rustのようにfunction由来のキーワードだとメソッド定義に違和感があるからです。Rustにはトレイトオブジェクトはあるものの、オブジェクトはないので、メソッドは存在しないはずです(追記: 公式ドキュメントでは、implブロックの関数をメソッドと呼んでいるようですので、これは間違いだったようです)。
この例ではどこにも明示されていません(というか変数宣言がない)が、型は後置です。その際、変数名と型の区切り記号には、(-という記号を使用します。これは、数学の∈に由来するExpresso独自の記号(のはず)です。あまり型は明示してほしくないという思想の元、入力しづらい2文字の記号を採用しています。Rustには似た記号を一元化してくれる機能があったと思いますが、Expressoには導入していないので、(-を∈と書いても、認識してくれませんので悪しからず。
関数、メソッドの戻り値を明示する場合は、Rustでも採用されている->記号を使います。関数の戻り値は、return文から推論するので、省略しても構いません(上のmain関数では省略しているが、voidに推論される)。
上記のプログラムで呼び出しているprintln関数は、組み込みの関数です。.NET環境のConsole.WriteLine関数を使用しているので、可変長の引数をとって、それをカンマ区切りで標準出力に出力します。他に、お尻に改行を追加しないprintや、第1引数にConsole.WriteLineに準じるフォーマット文字列を取るprintFormat関数(追記: string interpolation(日本語だと「文字列補完」ですかね)を実装したので、廃止しました)などが存在します。

いかがでしたでしょうか。以上で、オレオレ言語Expressoの導入は終わりです。あ、Expressoの骨格の説明などをしませんでしたが、機能詳細などは次回の記事で行いましょう。コンパイラ作りに興味のある方には、次の記事が参考になるかもしれませんね。