3v4l.org

run code in 300+ PHP versions simultaneously
<?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 Parser { public function parse(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 $parser; private $table; private $stdin; private $stdout; /** * Interpreter constructor. * * @param \AzJezz\BrainFuck\Parser $parser * @param resource $stdin * @param resource $stdout */ public function __construct(Parser $parser, $stdin, $stdout) { $this->parser = $parser; $this->stdin = $stdin; $this->stdout = $stdout; $this->table = new MemoryTable(); } public function run(string $code): void { $this->table->free(); $tokens = $this->parser->parse($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 Parser(), STDIN, STDOUT); $interpreter->run('+[-[<<[+[--->]-[<<<]]]>>>-]>-.---.>..>.<<<<-.<+.>>>>>.>.<<.<-.'); // hello world
Output for 7.1.25 - 7.1.33, 7.2.0 - 7.2.33, 7.3.0 - 7.3.33, 7.4.4 - 7.4.33, 8.0.0 - 8.0.30, 8.1.0 - 8.1.27, 8.2.0 - 8.2.17, 8.3.0 - 8.3.4
hello world

preferences:
198.48 ms | 403 KiB | 175 Q