7
\$\begingroup\$

Inspired by this question, I decided to give it a try and implemented a brainfuck interpreter myself. It includes various improvements:

  • It's object-oriented and modular
  • Unlimited tape size
  • It includes a call stack (no seeking for opening parenthesis)
  • It allows stepping and debugging
  • It throws exceptions on errors

Tape

/// <summary>
/// One-sided, infinite, default-initialized Tape. </summary>
public class Tape<T>
{
 List<T> _tape;
 int _pos;
 public Tape()
 {
 _tape = new List<T>();
 _tape.Add(default(T));
 _pos = 0;
 }
 /// <summary>
 /// Currently selected cell's value. </summary>
 public T Value
 {
 get
 {
 return _tape[_pos];
 }
 set
 {
 _tape[_pos] = value;
 }
 }
 /// <summary>
 /// Step one cell to the left. </summary>
 public void StepLeft()
 {
 if (_pos == 0)
 throw new InvalidOperationException();
 else
 _pos--;
 }
 /// <summary>
 /// Step one cell to the right. </summary>
 public void StepRight()
 {
 _pos++;
 if (_pos == _tape.Count)
 _tape.Add(default(T));
 }
 /// <summary>
 /// Return a full snapshot of the tape without altering anything. </summary>
 public T[] ToArray()
 {
 return _tape.ToArray();
 }
}

Interpreter

public class BrainfuckInterpreter
{ 
 Stream _program;
 Stream _input;
 Stream _output;
 Tape<byte> _tape;
 Stack<long> _callStack;
 /// <summary>
 /// Create a new BrainfuckInterpreter. </summary>
 /// <param name="program">
 /// Program to be executed. Must be readable and seekable.
 /// <param name="input">
 /// Must be readable. Can be null if the program doesn't take any input. </param>
 /// <param name="output">
 /// Must be writable. Can be null if the program doesn't produce output. </param>
 public BrainfuckInterpreter(Stream program, Stream input, Stream output)
 {
 _program = program;
 _input = input;
 _output = output;
 _tape = new Tape<byte>();
 _callStack = new Stack<long>();
 }
 /// <summary>
 /// Run the program until it terminates. </summary>
 public void Run()
 {
 while (Step());
 }
 /// <summary>
 /// Execute the next command. </summary>
 /// <returns>
 /// False if the program terminated. True otherwise. </returns>
 public bool Step()
 {
 int command = _program.ReadByte();
 switch (command)
 {
 default: throw new ArgumentException(); // Invalid command
 case -1: return false; // End reached
 case '+': _tape.Value++; break;
 case '-': _tape.Value--; break;
 case ',':
 int input = _input.ReadByte();
 if (input == -1) // Null-termination instead of error status
 _tape.Value = 0;
 else
 _tape.Value = (byte)input;
 break;
 case '.': _output.WriteByte(_tape.Value); break;
 case '>': _tape.StepRight(); break;
 case '<': _tape.StepLeft(); break;
 case '[': _callStack.Push(_program.Position); break;
 case ']':
 if (_tape.Value == 0)
 _callStack.Pop();
 else
 _program.Position = _callStack.Peek();
 break;
 }
 return true;
 }
 /// <summary>
 /// Print the tape for debug purposes.
 /// Format (hex): |XX|XX|XX|...|XX| </summary>
 public string Print()
 {
 string result = "|";
 foreach (var n in _tape.ToArray())
 {
 result += String.Format("{0:X2}|", n);
 }
 return result;
 }
}

Main routine (for testing)

Takes the program directly (not a filename) as first command line argument. Prints the tape's contents to stderr after each step.

public static class Program
{
 public static void Main(string[] args)
 {
 Stream program = new MemoryStream(args[0].Select(c => (byte)c).ToArray());
 var bf = new BrainfuckInterpreter(
 program,
 Console.OpenStandardInput(),
 Console.OpenStandardOutput()
 );
 while (bf.Step())
 {
 Console.Error.WriteLine(bf.Print());
 }
 }
}
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Oct 21, 2016 at 15:39
\$\endgroup\$
1
  • \$\begingroup\$ Did you test your code? The program [+[]] should terminate quickly because the + is never executed, while the program +[] should never terminate. \$\endgroup\$ Commented Oct 22, 2016 at 10:48

1 Answer 1

5
\$\begingroup\$
switch (command)
{
 ...
 case '[': _callStack.Push(_program.Position); break;
 case ']':
 if (_tape.Value == 0)
 _callStack.Pop();
 else
 _program.Position = _callStack.Peek();
 break;
}

A call stack in a tape-based language? Heresy!

Seriously, though, the code is wrong. You have implemented a do { ... } while (_tape.Value != 0) loop instead of a while (_tape.Value != 0) { ... } loop.

answered Oct 21, 2016 at 21:56
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.