В настоящей главе изучаются синтаксические анализаторы, или парсеры. Синтаксический анализатор проверяет строку на соответствие грамматике.
Формальная грамматика — это четверка вида $$\langle N, T, S, P \rangle$$, где $$N$$ — множество нетерминальных символов, $$T$$ — множество терминальных символов, $$S$$ — начальный нетерминальный символ, $$P $$— множество правил. Множества $$N$$ и $$T$$ не пересекаются В контекстно-свободной грамматике правила, в форме Бэкуса-Наура (БНФ), имеют вид:
$$x ::= X $$
(используется также обозначение $$x \to X$$), где $$x$$ — нетерминальный символ грамматики, а $$X$$ — последовательность терминальных и нетерминальных символов.
Язык состоит из фраз — последовательностей терминальных символов. Фраза (или слово, предложение, цепочка символов) принадлежит языку, если она может быть выведена с помощью применения правил грамматики из ее начального символа конечное число раз. Применение правила $$x ::= X$$ преобразует слово $$AxB$$ в слово $$AXB$$.
Результатом анализа, или разбора строки является дерево разбора, листьями которого являются терминальные символы грамматики, а остальными вершинами — нетерминальные символы.
Рассмотрим английские предложения вида [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.
(рис 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().
Предикат toLowerCase переводит все символы строки в нижний регистр.
Упражнение 1. Добавьте в предложения существительные множественного числа. Постройте грамматику и напишите парсер таких предложений.
Рассмотрим арифметические выражения вида:
-2 + 3 * x; -2^2 * 3 + 2 * 4 / (3 - 1) + (sin(pi/6 + pi/3)) ^ 2 - ln (2 * e^3 - 1); 2^3^2 - 3 * (4 + 10^2) * (3 - 10^2); 7.
В следующей программе выполняется синтаксический анализ таких выражений. Разбор выполняется в соответствии с праворекурсивной грамматикой, которая имеет следующий вид:
expr ::= addSignOpt item items item ::= deg potens deg ::= elem elems elem ::= fun [(] expr [)] | [(] expr [)] | [pi] | [e] | num | var items ::= addSign item items | none potens ::= multSign deg potens | none elems ::= [^] elem elems | none addSignOpt ::= addSign | none addSign ::= [+] | [-] multSign ::= [*] | [\] fun ::= [cos] | [sin] | [exp] | [ln]
Сначала программа строит терм дерева разбора. Если выражение не содержит переменных, то по терму вычисляется его значение. Затем выполняется обратное преобразование терма в строку. Предикат parser/4 по входному списку токенов и нетерминальному символу грамматики (expr, item, deg или elem) возвращает подтерм дерева разбора и остаток списка токенов. Предикат parser/5 имеет еще один аргумент — терм, который соответствует первому аргументу бинарной операции (сложения, вычитания, умножения, деления или возведения в степень). Он рекурсивно строит терм для последовательности аргументов этой операции (см. правила для символов items, potens и elems).
class predicates
scan: (string) -> string*.
clauses
scan(Str) = [Tok | scan(RestStr)]:-
string::frontToken(Str, Tok, RestStr),
!.
scan(_) = [].
domains % парсер
term = un(string Op, term); bin(string Op, term, term);
func(string Op, term); var(string); r(real); pi(); e().
nt = expr; item; items; deg; potens; elem; elems.
class predicates
parser: (nt, string*, term [out], string* [out]) determ.
parser: (nt, string*, term, term [out], string* [out]).
clauses
parser(expr, [S | L], Term, Rest):-
addOp(S),
!,
parser(item, L, Term1, L1),
parser(items, L1, un(S, Term1), Term, Rest).
parser(expr, L, Term, Rest):-
parser(item, L, Term1, L1),
parser(items, L1, Term1, Term, Rest).
parser(item, L, Term, Rest):-
parser(deg, L, Term1, L1),
parser(potens, L1, Term1, Term, Rest).
parser(deg, L, Term, Rest):-
parser(elem, L, Term1, L1),
parser(elems, L1, Term1, Term, Rest).
parser(elem, [S, "(" | L], func(S, Term), Rest):-
fun(S),
!,
parser(expr, L, Term, L1),
L1 = [")" | Rest].
parser(elem, ["(" | L], un("()", Term), Rest):- !,
parser(expr, L, Term, L1),
L1 = [")" | Rest].
parser(elem, ["pi" | L], pi(), L):- !.
parser(elem, ["e" | L], e(), L):- !.
parser(elem, [S | L], r(R), L):-
(R = tryToTerm(unsigned, S); R = tryToTerm(real, S)),
!.
parser(elem, [S | L], var(S), L):-
string::isName(S).
parser(items, [S | L], Term1, Term, Rest):-
addOp(S),
parser(item, L, Term2, L1),
!,
parser(items, L1, bin(S, Term1, Term2), Term, Rest).
parser(potens, [S | L], Term1, Term, Rest):-
multOp(S),
parser(deg, L, Term2, L1),
!,
parser(potens, L1, bin(S, Term1, Term2), Term, Rest).
parser(elems, ["^" | L], Term1, bin("^", Term1, Term2), Rest):-
parser(deg, L, Term2, Rest),
!.
parser(_, L, Term, Term, L).
class facts
addOp: (string).
multOp: (string).
fun: (string).
clauses
addOp("+").
addOp("-").
multOp("*").
multOp("/").
fun("sin").
fun("cos").
fun("exp").
fun("ln").
class predicates % вычислитель
calc: (term) -> real determ.
clauses
calc(r(R)) = R.
calc(pi()) = math::pi.
calc(e()) = math::e.
calc(un("-", X)) = - calc(X):- !.
calc(un(_, X)) = calc(X).
calc(bin("+", X, Y)) = calc(X) + calc(Y).
calc(bin("-", X, Y)) = calc(X) - calc(Y).
calc(bin("*", X, Y)) = calc(X) * calc(Y).
calc(bin("/", X, Y)) = calc(X) / R:-
R = calc(Y),
R <> 0.
calc(bin("^", X, Y)) = calc(X) ^ calc(Y).
calc(func("sin", X)) = math::sin(calc(X)).
calc(func("cos", X)) = math::cos(calc(X)).
calc(func("exp", X)) = math::exp(calc(X)).
calc(func("ln", X)) = math::ln(R):-
R = calc(X),
R > 0.
class predicates % преобразование в строку
toStr: (term) -> string.
clauses
toStr(pi()) = "pi".
toStr(e()) = "e".
toStr(r(R)) = toString(R).
toStr(var(V)) = V.
toStr (un("()", X)) = string::format("(%)", toStr(X)):- !.
toStr(un(S, X)) = string::format("% %", S, toStr(X)).
toStr(bin(S, X, Y)) = string::format("% % %", toStr(X), S,
toStr(Y)).
toStr(func(S, X)) = string::format("%(%)", S, toStr(X)).
run():-
S = "-2^3^2 + 3 * (10 - 3) + 20 ^2 + sin(pi / 3)",
write(S), nl, nl,
L = scan(S),
parser(expr, L, Term, Rest),
write(Rest), nl, nl,
write(Term), nl, nl,
write(toStr(Term)),
R = calc(Term),
write(" = ", R),
fail;
_ = readLine().
Предикат isName проверяет, удовлетворяет ли строка синтаксическим требованиям, предъявляемым к переменным (и ключевым словам). Предикат tryToTerm конвертирует элемент домена string в элемент другого домена. Предикаты exp и ln вычисляют значения экспоненты и натурального логарифма действительного числа (домена real и ureal, соответственно).
Программа не требует, чтобы строка полностью удовлетворяла грамматике. Выполняется разбор максимального префикса строки, который ей удовлетворяет. Неразобранная часть строки в виде токенов остается в списке Rest.
В определении предиката calc не используется хвостовая рекурсия. С помощью анонимных предикатов вычисления можно существенно ускорить:
domains
operation = (real, real) -> real.
unOperation = (real) -> real.
class predicates
op : (string) -> operation.
unop : (string) -> unOperation.
calc: (term) -> real determ.
clauses
op("+") = {(X, Y) = X + Y}:- !.
% …
unop("-") = {(X) = -X}:- !.
unop("sin") = {(X) = math::sin(X)}:- !.
% …
calc(bin(S, X, Y)) = op(S)(X, Y).
% …
Упражнение. Завершите определение вычислителя.
В данном параграфе реализуется алгоритм поиска наибольшего общего унификатора двух термов (или атомарных формул). Формулы подаются программе в виде двух строк. Парсер преобразует их в термы специального вида, которые и поступают на вход алгоритма.
Грамматика исходных формул описывается следующим образом:
term ::= var | const | fun [(] termlist [)] termlist ::= term | term [,] termlist
Переменные пишутся с прописной буквы, а константы, предикатные и функциональные символы — со строчной, например, $$p(X, f(a, Y, g(b)), h(1, Z))$$.
Алгоритм поиска наибольшего общего унификатора термов $$T_1$$ и $$T_2$$ с помощью двух стеков $$L$$ и $$S$$ описан в [9]. Он заключается в следующем. Вначале в $$L$$ помещается пара термов $$(T_1, T_2)$$, стек $$S$$ пуст. Алгоритм начинается с шага 1.
Из $$L$$ извлекается пара термов $$(X, Y)$$. Возможны случаи:
open core, console, string
domains
term = var(string Var); c(string Const); f(string Name, term*).
% парсер
class predicates
scan: (string) -> string*.
parser: (string*, term [out], string* [out]) determ.
parser: (string*, term* [out]) -> string* determ.
checkName: (string) determ.
clauses
scan(Str) = [Tok | scan(RestStr)]:-
frontToken(Str, Tok, RestStr),
!.
scan(_) = [].
parser([S, "(" | L], f(S, TermList), Rest):- !,
checkName(S),
[")" | Rest] = parser(L, TermList).
parser([S | L], var(S), L):-
isName(S),
Ch = frontChar(S),
Ch = charUpper(Ch),
!.
parser([S | L], c(S), L):-
(_ = tryToTerm(unsigned, S); _ = tryToTerm(real, S);
checkName(S)),
!.
parser(["," | L], TermList) = parser(L, TermList):- !.
parser(L, [Term | TermList]) = parser(Rest, TermList):-
parser(L, Term, Rest),
!.
parser(L, []) = L.
checkName(S):-
Ch = charToString(frontChar(S)),
hasAlpha(Ch),
isLowerCase(Ch).
% поиск наибольшего общего унификатора
class predicates
unify: (term, term) -> term* determ.
unif: (term*, term*) -> term* determ.
subterm: (term, term) determ.
replace: (term*, term, term) -> term*.
delete: (term*) -> term*.
put: (term*, term*, term*) -> term* determ.
clauses
unify(X, Y) = unif([X, Y], []).
unif([var(X), var(X) | L], Subst) = unif(L, Subst):- !.
unif([c(X), c(X) | L], Subst) = unif(L, Subst):- !.
unif([var(X), Y | L], Subst) = unif(replace(L, var(X), Y),
[var(X), Y | delete(replace(Subst, var(X), Y))]):- !,
not(subterm(var(X), Y)).
unif([Y, var(X) | L], Subst) = unif([var(X), Y | L], Subst):- !.
unif([f(N, TL1), f(N, TL2) | L], Subst) = unif(L1, Subst):-
L1 = put(TL1, TL2, L).
unif([], Subst) = Subst.
subterm(X, X):- !.
subterm(X, f(_, L)):-
list::exists(L, {(H):- subterm(X, H)}).
replace([X | L], X, Y) = [Y | replace(L, X, Y)]:- !.
replace([H | L], X, Y) = [H | replace(L, X, Y)].
replace([], _, _) = [].
delete([X, X | L]) = delete(L):- !.
delete([X, Y | L]) = [X, Y | delete(L)]:- !.
delete(_) = [].
put([X | L1], [Y | L2], L) = put(L1, L2, [X, Y | L]).
put([], [], L) = L.
% преобразование в строку
class predicates
s: (term) -> string.
toStr: (term*) -> string*.
clauses
s(var(X)) = X:- !.
s(c(X)) = X:- !.
s(f(N, L)) = format("%(%)", N,
concatWithDelimiter(list::map(L, {(T) = s(T)}), ", ")).
toStr([X, Y | L]) = [format("% = %", s(X), s(Y)) | toStr(L)]:- !.
toStr(_) = [].
run():-
S1 = "p(X, f(b, Y), g(a, a), k(h(1, 2, 3)), U)",
S2 = "p(a, f(b, a), g(a, Y), k(Z), V)",
parser(scan(S1), Term1, _),
parser(scan(S2), Term2, _),
Subst = unify(Term1, Term2),
writef("%\n%\n\n%", s(Term1), s(Term2),
concatWithDelimiter(toStr(Subst), ", ")),
fail;
_ = readLine().
Предикат charToString преобразует символ (char) в строку (string), предикат isLowerCase истинен, если все символы строки имеют нижний регистр, предикат hasAlpha истинен, если строка состоит только из букв, предикат exists проверяет, имеется ли в списке элемент, удовлетворяющий заданному условию.
Упражнение 2. Реализуйте в программе, приведенной в листинге 11.3, возможность использования анонимных переменных (напомним, что они унифицируются с любыми термами, но не принимают значений).
"[0, [1, [2, 3, [4]]], 5]". Список подается на вход в виде строки. Линейный список также выдается в виде строки.В настоящей главе изучаются синтаксические анализаторы, или парсеры. Синтаксический анализатор проверяет строку на соответствие грамматике.
Формальная грамматика — это четверка вида $$\langle N, T, S, P \rangle$$, где $$N$$ — множество нетерминальных символов, $$T$$ — множество терминальных символов, $$S$$ — начальный нетерминальный символ, $$P $$— множество правил. Множества $$N$$ и $$T$$ не пересекаются В контекстно-свободной грамматике правила, в форме Бэкуса-Наура (БНФ), имеют вид:
$$x ::= X $$
(используется также обозначение $$x \to X$$), где $$x$$ — нетерминальный символ грамматики, а $$X$$ — последовательность терминальных и нетерминальных символов.
Язык состоит из фраз — последовательностей терминальных символов. Фраза (или слово, предложение, цепочка символов) принадлежит языку, если она может быть выведена с помощью применения правил грамматики из ее начального символа конечное число раз. Применение правила $$x ::= X$$ преобразует слово $$AxB$$ в слово $$AXB$$.
Результатом анализа, или разбора строки является дерево разбора, листьями которого являются терминальные символы грамматики, а остальными вершинами — нетерминальные символы.
Рассмотрим английские предложения вида [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.
(рис 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().
Предикат toLowerCase переводит все символы строки в нижний регистр.
Упражнение 1. Добавьте в предложения существительные множественного числа. Постройте грамматику и напишите парсер таких предложений.
Рассмотрим арифметические выражения вида:
-2 + 3 * x; -2^2 * 3 + 2 * 4 / (3 - 1) + (sin(pi/6 + pi/3)) ^ 2 - ln (2 * e^3 - 1); 2^3^2 - 3 * (4 + 10^2) * (3 - 10^2); 7.
В следующей программе выполняется синтаксический анализ таких выражений. Разбор выполняется в соответствии с праворекурсивной грамматикой, которая имеет следующий вид:
expr ::= addSignOpt item items item ::= deg potens deg ::= elem elems elem ::= fun [(] expr [)] | [(] expr [)] | [pi] | [e] | num | var items ::= addSign item items | none potens ::= multSign deg potens | none elems ::= [^] elem elems | none addSignOpt ::= addSign | none addSign ::= [+] | [-] multSign ::= [*] | [\] fun ::= [cos] | [sin] | [exp] | [ln]
Сначала программа строит терм дерева разбора. Если выражение не содержит переменных, то по терму вычисляется его значение. Затем выполняется обратное преобразование терма в строку. Предикат parser/4 по входному списку токенов и нетерминальному символу грамматики (expr, item, deg или elem) возвращает подтерм дерева разбора и остаток списка токенов. Предикат parser/5 имеет еще один аргумент — терм, который соответствует первому аргументу бинарной операции (сложения, вычитания, умножения, деления или возведения в степень). Он рекурсивно строит терм для последовательности аргументов этой операции (см. правила для символов items, potens и elems).
class predicates
scan: (string) -> string*.
clauses
scan(Str) = [Tok | scan(RestStr)]:-
string::frontToken(Str, Tok, RestStr),
!.
scan(_) = [].
domains % парсер
term = un(string Op, term); bin(string Op, term, term);
func(string Op, term); var(string); r(real); pi(); e().
nt = expr; item; items; deg; potens; elem; elems.
class predicates
parser: (nt, string*, term [out], string* [out]) determ.
parser: (nt, string*, term, term [out], string* [out]).
clauses
parser(expr, [S | L], Term, Rest):-
addOp(S),
!,
parser(item, L, Term1, L1),
parser(items, L1, un(S, Term1), Term, Rest).
parser(expr, L, Term, Rest):-
parser(item, L, Term1, L1),
parser(items, L1, Term1, Term, Rest).
parser(item, L, Term, Rest):-
parser(deg, L, Term1, L1),
parser(potens, L1, Term1, Term, Rest).
parser(deg, L, Term, Rest):-
parser(elem, L, Term1, L1),
parser(elems, L1, Term1, Term, Rest).
parser(elem, [S, "(" | L], func(S, Term), Rest):-
fun(S),
!,
parser(expr, L, Term, L1),
L1 = [")" | Rest].
parser(elem, ["(" | L], un("()", Term), Rest):- !,
parser(expr, L, Term, L1),
L1 = [")" | Rest].
parser(elem, ["pi" | L], pi(), L):- !.
parser(elem, ["e" | L], e(), L):- !.
parser(elem, [S | L], r(R), L):-
(R = tryToTerm(unsigned, S); R = tryToTerm(real, S)),
!.
parser(elem, [S | L], var(S), L):-
string::isName(S).
parser(items, [S | L], Term1, Term, Rest):-
addOp(S),
parser(item, L, Term2, L1),
!,
parser(items, L1, bin(S, Term1, Term2), Term, Rest).
parser(potens, [S | L], Term1, Term, Rest):-
multOp(S),
parser(deg, L, Term2, L1),
!,
parser(potens, L1, bin(S, Term1, Term2), Term, Rest).
parser(elems, ["^" | L], Term1, bin("^", Term1, Term2), Rest):-
parser(deg, L, Term2, Rest),
!.
parser(_, L, Term, Term, L).
class facts
addOp: (string).
multOp: (string).
fun: (string).
clauses
addOp("+").
addOp("-").
multOp("*").
multOp("/").
fun("sin").
fun("cos").
fun("exp").
fun("ln").
class predicates % вычислитель
calc: (term) -> real determ.
clauses
calc(r(R)) = R.
calc(pi()) = math::pi.
calc(e()) = math::e.
calc(un("-", X)) = - calc(X):- !.
calc(un(_, X)) = calc(X).
calc(bin("+", X, Y)) = calc(X) + calc(Y).
calc(bin("-", X, Y)) = calc(X) - calc(Y).
calc(bin("*", X, Y)) = calc(X) * calc(Y).
calc(bin("/", X, Y)) = calc(X) / R:-
R = calc(Y),
R <> 0.
calc(bin("^", X, Y)) = calc(X) ^ calc(Y).
calc(func("sin", X)) = math::sin(calc(X)).
calc(func("cos", X)) = math::cos(calc(X)).
calc(func("exp", X)) = math::exp(calc(X)).
calc(func("ln", X)) = math::ln(R):-
R = calc(X),
R > 0.
class predicates % преобразование в строку
toStr: (term) -> string.
clauses
toStr(pi()) = "pi".
toStr(e()) = "e".
toStr(r(R)) = toString(R).
toStr(var(V)) = V.
toStr (un("()", X)) = string::format("(%)", toStr(X)):- !.
toStr(un(S, X)) = string::format("% %", S, toStr(X)).
toStr(bin(S, X, Y)) = string::format("% % %", toStr(X), S,
toStr(Y)).
toStr(func(S, X)) = string::format("%(%)", S, toStr(X)).
run():-
S = "-2^3^2 + 3 * (10 - 3) + 20 ^2 + sin(pi / 3)",
write(S), nl, nl,
L = scan(S),
parser(expr, L, Term, Rest),
write(Rest), nl, nl,
write(Term), nl, nl,
write(toStr(Term)),
R = calc(Term),
write(" = ", R),
fail;
_ = readLine().
Предикат isName проверяет, удовлетворяет ли строка синтаксическим требованиям, предъявляемым к переменным (и ключевым словам). Предикат tryToTerm конвертирует элемент домена string в элемент другого домена. Предикаты exp и ln вычисляют значения экспоненты и натурального логарифма действительного числа (домена real и ureal, соответственно).
Программа не требует, чтобы строка полностью удовлетворяла грамматике. Выполняется разбор максимального префикса строки, который ей удовлетворяет. Неразобранная часть строки в виде токенов остается в списке Rest.
В определении предиката calc не используется хвостовая рекурсия. С помощью анонимных предикатов вычисления можно существенно ускорить:
domains
operation = (real, real) -> real.
unOperation = (real) -> real.
class predicates
op : (string) -> operation.
unop : (string) -> unOperation.
calc: (term) -> real determ.
clauses
op("+") = {(X, Y) = X + Y}:- !.
% …
unop("-") = {(X) = -X}:- !.
unop("sin") = {(X) = math::sin(X)}:- !.
% …
calc(bin(S, X, Y)) = op(S)(X, Y).
% …
Упражнение. Завершите определение вычислителя.
В данном параграфе реализуется алгоритм поиска наибольшего общего унификатора двух термов (или атомарных формул). Формулы подаются программе в виде двух строк. Парсер преобразует их в термы специального вида, которые и поступают на вход алгоритма.
Грамматика исходных формул описывается следующим образом:
term ::= var | const | fun [(] termlist [)] termlist ::= term | term [,] termlist
Переменные пишутся с прописной буквы, а константы, предикатные и функциональные символы — со строчной, например, $$p(X, f(a, Y, g(b)), h(1, Z))$$.
Алгоритм поиска наибольшего общего унификатора термов $$T_1$$ и $$T_2$$ с помощью двух стеков $$L$$ и $$S$$ описан в [9]. Он заключается в следующем. Вначале в $$L$$ помещается пара термов $$(T_1, T_2)$$, стек $$S$$ пуст. Алгоритм начинается с шага 1.
Из $$L$$ извлекается пара термов $$(X, Y)$$. Возможны случаи:
open core, console, string
domains
term = var(string Var); c(string Const); f(string Name, term*).
% парсер
class predicates
scan: (string) -> string*.
parser: (string*, term [out], string* [out]) determ.
parser: (string*, term* [out]) -> string* determ.
checkName: (string) determ.
clauses
scan(Str) = [Tok | scan(RestStr)]:-
frontToken(Str, Tok, RestStr),
!.
scan(_) = [].
parser([S, "(" | L], f(S, TermList), Rest):- !,
checkName(S),
[")" | Rest] = parser(L, TermList).
parser([S | L], var(S), L):-
isName(S),
Ch = frontChar(S),
Ch = charUpper(Ch),
!.
parser([S | L], c(S), L):-
(_ = tryToTerm(unsigned, S); _ = tryToTerm(real, S);
checkName(S)),
!.
parser(["," | L], TermList) = parser(L, TermList):- !.
parser(L, [Term | TermList]) = parser(Rest, TermList):-
parser(L, Term, Rest),
!.
parser(L, []) = L.
checkName(S):-
Ch = charToString(frontChar(S)),
hasAlpha(Ch),
isLowerCase(Ch).
% поиск наибольшего общего унификатора
class predicates
unify: (term, term) -> term* determ.
unif: (term*, term*) -> term* determ.
subterm: (term, term) determ.
replace: (term*, term, term) -> term*.
delete: (term*) -> term*.
put: (term*, term*, term*) -> term* determ.
clauses
unify(X, Y) = unif([X, Y], []).
unif([var(X), var(X) | L], Subst) = unif(L, Subst):- !.
unif([c(X), c(X) | L], Subst) = unif(L, Subst):- !.
unif([var(X), Y | L], Subst) = unif(replace(L, var(X), Y),
[var(X), Y | delete(replace(Subst, var(X), Y))]):- !,
not(subterm(var(X), Y)).
unif([Y, var(X) | L], Subst) = unif([var(X), Y | L], Subst):- !.
unif([f(N, TL1), f(N, TL2) | L], Subst) = unif(L1, Subst):-
L1 = put(TL1, TL2, L).
unif([], Subst) = Subst.
subterm(X, X):- !.
subterm(X, f(_, L)):-
list::exists(L, {(H):- subterm(X, H)}).
replace([X | L], X, Y) = [Y | replace(L, X, Y)]:- !.
replace([H | L], X, Y) = [H | replace(L, X, Y)].
replace([], _, _) = [].
delete([X, X | L]) = delete(L):- !.
delete([X, Y | L]) = [X, Y | delete(L)]:- !.
delete(_) = [].
put([X | L1], [Y | L2], L) = put(L1, L2, [X, Y | L]).
put([], [], L) = L.
% преобразование в строку
class predicates
s: (term) -> string.
toStr: (term*) -> string*.
clauses
s(var(X)) = X:- !.
s(c(X)) = X:- !.
s(f(N, L)) = format("%(%)", N,
concatWithDelimiter(list::map(L, {(T) = s(T)}), ", ")).
toStr([X, Y | L]) = [format("% = %", s(X), s(Y)) | toStr(L)]:- !.
toStr(_) = [].
run():-
S1 = "p(X, f(b, Y), g(a, a), k(h(1, 2, 3)), U)",
S2 = "p(a, f(b, a), g(a, Y), k(Z), V)",
parser(scan(S1), Term1, _),
parser(scan(S2), Term2, _),
Subst = unify(Term1, Term2),
writef("%\n%\n\n%", s(Term1), s(Term2),
concatWithDelimiter(toStr(Subst), ", ")),
fail;
_ = readLine().
Предикат charToString преобразует символ (char) в строку (string), предикат isLowerCase истинен, если все символы строки имеют нижний регистр, предикат hasAlpha истинен, если строка состоит только из букв, предикат exists проверяет, имеется ли в списке элемент, удовлетворяющий заданному условию.
Упражнение 2. Реализуйте в программе, приведенной в листинге 11.3, возможность использования анонимных переменных (напомним, что они унифицируются с любыми термами, но не принимают значений).
"[0, [1, [2, 3, [4]]], 5]". Список подается на вход в виде строки. Линейный список также выдается в виде строки.Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.