<?php
/**
 * Brainfuck for PHP
 * Interpreter 
 *
 * @author Damien Walsh <walshd0@cs.man.ac.uk>
 * @version 1.0
 * @package brainfuck-php
 */
class BF_Interpreter
{
  
/**
   * The current program.
   * @var array
   */
  
private $program = array();
  
  
/**
   * The input buffer.
   * @var array
   */
  
private $input = array();

  
/** 
   * The internal data table.
   * @var array
   */
  
private $data = array();
  
  
/**
   * Debug mode.
   * @var boolean
   */
  
public $debug false;
  
  
/**
   * The data pointer
   * @var integer
   */
  
private $dPt 0;
  
  
/**
   * The program pointer
   * @var integer
   */
  
private $pPt 0;
  
  
/**
   * Initialise an interpreter with the specified program string.
   * @param string $program The program to load.
   * @return BF_Interpreter
   */
  
public static function initWithString($program)
  {
    
$interpreter = new self();
    
$interpreter->loadProgram($program);
    
    return 
$interpreter;
  }
  
  
/**
   * Load a program as a string of characters.
   * @param string $program The program to load.
   * @return boolean
   */
  
public function loadProgram($program)
  {
    for(
$index 0$index strlen($program); $index ++)
    {
      if(
self::isValidCharacter($program[$index]))
      {
        
$this->program[] = $program[$index];
      }
    }
    
    return 
true;
  }
  
  
/**
   * Write data to the input buffer.
   * Return the number of bytes of input loaded.
   * @param array $input The input to add to the buffer.
   * @return integer
   */
  
public function in($input)
  {
    foreach(
$input as $value)
    {
      
$this->input[] = $value;
    }
    
    
// Rewind input
    
reset($this->input);
    
    return 
count($input);
  }
  
  
/**
   * Verify that the specified character is a valid Brainfuck character.
   * @param string $character The character to evaluate.
   * @return boolean
   */
  
private static function isValidCharacter($character)
  {
    return 
in_array($character
      array(
'.'',''>''<''+''-''['']'));
  }
  
  
/**
   * Advance the interpreter by a single step.
   * @return boolean
   */
  
public function step()
  {
    switch(
$this->program[$this->pPt])
    {
      case 
'>':
        
        
// Advance the data pointer
        
$this->dPt ++;
        
        if(
$this->debug)
        {
          print 
'Moved up to pointer ' "\t\t" $this->dPt "\n";
        }
        
        break;
        
      case 
'<':
        
        
// Advance the data pointer
        
$this->dPt --;
        
        if(
$this->debug)
        {
          print 
'Moved down to pointer ' "\t\t" $this->dPt "\n";
        }
        
        break;
        
      case 
'+':
        
        
// Incrememnt the data value
        
$this->data[$this->dPt] ++;
        
        if(
$this->debug)
        {
          print 
'Incremented ' $this->dPt " to\t\t" $this->data[$this->dPt] . "\n";
        }
        
        break;
        
      case 
'-':
        
        
// Decrememnt the data value
        
$this->data[$this->dPt] --;

        if(
$this->debug)
        {
          print 
'Decremented ' $this->dPt " to\t\t" $this->data[$this->dPt] . "\n";
        }

        break;
        
      case 
'.':
        
        
// Output the current data byte
        
print chr($this->data[$this->dPt]);
        
        if(
$this->debug)
        {
          print 
'Outputted ' "\t\t" $this->data[$this->dPt] . "\n";
        }
        
        break;
        
      case 
',':
            
        
// Read a byte of input
        
$this->data[$this->dPt] = current($this->input);
        
next($this->input);

        if(
$this->debug)
        {
          print 
'Read ' pos($this->data) . " as\t\t" $this->data[$this->dPt] . "\n";
        }

        break;     
           
      case 
'[':

        
// Start loop
        
if($this->data[$this->dPt] == 0)
        {
          
$level 1;
          
          while(
$level != 0)
          {
            
$this->pPt ++;
            
$next $this->program[$this->pPt];
            
            switch(
$next)
            {
              case 
'[':
                
$level ++;
                break;
                
              case 
']':
                
$level --;
                break;
            }
          }
        }
            
        break;
        
      case 
']':

        
// End loop
        
if($this->data[$this->dPt] != 0)
        {
          
$level 1;
          
          while(
$level != 0)
          {
            
$this->pPt --;
            
$prev $this->program[$this->pPt];
            
            switch(
$prev)
            {
              case 
']':
                
$level ++;
                break;
                
              case 
'[':
                
$level --;
                break;
            }
          }
        }
            
        break;
    }
    
    
    
flush();
    
    
// Advance program
    
$this->pPt ++;
    
    return 
true;
  }
  
  
/**
   * Reset the interpreter to the beginning of the program.
   * @return boolean
   */
  
public function reset()
  {
    
// Reset all
    
$this->dPt 0;
    
$this->pPt 0;
    
    return 
true;
  }
  
  
/**
   * Run the current program from start to finish.
   * @return boolean
   */
  
public function run()
  {
    
// Rewind fully
    
$this->reset();
    
    
// Run program
    
while(!empty($this->program[$this->pPt]))
    {
      
$this->step();
    }
    
    return 
true;
  }
  
  
/**
   * Rewind the interpreter by a single step.
   * @return boolean
   */
  
public function reverse()
  {
    
$this->pPt --;
    
    return 
true;
  }  
}
?>