Как мне построить дерево синтаксического анализа из серии токенов S-выражения в Prolog?

#parsing #prolog #grammar #dcg #parse-tree

#синтаксический анализ #пролог #грамматика #dcg #синтаксическое дерево

Вопрос:

Мне любопытен Prolog как синтаксический анализатор, поэтому я делаю небольшой интерфейс Lisp. Я уже создал токенизатор, который вы можете увидеть здесь:

 base_tokenize([], Buffer, [Buffer]).
base_tokenize([Char | Chars], Buffer, Tokens) :-
    (Char = '(' ; Char = ')') ->
        base_tokenize(Chars, '', Tail_Tokens),
        Tokens = [Buffer, Char | Tail_Tokens];

    Char = ' ' ->
        base_tokenize(Chars, '', Tail_Tokens),
        Tokens = [Buffer | Tail_Tokens];

    atom_concat(Buffer, Char, New_Buffer),
    base_tokenize(Chars, New_Buffer, Tokens).

filter_empty_blank([], []).
filter_empty_blank([Head | Tail], Result) :-
    filter_empty_blank(Tail, Tail_Result),
    ((Head = [] ; Head = '') ->
        Result = Tail_Resu<
        Result = [Head | Tail_Result]).

tokenize(Expr, Tokens) :-
    atom_chars(Expr, Chars),
    base_tokenize(Chars, '', Dirty_Tokens),
    filter_empty_blank(Dirty_Tokens, Tokens).
 

Теперь у меня есть новая задача: построить дерево синтаксического анализа из этого. Сначала я попытался создать его без грамматики, но это оказалось действительно грязным. Итак, я использую DCGS. Страница Википедии на нем не очень понятна — особенно часть синтаксического анализа с помощью DCGS. Может быть, кто-нибудь может дать мне более четкое представление о том, как я мог бы построить дерево? Я был очень рад узнать, что списки Prolog являются нетипизированными, так что теперь это немного проще, поскольку типы сумм не нужны. Я просто очень смущен вводом в грамматические предложения типа sentence(s(NP,VP)) или verb(v(eats)) (в Вики), почему аргументы имеют такие заумные имена и как я могу начать работу с моим синтаксическим анализатором без особых хлопот.

 expr --> [foo].
expr --> list.

seq --> expr, seq.
seq --> expr.

list --> ['('], seq, [')'].
 

Комментарии:

1. Хорошее введение в dcg, metalevel.at/prolog/dcg . У него есть хороший пример построения дерева.

2. Да, эта страница Википедии не так хороша, как обычно.

Ответ №1:

Вот начало: синтаксический анализ списка атомов LISP, который сначала является неструктурированным списком токенов:

 List = [ '(', '(', foo, bar, ')', baz ')' ].
 

Сначала просто примите это.

Запишите грамматику напрямую:

 so_list         --> ['('], so_list_content, [')'].

so_list_content --> [].
so_list_content --> so_atom, so_list_content.
so_list_content --> so_list, so_list_content.

so_atom         --> [X], {   member(X,['(',')']),atom(X) }.
 

Добавьте несколько тестовых примеров (есть ли plunit в GNU Prolog?)

 :- begin_tests(accept_list).

test(1,[fail])        :- phrase(so_list,[]).
test(2,[true,nondet]) :- phrase(so_list,['(',')']).
test(3,[true,nondet]) :- phrase(so_list,['(',foo,')']).
test(4,[true,nondet]) :- phrase(so_list,['(',foo,'(',bar,')',')']).
test(5,[true,nondet]) :- phrase(so_list,['(','(',bar,')',foo,')']).
test(6,[fail])        :- phrase(so_list,['(',foo,'(',bar,')']).

:- end_tests(accept_list).
 

И так:

 ?- run_tests.
% PL-Unit: accept_list ...... done
% All 6 tests passed
true.
 

Прохладный. Похоже, мы можем принимать списки токенов.

Теперь создайте дерево синтаксического анализа. Это делается путем увеличения термина Prolog с помощью параметров «предикатов DCG». Термин (или несколько терминов) в заголовке естественным образом собирает термины (или несколько терминов), появляющиеся в теле, в более крупную структуру. После достижения токенов терминала структура начинает заполняться фактическим содержимым:

 so_list(list(Stuff))   --> ['('], so_list_content(Stuff), [')'].

so_list_content([])        --> [].
so_list_content([A|Stuff]) --> so_atom(A), so_list_content(Stuff).
so_list_content([L|Stuff]) --> so_list(L), so_list_content(Stuff).

so_atom(X) --> [X], {   member(X,['(',')']),atom(X) }.
 

Да, тесты (удалите ожидаемый результат из тестовой головки, потому что визуальный шум слишком велик)

 :- begin_tests(parse_list).

test(1,[fail]) :-
   phrase(so_list(_),[]).

test(2,[true(L==Result),nondet]) :-
   phrase(so_list(L),['(',')']),
   Result = list([]).
   
test(3,[true(L==Result),nondet]) :-
   phrase(so_list(L),['(',foo,')']),
   Result = list([foo]).
   
test(4,[true(L==Result),nondet]) :-
   phrase(so_list(L),['(',foo,'(',bar,')',')']),
   Result = list([foo,list([bar])]).
   
test(5,[true(L==Result),nondet]) :-
   phrase(so_list(L),['(','(',bar,')',foo,')']),
   Result = list([list([bar]),foo]).
   
test(6,[fail]) :-
   phrase(so_list(_),['(',foo,'(',bar,')']).

:- end_tests(parse_list).
 

И так:

 ?- run_tests.
% PL-Unit: parse_list ...... done
% All 6 tests passed
true.
 

Комментарии:

1. Я не могу найти plunit GNU Prolog, но, вероятно, существует какая-то другая система модульного тестирования. На самом деле, я уже выяснил, как проверить, является ли последовательность токенов допустимой — построение дерева — это то, что сбивает меня с толку. Не могли бы вы подробнее рассказать об этом шаге?

2. Это было так полезно! Спасибо.