以下では「Dev Basics/Keyword:Brainfuck」で紹介したBrainfuckというシンプルなプログラミング言語のインタープリタを.NET Standard 2.0ベースのクラスライブラリとして実装して、それを幾つかのソリューションから実際に使ってみよう。
簡単に説明をしておくと、Brainfuckは次の8種類の命令を使って、データを格納する配列(とそれを参照するためのポインター)を操作しながら、何らかの処理を行う言語だ。
例えば、「@IT」と出力するBrainfuckコードは次のようになる(このコードがどのように動作するのかについては前述の記事「Dev Basics/Keyword:Brainfuck」を参照されたい。どう見ても、人が読むことを前提とはしていないコードだ)。
++++++++[>++++++++<-]>.+++++++++.+++++++++++.
なお、ここでは外部からの入力に対応する「,」命令を除く、他の7つの命令列で構成されるBrainfuckコードを処理できるようなインタープリタをクラスライブラリとして実装する。
本稿執筆時点では、.NET Standard 2.0を使ってクラスライブラリを作成するにはVisual Studio 2017 Preview(以下、VS 2017 Preview)、.NET Core 2.0 Previewなどを使用する必要がある。製品版のVS 2017では、これはまだサポートされていない。.NET Core 2.0 Preview+VS Codeという選択肢もあるが、ここではVS 2017 Previewを使用することにしよう。
以下では、VS 2017 Preview(15.3.0 Preview 5.0)と.NET Core 2.0.0 Preview 2を使用して、.NET Standard 2.0ベースのクラスライブラリと、それを使用するプロジェクトを作成している。
.NET Core 2.0 Preview 2については「Announcing .NET Core 2.0 Preview 2」ページなどを参照されたい。
VS 2017 Previewと.NET Core 2.0 Previewをインストールした環境で、プロジェクトを新規に作成して、[新しいプロジェクト]ダイアログでカテゴリーに[.NET Standard]を選択すると、次のように.NET Standardベースのクラスライブラリを作成するプロジェクトテンプレートが表示される。
ここでは、MyBrainfuckLibという名前でプロジェクトを作成した。.NET Standard 2.0が使用可能であれば、クラスライブラリプロジェクトのプロパティを表示すると、次の画像のように[ターゲット フレームワーク]欄に[.NET Standard 2.0]が表示されるはずだ。
本稿では、このようにして作成したクラスライブラリに、Brainfuckインタープリタを次のように(スタティッククラス/スタティックメソッドとして)実装した。
using System;
using System.Collections.Generic;
using System.Linq;
namespace MyBrainfuckLib
{
public static class MyBrainfuck
{
private static IEnumerable<(int k, int v)> GetBracketPair(string i_buf)
{
int i_ptr = 0;
int loopcount = 0;
var bracketpair = new List<(int k, int v)>();
while (i_ptr < i_buf.Length)
{
switch (i_buf[i_ptr])
{
case '[':
loopcount = 1;
int tmpidx = i_ptr;
while (loopcount != 0)
{
tmpidx++;
if (i_buf[tmpidx] == '[') loopcount++;
if (i_buf[tmpidx] == ']') loopcount--;
}
bracketpair.Add((i_ptr, tmpidx));
break;
default:
break;
}
i_ptr++;
}
return bracketpair;
}
public static string ExecuteBrainfuckCode(string i_buf)
{
const int MAX_BUFFER_LENGTH = 30000;
byte[] d_buf = new byte[MAX_BUFFER_LENGTH];
int d_ptr = 0;
int i_ptr = 0;
var result = new List<char>();
var bracketpair = GetBracketPair(i_buf);
while (i_ptr < i_buf.Length)
{
switch (i_buf[i_ptr])
{
case '>':
if (d_ptr < d_buf.Length - 1)
d_ptr++;
else
throw new IndexOutOfRangeException("Buffer Overflow");
break;
case '<':
if (d_ptr > 0)
d_ptr--;
else
throw new IndexOutOfRangeException("Buffer Underflow");
break;
case '+':
d_buf[d_ptr]++;
break;
case '-':
d_buf[d_ptr]--;
break;
case '.':
result.Add(Convert.ToChar(d_buf[d_ptr]));
break;
case '[':
if (d_buf[d_ptr] == 0)
i_ptr = bracketpair.First(item => item.k == i_ptr).v;
break;
case ']':
if (d_buf[d_ptr] != 0)
i_ptr = bracketpair.First(item => item.v == i_ptr).k;
break;
default:
break;
}
i_ptr++;
}
return new string(result.ToArray());
}
}
}
これはあくまでもサンプルなので、詳しい説明はしないが、2つのメソッドについて簡単に述べておこう。
GetBracketPairメソッドはBrainfuckの命令列からループを構成する「[」と対応する「]」の組み合わせを探して、それらの命令列内での位置をタプルのリストとして取り出すものだ(C# 7で追加されたタプル(値タプル)を利用しているので、プロジェクトにはNuGetからSystem.ValueTupleパッケージを追加している)。
ExecuteBrainfuckCodeメソッドは、パラメーターに受け取った命令列を1文字ずつ走査して、それらの命令に応じた処理をしていく。このとき、データ配列に保存されているデータを出力する「.」命令に遭遇したら、それをchar型の要素を持つリストに追加していき、最終的にそれを文字列に変換したものを返送する。
なお、GetBracketPairメソッドで取り出したタプルのリストは、ExecuteBrainfuckCodeメソッド内で命令列の解釈/実行時、「[」命令あるいは「]」命令が登場した際にループ内の処理を行うか、ループの次の処理を行うかに応じて、次に実行する命令に制御を移動するのに使われている。
次にこのクラスライブラリを利用するコンソールアプリを見てみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.