PHP 的属性驱动标记化。

v0.2.0 2023-08-09 23:15 UTC

README

Latest Version on Packagist Tests Total Downloads

安装

您可以通过 Composer 安装此包

composer require ryangjchandler/lexical

用法

让我们编写一个简单的数学表达式词法分析器。表达式可以包含数字(只有整数)和一些运算符(+-*/)。

首先创建一个新枚举,描述标记类型。

enum TokenType
{
    case Number;
    case Add;
    case Subtract;
    case Multiply;
    case Divide;
}

Lexical 提供了一组可以添加到枚举中每个情况的属性

  • Regex - 接受单个正则表达式。
  • Literal - 接受一串连续字符。
  • Error - 将特定的枚举情况指定为“错误”类型。

使用这些属性与 TokenType 一起使用如下所示。

enum TokenType
{
    #[Regex("[0-9]+")]
    case Number;
    
    #[Literal("+")]
    case Add;
    
    #[Literal("-")]
    case Subtract;
    
    #[Literal("*")]
    case Multiply;

    #[Literal("/")]
    case Divide;
}

属性就位后,我们可以开始使用 LexicalBuilder 构建词法分析器。

$lexer = (new LexicalBuilder)
    ->readTokenTypesFrom(TokenType::class)
    ->build();

使用 readTokenTypesFrom() 方法告诉构建器我们在哪里应该查找各种标记化属性。使用 build() 方法将那些属性转换成一个实现了 LexerInterface 的对象,配置为查找指定的标记类型。

然后只需调用词法分析器对象的 tokenise() 方法即可检索一个标记数组。

$tokens = $lexer->tokenise('1+2'); // -> [[TokenType::Number, '1', Span(0, 1)], [TokenType::Add, '+', Span(1, 2)], [TokenType::Number, '2', Span(2, 3)]]

tokenise() 方法返回一个元组列表,其中第一个元素是“类型”(本例中的 TokenType),第二个元素是“文字”(包含匹配字符的字符串),第三个元素是标记的“范围”(原始字符串中的起始和结束位置)。

跳过空白和其他模式

继续以数学表达式的例子,词法分析器目前理解 1+2,但它无法标记化 1 + 2(添加的空白)。这是因为默认情况下,它期望每个可能的字符都落入一个模式。

在这种情况下,空白是无关紧要的,所以可以安全地跳过。为此,我们需要在 TokenType 枚举中添加一个新的 Lexer 属性,并传递一个匹配我们想要跳过的字符的正则表达式。

#[Lexer(skip: "[ \t\n\f]+")]
enum TokenType
{
    // ...
}

现在词法分析器将跳过任何空白字符并成功标记化 1 + 2

错误处理

当词法分析器遇到意外的字符时,它将抛出 UnexpectedCharacterException

try {
    $tokens = $lexer->tokenise();
} catch (UnexpectedCharacterException $e) {
    dd($e->character, $e->position);
}

如上所述,有一个 Error 属性可以用来将枚举情况标记为“错误”类型。

enum TokenType
{
    // ...

    #[Error]
    case Error;
}

现在当输入被标记化时,未识别的字符将被像其他标记一样消费,并将类型设置为 TokenType::Error

$tokens = $lexer->tokenise('1 % 2'); // -> [[TokenType::Number, '1'], [TokenType::Error, '%'], [TokenType::Number, '2']]

自定义 Token 对象

如果您更喜欢使用与 Lexical 默认的每个标记的元组值不同的专用对象,则可以提供一个自定义回调来将匹配的标记类型和文字映射到自定义对象。

class Token
{
    public function __construct(
        public readonly TokenType $type,
        public readonly string $literal,
        public readonly Span $span,
    ) {}
}

$lexer = (new LexicalBuilder)
    ->readTokenTypesFrom(TokenType::class)
    ->produceTokenUsing(fn (TokenType $type, string $literal, Span $span) => new Token($type, $literal, $span))
    ->build();

$lexer->tokenise('1 + 2'); // -> [Token { type: TokenType::Number, literal: "1" }, ...]

测试

composer test

变更日志

有关最近更改的更多信息,请参阅 CHANGELOG

贡献

有关详细信息,请参阅 CONTRIBUTING

安全漏洞

有关报告安全漏洞的详细信息,请参阅 我们的安全策略

鸣谢

许可证

MIT许可证(MIT)。请参阅许可证文件获取更多信息。