The combinator of combinator

285 阅读4分钟
原文链接: zhuanlan.zhihu.com

组合子的组合子

大雾~


最近把之前写的Parser combinator拿了出来, 因为刚好想到一些有趣的东西. 之前的组合子是不能回溯的, 因为所有的组合子的构建是自下而上的, 当前组合子的语法分析只能看到其子组合子的综合属性, 返回一条分支的值之后便不再尝试其他分支. 这会对其表达能力造成一些限制.

一个解决办法是像一般的Parser combinator一样, 传入一个所有的当前分析位置的list, 对每一个位置进行分析, 保存所有分支的结果传出一个新的list.

我使用的是另一种思路, 对组合子的组合函数进行CPS变换, 这样每一个组合子除了能看见子组合子的综合属性还能看见后续的分析结果, 于是分支选择变成了可能, 即是对应与Direct style的回溯.

详细的构造请看文章Parser combinator 在C++里的DSL更新的第4节, 是我新加入哒.


为什么需要回溯呢? 那当然是我想到了一些好玩的东西啦, 在之前的文章里通过Parser combinator构造了一个小的四则运算计算器, 组合子是:

ParserCombinator<int> Additive

返回的是表达式的语义值, 一个 \texttt{int} 类型的数值表示算式的值.

于是问题来了, 很自然地会想到一个生成Parser combinator的Parser combinator是什么呢?


一个典型的最简单的例子, 正则表达式!

对! 又是正则表达式~

理由很简单, 在Chomsky hierarchy里, 正则表达式是Type-3 grammars, 弱于Parser combinator描述的Type-2 grammars上下文无关文法. 显然地, 我们可以使用组合子来构造一个正则表达式的匹配器.

正则表达式的匹配器是一个返回 \texttt{bool} 型的Parser combinator, 返回 \texttt{true} 表示匹配成功.

对于正则表达式的字符匹配:

ParserCombinator<bool> match;
match = (Token(ch) >> Return(true));

\texttt{ch} 是需要匹配的 \texttt{char} .

对于正则表达式的连接\texttt{(+)}:

//ParserCombinator<bool> part_1, part_2;
ParserCombinator<bool> match;
match = part_1 + part_2 >> Return(true);

\texttt{part_1}\texttt{part_2} 是子表达式.

对于正则表达式的分支 \texttt{(|)} :

ParserCombinator<bool> match;
match = part_1 | part_2;

\texttt{part_1}\texttt{part_2} 是子表达式.

对于正则表达式的闭包\texttt{(*)}:

ParserCombinator<bool> match;
match = match + unit >> Return(true) | Epsilon(true);

\texttt{unit} 是子表达式.

有了以上的构造之后可以轻松地写出生成正则表达式组合子:

ParserCombinator<ParserCombinator<bool>> unit, repeat, connection, branch, regex;

unit = Token([](char ch) { return ch != '(' && ch != ')' && ch != '|' && ch != '+'; }) >>
	[](char ch)
{
	ParserCombinator<bool> match;
	match = (Token(ch) >> Return(true));
	return match;
} | '\\'_T + Token(AnyChar{}) >> [](Placeholder, char ch)
{
	ParserCombinator<bool> match;
	match = (Token(ch) >> Return(true));
	return match;
} | '('_T + regex + ')'_T >> [](Placeholder, const ParserCombinator<bool>& unit, Placeholder)
{
	return unit;
};

repeat = unit + '*'_T >> [](const ParserCombinator<bool>& unit, Placeholder)
{
	ParserCombinator<bool> match;
	match = match + unit >> Return(true) | Epsilon(true);
	return match;
} | unit;

connection = connection + repeat >>
	[](const ParserCombinator<bool>& part_1, const ParserCombinator<bool>& part_2)
{
	ParserCombinator<bool> match;
	match = part_1 + part_2 >> Return(true);
	return match;
} | repeat;

branch = branch + '|'_T + connection >>
	[](const ParserCombinator<bool>& part_1, Placeholder, const ParserCombinator<bool>& part_2)
{
	ParserCombinator<bool> match;
	match = part_1 | part_2;
	return match;
} | connection;

regex = branch >> [](const ParserCombinator<bool>& match)
{
	ParserCombinator<bool> regex(match);
	regex.ReturnDefaultWhenFail(true);	//return false
	return regex;
};

std::string pattern = "(a|b)*abb";
std::string match   = "babaababb";

std::cout << regex(pattern)(match) << std::endl;
//print: 1

看到 \texttt{ParserCombinator<ParserCombinator<bool>>} 了嘛, 于是我们在50行内写出了一个正则表达式引擎(特大雾).

完整的代码在这里下载: Parser Combinator [.zip]

除了简单的正则表达式, 更加实用的是使用这种方式将一段BNF文法文本自动生成一个Parser.


我感觉我都快成正则表达式专业户了(笑~). 其实我是用CPS改写了我的Parser combinator, 更新文章了不过感觉没什么人会看到, 于是来水了一篇文章.

(Parser combinator是用C++以一种极其扭曲的FP风格写出来的, 极其难以调试和测试, 所以保不准会出一些奇怪的bug啊哈)

继续拓展, 一个能生成生成Parser combinator的Parser combinator的Parser combinator会是什么呢? 这个嘛...嗯...我不知道. 更加地, 一个能生成自身Parser combinator的Parser combinator又会是什么呢? (无限嵌套的递归类型, C++也里实现不出来呀哈)

知道的小伙伴请告诉我>.<