特集
» 2017年07月28日 05時00分 公開

特集:マイクロソフトテクノロジーの現在と未来:.NET Standardとは (2/3)

[かわさきしんじ,Insider.NET編集部]

.NET Standard 2.0を使ったコード例

 以下では「Dev Basics/Keyword:Brainfuck」で紹介したBrainfuckというシンプルなプログラミング言語のインタープリタを.NET Standard 2.0ベースのクラスライブラリとして実装して、それを幾つかのソリューションから実際に使ってみよう。

 簡単に説明をしておくと、Brainfuckは次の8種類の命令を使って、データを格納する配列(とそれを参照するためのポインター)を操作しながら、何らかの処理を行う言語だ。

  • >: ポインターをインクリメントして、配列内の次の要素を指すようにする
  • <: ポインターをデクリメントして、配列内の前の要素を指すようにする
  • +: ポインター位置にあるデータをインクリメントする
  • -: ポインター位置にあるデータをデクリメントする
  • .: ポインター位置にあるデータを出力する
  • ,: 1バイトのデータの入力を受け取り、ポインター位置に保存
  • [: ポインター位置にあるデータが0であれば、対応する「]」の後までジャンプ
  • ]: 対応する「[」にジャンプ

 例えば、「@IT」と出力するBrainfuckコードは次のようになる(このコードがどのように動作するのかについては前述の記事「Dev Basics/Keyword:Brainfuck」を参照されたい。どう見ても、人が読むことを前提とはしていないコードだ)。

++++++++[>++++++++<-]>.+++++++++.+++++++++++.


「@IT」と出力する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」ページなどを参照されたい。

Brainfuckインタープリタを実装するクラスライブラリ

 VS 2017 Previewと.NET Core 2.0 Previewをインストールした環境で、プロジェクトを新規に作成して、[新しいプロジェクト]ダイアログでカテゴリーに[.NET Standard]を選択すると、次のように.NET Standardベースのクラスライブラリを作成するプロジェクトテンプレートが表示される。

[新しいプロジェクト]ダイアログ [新しいプロジェクト]ダイアログ

 ここでは、MyBrainfuckLibという名前でプロジェクトを作成した。.NET Standard 2.0が使用可能であれば、クラスライブラリプロジェクトのプロパティを表示すると、次の画像のように[ターゲット フレームワーク]欄に[.NET Standard 2.0]が表示されるはずだ。

.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());
    }
  }
}

Brainfuck(の7つの命令をサポートする)インタープリタを実装したクラスライブラリ

 これはあくまでもサンプルなので、詳しい説明はしないが、2つのメソッドについて簡単に述べておこう。

 GetBracketPairメソッドはBrainfuckの命令列からループを構成する「[」と対応する「]」の組み合わせを探して、それらの命令列内での位置をタプルのリストとして取り出すものだ(C# 7で追加されたタプル(値タプル)を利用しているので、プロジェクトにはNuGetからSystem.ValueTupleパッケージを追加している)。

 ExecuteBrainfuckCodeメソッドは、パラメーターに受け取った命令列を1文字ずつ走査して、それらの命令に応じた処理をしていく。このとき、データ配列に保存されているデータを出力する「.」命令に遭遇したら、それをchar型の要素を持つリストに追加していき、最終的にそれを文字列に変換したものを返送する。

 なお、GetBracketPairメソッドで取り出したタプルのリストは、ExecuteBrainfuckCodeメソッド内で命令列の解釈/実行時、「[」命令あるいは「]」命令が登場した際にループ内の処理を行うか、ループの次の処理を行うかに応じて、次に実行する命令に制御を移動するのに使われている。

 次にこのクラスライブラリを利用するコンソールアプリを見てみよう。

Copyright© Digital Advantage Corp. All Rights Reserved.

編集部からのお知らせ

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。