Парсеры
В настоящей главе изучаются синтаксические анализаторы, или парсеры. Синтаксический анализатор проверяет строку на соответствие грамматике.
Формальная грамматика — это четверка вида
, где
— множество нетерминальных символов,
— множество терминальных символов,
— начальный нетерминальный символ,
— множество правил. Множества
и
не пересекаются В контекстно-свободной грамматике правила, в форме Бэкуса-Наура (БНФ), имеют вид:

(используется также обозначение
), где
— нетерминальный символ грамматики, а
— последовательность терминальных и нетерминальных символов.
Язык состоит из фраз — последовательностей терминальных символов. Фраза (или слово, предложение, цепочка символов) принадлежит языку, если она может быть выведена с помощью применения правил грамматики из ее начального символа конечное число раз. Применение правила
преобразует слово
в слово
.
Результатом анализа, или разбора строки является дерево разбора, листьями которого являются терминальные символы грамматики, а остальными вершинами — нетерминальные символы.
11.1. Анализ английских предложений
Рассмотрим английские предложения вида [4]:
Every man that lives loves a woman. John likes Mary.
Грамматику таких предложений можно описать следующим образом:
- предложение состоит из группы существительного и группы глагола;
- группа существительного состоит из собственного имени или спецификатора, нарицательного имени и придаточного предложения;
- придаточное предложение состоит из указательного местоимения и глагольной группы;
- глагольная группа состоит из непереходного глагола или переходного глагола и группы существительного.
Грамматика в БНФ имеет вид:
sent ::= np vp np ::= spec1 noun relcOpt | np1 np1 ::= pnoun | spec2 noun relcOpt vp ::= verb1 | verb2 np1 relcOpt ::= relprn vp | none noun ::= [man] | [woman] | [cat] | [dog] pnoun ::= [John] | [Mary] verb1 ::= [lives] | [sings] | [runs] verb2 ::= [loves] | [likes] spec1 ::= [every] spec2 ::= [a] relprn ::= [that]
Множество нетерминальных символов образуют символы из левых частей правил. Терминальные символы заключены в квадратные скобки. Начальным символом грамматики является символ sent.
Дерево разбора предложения "every man that lives loves a woman" в соответствии с данной грамматикой приведено на рис. 11.1.
В следующей программе строится дерево разбора предложений, удовлетворяющих грамматике. Дерево разбора представляется в виде терма. Домен таких термов определяется рекурсивно (см. программу).
Первым аргументом предиката parser/4 является нетерминальный символ грамматики. Каждое правило, описывающее этот предикат, соответствует правилу грамматики, указанному в комментарии. На вход предиката также поступает список токенов. Предикат возвращает терм фрагмента дерева разбора и остаток списка токенов.
Правила грамматики, в правых частях которых отсутствуют нетерминальные символы, реализуются в виде фактов.
open core, console, string
class predicates % разбиение на токены
scan: (string) -> string*.
clauses
scan(Str) = [Tok | scan(RestStr)]:-
frontToken(Str, Tok, RestStr),
!.
scan(_) = [].
domains % парсер
term = sent(term Np, term Vp); np(string, string, term Relc);
pn(string); vp(string, term Np); relc(string, term Vp); empty.
nt = sent; np; np1; vp; relc.
class predicates
parser: (nt, string*, term [out], string* [out]).
clauses
% sent ::= np vp
parser(sent, L, sent(Np, Vp), Rest):-
parser(np, L, Np, L1),
parser(vp, L1, Vp, Rest),
!.
% np ::= spec1 noun relcOpt
parser(np, [Spec, Noun | L], np(Spec, Noun, Relc), Rest):-
spec1(Spec),
noun(Noun),
!,
parser(relc, L, Relc, Rest).
% np ::= np1
parser(np, L, Np, Rest):- !,
parser(np1, L, Np, Rest).
% np1 ::= pnoun
parser(np1, [Name | L], pn(Name1), L):-
pnoun(Name1),
Name = toLowerCase(Name1),
!.
% np1 ::= spec2 noun relcOpt
parser(np1, [Spec, Noun | L], np(Spec, Noun, Relc), Rest):-
spec2(Spec),
noun(Noun),
!,
parser(relc, L, Relc, Rest).
% vp ::= verb1
parser(vp, [Verb | L], vp(Verb, empty), L):-
verb1(Verb),
!.
% vp ::= verb2 np1
parser(vp, [Verb | L], vp(Verb, Np), Rest):-
verb2(Verb),
!,
parser(np1, L, Np, Rest).
% relcOpt ::= relprn vp
parser(relc, [Rel | L], relc(Rel, Vp), Rest):-
relprn(Rel),
!,
parser(vp, L, Vp, Rest).
% relcOpt ::= none
parser(_, L, empty, L).
class facts
noun: (string).
pnoun: (string).
verb1: (string).
verb2: (string).
spec1: (string).
spec2: (string).
relprn: (string).
clauses
noun("man").
noun("woman").
noun("cat").
noun("dog").
pnoun("John").
pnoun("Mary").
spec1("every").
spec2("a").
verb1("lives").
verb1("sings").
verb1("runs").
verb2("loves").
verb2("likes").
relprn("that").
class predicates % печать дерева разбора
print: (term).
print: (term, charCount).
f: (charCount, string).
clauses
f(N, S):-
write(string::create(N, "\t"), S), nl.
print(Term):-
print(Term, 0).
print(sent(Np, Vp), N):-
f(N, "sent"),
print(Np, N + 1),
print(Vp, N + 1).
print(np(Spec, Noun, Relc), N):-
f(N, "np"),
f(N + 1, "spec"), f(N + 2, Spec),
f(N + 1, "noun"), f(N + 2, Noun),
print(Relc, N + 1).
print(pn(Name), N):-
f(N, "np"), f(N + 1, Name).
print(relc(Rel, Vp), N):-
f(N, "relc"),
f(N + 1, "relprn"), f(N + 2, Rel),
print(Vp, N + 1).
print(vp(Verb, Np), N):-
f(N, "vp"),
f(N + 1, "verb"), f(N + 2, Verb),
print(Np, N + 1).
print(empty, _).
run():-
Str = "Every man that lives loves a woman.",
write(Str), nl,
L = scan(toLowerCase(Str)),
parser(sent, L, Term, Rest),
write(Rest), nl,
write(Term), nl, nl,
print(Term),
_ = readLine().
Пример
11.1.
Разбор английских предложений
Предикат toLowerCase переводит все символы строки в нижний регистр.
Упражнение 1. Добавьте в предложения существительные множественного числа. Постройте грамматику и напишите парсер таких предложений.
