1

在我再次被迫用 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);
}

然而,这真的很讨厌,它需要在最高范围内定义解析器(这也不是一个好主意)。

您能否给我一些技巧,帮助我在定义语法时无需太多额外代码就可以克服这个问题?

谢谢

4

0 回答 0