affinity4/tokenizer

一个用PHP编写的无依赖性分词器。返回一个包含具有公共类型、值、偏移和长度属性的Token对象的易于导航的Stream对象

0.0.4 2022-05-30 22:42 UTC

This package is auto-updated.

Last update: 2024-09-29 06:20:29 UTC


README

Affinity4

一个用PHP编写的无依赖性分词器。返回一个包含具有公共类型、值、偏移和长度属性的Token对象的易于导航的Stream对象

只需传递一个关联数组 [match_pattern => type] ('\s+' => 'T_WHITESPACE', '[a-zA-Z]\w+' => 'T_STRING'),分词器将返回所有匹配项作为Token对象的数组

安装

Composer

composer require affinity4/tokenizer

基本示例

假设我们想要创建一个看起来更像代码而不是标记的模板引擎语言的自定义域特定语言(DSL)

示例模板片段

$template = <<<TEMPLATE
html(lang="en_IE") {
    // child nodes are inside curly brackets!
    head() {
        title(): This is a title;
        link(src="./style.css");
        script(src="./main.js");
    }

    body.app#app() {
        h1.title(): Page title;
    }
}
TEMPLATE;

现在我们定义我们的“词法”,它被传递给分词器

注意
词法必须提供您语法中预期遇到的所有字符和模式。当前您不能跳过任何字符。无论您是否以后使用,所有内容都必须进行分词。

$lexicon = [
    /*
    It's a good idea to do the punctuation first, or anything you want to remove early on (e.g. comments or whitespace)
    This would be single chars that have meaning in your language. 
    For us, the # means an id attribute, the . is before any classname, 
    and :, ;, (, ), {, } all have their own purpose too
    */
    'T_WHITESPACE' => '\s+', // We might want to remove all whitespace not within quotes ("") to minify our compiled html
    'T_FORWARD_SLASH' => '/',
    'T_DOT' => '\.',
    'T_HASH' => '#',
    'T_COLON' => ':',
    'T_SEMICOLON' => ';',
    'T_EQUALS' => '=',
    'T_DOUBLE_QOUTE' => '"',
    'T_SINGLE_QUOTE' => "'",
    'T_EXCLAIMATION_MARK' => '!',
    'T_OPEN_PARENTHESIS' => '\(',
    'T_CLOSE_PARENTHESIS' => '\)',
    'T_OPEN_CURLY' => '\{',
    'T_CLOSE_CURLY' => '\}',

    // Now we can define some more generic "lexemes"
    
    // Match All words as T_STRING. Our parser can then 
    // check for the first string in each line that is followed by 
    // T_DOT | T_HASH | T_OPENING_PARENTHESIS. This will be the HTML tag name
    'T_STRING' => "\w+"
];

我们将词法传递给分词器...

use Affinity4\Tokenizer\Tokenizer;

$Tokenizer = new Tokenizer($template);
$Tokenizer->registerLexicon($lexicon);
$Stream = $Tokenizer->tokenize(); // Affinity4\Tokenizer\Stream of Affinity4\Tokenizer\Token objects

while ($Stream->hasNext()) {
    $Token = $Stream->nextToken();
    echo $Token->type; // T_HTML_TAG
    echo $Token->value; // html
    echo $Token->linenumber; // 1
    echo $Token->offset[0]; // 0 Start position of match
    echo $Token->offset[1]; // 3 End position of match
    
}

从这里开始,您只需要编写您的“有限自动机”和/或解析器。

提示

debug()

分词器有一个debug()方法,它将返回编译后的正则表达式,以便您进行检查。

提示
一个测试PHP正则表达式的好网站是: https://regexr.com/

默认情况下,debug方法将返回正则表达式作为字符串,但是,您也可以echo、var_dump和“dump and die”(或Laravel用户中的dd())。

已定义了所有这些常量,以帮助您避免使用这些开关

  • $Tokenizer->debug(Tokenizer::DEBUG_ECHO)
  • $Tokenizer->debug(Tokenizer::DEBUG_DUMP)
  • $Tokenizer->debug(Tokenizer::DEBUG_DUMP_AND_DIE)

preg_match_all(): Compilation failed: missing closing parenthesis at offset x

尝试匹配转义字符或换行符(例如 \r|\n|\r\n)很可能是您问题的原因。

您将需要双转义转义字符。为了帮助您避免需要解决这个问题,我提供了正确的T_ESCAPE_CHAR正则表达式模式。

$lexicon = [
    // ...
    Tokenize::T_ESCAPE_CHAR => 'T_ESCAPE', // '\\\\'
    Tokenize::T_NEWLINE     => 'T_NEWLINE', // ';T_NEWLINE;'
    // ...
]

换行符

在匹配之前,需要将换行符替换为标记。默认情况下,T_NEWLINE_ALL常量将匹配;T_NEWLINE;

$lexicon = [
    // ...
    Tokenize::T_NEWLINE => 'T_NEWLINE', // ';T_NEWLINE;'
    // ...
]

如果您需要为特定环境匹配单个换行符,可以使用以下常量

有关更多信息,请参阅以下关于匹配转义字符和特殊字符的部分

匹配转义字符和特殊字符

如上所述,反斜杠必须双转义。

因此,要匹配单个反斜杠,您必须使用正则表达式'\\\\'(我知道,这很糟糕,但您必须这样做)

要匹配特殊字符(制表符、换行符、回车等),您需要首先将它们替换为另一个标记,然后为替换字符串添加一个标记。

$input = str_replace(["\r\n", "\r", "\n"], ";T_NEWLINE;", $input);
$input = str_replace("\t", ";T_TAB;", $input);

$lexicon = [
    // ...
    Token::T_NEWLINE => 'T_NEWLINE',
    Token::T_TAB     => 'T_TAB'
    // ...
];

$Tokenizer = new Tokenizer($lexicon);
$Steam = $Tokenizer->tokenize($input);

我正在努力对这些模式进行更好的内部检测,并在遇到这些错误时提供更好的错误消息 (我会在运行之前正则化正则表达式或类似的东西)