dekor / php-syntax-tree-builder
该库允许您通过描述词法/语法规则来构建自定义语言。
Requires
- php: ^7.0
This package is auto-updated.
Last update: 2024-09-18 12:39:10 UTC
README
该库允许您通过描述词法/语法规则来构建自定义语法左-右,RD(递归下降)解析器。它基于有限状态机并提供了一个抽象语法树的实例。
灵感来源于c/c++的lex/yacc(但并不相同!)。
安装
composer install dekor/php-syntax-tree-builder
它是如何工作的?
构建语法需要两个主要步骤
- 词法分析 - 将原始文件分割成语言词素。
- 解析器 - 使用有限状态机,按照一些语法规则解析词素序列。
词法分析
这部分非常简单。您需要描述正则表达式 => 词素名称的对应关系。以下是一个简单的词法分析器列表
$lexer = new \Dekor\PhpSyntaxTreeBuilder\Lexer([ '[\s\t\n]+' => '', '\;' => ';', '=' => '=', '\$([a-z]+)' => 'VAR', 'if' => 'IF', '([0-9]+)' => 'NUMBER', '\"(.*)\"' => 'STRING', 'print' => 'PRINT', '(\+|\-|\*\/)' => 'MATH_OPERATOR', ]); $lexems = $lexer->parse($pathToFile);
parse
方法的结果是一个词素的数组。在词法分析器无法确定当前符号序列为任何描述的词素时,它将抛出LexerAnalyseException
。这可以被捕获并处理。
解析
这部分更复杂。当您描述结构时,您需要有一个启动结构,这是有限状态机开始的地方
$parser = new \Dekor\PhpSyntaxTreeBuilder\Parser([ 'startFrom' => 'g:php', ], [ 'g:php' => [ 'sequence' => [ 'OPENING_PHP_TAG', 'g:statements', ], ], 'g:statements' => [ 'sequence' => [ 'g:statement', '?g:statements', ], ], 'g:statement' => [ 'or' => [ 'g:var_assign', 'g:if', ], ], ... ]);
如这个示例所示,在配置中我们说它将从g:php开始解析。为什么我们添加g:
前缀?实际上,这只是为了视觉效果:它可以在提及组和令牌时更容易理解。
让我们继续前进。这里我们可以看到sequence
和or
。在每组中您只能有一个。这意味着什么?当有sequence
时,解析器将遍历序列中的每个元素,并尝试按照此顺序解析词素。当组在序列中提及时,它将被递归解析。这意味着解析器将检查打开php词素的词素,然后解析语句。语句由另一个序列组成:statement
和?statements
。这里的语句是下面可以看到的单个语句,?statements
表示在单个语句之后将会有另一个语句。但是,?
符号告诉解析器它是可选的,这意味着解析器将尝试解析组,如果失败,则继续下一个序列元素。下面,我们可以看到or
。这允许解析器尝试解析列表中的每个组。一旦解析器匹配结构,它将继续当前语句中工作的组。这种结构允许您将算法分成多个分支。顺便说一句,它从左到右尝试解析。
可能出错的地方?
在某些情况下,您可能遇到可能导致无限循环的递归语法。由于这个解析器是左递归的,所以这里出现了左递归。您可以检查文章以解决此问题。这个特定的情况已经在/demo/php/demo.php
中的公式解析中解决了。请检查语法部分。
使用方法
这里是一个简单的使用结构
<?php require __DIR__ . '/../../vendor/autoload.php'; $lexer = new \Dekor\PhpSyntaxTreeBuilder\Lexer([ '[\s\t\n]+' => '', '([0-9]+)' => 'NUMBER', '\"(.*)\"' => 'STRING', // lexing rules ]); $lexems = $lexer->parse($inFile); $parser = new \Dekor\PhpSyntaxTreeBuilder\Parser([ 'startFrom' => 'g:statements', ], [ 'g:statements' => [ 'sequence' => [ 'g:statement', '?g:statements', ], ], 'g:statement' => [ 'or' => [ 'g:string', 'g:number', ], ], 'g:string' => [ 'sequence' => [ 'STRING', ], ], 'g:number' => [ 'sequence' => [ 'NUMBER', ], ], ]); try { file_put_contents( 'out.json', json_encode($parser->parse($lexems), JSON_PRETTY_PRINT) ); } catch (\Throwable $e) { echo get_class($e) . ': ' . $e->getMessage(); }
尝试我们的所有演示,以便更好地了解它是如何工作的以及如何应用。
ASTNode
您可以看到类 Dekor\PhpSyntaxTreeBuilder\ASTNode
。这是我们实现的抽象语法树节点。通常,Parser::parse(Lexems[] $lexems)
返回 ASTNode
实例(可能返回节点数组)。您可以在我们的演示中尝试 out.json 来了解其外观。
如果您有自己的 ASTNode 实现或类组合(每个节点类型使用不同的类),您可以覆盖节点创建,添加以下构造
'g:php' => [ 'sequence' => [ 'OPENING_PHP_TAG', 'g:statements', ], 'callback' => function($lexems, $subGroupsSequence) { return new \Dekor\PhpSyntaxTreeBuilder\ASTNode('PHP', $subGroupsSequence, $lexems); } ],
它是如何工作的?解析序列后,如果存在,回调函数将被执行。它应该始终返回节点对象。$subGroupsSequence
是一个包含已解析子组的数组。在这个例子中,它是 g:statements
。这里的 $lexems
是在这个序列中解析的词法单元的简单列表(不包括嵌套组的词法单元)。
有任何想法或需要帮助吗?
您可以通过电子邮件联系我:CONCAT("denis", "koronets", "@", "woo.zp.ua") // 很抱歉混淆,这是一种防垃圾邮件保护措施 :)