如何在Prolog DCG中不使用cut实现正确的非token匹配?
问题分析
你的核心问题出在not_token的定义上:string(Codes), {\+ phrase(token(_), Codes)}允许匹配任意长度的字符序列(包括空),只要该序列不是完整token。这会导致Prolog回溯时,将token的部分前缀当作非token字符吃掉,进而产生错误解析结果(比如仅匹配单个token或拆分token的情况)。
解决方案
重新定义not_token,使其仅匹配无法作为任何token起始的字符序列,确保不会破坏合法token的完整性。具体做法是逐个检查字符,只吃掉那些绝对不可能启动任何token的字符,直到遇到可能的token起始位置或字符串结束。
修正后的完整代码:
% 主解析器:跳过非token字符 → 匹配token → 跳过非token字符 → 递归解析剩余部分 parser([Token|Rest]) --> not_token, token(Token), not_token, parser(Rest). parser([]) --> []. % 定义目标token token(mul(N1, N2)) --> "mul(", number(N1), ",", number(N2), ")". token(do) --> "do()". token(dont) --> "don't()". % 数字解析辅助规则(确保匹配连续数字) number(N) --> digits(Ds), { number_codes(N, Ds) }. digits([D|Ds]) --> digit(D), digits(Ds). digits([D]) --> digit(D). digit(D) --> [D], { code_type(D, digit) }. % 严格的非token匹配:仅吃掉不能启动任何token的字符 not_token --> ( [C], { \+ starts_token([C|_]) } % 当前字符无法作为任何token的起始 -> [C], not_token ; []). % 辅助谓词:检查输入是否可以启动某个token(即输入是某个token的前缀) starts_token(Input) :- phrase(token(_), Input, _).
方案说明
starts_token/1:判断当前位置的字符序列是否是某个token的前缀,确保不会误吞token的起始部分。not_token递归逻辑:逐个字符检查,只吃掉绝对不属于任何token开头的字符,直到遇到可能的token起始点或字符串结束。- 消除回溯歧义:由于
not_token不会破坏token的完整性,解析器只能在合法的token位置匹配,不会产生多余错误解,无需使用cut终止回溯。
测试你的查询:
?- phrase(parser(Tokens), `mul(mul(123,456)dommmmulmul(1,2))`).
现在只会返回唯一正确的解析结果。
内容的提问来源于stack exchange,提问作者Mo...
相关产品推荐
相关产品推荐

