这个周末有一些空闲时间,我和一个hackerfuck解释器一起破解了,目的是向朋友解释PHP的OO风格。这是故意过分设计的,并且并未真正考虑性能(或者我不会一开始就用PHP编写)。

有什么想法?批评?饼干?

我对设计的评论更感兴趣,但与任何评论一样,一切都是公平的游戏。代码很多,所以我删除了注释,一些空格和一个或两个接口。您可以在github上查看完整代码和测试。

示例

$source = "5,2,10,1!!>>,[>>,]<<[[-<+<]>[>[>>]<[.[-]<[[>>+<<-]<]>>]>]<<]";
$bf     = new \Brainfart\Brainfart();
$output = $bf->run($source);


$output将是:

array (size=4)
  0 => int 1
  1 => int 2
  2 => int 5
  3 => int 10




解释器

namespace Brainfart;

use Brainfart\VM\Output;
use Brainfart\Parser\Parser;
use Brainfart\VM\VM;

class Brainfart {
    private $optimize = true;
    private $vm;
    private $parser;
    private $output;

    public function __construct($loopLimit = 100, $optimize = true) {
        $this->vm     = new VM(array(), $loopLimit);
        $this->parser = new Parser();

        $this->setOptimize($optimize);
    }

    public function setOptimize($optimize = true) {
        $this->optimize = ($optimize === true);

        return $this;
    }

    public function run($source, $input = "", $fetchMode = Output::FETCH_ARRAY) {
        $this->parser->loadSource($source);
        if ($this->parser->getFlag("string_output") === true) $fetchMode = Output::FETCH_STRING;

        $appLoop  = $this->parser->parse($this->optimize);
        $appInput = $this->parser->getInput();
        if (!empty($appInput)) $input = $appInput;

        $this->vm->init($input);

        $appLoop->execute($this->vm);

        return $this->vm->getOutput()->fetch($fetchMode);
    }
}





虚拟机

VM

namespace Brainfart\VM;

class VM {
    private $input;
    private $output;
    private $memory;
    private $loopLimit = 0;

    public function __construct($input = array(), $loopLimit = 0) {
        $this->init($input, $loopLimit);
    }

    public function init($input = array(), $loopLimit = 0) {
        $this->input  = new Input($input);
        $this->output = new Output();
        $this->memory = new Memory();

        $this->setLoopLimit($loopLimit);

        return $this;
    }

    public function getInput() { return $this->input; }

    public function getOutput() { return $this->output; }

    public function getMemory() { return $this->memory; }

    public function setLoopLimit($loopLimit = 100) {
        $this->loopLimit = is_numeric($loopLimit) && $loopLimit > 0 ? (int) $loopLimit : 0;

        return $this;
    }

    public function getLoopLimit() { return $this->loopLimit; }
}


内存

namespace Brainfart\VM;

class Memory {
    private $memory = array();
    private $pointer = 0;

    public function move($value) {
        $this->pointer += is_numeric($value) ? (int) $value : 0;

        return $this;
    }

    public function fetch() {
        return isset($this->memory[$this->pointer]) ? $this->memory[$this->pointer] : 0;
    }

    public function store($value) {
        $this->memory[$this->pointer] = $this->fetch() + (is_numeric($value) ? (int) $value : 0);

        return $this;
    }    
}    


输入

namespace Brainfart\VM;

class Input {
    private $input = array();

    public function __construct($input) {
        $this->store($input);
    }

    public function store($input) {
        if (is_scalar($input)) $input = str_split(trim($input));
        if (!is_array($input)) throw new \InvalidArgumentException();

        foreach ($input as $key => $value) $input[$key] = is_numeric($value) ? (int) $value : ord($value);

        $this->input = $input;

        return $this;
    }

    public function fetch() {
        return
            !(empty($this->input))
                ? array_shift($this->input)
                : 0;
    }    
}


输出

namespace Brainfart\VM;

