patternphpMinor
Thoughts on my brain fart interpreter?
Viewed 0 times
interpreterbrainfartthoughts
Problem
Had some free time this weekend and I hacked together a small Brainfuck interpreter, with the sole intention of explaining PHP's flavour of OO to a friend. This is a bit over-engineered on purpose, and performance wasn't really considered (or I wouldn't have written this in PHP in the first place).
Thoughts? Criticisms? Cookies?
I'm more interested on comments on the design, but as with any review, everything is fair game. The code is a bit much, so I removed comments, some whitespace and an interface or two. You can check out the full code and tests on github.
Example
The interpreter
The Virtual Machine
VM
```
namespace Brainfart\VM;
class VM {
private $input;
private $output;
private $memory;
private $loopLimit = 0;
pu
Thoughts? Criticisms? Cookies?
I'm more interested on comments on the design, but as with any review, everything is fair game. The code is a bit much, so I removed comments, some whitespace and an interface or two. You can check out the full code and tests on github.
Example
$source = "5,2,10,1!!>>,[>>,][>[>>]>+>]>]run($source);$output will be: array (size=4)
0 => int 1
1 => int 2
2 => int 5
3 => int 10The interpreter
namespace Brainfart;
use Brainfart\VM\Output;
use Brainfart\Parser\Parser;
use Brainfart\VM\VM;
class Brainfart {
private $optimize = true;
private $vm;
private $parser;
private $output;
public function __construct($loopLimit = 100, $optimize = true) {
$this->vm = new VM(array(), $loopLimit);
$this->parser = new Parser();
$this->setOptimize($optimize);
}
public function setOptimize($optimize = true) {
$this->optimize = ($optimize === true);
return $this;
}
public function run($source, $input = "", $fetchMode = Output::FETCH_ARRAY) {
$this->parser->loadSource($source);
if ($this->parser->getFlag("string_output") === true) $fetchMode = Output::FETCH_STRING;
$appLoop = $this->parser->parse($this->optimize);
$appInput = $this->parser->getInput();
if (!empty($appInput)) $input = $appInput;
$this->vm->init($input);
$appLoop->execute($this->vm);
return $this->vm->getOutput()->fetch($fetchMode);
}
}The Virtual Machine
VM
```
namespace Brainfart\VM;
class VM {
private $input;
private $output;
private $memory;
private $loopLimit = 0;
pu
Solution
So for starters, make the operations lazy-loaded (yeah big surprise, Haskeller says make it lazy)
As such:
becomes:
Then you either add operations to the public array as you interpret them, or create an addOperation method, either way you need to be able to construct the LoopOperation and then as parsing continues add individual operations to it.
Then here's some parser combinators with an error monad for ya (I don't know PHP so bear with me):
Study closely and you'll realize the loop operation object where all the operations are added, is lost after the loop interpretation is complete, for this reason I would suggest rather than the way I'm threading the source through all the functions, actually create a structure that has source, and VM, so the functions can parse the source and push operations onto the VM as they go, the resulting code would give you ability to write things like:
As such:
class LoopOperation implements OperationInterface {
private $master = false;
private $operations = array();
public function __construct(array $operations, $master = false) {
$this->setOperations($operations)->setMaster($master);
}becomes:
class LoopOperation implements OperationInterface {
public $master = false;
public $operations = array();Then you either add operations to the public array as you interpret them, or create an addOperation method, either way you need to be able to construct the LoopOperation and then as parsing continues add individual operations to it.
Then here's some parser combinators with an error monad for ya (I don't know PHP so bear with me):
abstract class M
{
abstract public function then($f); // Formally this is 'bind', the f is the function to bind to the action
abstract public function otherwise($f); // Formally this would be an application of an `alternative` or monoid
public $a; // The a is for 'action'
}
class Success extends M
{
public function __construct($a) {
$this->a = $a;
}
public function then($f) {
return $f($this->a);
}
public function otherwise($f) {
return $this;
}
}
class Failure extends M
{
public function __construct($a) {
$this->a = $a;
}
public function then($f) {
return $this;
}
public function otherwise($f) {
return new Success($this->a)->then($f);
}
}
public function pop($a) {
return new Success(substr($a, 1));
}
public function charIs($char) {
return function($a) use($char) {
if ($a[0] == $char) {
return new Success($a);
}
return new Failure($a);
}
}
public function charExists($char) {
return function($a) use($char) {
if (strpos($a, $char) === false) {
return new Failure($a);
}
return new Success($a);
}
}
public function addLoopOperation($operation) {
return function($a) user ($operation) {
$operation->addOperation(strpos($a, 0, 1));
return new Success($a);
}
}
public function throwException($ex) {
return function($a) use ($ex) {
throw $ex;
}
}
public function addLoop($operation = new LoopOperation()) {
return function($a) use (&$operation) {
return new Success($a)
->then(charExists("]"))->otherwise(throwException(new LogicException("Neverending loop."))
->then(addLoopOperation($operation))
->then(pop)
->then(charIs("]"))->then(pop)->otherwise(addLoop($operation));
}
// Because it's recursed the way it is, it will continue adding operations until it hits the char "]"
}Study closely and you'll realize the loop operation object where all the operations are added, is lost after the loop interpretation is complete, for this reason I would suggest rather than the way I'm threading the source through all the functions, actually create a structure that has source, and VM, so the functions can parse the source and push operations onto the VM as they go, the resulting code would give you ability to write things like:
while(true)
new Success($someStructure)->then(charIs("["))->then(addLoop())->otherwise(processOperation)->then(executeOperations);Code Snippets
class LoopOperation implements OperationInterface {
private $master = false;
private $operations = array();
public function __construct(array $operations, $master = false) {
$this->setOperations($operations)->setMaster($master);
}class LoopOperation implements OperationInterface {
public $master = false;
public $operations = array();abstract class M
{
abstract public function then($f); // Formally this is 'bind', the f is the function to bind to the action
abstract public function otherwise($f); // Formally this would be an application of an `alternative` or monoid
public $a; // The a is for 'action'
}
class Success extends M
{
public function __construct($a) {
$this->a = $a;
}
public function then($f) {
return $f($this->a);
}
public function otherwise($f) {
return $this;
}
}
class Failure extends M
{
public function __construct($a) {
$this->a = $a;
}
public function then($f) {
return $this;
}
public function otherwise($f) {
return new Success($this->a)->then($f);
}
}
public function pop($a) {
return new Success(substr($a, 1));
}
public function charIs($char) {
return function($a) use($char) {
if ($a[0] == $char) {
return new Success($a);
}
return new Failure($a);
}
}
public function charExists($char) {
return function($a) use($char) {
if (strpos($a, $char) === false) {
return new Failure($a);
}
return new Success($a);
}
}
public function addLoopOperation($operation) {
return function($a) user ($operation) {
$operation->addOperation(strpos($a, 0, 1));
return new Success($a);
}
}
public function throwException($ex) {
return function($a) use ($ex) {
throw $ex;
}
}
public function addLoop($operation = new LoopOperation()) {
return function($a) use (&$operation) {
return new Success($a)
->then(charExists("]"))->otherwise(throwException(new LogicException("Neverending loop."))
->then(addLoopOperation($operation))
->then(pop)
->then(charIs("]"))->then(pop)->otherwise(addLoop($operation));
}
// Because it's recursed the way it is, it will continue adding operations until it hits the char "]"
}while(true)
new Success($someStructure)->then(charIs("["))->then(addLoop())->otherwise(processOperation)->then(executeOperations);Context
StackExchange Code Review Q#23707, answer score: 6
Revisions (0)
No revisions yet.