charm/parsing

PEG语法解析器,用于解析大多数上下文无关语法,例如编程语言、数据库查询和数学表达式。

0.0.7 2022-04-01 09:54 UTC

This package is auto-updated.

Last update: 2024-08-29 05:44:28 UTC


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]:匹配范围az的字符。

[abc]:匹配字符abc之一。

^:仅匹配源文件的开头。

$:仅匹配源文件的结尾。

.:匹配任何单个UTF-8字符。此子句仅在字符串末尾失败

of the file.

():可以使用括号将多个子句组合在一起。

“片段” - 内联PHP代码

为了构建更好的解析树或实现您想要的功能,您可以使用嵌入的PHP代码。

代码通过短标签嵌入

SumExpression
    = MulExpression "+" SumExpression
      <?
        /* inline PHP code */
        ?>

PHP代码可以通过变量$_0$_1等访问之前匹配的子句的值。

不捕获数据的子句,如^$,不包括在内。

可以使用名为$globals的特定变量来记录全局状态,该状态将在不同的片段间保持可用。

片段的返回值将成为整个规则的返回值。

返回值truefalse有特殊含义

  • 返回true将导致解析继续,但不会添加任何内容到结果中。
  • 返回false将导致子句解析失败,解析器将尝试下一个选择。
  • 任何其他返回值都将添加到解析树中。

片段可以通过调用error()函数来触发解析错误。