class Output {
    const FETCH_ARRAY  = 0;
    const FETCH_STRING = 1;

    private $output = array();

    public function store($value) {
        $this->output[] = $value;

        return $this;
    }

    public function fetch($fetchMode = self::FETCH_ARRAY) {
        return
            ($fetchMode === self::FETCH_STRING)
                ? implode("", array_map("chr", $this->output))
                : $this->output;
    }    
}




虚拟机操作

ChangeOperation

namespace Brainfart\Operations;

use Brainfart\VM\VM;

class ChangeOperation implements OperationInterface, MutableInterface {
    use MutableTrait;

    public function execute(VM $vm) {
        $vm->getMemory()->store($this->getValue());
    }

}


InputOperation

namespace Brainfart\Operations;

use Brainfart\VM\VM;

class InputOperation implements OperationInterface {

    public function execute(VM $vm) {
        $vm->getMemory()->store($vm->getInput()->fetch());
    }
}


LoopOperation

namespace Brainfart\Operations;

use Brainfart\VM\VM;

class LoopOperation implements OperationInterface {

    private $master = false;
    private $operations = array();

    public function __construct(array $operations, $master = false) {

        $this->setOperations($operations)->setMaster($master);
    }

    public function setOperations(array $operations) {
        $this->operations = $operations;

        return $this;
    }

    public function getOperations() { return $this->operations; }

    public function setMaster($master) {
        $this->master = ($master === true);    
        return $this;
    }

    public function getMaster() { return $this->master; }

    public function execute(VM $vm) {
        $operations = $this->getOperations();
        $limit      = $vm->getLoopLimit();

        $i = 0;
        while (
            $this->getMaster() // master loop is the whole app, runs regardless of memory value
            || ($vm->getMemory()->fetch() != 0)
        ) {
            foreach ($operations as $operation) 
                $operation->execute($vm);

            if ($this->getMaster()) break;

            $i++;
            if ($limit > 0 && $limit < $i) throw new \RuntimeException("Limit of {$limit} operations per loop reached.");
        }
    }

}


MoveOperation

namespace Brainfart\Operations;

use Brainfart\VM\VM;

class MoveOperation implements OperationInterface, MutableInterface {
    use MutableTrait;

    public function execute(VM $vm) {
        $vm->getMemory()->move($this->getValue());
    }    
}


OutputOperation

namespace Brainfart\Operations;

use Brainfart\VM\VM;

class OutputOperation implements OperationInterface {

    public function execute(VM $vm) {
        $vm->getOutput()->store($vm->getMemory()->fetch());
    }    
}


SleepOperation

这就是你的转动方式好笑话变成坏话。

namespace Brainfart\Operations;

use Brainfart\VM\VM;

class SleepOperation implements OperationInterface {    
    public function execute(VM $vm) { sleep($vm->getMemory()->fetch()); }    
}


MutableTrait

namespace Brainfart\Operations;

trait MutableTrait {
    private $value;

    public function __construct($value) { $this->setValue($value); }

    public function setValue($value) {
        $this->value = is_numeric($value) ? (int) $value : 0;

        return $this;
    }

    public function getValue() { return $this->value; }

    public function combine(MutableInterface $operation) {
        $class = get_class($this);
        if ($operation instanceof $class) {
            $this->setValue($this->getValue() + $operation->getValue());

            return $this;
        }    
        return false;
    }
}





>解析器

Loader

namespace Brainfart\Parser;

class Loader {
    private $input;
    private $source = "";
    private $flags = array();

    public function __construct($source = null) {
        if (!is_null($source)) $this->loadSource($source);

        $this->setFlag("no_optimization", false)->setFlag("string_output", false);
    }

    public function loadSource($source) {
        if (is_file($source)) $source = @ file_get_contents($source);
        if (!is_string($source)) throw new \InvalidArgumentException();

        $source = $this->prepare($source);
        $source = $this->skintoad($source);
        $source = $this->cleanup($source);

        return $this->source = $source;
    }

