nextxes/tokenmatcher

PHP库,用于匹配标记流(标记数组)以供后续解析。

dev-master 2014-09-08 01:37 UTC

This package is not auto-updated.

Last update: 2024-09-24 02:59:31 UTC


README

Build Status

PHP库,用于匹配标记流(标记数组)以供后续解析。

tokenmatcher库依赖于tokenizer包。要使用它,您必须对字符串进行标记或自己构建一个标记流。

安装

使用composer

"require": {
	"nexxes/tokenmatcher": "dev-master"
}

该库目前正在开发中,API尚不稳定。开发基于创建一个工作且符合标准的CommonMark解析器所需的功能。请参阅我们的nexxes/commonmark包。

匹配一个标记

// Produces two tokens: a Token::WHITESPACE token and a Token::MINUS token
$tokenizer = new Tokenizer("   ---");
$tokens = $tokenizer->getTokens();

$whitespaceMatcher = new Matches(Token::WHITESPACE);
// stores 1, the number of tokens matched
$matched = $whitespaceMatcher->match($tokens);

Matches类是可用的最简单的匹配器,仅匹配指定类型的一个标记。

匹配可选类型的标记

Choice类型的工作方式类似于Matches类型,但可以接受任意数量的标记类型。

$tokenizer = new Tokenizer('{{[[<<');
$tokens = $tokenizer->getTokens();

// Will match all types of brackets: (), [], {} and <>
$bracketMatcher = new Choice(Token::PARENTHESIS_LEFT, Token::PARENTHESIS_RIGHT, Token::SQUARE_BRACKET_LEFT, Token::SQUARE_BRACKET_RIGHT, Token::CURLY_BRACKET_LEFT, Token::CURLY_BRACKET_RIGHT, Token::ANGLE_BRACKET_LEFT, Token::ANGLE_BRACKET_RIGHT);
// will again be 1
$matched = $bracketMatcher->match($tokens);

匹配多个标记

简单的类型MatchesChoice不会走得太远,所以我们需要能够将基本类型组合成更强大模式的类型。

Range的工作方式类似于正则表达式中的花括号

// Matches the same thing as /[()\[\]{}<>]{1,5}/
$bracketsMatcher = new Range($bracketMatcher, 1, 5);
// Will be 5 this time
$matched = $bracketMatcher->match($tokens);

Sequence匹配器用于定义一个必须匹配的标记列表

$sequenceMatcher = new Sequence(
  new Matches(Token::CURLY_BRACKET_LEFT),
  new Matches(Token::CURLY_BRACKET_LEFT),
  new Matches(Token::SQUARE_BRACKET_LEFT)
);

// 3 tokens
$matched = $sequenceMatcher->match($tokens);

您可能会奇怪,为什么上面的序列包含了两个针对Token::CURLY_BRACKET_LEFT的匹配器。标记化器实现有几个仅匹配一个字符的标记类。这些是括号、引号和一些其他特殊字符。查看Tokenizer实现,了解哪些字符是如何标记化的。

查看标记流

匹配器的match()方法并非绑定在标记流的开头开始匹配。您可以为方法提供一个偏移量,以定位到标记流的较后位置。

// 2 tokens: < and <
$matched = $bracketsMatcher->match($tokens, 4);

如果您想匹配标记流的末尾,请使用Tail匹配器。

$tailMatcher = new Tail();
// will be false
$matched = $tailMatcher->match($tokens, 5);

// will be 0 !== false, because there is "no end of stream" token
$matched = $tailMatcher->match($tokens, 6);

// will be false, behind the end
$matched = $tailMatcher->match($tokens, 7);

长度匹配

某些匹配仅在匹配的原始数据满足特定的长度约束时才适用。在Markdown中,您可能只能使用最多三个空格进行缩进,否则您将形成一个代码块。

要匹配特定数量的空格,请使用Length匹配器。

$tokenizer = new Tokenizer('  This is just some text');
$tokens = $tokenizer->getTokens();

// Allows 3 or less space chars in the raw data of the matched whitespace token
$matchIndent = new Range($whitespaceMatcher, 3, '<=');

