Last active
November 15, 2019 10:55
-
-
Save azjezz/616fc5b934433c4e8de3348221fc00e2 to your computer and use it in GitHub Desktop.
Brainfuck Lexer and Interpreter written in PHP ( https://3v4l.org/9lEte )
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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