remorhaz / php-unilex
Unilex:支持Unicode的PHP编写的词法分析器生成器
Requires
- php: ~8.1.0 || ~8.2.0 || ~8.3.0
- nikic/php-parser: ^4.12 || ^5
- phpdocumentor/reflection-docblock: ^4.3 || ^5
- remorhaz/int-rangesets: ^0.3
- remorhaz/ucd: ^0.3
- symfony/console: ^6.1 || ^7
- thecodingmachine/safe: ^1.3.1 || ^2
Requires (Dev)
- bamarni/composer-bin-plugin: ^1.8
- phpunit/phpunit: ^10.1 || ^11
README
UniLex是具有Unicode支持的词法分析器生成器(类似于lex和flex)。它用PHP编写,并生成PHP代码。
[WIP] Work in progress
要求
- PHP 8
许可证
UniLex库采用MIT许可证。
安装
安装与其他任何composer库一样简单
composer require remorhaz/php-unilex
使用
在示例中的快速入门
让我们假设我们想编写一个简单的计算器,并且我们需要一个提供ID、数字和运算符流式的词法分析器。创建一个新的Composer项目,并在项目目录中执行以下命令
composer require --dev remorhaz/php-unilex
下一步是创建一个在LexerSpec.php
文件中的词法分析器规范。我们使用@lexToken
标签在注释中指定标记的正则表达式
<?php /** * @var \Remorhaz\UniLex\Lexer\TokenMatcherContextInterface $context * @lexTargetClass TokenMatcher * @lexHeader */ const TOKEN_ID = 1; const TOKEN_OPERATOR = 2; const TOKEN_NUMBER = 3; /** @lexToken /[a-zA-Z][0-9a-zA-Z]*()/ */ $context->setNewToken(TOKEN_ID); /** @lexToken /[+\-*\/]/ */ $context->setNewToken(TOKEN_OPERATOR); /** @lexToken /[0-9]+/ */ $context->setNewToken(TOKEN_NUMBER);
下一步是从规范构建一个标记匹配器
vendor/bin/unilex LexerSpec.php > TokenMatcher.php
现在我们有一个在TokenMatcher.php
文件中的编译后的标记匹配器。让我们使用它并从缓冲区中读取所有标记
<?php use Remorhaz\UniLex\Lexer\TokenFactory; use Remorhaz\UniLex\Lexer\TokenReader; use Remorhaz\UniLex\Unicode\CharBufferFactory; require_once "vendor/autoload.php"; require_once "TokenMatcher.php"; $buffer = CharBufferFactory::createFromString("x+2*3"); $tokenReader = new TokenReader($buffer, new TokenMatcher, new TokenFactory(0xFF)); do { $token = $tokenReader->read(); echo "Token ID: {$token->getType()}\n"; } while (!$token->isEoi());
在执行此脚本时,它会输出
Token ID: 1
Token ID: 2
Token ID: 3
Token ID: 2
Token ID: 3
Token ID: 255
让我们更进一步,使其能够从输入缓冲区中检索每个标记的文本表示。我们需要修改词法分析器规范,将结果附加到每个非EOI标记作为属性
<?php /** * @var \Remorhaz\UniLex\Lexer\TokenMatcherContextInterface $context * @lexTargetClass TokenMatcher * @lexHeader */ const TOKEN_ID = 1; const TOKEN_OPERATOR = 2; const TOKEN_NUMBER = 3; /** @lexToken /[a-zA-Z][0-9a-zA-Z]*()/ */ $context ->setNewToken(TOKEN_ID) ->setTokenAttribute('text', $context->getSymbolString()); /** @lexToken /[+\-*\/]/ */ $context ->setNewToken(TOKEN_OPERATOR) ->setTokenAttribute('text', $context->getSymbolString()); /** @lexToken /[0-9]+/ */ $context ->setNewToken(TOKEN_NUMBER) ->setTokenAttribute('text', $context->getSymbolString());
在用CLI实用程序重新构建标记匹配器后,我们需要修改示例程序的输出循环,使其打印带有标记ID的文本
do { $token = $tokenReader->read(); echo "Token ID: {$token->getType()}", $token->isEoi() ? "\n" : " / '{$token->getAttribute('text')}'\n"; } while (!$token->isEoi());
现在程序会打印
Token ID: 1 / 'x'
Token ID: 2 / '+'
Token ID: 3 / '2'
Token ID: 2 / '*'
Token ID: 3 / '3'
Token ID: 255
CLI
您可以使用命令行实用程序从规范构建标记匹配器
vendor/bin/unilex path/to/spec/LexerSpec.php path/to/target/TokenMatcher.php --desc="My example matcher."
规范
规范是一个PHP文件,它被特殊的DocBlock注释分隔成几个部分。有一个特殊的变量$context
,它包含一个具有\Remorhaz\UniLex\Lexer\TokenMatcherContextInterface
接口的上下文对象。当前实现还使用一个int
变量$char
,它包含当前符号(TODO:应将其移动到上下文对象中)。
@lexHeader
此块可以包含将用于匹配器生成的namespace
和use
语句。
@lexBeforeMatch
此块在匹配过程开始之前执行,可以用于初始化一些额外的变量。
@lexOnTransition
此块在每个与标记正则表达式匹配的符号上执行。
@lexToken /regexp/
在匹配给定正则表达式从输入缓冲区时执行此块。通常,它只是在上下文对象中设置新的标记。
@lexMode 'mode_name'
此标签告诉解析器,只有当当前词法模式是mode_name
时,@lexToken
表达式才会匹配。词法模式可以使用$context->setMode('mode_name')
方法切换。使用词法模式允许在单个规范中具有多个“子语法”(即,某些标记只能在注释或字符串中识别)。
@lexOnError
如果匹配器无法匹配任何标记的正则表达式,则执行此块。默认情况下,它只是返回false
。