farafiri/php-parsing-tool

v2.0.0 2017-12-28 00:13 UTC

README

  • 尽量使用正则表达式的方式便于使用(无需编译,无需额外文件来保存语法)
  • 像其他语法编译器一样强大
  • 返回易于操作的语法树对象
  • 使用方便的语法,结合了BNF符号(思想)和正则表达式(*和+表示重复)的优点
  • 允许处理任何上下文无关语法

示例

假设你有一个包含日期的字符串,格式为d.m.y或y-m-d,由逗号分隔。

  $dates = '2012-03-04,2013-02-08,23.06.2012';

  $parser = new \ParserGenerator\Parser('start     :=> datesList.
                                         datesList :=> date "," datesList
                                                   :=> date.
                                         date      :=> year "-" month "-" day
                                                   :=> day "." month "." year.
                                         year      :=> /\d{4}/.
                                         month     :=> /\d{2}/.
                                         day       :=> /\d{2}/.');

  $parsed = $parser->parse($dates);

  //and now you want to get list of years
  foreach($parsed->findAll('year') as $year) {
    echo $year;
  }

  //this time you want to print all months form year 2012
  foreach($parsed->findAll('date') as $date) {
    if ((string) $date->findFirst('year') === '2012') {
      echo $date->findFirst('month');
    }
  }

更多示例

ParserGenerator\Example命名空间中实现了几个不同的示例解析器,你还可以找到它们的测试用例,例如

  • \ParserGenerator\Examples\CSVParser(以及\ParserGenerator\Tests\Examples\CSVParserTest
  • \ParserGenerator\Examples\JSONParser(以及\ParserGenerator\Tests\Examples\JSONParserTest

分支类型

你可以将前面的语法声明为PEG,这将提高速度10倍。为此,将选项['defaultBranchType' => 'PEG']作为第二个参数添加到Parser。请注意,并非所有语法都可以使用PEG packrat算法解析。

  // by adding 'defaultBranchType' with 'PEG' value into options we declare grammar as PEG
  $parser = new \ParserGenerator\Parser('start :=> start "x"
                                               :=> "x".', array('defaultBranchType' => 'PEG'));
  // but PEG grammar cannot be left recursive, call parse will run infinite loop in this case
  //You have 2 solutions now
  //1-st: you can change grammar a bit:
  $parser = new \ParserGenerator\Parser('start :=> "x" start
                                               :=> "x".', array('defaultBranchType' => 'PEG'));

  //2-nd: use default branch type
  $parser = new \ParserGenerator\Parser('start :=> start "x"
                                               :=> "x".');

错误处理

如果你的输入无法解析,\ParserGenerator\Parser::parse将返回false

使用\ParserGenerator\Parser::getErrorString()并提供你的输入数据以获取可读的错误描述。

或者,你可以使用\ParserGenerator\Parser::getError()直接处理错误节点。

符号

"text"

匹配文本。你也可以使用单引号。你可以使用转义序列,因此"\n"将匹配换行符。

/regular|expression/

匹配给定的正则表达式。你可以使用模式修饰符。语法如"start :=> /[a-z]/i."也将匹配大写字母。正则表达式无法回溯。它们像第一次匹配是唯一的匹配一样工作。例如:"start :=> /a+/ 'a'.",当我们尝试解析字符串"aa"时,正则表达式将捕获两个字符,并且字符串将不匹配。

symbolName

将匹配定义的符号。以下示例将匹配任何一对字母后跟数字。

start  :=> letter digit.
letter :=> /\w/.
digit  :=> /\d/.
whiteSpace, space, newLine, tab

whiteSpace匹配空格、制表符或换行符。如果ignoreWhitespaces模式关闭,这些符号与/\s/、" "、/\t/、/\n/的工作方式相同。如果ignoreWhitespaces模式打开,则/\s/、" "、"\t"、"\n"将不起作用,你必须使用whiteSpace、space、等符号。在ignoreWhitespaces模式下,这些符号检查上下文,而不消耗输入中的字符。例如,序列:"a" newLine space space "b"将匹配字符"a"和"b",它们由至少一个空格和一个换行符分隔

text

匹配任何文本

symbol+

将尝试匹配符号多次(至少一次)。例如start :=> "a"+.将匹配"a" "aa" "aaa"但不匹配""

symbol?

符号是可选的。例如start :=> "a"?.将匹配"a"和""但不匹配"aa"

symbol*

将尝试匹配符号多次(符号是可选的)。例如start :=> "a"*.将匹配"a" "aa" "aaa"和""

symbol++, symbol**, symbol??

与适当的symbol+、symbol*和symbol*相同,但以贪婪的方式消耗它。示例

$nonGreedy = new \ParserGenerator\Parser('start :=> "a"* "a"*.');
$nonGreedy->parse("aaa")->getSubnode(0)->toString(); // "" first "a"* takes nothing
$nonGreedy->parse("aaa")->getSubnode(1)->toString(); // "aaa" so second must consume all left

$greedy = new \ParserGenerator\Parser('start :=> "a"** "a"**.');
$greedy->parse("aaa")->getSubnode(0)->toString(); // "aaa" first "a"** takes all
$greedy->parse("aaa")->getSubnode(1)->toString(); // "" so nothing left for second

$greedy = new \ParserGenerator\Parser('start :=> "a"** "a"+.');
// "aa" "a"** tries to take all but then parsing would fail and he must leave last char for "a"+
$greedy->parse("aaa")->getSubnode(0)->toString();
$greedy->parse("aaa")->getSubnode(1)->toString(); // "a"

如果 'defaultBranchType' 设置为 'PEG',则 symbol* 等于 symbol**(总是贪婪的)。同样,"+" 和 "?" 也是如此。在此模式下,最后一个情况将失败(PEG 无法解析)

?symbol

前瞻。检查符号是否可以解析但不要捕获它。例如 "start :=> 'a' ?/.{3}/ integer. integer :=> /\d+/." 将匹配 "a" 后跟至少 3 个数字。

!symbol

负前瞻。类似于 ?symbol,但只有在不匹配符号的情况下才继续解析

symbol1+symbol2

几个 symbol1 出现,由 symbol2 分隔(*,++,** 类似)

$parser = new \ParserGenerator\Parser('start :=> word+",".
                                       word  :=> /\w+/.');
foreach($parser->parse("a,bc,d")->getSubnode(0)->getSubnodes() as $subnode) {
  echo $subnode . ' ';
} //prints "a , bc , d "

foreach($parser->parse("a,bc,d")->getSubnode(0)->getMainNodes() as $subnode) {
  echo $subnode . ' ';
} //prints "a bc d "

请注意,symbol1+ symbol2 与 symbol1+symbol2 是不同的。"+" 和 symbol2 之间的空格至关重要:"a"+ "b" 匹配 "aaaab",但不匹配 "ababa"。"a"+"b" 匹配 "ababa",但不匹配 "aaaab"。

(symbol1 | symbol2)

选择,匹配 symbol1 或 symbol2。例如 "start :=> ('a' | 'b') 'c'." 将解析字符串 "ac" 和 "bc"。

字符串

正则表达式语法糖,例如:/"([^\\]|\.)*"/ 匹配引号字符串 示例

$parser = new \ParserGenerator\Parser('start :=> string.');
$stringNode = $parser->parse('"a\tb\"c"')->getSubnode(0);
echo (string) $stringNode; //prints:"a\tb\"c"
echo $stringNode->getValue(); //prints:a    b"c

默认情况下,字符串可以用引号或撇号引用。字符串/撇号:只能用撇号引用;字符串/引号:只能用引号引用;字符串/简单:只能用引号引用,没有字符通过逗号转义,引号字符通过重复使用(用于 Pascal 或 CSV 的样式)

数字

当然你可以使用 /\d+/,但使用内置工具包对数字进行操作要容易且可读性更强

//parser matching only integers from 3 to 17 (inclusive)
$parser = new \ParserGenerator\Parser('start :=> 3..17 .');
$parser->parse('2'); //false
$parser->parse('18'); //false
$parser->parse('12'); //syntax tree object

//parser matching only integers > 0
$parser = new \ParserGenerator\Parser('start :=> 1..infinity .');

//parser matching integers in hex decimal and oct
$parser = new \ParserGenerator\Parser('start :=> -inf..inf/hdo .');
$parser->parse('0x21')->getSubnode(0)->getValue(); // 33
$parser->parse('21')->getSubnode(0)->getValue(); //21
$parser->parse('021')->getSubnode(0)->getValue(); //17

//matching month number with leading 0 for < 10
$parser = new \ParserGenerator\Parser('start :=> 01..12 .');
$parser->parse('4'); //false
$parser->parse('04'); //syntax tree object
time()

匹配给定格式的日期

$parser = new \ParserGenerator\Parser('start :=> (time(Y-m-d) | time(d.m.Y)) .');
$parser->parse('2017-01-02')->getSubnode(0)->getValue(); // equal to new \DateTime('2017-01-02')
$parser->parse('03.05.2014')->getSubnode(0)->getValue(); // equal to new \DateTime('2014-05-03')
包含,是

有时你可能想在解析的节点上进行额外的检查。多亏了这些结构,你可以检查节点是否包含某些文本或是否匹配某个模式

$parser = new \ParserGenerator\Parser('start   :=> word not is keyword.
                                       word    :=> /\w+/.
                                       keyword :=> ("do" | "while" | "if").');
$parser->parse('do'); //false
$parser->parse('doSomething'); // syntax tree object

可以在检查上进行一些基本的逻辑操作,并将它们放入大括号中

$parser = new \ParserGenerator\Parser('start   :=> word not(is keyword or
                                                            is ("p" text /* we don`t want words starting with "p" */) or
                                                            is /./ /* we don`t want one letter words */ ).
                                       word    :=> /\w+/.
                                       keyword :=> ("do" | "while" | "if").');
$parser->parse('do'); //false
$parser->parse('d'); //false
$parser->parse('post'); //false
$parser->parse('doSomething'); // syntax tree object
unorder(separator, choice1, choice2...)

当预期有多个元素以任何顺序出现时,应使用 unorder

x :=> unorder(s, A, B, C).
/* is equivalent to */
x :=> A s B s C
  :=> A s C s B
  :=> B s A s C
  :=> B s C s A
  :=> C s A s B
  :=> C s B s A.

默认情况下,每个元素都期望恰好出现一次,但你也可以更改它

x :=> unorder(s, ?A, *B, +C).
A is optional
B may be used multiple (or zero) times
C may be used multiple (at least once) times

至少需要一个元素

$parser = new \ParserGenerator\Parser('start   :=> unorder("", ?"a", ?"b").');

$parser->parse('a'); //syntax tree object
$parser->parse('b'); //syntax tree object
$parser->parse(''); //false