├── .gitignore ├── README.md ├── composer.json ├── lib └── BrainFuck │ ├── IO.php │ ├── Language.php │ ├── Memory.php │ ├── Op.php │ ├── Op │ ├── Change.php │ ├── Input.php │ ├── Loop.php │ ├── Move.php │ └── Output.php │ └── Parser.php ├── phpunit.xml.dist └── test ├── BrainFuck └── LanguageTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP-BrainFuck 2 | ============= 3 | 4 | A brainfuck interpreter for PHP 5 | 6 | This repository goes along with my blog post: [The Brain Is A Muscle](http://blog.ircmaxell.com/2012/12/the-brain-is-muscle.html). 7 | 8 | It also is result of this YouTube video: [BrainFuck Implementation In PHP](https://www.youtube.com/watch?v=s3CncuzRzFA) 9 | 10 | ## Install 11 | Install PHP-BrainFuck using [Composer](http://getcomposer.org/) 12 | ``` 13 | php composer.phar require ircmaxell/php-brain-fuck 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```php 19 | run('++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'); 27 | var_dump($output); 28 | 29 | // Output: 30 | /* 31 | array (size=13) 32 | 0 => int 72 33 | 1 => int 101 34 | 2 => int 108 35 | 3 => int 108 36 | 4 => int 111 37 | 5 => int 32 38 | 6 => int 87 39 | 7 => int 111 40 | 8 => int 114 41 | 9 => int 108 42 | 10 => int 100 43 | 11 => int 33 44 | 12 => int 10 45 | */ 46 | 47 | $output = $Language->run(',+.', array(5)); 48 | var_dump($output); 49 | 50 | // Output: 51 | /* 52 | array (size=1) 53 | 0 => int 6 54 | */ 55 | ``` 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ircmaxell/php-brain-fuck", 3 | "description": "A brainfuck interpreter for PHP", 4 | "authors": [ 5 | { 6 | "name": "Anthony Ferrara", 7 | "email": "me@ircmaxell.com", 8 | "role": "Lead" 9 | }, 10 | { 11 | "name": "Emanuele Minotto", 12 | "email": "minottoemanuele@gmail.com", 13 | "role": "Contributor" 14 | } 15 | ], 16 | "require": { 17 | 18 | }, 19 | "autoload": { 20 | "psr-0": { "BrainFuck": "lib/" } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/BrainFuck/IO.php: -------------------------------------------------------------------------------- 1 | input = $input; 14 | } 15 | 16 | public function getOutput() 17 | { 18 | return $this->output; 19 | } 20 | 21 | public function read() 22 | { 23 | return isset($this->input[$this->inputPos]) ? $this->input[$this->inputPos++] : 0; 24 | } 25 | 26 | public function write($value) 27 | { 28 | $this->output[] = $value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/BrainFuck/Language.php: -------------------------------------------------------------------------------- 1 | parser = $parser; 15 | } 16 | 17 | public function run($program, array $input = array()) 18 | { 19 | $memory = new Memory; 20 | $io = new IO($input); 21 | 22 | $ops = $this->parser->parse($program); 23 | 24 | $ops->executeProgram($memory, $io); 25 | 26 | return $io->getOutput(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /lib/BrainFuck/Memory.php: -------------------------------------------------------------------------------- 1 | memory[$this->pos]; 13 | } 14 | 15 | public function write($value) 16 | { 17 | $this->memory[$this->pos] = $value; 18 | } 19 | 20 | public function move($amount) 21 | { 22 | $this->pos += $amount; 23 | if (!isset($this->memory[$this->pos])) { 24 | $this->memory[$this->pos] = 0; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/BrainFuck/Op.php: -------------------------------------------------------------------------------- 1 | amount = $amount; 16 | } 17 | 18 | /** 19 | * @param Memory $memory The active memory for the program 20 | * @param Input $input The current input for the program 21 | * 22 | * @return array The output of the op (if any) 23 | */ 24 | public function execute(Memory $memory, IO $io) 25 | { 26 | $memory->write($memory->read() + $this->amount); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/BrainFuck/Op/Input.php: -------------------------------------------------------------------------------- 1 | write($io->read()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/BrainFuck/Op/Loop.php: -------------------------------------------------------------------------------- 1 | ops = $ops; 16 | } 17 | 18 | /** 19 | * @param Memory $memory The active memory for the program 20 | * @param Input $input The current input for the program 21 | * 22 | * @return array The output of the op (if any) 23 | */ 24 | public function execute(Memory $memory, IO $io) 25 | { 26 | while ($memory->read()) { 27 | $this->executeProgram($memory, $io); 28 | } 29 | } 30 | 31 | public function executeProgram(Memory $memory, IO $io) 32 | { 33 | foreach ($this->ops as $op) { 34 | $op->execute($memory, $io); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/BrainFuck/Op/Move.php: -------------------------------------------------------------------------------- 1 | direction = $direction; 16 | } 17 | 18 | /** 19 | * @param Memory $memory The active memory for the program 20 | * @param Input $input The current input for the program 21 | * 22 | * @return array The output of the op (if any) 23 | */ 24 | public function execute(Memory $memory, IO $io) 25 | { 26 | $memory->move($this->direction); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/BrainFuck/Op/Output.php: -------------------------------------------------------------------------------- 1 | write($memory->read()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/BrainFuck/Parser.php: -------------------------------------------------------------------------------- 1 | stdOps = array( 20 | '+' => new Change(1), 21 | '-' => new Change(-1), 22 | '.' => new Output, 23 | ',' => new Input, 24 | '>' => new Move(1), 25 | '<' => new Move(-1), 26 | ); 27 | } 28 | 29 | public function parse($program) 30 | { 31 | $ops = $this->parseProgram($program); 32 | 33 | return new Loop($ops); 34 | } 35 | 36 | protected function filterNoOps(array $ops) 37 | { 38 | return array_filter($ops, function($op) { 39 | return (bool) $op; 40 | }); 41 | } 42 | 43 | protected function parseProgram($program) 44 | { 45 | $ops = array(); 46 | $programPos = 0; 47 | while (isset($program[$programPos])) { 48 | $ops[] = $this->getOp($program, $programPos); 49 | $programPos++; 50 | } 51 | 52 | return $this->filterNoOps($ops); 53 | } 54 | 55 | protected function getOp($program, &$pos) 56 | { 57 | if (isset($this->stdOps[$program[$pos]])) { 58 | return $this->stdOps[$program[$pos]]; 59 | } elseif ($program[$pos] == ']') { 60 | throw new \LogicException('Unmatch Brace at pos ' . $pos); 61 | } elseif ($program[$pos] == '[') { 62 | return $this->parseLoop($program, $pos); 63 | } 64 | 65 | return null; 66 | } 67 | 68 | protected function parseLoop($program, &$pos) 69 | { 70 | /** 71 | * This regex will parse out and match [] braces. 72 | * 73 | * Basically, it's a recursive regex, that looks for either 74 | * non-[] content, or recurses on itself (if it sees a [ character) 75 | * 76 | * Errors would only happen if there is no matching ], in which case 77 | * either no match will occur, or the match will not be for that loop. 78 | */ 79 | $regex = '((\[((?:[^\[\]]+|(?1))*)\]))'; 80 | $matches = array(); 81 | preg_match($regex, $program, $matches, PREG_OFFSET_CAPTURE, $pos); 82 | if (!$matches || $matches[0][1] !== $pos) { 83 | throw new LogicException('Unmatched Brace at pos ' . $pos); 84 | } 85 | $pos += strlen($matches[0][0]) - 1; 86 | 87 | return $this->parse($matches[2][0]); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | ./test/ 22 | 23 | 24 | 25 | 26 | ./lib/ 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/BrainFuck/LanguageTest.php: -------------------------------------------------------------------------------- 1 | ++<.', array(1), array(1)), 14 | array(',>,>+.<.<.', array(3, 2), array(1, 2, 3)), 15 | array('[+].', array(), array(0)), 16 | array( 17 | '++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.', 18 | array(), 19 | // This is ascii code for "Hello World!\n" 20 | array(72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10), 21 | ), 22 | array(',/*this should be ignored as a comment*/+.', array(0), array(1)), 23 | array( 24 | '-,+[ Read first character and start outer character reading loop 25 | -[ Skip forward if character is 0 26 | >>++++[>++++++++<-] Set up divisor (32) for division loop 27 | (MEMORY LAYOUT: dividend copy remainder divisor quotient zero zero) 28 | <+<-[ Set up dividend (x minus 1) and enter division loop 29 | >+>+>-[>>>] Increase copy and remainder / reduce divisor / Normal case: skip forward 30 | <[[>+<-]>>+>] Special case: move remainder back to divisor and increase quotient 31 | <<<<<- Decrement dividend 32 | ] End division loop 33 | ]>>>[-]+ End skip loop; zero former divisor and reuse space for a flag 34 | >--[-[<->+++[-]]]<[ Zero that flag unless quotient was 2 or 3; zero quotient; check flag 35 | ++++++++++++<[ If flag then set up divisor (13) for second division loop 36 | (MEMORY LAYOUT: zero copy dividend divisor remainder quotient zero zero) 37 | >-[>+>>] Reduce divisor; Normal case: increase remainder 38 | >[+[<+>-]>+>>] Special case: increase remainder / move it back to divisor / increase quotient 39 | <<<<<- Decrease dividend 40 | ] End division loop 41 | >>[<+>-] Add remainder back to divisor to get a useful 13 42 | >[ Skip forward if quotient was 0 43 | -[ Decrement quotient and skip forward if quotient was 1 44 | -<<[-]>> Zero quotient and divisor if quotient was 2 45 | ]<<[<<->>-]>> Zero divisor and subtract 13 from copy if quotient was 1 46 | ]<<[<<+>>-] Zero divisor and add 13 to copy if quotient was 0 47 | ] End outer skip loop (jump to here if ((character minus 1)/32) was not 2 or 3) 48 | <[-] Clear remainder from first division if second division was skipped 49 | <.[-] Output ROT13ed character from copy and clear it 50 | <-,+ Read next character 51 | ] End character reading loop', 52 | array(65, 66, -1), 53 | array(78, 79), 54 | ), 55 | ); 56 | } 57 | 58 | public function testInstantiation() 59 | { 60 | $language = new Language(); 61 | } 62 | 63 | /** 64 | * @dataProvider provideTestProgram 65 | */ 66 | public function testProgram($program, $input, $expectedOutput) 67 | { 68 | $language = new Language; 69 | $actual = $language->run($program, $input); 70 | $this->assertEquals($expectedOutput, $actual); 71 | } 72 | 73 | /** 74 | * @expectedException LogicException 75 | */ 76 | public function testParseError() 77 | { 78 | $language = new Language; 79 | $language->run('[', array()); 80 | } 81 | 82 | /** 83 | * @expectedException LogicException 84 | */ 85 | public function testParseError2() 86 | { 87 | $language = new Language; 88 | $language->run(']', array()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('BrainFuck', __DIR__.'/../lib/'); 9 | $ClassLoader -> register(); --------------------------------------------------------------------------------