在我再次被迫用 PHP 做一些事情之前,我已经在 Scala 中编码了几个月。我已经意识到,对于我的项目来说,用这种语言准备好解析器组合器会很方便。
我找到了Loco实现,但是我对此感到非常失望(特别是因为它与 Scala 相比非常冗长)。
我开始使用二阶函数自己在 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
组合器成功。将变量作为引用传递应该可以解决这个问题,但是新版本的 PHP 要求在函数声明中指示通过引用传递 - 这在使用具有可变数量参数的函数时是不可能的。
我通过传递一个函数作为参数解决了这个问题,如下所示:
function($input) {
$fn = $GLOBALS['brackets'];
return $fn($input);
}
然而,这真的很讨厌,它需要在最高范围内定义解析器(这也不是一个好主意)。
您能否给我一些技巧,帮助我在定义语法时无需太多额外代码就可以克服这个问题?
谢谢