Skip to content

Instantly share code, notes, and snippets.

@azjezz
Last active November 15, 2019 10:55
Show Gist options
  • Save azjezz/616fc5b934433c4e8de3348221fc00e2 to your computer and use it in GitHub Desktop.
Save azjezz/616fc5b934433c4e8de3348221fc00e2 to your computer and use it in GitHub Desktop.
Brainfuck Lexer and Interpreter written in PHP ( https://3v4l.org/9lEte )
<?php
declare(strict_types=1);
namespace AzJezz\BrainFuck;
use ArrayIterator;
use Iterator;
use RuntimeException;
final class MemoryCell
{
public $value = 0;
public function increase(): void
{
++$this->value;
if (256 === $this->value) {
$this->value = 0;
}
}
public function decrease(): void
{
--$this->value;
if (-1 === $this->value) {
$this->value = 255;
}
}
}
final class MemoryTable
{
private $position = 0;
private $cells = [];
public function current(): MemoryCell
{
$this->cells[$this->position] = $this->cells[$this->position] ?? new MemoryCell();
return $this->cells[$this->position];
}
public function right(): void
{
++$this->position;
}
public function left(): void
{
--$this->position;
}
public function free(): void
{
$this->position = 0;
$this->cells = [];
}
}
final class Token
{
public const Right = 0;
public const Left = 2;
public const Read = 4;
public const Write = 8;
public const Increase = 16;
public const Decrease = 32;
public const LoopIn = 64;
public const LoopOut = 128;
private const tokens = [
'right' => Token::Right, // >
'left' => Token::Left, // <
'read' => Token::Read, // .
'write' => Token::Write, // ,
'increase' => Token::Increase, // +
'decrease' => Token::Decrease, // -
'loop-in' => Token::LoopIn, // [
'loop-out' => Token::LoopOut, // ]
];
private $token;
public function __construct(string $token)
{
$this->token = static::tokens[$token];
}
public function kind(): int
{
return $this->token;
}
}
final class Lexer
{
public function tokenize(string $code): Iterator
{
foreach (str_split($code) as $i => $chr) {
switch ($chr) {
case '>':
yield $i => new Token('right');
break;
case '<':
yield $i => new Token('left');
break;
case '.':
yield $i => new Token('write');
break;
case ',':
yield $i => new Token('read');
break;
case '+':
yield $i => new Token('increase');
break;
case '-':
yield $i => new Token('decrease');
break;
case '[':
yield $i => new Token('loop-in');
break;
case ']':
yield $i => new Token('loop-out');
break;
}
}
}
}
final class Interpreter
{
private $lexer;
private $table;
private $stdin;
private $stdout;
/**
* Interpreter constructor.
*
* @param resource $stdin
* @param resource $stdout
*/
public function __construct(Lexer $lexer, $stdin, $stdout)
{
$this->lexer = $lexer;
$this->stdin = $stdin;
$this->stdout = $stdout;
$this->table = new MemoryTable();
}
public function run(string $code): void
{
$this->table->free();
$tokens = $this->lexer->tokenize($code);
$this->process($tokens);
}
public function read(): void
{
do {
$this->table->current()->value = ord(fread($this->stdin, 1));
} while ($this->table->current()->value > 255);
}
public function write(): void
{
fwrite($this->stdout, chr($this->table->current()->value));
}
private function process(Iterator $tokens): void
{
while ($tokens->valid()) {
/**
* @var Token
*/
$token = $tokens->current();
if (Token::LoopIn === $token->kind()) {
$inner = [];
$tokens->next();
$nest = 1;
while ($tokens->valid()) {
$current = $tokens->current();
$inner[$tokens->key()] = $current;
if (Token::LoopOut === $current->kind()) {
--$nest;
} elseif (Token::LoopIn === $current->kind()) {
++$nest;
}
$tokens->next();
if (0 === $nest) {
break;
}
}
assert(0 === $nest);
array_pop($inner);
while (0 !== $this->table->current()->value) {
$this->process(new ArrayIterator($inner));
}
continue;
}
if (Token::LoopOut === $token->kind()) {
throw new RuntimeException('Unexpected "]" token.');
}
$this->operate($token);
$tokens->next();
}
}
private function operate(Token $token): void
{
switch ($token->kind()) {
case Token::Right:
$this->table->right();
break;
case Token::Left:
$this->table->left();
break;
case Token::Increase:
$this->table->current()->increase();
break;
case Token::Decrease:
$this->table->current()->decrease();
break;
case Token::Read:
$this->read();
break;
case Token::Write:
$this->write();
break;
}
}
}
$interpreter = new Interpreter(new Lexer(), STDIN, STDOUT);
$interpreter->run('+[-[<<[+[--->]-[<<<]]]>>>-]>-.---.>..>.<<<<-.<+.>>>>>.>.<<.<-.'); // hello world
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment