remorhaz/php-unilex

Unilex:支持Unicode的PHP编写的词法分析器生成器

v0.5.3 2024-02-12 15:01 UTC

README

Latest Stable Version Build Scrutinizer Code Quality codecov Mutation testing badge Total Downloads License

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

此块可以包含将用于匹配器生成的namespaceuse语句。

@lexBeforeMatch

此块在匹配过程开始之前执行,可以用于初始化一些额外的变量。

@lexOnTransition

此块在每个与标记正则表达式匹配的符号上执行。

@lexToken /regexp/

在匹配给定正则表达式从输入缓冲区时执行此块。通常,它只是在上下文对象中设置新的标记。

@lexMode 'mode_name'

此标签告诉解析器,只有当当前词法模式是mode_name时,@lexToken表达式才会匹配。词法模式可以使用$context->setMode('mode_name')方法切换。使用词法模式允许在单个规范中具有多个“子语法”(即,某些标记只能在注释或字符串中识别)。

@lexOnError

如果匹配器无法匹配任何标记的正则表达式,则执行此块。默认情况下,它只是返回false