読者です 読者をやめる 読者になる 読者になる

亀岡的プログラマ日記

京都のベッドタウン、亀岡よりだらだらとお送りいたします。

だらだらTDDなBrainFuckCompiler! その2

さてさて、今日は値のインクリメントあたりまで実装してみましょう。まずまず、テストコードですね。とりあえずは、Runした後に値がひとつインクリメントされるだけ、ってことで・・・まずはインクリメント。

		[Test]
		public void 値インクリメントテスト()
		{
			var compiler = new BrainFuckCompiler.BrainFuckCompiler("+");
			compiler.Run();
			compiler.CurrentValue.Is((byte)1);
		}

当然レッドです。それでは実装を考えて行きましょう。ポインタがあるので、気持ちとしては対応する配列を持たせておきたいところ。一応Brainfuckの仕様として

少なくとも30000個の要素を持つバイトの配列(各要素はゼロで初期化される)

というのがありますので、もう確保しちゃってもいいのですが、それはそれでアレなので、まぁListで持つことにしましょう。リスト内の要素を増やすタイミングは、ポインタを進めた時が良いでしょう。進めたポインタに対応する中身が存在しないときに増やすようにしましょう。結構大きな変更ですが、ポインタインクリメントは単体テストを書いているので安心です。
こうですね。

		//メモリ領域
		private List<byte> _memoryStream = new List<byte>();
		public void Run()
		{
			foreach(var token in _input)
			{
				switch (token)
				{
					case '>':
						_programPointer++;
						while(_memoryStream.Count  - 1 < _programPointer)
						{
							_memoryStream.Add((byte)0);
						}
						break;
					case '<':
						if(_programPointer <= 0)
						{
							throw new IndexOutOfRangeException();
						}
						_programPointer--;
						break;
					case '+':
						_memoryStream[_programPointer]++;
						break;
					default:
						break;
				}
			}
		}

・・・あれ?OutOfRangeがでちゃいますね。あぁ、最初の一つは入れておかないとダメなんだった!

		private List<byte> _memoryStream = new List<byte>(){0};

これでOKなんですが、どうもポインタを進めてから入れるってのが無いのはちょっと怖いですね。。。
てなわけで、ポインタのインクリメント+値のインクリメントもテストに入れておきます。こういうのって本来やっていいことなのかはわかんないのですが、まぁTDDは僕の中では安全装置でもあるので、気になるのは書いておきたい主義です。

とりあえずこんな感じですかね。

		[Test]
		public void ポインタインクリメント値インクリメントテスト()
		{
			var compiler = new BrainFuckCompiler.BrainFuckCompiler(">+");
			compiler.Run();
			compiler.CurrentValue.Is((byte)1);
		}

そいじゃ次にデクリメントです、が、これもマイナスを許容するかどうか、という問題になります。まぁC#の組み込みbyte型はunsignedですし、もうそのまんまアンダーフローでいいでしょう。
てなわけで。

		[Test]
		public void 値デクリメントテスト_アンダーフロー()
		{
			var compiler = new BrainFuckCompiler.BrainFuckCompiler("-");
			compiler.Run();
			compiler.CurrentValue.Is(byte.MaxValue);
		}
		
		[Test]
		public void 値デクリメントテスト_通常()
		{
			var compiler = new BrainFuckCompiler.BrainFuckCompiler("++-");
			compiler.Run();
			compiler.CurrentValue.Is((byte)1);
		}

では実装してみましょう。おっとっと、byteだと簡単にOverflowExceptionが出ますね。ちゃんとハンドリングせねば。
なんて試行錯誤の結果、デクリメントだけじゃなくてちょっとした処理が必要だとわかりましたね。チャンチャン。

					case '-':
						if(_memoryStream[_programPointer] == byte.MinValue)
						{
							_memoryStream[_programPointer] = byte.MaxValue;
						}
						else{
							_memoryStream[_programPointer]--;
						}
						break;

ふむ、コレを見ちゃうとインクリメントの最大値もチェックしておくべきですね。以下のように、最大値から加算されたら0になるテストを追加します。

		[Test]
		public void 値インクリメントテスト_オーバフロー()
		{
			var compiler = new BrainFuckCompiler.BrainFuckCompiler(
				new String( Enumerable.Range(0,256).Select(_=> '+' ).ToArray())
			);
			compiler.Run();
			compiler.CurrentValue.Is(byte.MinValue);
		}

こいつらをすべて通すコードはこんな感じになります。うーむ、2つの演算子実装程度でここまでかかっちゃダメですが、まぁダラダラやるのがモットーなので。

		public void Run()
		{
			foreach(var token in _input)
			{
				switch (token)
				{
					case '>':
						_programPointer++;
						while(_memoryStream.Count - 1 < _programPointer)
						{
							_memoryStream.Add((byte)0);
						}
						break;
					case '<':
						if(_programPointer <= 0)
						{
							throw new IndexOutOfRangeException();
						}
						_programPointer--;
						break;
					case '+':
						if(_memoryStream[_programPointer] == byte.MaxValue)
						{
							_memoryStream[_programPointer] = byte.MinValue;
						}
						else{
							_memoryStream[_programPointer]++;
						}
						break;
					case '-':
						if(_memoryStream[_programPointer] == byte.MinValue)
						{
							_memoryStream[_programPointer] = byte.MaxValue;
						}
						else{
							_memoryStream[_programPointer]--;
						}
						break;
					default:
						break;
				}
			}
		}

眠くなってきたので今日はこの辺で。