charm / parsing
PEG语法解析器,用于解析大多数上下文无关语法,例如编程语言、数据库查询和数学表达式。
Requires
- charm/map: ^1.0.2
- charm/options: ^1.0
- charm/util-phpencode: ^1.0
- charm/vector: ^1.0
- psr/log: >=1.0
Requires (Dev)
- nikic/php-parser: ^4.13
- phpunit/phpunit: ^9
README
这是一个解析库,可以轻松地将句子、编程语言以及自定义配置语言解析成任何东西。
使用此库,您可以轻松创建以下功能
calc("1+2^3*4");
interpret("if visitorCount > 10 return true; else return false;");
事实上,要解析上述表达式,您只需创建以下类
<?php
class MyExpressionParser extends Charm\Parser {
const GRAMMAR = <<<'GRAMMAR
Expr
= Expr "^" Expr %assoc=right
<?
return $_0 ^ $_2;
?>
| Expr ("*" | "/") Expr <?
if ($_1 == "*")
return $_0 * $_2;
else
return $_0 / $_2;
?>
| Expr ("+" | "-") Expr <?
if ($_1 == "+")
return $_0 + $_2;
else
return $_0 - $_2;
?>
| Number
Number
= /[1-9][0-9]*|0/ <? return intval($_0); ?>
GRAMMAR;
}
要使用此表达式解析器
<?php
$parser = new MyExpressionParser();
echo $parser->parse("1+2"); // output: 3
快速入门
开始解析的最简单方法是使用Charm\cpeg_parse()
方法。
语法
cpeg_parse(string $expression, string $subject, array $initialState=[]): ?array;
解析JSON的示例
use function Charm\cpeg_parse;
$expression = '
Value = Number | String | "true" | "false" | "null" | Object | Array
Array = "[" (Value ("," Value)*)? "]"
Object = "{" (String ":" Value ("," String ":" Value)*)? "}"
Number = /(0|[1-9][0-9]*)(\.[0-9]+)?/
String = /"(\\[\\"]|[^"])*"/
';
$result = cpeg_parse($expression, '[1,2,"A string",{"key":[3,4]}');
解析表达式的示例
use function Charm\cpeg_parse;
$expression = '
Expr = Expr "^" Expr %assoc=right
| Expr ("*" | "/") Expr
| Expr ("+" | "-") Expr
| Number
Number = /(0|[1-9][0-9]*)(\.[0-9]+)?/
';
$result = cpeg_parse($expression, '1*2+3*4');
构建解析树
通常,解析器将生成一个结构化数组,您可以直接从语法中使用的终端符号开始遍历。
如果您想在解析过程中生成解析树或其他方式处理数据,可以在语法中嵌入代码片段,通过将PHP代码包围在<?
和?>
标记中实现。
use function Charm\cpeg_parse;
$expression = '
Expr = Expr "^" Expr %assoc=right
<?
return $_0 ^ $_2;
?>
| Expr ("*" | "/") Expr
<?
if ($_1 === "*") return $_0 * $_2; ?>
else return $_0 / $_2;
?>
| Expr ("+" | "-") Expr
<?
if ($_1 === "+") return $_0 + $_2; ?>
else return $_0 - $_2;
?>
| Number
<?
return floatval($_0);
?>
Number = /(0|[1-9][0-9]*)(\.[0-9]+)?/
';
$result = cpeg_parse($expression, '1*2+3*4');
解析简介
网上有大量关于编写语法的示例。它与解析正则表达式非常相似,但语法更简单、更详细。
该语法称为正在解析的任何语言的语法。
术语
解析学中存在大量术语。要完全理解所有这些是困难的。以下是一个示例“语法”,我们将用它来解释在讨论语法和解析时最重要的术语
Statement
= Expression
Expression
= Expression "+" Expression
| Expression "-" Expression
| Number
Number
= /[0-9]+/
上面的语法是用BNF形式编写的。
语法:语法包含一组用于解析字符串的规则。对于程序员来说,这就像
an analog to a full program. `Statement`, `Expression` and `Number` are the names
of the rules in the above grammar.
BNF:代表“Backus-Naur形式”,是此类语法的通用术语。
规则:规则是语法的主体组成部分。对于程序员来说,这就像
functions. Rules consists of a `Clause` which is similar to an expression in
a programming language. A clause can be very simple, like in `Statement` where
the clause is simply `Expression`. Clauses can be more advanced, like in
`Expression` where you see the or-*operator* `|` as well as `"+"` and `"-"`.
目标:语法中的第一个规则是我们正在解析的目标。在上面的
grammar you can see that `Statement` is the goal.
子句:如果一个规则是函数,那么子句就是一个函数调用。有两种类型
of clauses; built-in clauses which actually match the source code, and clauses
which invoke a rule. The built-in clauses are called *terminal clauses*, and
clauses which invoke other rules are called *non-terminal clauses*.
非终端子句:一个简单地引用另一个规则的子句。
终端子句:一个实际上会匹配源字符串并将解析
process forward or cause it to fail. See below for a summary of all the
terminal clauses supported in this parser.
运算符:如果您想接受“+”或“-”,您可以使用|
运算符,如下所示
this: `"+" | "-"`. The `|` operator is the only binary operator we need
for parsing. Also there are unary suffix operators: `?`, `+` and `*` which
you may recognize from regular expressions, as well as `&` and `!` unary
prefix operators.
内置终端子句
"str"
:期望源中的字符str
。您可以使用单引号和双引号
quotes to write this clause. Use `\"` or `\'` to escape the quote symbol.
/regex/
:期望正则表达式与源在当前偏移量处匹配。
[a-z]
:匹配范围a
到z
的字符。
[abc]
:匹配字符a
、b
或c
之一。
^
:仅匹配源文件的开头。
$
:仅匹配源文件的结尾。
.
:匹配任何单个UTF-8字符。此子句仅在字符串末尾失败
of the file.
(
和)
:可以使用括号将多个子句组合在一起。
“片段” - 内联PHP代码
为了构建更好的解析树或实现您想要的功能,您可以使用嵌入的PHP代码。
代码通过短标签嵌入
SumExpression
= MulExpression "+" SumExpression
<?
/* inline PHP code */
?>
PHP代码可以通过变量$_0
、$_1
等访问之前匹配的子句的值。
不捕获数据的子句,如^
和$
,不包括在内。
可以使用名为$globals
的特定变量来记录全局状态,该状态将在不同的片段间保持可用。
片段的返回值将成为整个规则的返回值。
返回值true
和false
有特殊含义
- 返回
true
将导致解析继续,但不会添加任何内容到结果中。 - 返回
false
将导致子句解析失败,解析器将尝试下一个选择。 - 任何其他返回值都将添加到解析树中。
片段可以通过调用error()
函数来触发解析错误。