ryangjchandler / lexical
PHP 的属性驱动标记化。
Requires
- php: ^8.1
Requires (Dev)
- laravel/pint: ^1.2
- pestphp/pest: ^1.20
- spatie/ray: ^1.28
This package is auto-updated.
Last update: 2024-09-09 12:13:17 UTC
README
安装
您可以通过 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)。请参阅许可证文件获取更多信息。