nette/tokenizer

此包已被 放弃 且不再维护。没有建议的替代包。

Nette Tokenizer

维护者

详细信息

github.com/nette/tokenizer

主页

源码

安装数: 3,801,390

依赖: 47

建议者: 0

安全: 0

星标: 141

关注者: 34

分支: 23

v3.1.1 2022-02-09 22:28 UTC

This package is auto-updated.

Last update: 2024-02-12 20:07:09 UTC


README

Downloads this Month Tests Coverage Status Latest Stable Version License

简介

Tokenizer 是一个使用正则表达式将给定字符串拆分为标记的工具。这有什么用呢?你可能想知道。嗯,你可以创建自己的语言!

文档可以在 网站 上找到。如果您喜欢它,请现在捐款。谢谢!

安装

composer require nette/tokenizer

需要 PHP 版本 7.1,并支持 PHP 8.1。

支持我

你喜欢 Nette Tokenizer 吗?你在期待新功能吗?

Buy me a coffee

谢谢!

使用

让我们创建一个简单的标记器,将字符串分开成数字、空白和字母。

$tokenizer = new Nette\Tokenizer\Tokenizer([
	T_DNUMBER => '\d+',
	T_WHITESPACE => '\s+',
	T_STRING => '\w+',
]);

提示:如果你想知道 T_ 常量从哪里来,它们是 解析代码时使用的内部类型。它们涵盖了通常需要的大多数常见标记名称。请注意,它们的值不保证,所以不要用数字进行比较。

现在当我们给它一个字符串时,它将返回标记 Nette\Tokenizer\StreamNette\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()重置光标,因此您可以再次遍历标记流。