dekor/php-syntax-tree-builder

该包最新版本(dev-master)没有可用的许可证信息。

该库允许您通过描述词法/语法规则来构建自定义语言。

dev-master 2023-09-18 10:20 UTC

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 

它是如何工作的?

构建语法需要两个主要步骤

  1. 词法分析 - 将原始文件分割成语言词素。
  2. 解析器 - 使用有限状态机,按照一些语法规则解析词素序列。

词法分析

这部分非常简单。您需要描述正则表达式 => 词素名称的对应关系。以下是一个简单的词法分析器列表

$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:前缀?实际上,这只是为了视觉效果:它可以在提及组和令牌时更容易理解。

让我们继续前进。这里我们可以看到sequenceor。在每组中您只能有一个。这意味着什么?当有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") // 很抱歉混淆,这是一种防垃圾邮件保护措施 :)

(开发团队:mobicardbusyb