Mise à jour le 13/11/2021
3 kyu - Esolang Interpreters 4 - Boolfuck Interpreter

3 kyu - Esolang Interpreters 4 - Boolfuck Interpreter

1. Le challenge

URL : https://www.codewars.com/kata/5861487fdb20cff3ab000030/train/php

1.1 Instructions

1.1.1 The Language

Boolfuck is an esoteric programming language (Esolang) based on the famous Brainfuck (also an Esolang) which was invented in 2004 or 2005 according to the official website. It is very similar to Brainfuck except for a few key differences:

* Boolfuck works with bits as opposed to bytes
* The tape for Brainfuck contains exactly 30,000 cells with the pointer starting from the very left; Boolfuck contains an infinitely long tape with the pointer starting at the "middle" (since the tape can be extended indefinitely either direction)
* Each cell in Boolfuck can only contain the values 0 or 1 (i.e. bits not bytes) as opposed to Brainfuck which has cells containing values ranging from 0 to 255 inclusive.
* The output command in Boolfuck is ; NOT .
* The - command does not exist in Boolfuck since either + or - would flip a bit anyway

Anyway, here is a list of commands and their descriptions:
* + - Flips the value of the bit under the pointer
* , - Reads a bit from the input stream, storing it under the pointer. The end-user types information using characters, though. Bytes are read in little-endian order—the first bit read from the character a, for instance, is 1, followed by 0, 0, 0, 0, 1, 1, and finally 0. If the end-of-file has been reached, outputs a zero to the bit under the pointer.
* ; - Outputs the bit under the pointer to the output stream. The bits get output in little-endian order, the same order in which they would be input. If the total number of bits output is not a multiple of eight at the end of the program, the last character of output gets padded with zeros on the more significant end.
* < - Moves the pointer left by 1 bit
* > - Moves the pointer right by 1 bit
* [ - If the value under the pointer is 0 then skip to the corresponding ]
* ] - Jumps back to the matching [ character, if the value under the pointer is 1

1.1.2 The Task

Write a Boolfuck interpreter which accepts up to two arguments. The first (required) argument is the Boolfuck code in the form of a string. The second (optional) argument is the input passed in by the end-user (i.e. as actual characters not bits) which should default to "" if not provided. Your interpreter should return the output as actual characters (not bits!) as a string.

function boolfuck (code, input = "")

Preloaded for you is a function brainfuckToBoolfuck()/brainfuck_to_boolfuck()/BrainfuckToBoolfuck() which accepts 1 required argument (the Brainfuck code) and returns its Boolfuck equivalent should you find it useful.

Please note that your interpreter should simply ignore any non-command characters. This will be tested in the test cases.

If in doubt, feel free to refer to the official website (link at top).

Good luck :D

1.2 Your code

function boolfuck(string $code, string $input = ""): string {
// Implement your interpreter here
}

1.3 Sample Tests

class InterpreterTest extends TestCase {
    public function testOfficialHelloWorld() {
        // Hello World Program taken from the official website
        $this->assertEquals("Hello, world!\n", boolfuck(";;;+;+;;+;+;
        +;+;+;+;;+;;+;
        ;;+;;+;+;;+;
        ;;+;;+;+;;+;
        +;;;;+;+;;+;
        ;;+;;+;+;+;;
        ;;;;;+;+;;
        +;;;+;+;;;+;
        +;;;;+;+;;+;
        ;+;+;;+;;;+;
        ;;+;;+;+;;+;
        ;;+;+;;+;;+;
        +;+;;;;+;+;;
        ;+;+;+;"), "Your interpreter did not work with the code example provided on the official website");
    }
    public function testMoreExamples() {
        // Echo until byte(0) encountered
        $this->assertEquals("Codewars", boolfuck(">,>,>,>,>,>,>,>,>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>;>;>;>;>;>;>;>;>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]>,>,>,>,>,>,>,>,>+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]", "Codewars" . chr(0)));
        // Two numbers multiplier
        $this->assertEquals(chr(72), boolfuck(">,>,>,>,>,>,>,>,>>,>,>,>,>,>,>,>,<<<<<<<<+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]>[>]+<[+<]>>>>>>>>>[+]>[>]+<[+<]>>>>>>>>>[+]<<<<<<<<<<<<<<<<<<+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]>>>>>>>>>>>>>>>>>>>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+<<<<<<<<[>]+<[+<]>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]<<<<<<<<<<<<<<<<<<<<<<<<<<[>]+<[+<]>>>>>>>>>[+]>>>>>>>>>>>>>>>>>>+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]<<<<<<<<<<<<<<<<<<+<<<<<<<<+[>+]<[<]>>>>>>>>>[+]+<<<<<<<<+[>+]<[<]>>>>>>>>>]<[+<]>>>>>>>>>>>>>>>>>>>;>;>;>;>;>;>;>;<<<<<<<<", chr(8) . chr(9)));
    }
}

2. Proposition de solution

function boolfuck(string $code, string $input = ""): string {
    $i = 0;
    $data = [];
    $inputs = str_split($input);
    $bitInput = '';
    $bitInputs = [];

    if ($input !== '') {
        foreach ($inputs as $letter) {
            $bitInput .= str_pad(strrev(decbin(ord($letter))) , 8, '0', STR_PAD_RIGHT);
        }
        $bitInputs = str_split($bitInput);
    }
    
    $ptr = 0;
    $result = '';
    do {
        $instruction = $code[$i];

        switch ($instruction) {
            // > Moves the pointer right by 1 bit.
            case '>':
                ++$ptr;
                break;
            // < Moves the pointer left by 1 bit.
            case '<':
                --$ptr;
                break;
            // + Flips the value of the bit under the pointer
            case '+':
                $data = initializeData($data, $ptr);
                $data[$ptr] = (int) !$data[$ptr];
                break;
            // ; Outputs the bit under the pointer to the output stream. 
            // The bits get output in little-endian order, the same order in which they would be input. 
            // If the total number of bits output is not a multiple of eight at the end of the program, 
            // the last character of output gets padded with zeros on the more significant end.
            case ';':
                $data = initializeData($data, $ptr);
                $result .= $data[$ptr];
                break;
            // , Reads a bit from the input stream, storing it under the pointer. 
            // The end-user types information using characters, though. 
            // Bytes are read in little-endian order—the first bit read from the character 
            case ',':
                if (count($bitInputs) > 0) {
                    $dataToAdd = array_shift($bitInputs);
                    $data[$ptr] = $dataToAdd;
                }
                break;
            // [  If the value under the pointer is 0 then skip to the corresponding ].
            case '[':
                $data = initializeData($data, $ptr);
                if (0 === $data[$ptr]) {
                    $nbDoors = 0;
                    ++$i;
                    do {
                        if ('[' === $code[$i]) {
                            ++$nbDoors;
                        } elseif ($nbDoors > 0 && ']' === $code[$i]) {
                            --$nbDoors;
                        }
                        ++$i;
                    } while (!(']' === $code[$i] && $nbDoors === 0));
                }
                break;
            // ] Jumps back to the matching [ character.
            case ']':
                $data = initializeData($data, $ptr);
                if (1 === $data[$ptr]) {
                    $nbDoors = 0;
                    --$i;
                    do {
                        if (']' === $code[$i]) {
                            ++$nbDoors;
                        } elseif ($nbDoors > 0 && '[' === $code[$i]) {
                            --$nbDoors;
                        }
                        
                        --$i;
                    } while (!('[' === $code[$i] && $nbDoors === 0));
                }
                break;
        }
        ++$i;

    } while (isset($code[$i]));

    $decodedResult = '';
    foreach (array_chunk(str_split($result), 8) as $resultChunk) {
        $decodedResult .= chr(bindec(strrev(implode('', $resultChunk))));
    }
    
    return $decodedResult;
}

function initializeData($data, $ptr)
{
    if (!isset($data[$ptr])) {
        $data[$ptr] = 0;
    }

    return $data;
}