nette / tokenizer
Nette Tokenizer
Requires
- php: >=7.1
Requires (Dev)
- nette/tester: ~2.0
- phpstan/phpstan: ^0.12
- tracy/tracy: ^2.3
README
简介
Tokenizer 是一个使用正则表达式将给定字符串拆分为标记的工具。这有什么用呢?你可能想知道。嗯,你可以创建自己的语言!
安装
composer require nette/tokenizer
需要 PHP 版本 7.1,并支持 PHP 8.1。
支持我
你喜欢 Nette Tokenizer 吗?你在期待新功能吗?
谢谢!
使用
让我们创建一个简单的标记器,将字符串分开成数字、空白和字母。
$tokenizer = new Nette\Tokenizer\Tokenizer([ T_DNUMBER => '\d+', T_WHITESPACE => '\s+', T_STRING => '\w+', ]);
提示:如果你想知道 T_ 常量从哪里来,它们是 解析代码时使用的内部类型。它们涵盖了通常需要的大多数常见标记名称。请注意,它们的值不保证,所以不要用数字进行比较。
现在当我们给它一个字符串时,它将返回标记 Nette\Tokenizer\Stream 的 Nette\Tokenizer\Token 流。
$stream = $tokenizer->tokenize("say \n123");
生成的标记数组 $stream->tokens
将看起来像这样。
[ new Token('say', T_STRING, 0), new Token(" \n", T_WHITESPACE, 3), new Token('123', T_DNUMBER, 5), ]
此外,您还可以访问标记的各个属性
$firstToken = $stream->tokens[0]; $firstToken->value; // say $firstToken->type; // value of T_STRING $firstToken->offset; // position in string: 0
简单,不是吗?
处理标记
现在我们知道了如何从字符串创建标记。让我们使用 Nette\Tokenizer\Stream
有效地处理它们。如果您需要遍历标记,它有许多真正酷的方法!
让我们尝试解析一个简单的 PHPDoc 注释并从中创建一个对象。我们需要哪些正则表达式来处理标记?所有注释都从 @
开始,然后是一个名称,一个空白,以及它的值。
@
用于注释开始\s+
用于空白\w+
用于字符串
(在 Tokenizer 的正则表达式中永远不要使用捕获子模式,如 '(ab)+c'
,只使用非捕获的 '(?:ab)+c'
.)
这应该适用于简单的注释,对吗?现在让我们展示我们将尝试解析的输入字符串。
$input = ' @author David Grudl @package Nette ';
让我们创建一个 Parser
类,它将接受一个字符串并返回一个包含键值对 [name, value]
的数组。这将非常简单和原始。
use Nette\Tokenizer\Tokenizer; use Nette\Tokenizer\Stream; class Parser { const T_AT = 1; const T_WHITESPACE = 2; const T_STRING = 3; /** @var Tokenizer */ private $tokenizer; /** @var Stream */ private $stream; public function __construct() { $this->tokenizer = new Tokenizer([ self::T_AT => '@', self::T_WHITESPACE => '\s+', self::T_STRING => '\w+', ]); } public function parse(string $input): array { $this->stream = $this->tokenizer->tokenize($input); $result = []; while ($this->stream->nextToken()) { if ($this->stream->isCurrent(self::T_AT)) { $result[] = $this->parseAnnotation(); } } return $result; } private function parseAnnotation(): array { $name = $this->stream->joinUntil(self::T_WHITESPACE); $this->stream->nextUntil(self::T_STRING); $content = $this->stream->joinUntil(self::T_AT); return [$name, trim($content)]; } }
$parser = new Parser; $annotations = $parser->parse($input);
那么 parse()
方法做什么?它遍历标记并搜索 @
,这是注释开始的符号。调用 nextToken()
将光标移动到下一个标记。方法 isCurrent()
检查光标处的当前标记是否是给定类型。然后,如果找到 @
,则 parse()
方法调用 parseAnnotation()
,它期望注释以非常特定的格式。
首先,使用joinUntil()
方法,流会移动光标并追加标记值的缓冲区,直到找到所需类型的标记,然后停止并返回缓冲区输出。因为在给定位置只有一个T_STRING
类型的标记,并且它是'name'
,所以变量$name
中将有值'name'
。
nextUntil()
方法与joinUntil()
类似,但它没有缓冲区。它只移动光标直到找到标记。因此,这个调用简单地跳过了注释名称之后的所有空格。
然后,还有一个joinUntil()
,它会搜索下一个@
。这个特定的调用将返回"David Grudl\n "
。
好了,我们已经解析了一个完整的注释!$content
可能以空格结束,所以我们必须对其进行修剪。现在我们可以将这个特定的注释作为对[$name, $content]
的配对返回。
尝试复制粘贴代码并运行。如果您打印$annotations
变量,它应该返回类似的结果。
array (2)
0 => array (2)
| 0 => 'author'
| 1 => 'David Grudl'
1 => array (2)
| 0 => 'package'
| 1 => 'Nette'
流方法
流可以使用currentToken()
方法返回当前标记,或者使用currentValue()
方法返回它的值。
nextToken()
移动光标并返回标记。如果您不提供任何参数,它只返回下一个标记。
nextValue()
与nextToken()
类似,但它只返回标记值。
大多数方法也接受多个参数,因此您可以一次性搜索多个类型。
// iterate until a string or a whitespace is found, then return the following token $token = $stream->nextToken(T_STRING, T_WHITESPACE); // give me next token $token = $stream->nextToken();
您还可以按标记值进行搜索。
// move the cursor until you find token containing only '@', then stop and return it $token = $stream->nextToken('@');
nextUntil()
移动光标并返回它看到的所有标记的数组,直到它找到所需的标记,但它会在标记之前停止。它可以接受多个参数。
joinUntil()
与nextUntil()
类似,但它将所有经过的标记连接起来并返回字符串。
joinAll()
简单地连接所有剩余的标记值并返回它。它将光标移动到标记流的末尾。
nextAll()
与joinAll()
类似,但它返回标记的数组。
isCurrent()
检查当前标记或当前标记的值是否等于给定的参数之一。
// is the current token '@' or type of T_AT? $stream->isCurrent(T_AT, '@');
isNext()
与isCurrent()
类似,但它检查下一个标记。
isPrev()
与isCurrent()
类似,但它检查前一个标记。
最后一种方法reset()
重置光标,因此您可以再次遍历标记流。