    public function getSource() { return $this->source; }

    public function getInput() { return $this->input; }

    public function getFlag($flag = null) {
        if (is_null($flag) || !is_scalar($flag)) return $this->flags;

        $flag = strtolower(trim($flag));

        return isset($this->flags[$flag]) ? $this->flags[$flag] : null;
    }

    protected function setFlag($flag, $value = null) {
        $flag = (!is_scalar($flag)) ? "unknown" : strtolower(trim($flag));
        if (!is_null($value)) $value = ($value === true);

        $this->flags[$flag] = $value;

        return $this;
    }

    public function setInput($input) {
        if (!is_scalar($input)) throw new \InvalidArgumentException();

        $input = (string) $input;
        $input = trim($input, ", ");
        $input = explode(",", $input);
        $input = array_map("trim", $input);

        $this->input = $input;

        return $this;
    }

    private function prepare($source) {
        $flags = array("@@" => "no_optimization", "$$" => "string_output");

        foreach ($flags as $operator => $flag) {
            if (strpos($source, $operator) !== false) {
                $this->setFlag($flag, true);
                $source = str_replace($operator, "", $source);
            }
        }

        $pos = strpos($source, "!!");
        if ($pos !== false) {
            $input  = substr($source, 0, $pos);
            $source = substr($source, $pos + 2);

            $this->setInput($input);
        }

        return preg_replace('/\s+/', "", strtolower($source));
    }

    private function skintoad($source) {
        if (!preg_match_all('/:(.*?);/', $source, $matches)) return $source;

        foreach ($matches[0] as $match) {
            $source = str_replace($match, "", $source);
            $match  = trim($match, ":;");
            if (preg_match('/^[a-zA-Z0-9_]*/', $match, $identifier)) {
                $identifier = $identifier[0];
                $sequence   = str_replace($identifier, "", $match);
                $source     = str_replace($identifier, $sequence, $source);
            }
        }

        return $source;
    }

    private function cleanup($source) {
        return preg_replace('/[^<|>|\-|\+|\.|\~|\,|\]|\[]/', "", $source);
    }

}


解析器

namespace Brainfart\Parser;

use Brainfart\Operations\LoopOperation;
use Brainfart\Operations\SleepOperation;
use Brainfart\Operations\ChangeOperation;
use Brainfart\Operations\MoveOperation;
use Brainfart\Operations\InputOperation;
use Brainfart\Operations\OutputOperation;
use Brainfart\Operations\MutableInterface;

class Parser extends Loader {
    private $operations;

    public function parse($optimize = true) {
        if ($this->getFlag("no_optimization") === true) $optimize = false;

        $operations = $this->tokenize($this->getSource(), $optimize);

        return $this->operations = new LoopOperation($operations, true);
    }

    public function getOperations() { return $this->operations; }

    private function tokenize($source, $optimize) {
        $result   = array();
        $optimize = $optimize === true;
        $length   = strlen($source);

        for ($i = 0; $i < $length; $i++) {
            $token = isset($source[$i]) ? $source[$i] : false;
            if (!$token) break;

            if ($token == "[") {
                $loopEnd = $this->findLoopEnd(substr($source, $i + 1));
                if (!$loopEnd) throw new \LogicException("Neverending loop.");

                $loopSource = substr($source, $i + 1, $loopEnd);
                $loopTokens = $this->tokenize($loopSource, $optimize);
                $operation  = new LoopOperation($loopTokens);

                $i += $loopEnd + 1;
            } else {
                $operation = $this->getOperation($token);
                if (!$operation) continue;

                if ($optimize && ($operation instanceof MutableInterface)) {
                    $index    = count($result) - 1;
                    $previous = isset($result[$index]) ? $result[$index] : false;
                    $combined = ($previous instanceof MutableInterface) ? $previous->combine($operation) : false;

                    if ($combined) {
                        $result[$index] = $combined;
                        continue;
                    }
                }
            }    
            $result[] = $operation;
        }    
        return $result;
    }

