我曾经在Scala编写了几个月的代码,之后我再次被迫用PHP做一些事情。我已经意识到,对于我的项目来说,使用这种语言准备解析器组合器会很方便。
我找到了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);
}
然而这真的很讨厌,它需要在最顶层的范围内定义解析器(这也不是一个好主意)。
你能不能给我一些技巧来帮助我克服这个问题而不需要在定义语法时需要太多的额外代码?
由于