数か月間 Scala でコーディングしていたのですが、再び PHP で何かをやらなければならなくなりました。私のプロジェクトでは、この言語でパーサー コンビネーターを用意すると便利であることに気付きました。
私はLocoの実装を見つけましたが、それには大いにがっかりしました (特に、Scala に比べて非常に冗長であるという事実のため)。
2 次関数を使用して、PHP にパーサー コンビネーターを自分で実装し始めました。正規表現パーサーの例は次のとおりです。
interface Result {};
class Success implements Result { function __construct($payload, $next) { $this->payload = $payload; $this->next = $next; } }
class Failure implements Result { function __construct($payload, $next) { $this->payload = $payload; $this->next = $next; } }
function r($regex) {
return function($input) use ($regex) {
if(preg_match($regex, $input, $matches)) {
return new Success($matches[0], substr($input, strlen($matches[0])));
} else {
return new Failure('Did not match', $input);
}
};
}
そして、cons
コンビネータの例として:
function consF($fn) {
$args = array_slice(func_get_args(), 1);
return function($input) use ($fn, $args) {
$matches = array();
foreach($args as $p) {
$r = $p(ltrim($input));
if($r instanceof Failure) return $r;
$input = $r->next;
$matches[] = $r->payload;
}
return new Success($fn($matches), $input);
};
}
これにより、パーサーを非常にコンパクトに書くことができます - 次のように:
$name = r('/^[A-Z][a-z]*/');
$full_name = consF(function($a) { return $a; }, $name, $name);
問題は、文法を再帰的にする必要がある場合に発生します。そのような場合、一度使用するとすべての変数が定義されるように変数を並べ替えることができません。例えば。次のようなものが必要になるように、ブラケットの入力を解析する文法を書くために(()())
:
$brackets = alt('()', cons('(', $brackets, ')'));
alt
代替案の 1 つが成功すると、コンビネータが成功します。変数を参照として渡すことで解決するはずですが、新しいバージョンの PHP では、関数宣言で参照渡しを指定する必要があります。これは、可変数の引数を持つ関数を使用する場合には不可能です。
次のように関数を引数として渡すことで、この問題を解決しました。
function($input) {
$fn = $GLOBALS['brackets'];
return $fn($input);
}
ただし、これは非常に厄介で、パーサーを最上位のスコープで定義する必要があります (これも良い考えではありません)。
文法を定義する際に追加のコードをあまり必要とせずに、この問題を克服するのに役立つトリックを教えてください。
ありがとう