    private function findLoopEnd($source) {
        $posCloseBracket = strpos($source, "]");
        $posOpenBracket  = strpos($source, "[");

        if ($posOpenBracket === false || $posCloseBracket < $posOpenBracket) return $posCloseBracket;
        $source[$posOpenBracket] = $source[$posCloseBracket] = "_";

        return $this->findLoopEnd($source);
    }

    private function getOperation($token) {
        $operation = false;
        switch ($token) {
            case ">":
                $operation = new MoveOperation(1);
                break;
            case "<":
                $operation = new MoveOperation(-1);
                break;
            case "+":
                $operation = new ChangeOperation(1);
                break;
            case "-":
                $operation = new ChangeOperation(-1);
                break;
            case ".":
                $operation = new OutputOperation();
                break;
            case ",":
                $operation = new InputOperation();
                break;
            case "~":
                $operation = new SleepOperation();
        }    
        return $operation;
    }    
}


#1 楼

因此,对于初学者来说,让操作变得懒惰(是的,令人惊讶的是,Haskeller说让它变得懒惰)。


class LoopOperation implements OperationInterface {

    private $master = false;
    private $operations = array();

    public function __construct(array $operations, $master = false) {

        $this->setOperations($operations)->setMaster($master);
    }


然后,您可以在解释公共数组时将其添加到操作中,或者创建addOperation方法(这两种方法都可以构造LoopOperation和然后随着解析的继续,向其中添加单独的操作。

然后是一些解析器组合器,它们的错误monad为ya(我不知道PHP,所以请多多包涵):

class LoopOperation implements OperationInterface {

    public $master = false;
    public $operations = array();


仔细研究,您会发现添加了所有操作的循环操作对象在循环解释完成后会丢失,因此,我建议使用而不是我使用线程的方式所有功能的源代码,实际上创建了一个具有源代码和VM的结构,因此这些函数可以解析源代码并将操作推送到VM上,结果代码将使您能够o编写如下内容:

abstract class M
{
    abstract public function then($f); // Formally this is 'bind', the f is the function to bind to the action

    abstract public function otherwise($f); // Formally this would be an application of an `alternative` or monoid

    public $a; // The a is for 'action'
}

class Success extends M
{
    public function __construct($a) {
        $this->a = $a;
    }

    public function then($f) {
        return $f($this->a);
    }

    public function otherwise($f) {
        return $this;
    }
}

class Failure extends M
{
    public function __construct($a) {
        $this->a = $a;
    }

    public function then($f) {
        return $this;
    }

    public function otherwise($f) {
        return new Success($this->a)->then($f);
    }
}

public function pop($a) {
    return new Success(substr($a, 1));
}

public function charIs($char) {
    return function($a) use($char) {
        if ($a[0] == $char) {
            return new Success($a);
        }

        return new Failure($a);
    }
}

public function charExists($char) {
    return function($a) use($char) {
        if (strpos($a, $char) === false) {
            return new Failure($a);
        }

        return new Success($a);
    }
}


public function addLoopOperation($operation) {
    return function($a) user ($operation) {
        $operation->addOperation(strpos($a, 0, 1));
        return new Success($a);
    }
}

public function throwException($ex) {
    return function($a) use ($ex) {
        throw $ex;
    }
}

public function addLoop($operation = new LoopOperation()) {
    return function($a) use (&$operation) {
        return new Success($a)
            ->then(charExists("]"))->otherwise(throwException(new LogicException("Neverending loop."))
            ->then(addLoopOperation($operation))
            ->then(pop)
            ->then(charIs("]"))->then(pop)->otherwise(addLoop($operation));
    }

    // Because it's recursed the way it is, it will continue adding operations until it hits the char "]"
}