farafiri / php-parsing-tool
解析库
Requires
- php: >=7.0
Requires (Dev)
- phpunit/phpunit: ^6.5
This package is not auto-updated.
Last update: 2024-09-15 00:49:22 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