PHP正则表达式解析 - 用我自己的语言拆分令牌。有没有更好的办法?

时间:2014-09-26 15:49:43

标签: php string parsing

我正在创建自己的语言。

目标是将其“编译”为PHP或Javascript,并最终解释并使用相同的语言运行它,使其看起来像“中级”语言。

现在,我专注于在PHP中解释它并运行它。

目前,我正在使用正则表达式来分割字符串并提取多个标记。

这是我的正则表达式:

/\:((?:cons@(?:\d+(?:\.\d+)?|(?:"(?:(?:\\\\)+"|[^"]|(?:\r\n|\r|\n))*")))|(?:[a-z]+(?:@[a-z]+)?|\^?[\~\&](?:[a-z]+|\d+|\-1)))/g

这很难阅读和维护,即使它有效。

有更好的方法吗?

以下是我语言代码的示例:

:define:&0:factorial
    :param:~0:static
    :case
        :lower@equal:cons@1
    :case:end
    :scope
        :return:cons@1
    :scope:end
    :scope
        :define:~0:static
        :define:~1:static
        :require:static
        :call:static@sub:^~0:~1 :store:~0
        :call:&-1:~0 :store:~1
        :call:static@sum:^~0:~1 :store:~0
        :return:~0
    :scope:end
:define:end

这定义了一个递归函数来计算阶乘(不太好写,不重要)。

我们的目标是获得 :之后的,包括@:static@sub是一个完整的令牌,在没有:的情况下保存它。

除了令牌:cons之外,所有内容都是相同的,后者可以取值。该值是一个数值(integerfloat,分别在语言中称为staticdynamic)或字符串,必须以{{1开头和结尾},支持像"这样的转义。不支持多行字符串。

变量是\"的变量,之前使用~0将获得上述^的值。

函数类似,使用:scope&0指向当前函数(此处不需要&-1)。

说这个,有没有更好的方法来获得令牌?

您可以在此处看到它:http://regex101.com/r/nF7oF9/2

1 个答案:

答案 0 :(得分:3)

[更新]要发布模式复杂且可维护性,您可以使用PCRE_EXTENDEDcomments对其进行拆分:

preg_match('/
  # read constant (?)
  \:((?:cons@(?:\d+(?:\.\d+)?|
  # read a string (?)
  (?:"(?:(?:\\\\)+"|[^"]|(?:\r\n|\r|\n))*")))|
  # read an identifier (?)
  (?:[a-z]+(?:@[a-z]+)?|
  # read whatever 
  \^?[\~\&](?:[a-z]+|\d+|\-1)))
  /gx
', $input)

请注意忽略所有空格,除非在某些条件下(\n通常是“安全的”)。


现在,如果你想让皮条客和解析器皮条客,那么请阅读:

(f)lex [GNU等效于LEX]只是让你传递一个regexp列表,最后是一个“group”。您还可以尝试使用ANTLRPHP Target Runtime来完成工作。

至于你的要求,我在过去遵循FLEX的原则制作了词法分析器。我的想法是像FLEX那样循环使用正则表达式:

$regexp = [reg1 => STRING, reg2 => ID, reg3 => WS];
$input = ...;
$tokens = [];
while ($input) {
  $best = null;
  $k = null;
  for ($regexp as $re => $kind) {
    if (preg_match($re, $input, $match)) {
      $best = $match[0];
      $k = $kind;
      break;
    }
  }

  if (null === $best) {
    throw new Exception("could not analyze input, invalid token");
  }

  $tokens[] = ['kind' => $kind, 'value' => $best];

  $input = substr($input, strlen($best)); // move.
}

由于FLEX和Yacc / Bison集成,通常的模式是读取直到下一个标记(也就是说,它们在解析之前不会执行读取所有输入的循环)。

$regexp数组可以是任何内容,我希望它是"regexp" => "kind"键/值,但你也可以使用这样的数组:

$regexp = [['reg' => '...', 'kind' => STRING], ...]

您还可以使用组启用/禁用regexp(例如FLEX组工作):例如,请考虑以下代码:

class Foobar {
  const FOOBAR = "arg";
  function x() {...}  
}

在您需要读取表达式之前,不需要激活字符串regexp(此处,表达式是“=”之后的表达式)。当您实际在class时,无需激活class标识符。

FLEX的小组允许使用第一个正则表达式读取注释,激活一些忽略其他正则表达式的组,直到某些匹配完成(如“*/”)。

请注意,这种方法是一种天真的方法:像FLEX这样的词法分析器实际上会生成一个自动机,它使用不同的状态来表示你的需要(正则表达式本身就是一个自动机)。

这使用了打包索引或类似的算法(我使用了每个“天真”,因为我不太了解算法),这是内存和速度效率。

正如我所说,这是我过去做的事情 - 就像6/7年前一样。

  • 它在Windows上。
  • 不是特别快(因为两个循环,它是 O(N²)。)
  • 我认为PHP每次都在编译正则表达式。现在我做了Java,我使用了一次编译regexp的Pattern实现,让你重用它。我不知道如果已经有一个已编译的正则表达式,首先查看正则表达式缓存,PHP会做同样的事情。
  • 我正在使用带有偏移量的preg_match,以避免在结尾处执行substr($input, ...)

您应该尝试使用ANTLR3 PHP代码生成目标,因为ANTLR语法编辑器非常易于使用,并且您将拥有更易读/可维护的代码:)