Предположим, я хочу токенизировать строку слов (символов) и чисел, разделенных пробелами. Например, ожидаемым результатом токенизации "aa 11"
будет [tkSym("aa"), tkNum(11)]
.Tokenizing string в Prolog с использованием DCG
Моя первая попытка была ниже код:
whitespace --> [Ws], { code_type(Ws, space) }, whitespace.
whitespace --> [].
letter(Let) --> [Let], { code_type(Let, alpha) }.
symbol([Sym|T]) --> letter(Sym), symbol(T).
symbol([Sym]) --> letter(Sym).
digit(Dg) --> [Dg], { code_type(Dg, digit) }.
digits([Dg|Dgs]) --> digit(Dg), digits(Dgs).
digits([Dg]) --> digit(Dg).
token(tkSym(Token)) --> symbol(Token).
token(tkNum(Token)) --> digits(Digits), { number_chars(Token, Digits) }.
tokenize([Token|Tokens]) --> whitespace, token(Token), tokenize(Tokens).
tokenize([]) --> whitespace, [].
Вызов tokenize
на "aa bb"
оставляет меня несколько возможных ответов:
?- tokenize(X, "aa bb", []).
X = [tkSym([97|97]), tkSym([98|98])] ;
X = [tkSym([97|97]), tkSym(98), tkSym(98)] ;
X = [tkSym(97), tkSym(97), tkSym([98|98])] ;
X = [tkSym(97), tkSym(97), tkSym(98), tkSym(98)] ;
false.
В этом случае, однако, представляется целесообразным рассчитывать только один правильный ответ. Вот еще один, более детерминированный подход:
whitespace --> [Space], { char_type(Space, space) }, whitespace.
whitespace --> [].
symbol([Sym|T]) --> letter(Sym), !, symbol(T).
symbol([]) --> [].
letter(Let) --> [Let], { code_type(Let, alpha) }.
% similarly for numbers
token(tkSym(Token)) --> symbol(Token).
tokenize([Token|Tokens]) --> whitespace, token(Token), !, tokenize(Tokens).
tokenize([]) --> whiteSpace, [].
Но есть проблема: хотя однозначного ответа на token
призвал "aa"
теперь хороший список, то tokenize
предикат заканчивается в бесконечной рекурсии:
?- token(X, "aa", []).
X = tkSym([97, 97]).
?- tokenize(X, "aa", []).
ERROR: Out of global stack
Что мне не хватает? Как обычно проблема решается в Prolog?
Один комментарий о наименовании: Я предлагаю имя 'лексемы // 1', так что у вас есть' маркер // 1' и 'лексемы // 1', по аналогии с цифрой // 1 и 'цифры/1'. Таким образом, вы избегаете императивного имени, которое предполагает, что нетерминал может использоваться только в одном направлении.'tokens // 1' является более декларативным и имеет смысл и тогда, когда нетерминал необходим в других направлениях. – mat
Два очень хороших ресурса для изучения DCG: во-первых, [DCG Primer [Markus Triska]] (http://www.metalevel.at/dcg.html); затем взгляните на (несколько трудно найти) [сборник общедоступных правил DCG] (http://www.swi-prolog.org/pldoc/doc/swi/library/dcg/basics.pl?show= src), доступный как часть источника SWI-Prolog. Оба они довольно малы и содержат много очень полезных примеров/рецептов кода. –