// 1 as the whitespace token is matched
$matched = $matchIndent->match($tokens);

匹配先前匹配的类型

某些匹配需要回指先前的匹配。一个例子是内联代码块:匹配一个`(反引号)或一个~(波浪号),然后是文本,然后再次匹配`或~。

$tokenizer = new Tokenizer('`inlinecode~');
$tokens = $tokenizer->getTokens();

$inlineCodeMatcher = new Sequence(
  new Choice(Token::TILDE, Token::BACKTICK),
  new Matcher(Token::TEXT),
  new Choice(Token::TILDE, Token::BACKTICK)
);

// matched 3 tokens
$matched = $inlineCodeMatcher->match($tokens);

上面的代码将匹配标记化的文本,但它不是一个有效的内联代码块。内联代码必须由反引号或波浪号包围,而不是两者的混合。

SameType匹配器提供了帮助

$inlineCodeMatcher = new Sequence(
  $codeFence = new Choice(Token::TILDE, Token::BACKTICK),
  new Matcher(Token::TEXT),
  new SameType($codeFence)
);

// false
$matched = $inlineCodeMatcher->match($tokens);

$tokenizer = new Tokenizer('`inlinecode`');
$tokens = $tokenizer->getTokens();

// 3 tokens: backtick, text, backtick
$matched = $inlineCodeMatcher->match($tokens);

一个复杂的例子

匹配器的强大之处在于它们能够从简单的构建块构建复杂的模式。以下匹配器构造将匹配CommonMark中的水平规则。模式以文本形式表示

  • 第一个可选的空格缩进,最多三个空格
  • 一个星号(*),减号(-)或下划线(_)(标记)
  • 可选的空格后跟与标记相同的标记
  • 前面的至少如此多次,以至于该行包含标记字符的“of”
  • 最后是可选的空白字符、换行符或流的末尾(详细规范可参考CommonMark规范。)

用于捕获此模式的匹配器可以是

$horizontalRulerMatcher = new Length(
	new Sequence(
		new Length(new Optional(new Matches(Token::WHITESPACE)), 3, '<='),
		$char = new Choice(Token::MINUS, Token::STAR, Token::UNDERSCORE),

		new Range(new Sequence(
			new Matches(Token::WHITESPACE),
			new SameType($char)
		), 0),
		new Optional(new Matches(Token::WHITESPACE)),
		new Either(
			new Matches(Token::NEWLINE),
			new Tail()
		)
	), 3, '>=', [ Token::NEWLINE, Token::WHITESPACE ]
);

根据CommonMark的合规性测试,这甚至可以工作!

之前没有提到两个匹配器 OptionalEitherOptional 很明显:如果可能,它会匹配包含的匹配器,但它总是成功的。而 Either 匹配器本来应该是 Or 匹配器,但 Or 在PHP中是保留字。只要有一个匹配器成功,它就会成功。

调试模式

tokenmatcher包设计时考虑了易于调试。如果您想知道匹配器实际上做了什么,只需打印出来即可!

上面的匹配器在标记文本 *** 上执行将会

$tokenizer = new Tokenizer("***\n");
$tokens = $tokenizer->getTokens();

$matched = $horizontalRulerMatcher->match($tokens);
echo $matched . PHP_EOL;

输出

Length checked 3 >= 3 ignored NEWLINE, WHITESPACE has status "Token type matched."
  Sequence has status "Token type matched."
    Length checked 0 <= 3 has status "Token type matched."
      Optional has status "Token type matched."
        Matches for type "WHITESPACE" has status "Token type did not match."
    Choice for types (MINUS, STAR, UNDERSCORE) has status "Token type matched." matched type STAR
    Range for limits {0, 9223372036854775807} has status "Token type matched." had 0 successful matches
      Sequence has status "Token type did not match."
        Matches for type "WHITESPACE" has status "Token type did not match."
        SameType for type "UNKNOWN" has status "Not executed yet."
    Optional has status "Token type matched."
      Matches for type "WHITESPACE" has status "Token type did not match."
    Either has status "Token type matched." matches choice #1
      Matches for type "NEWLINE" has status "Token type matched."
      Tail has status "Not executed yet."