Парсеры
11.2. Анализ арифметических выражений
Рассмотрим арифметические выражения вида:
-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().
Пример
11.2.
Разбор арифметических выражений
Предикат 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).
% …
Упражнение. Завершите определение вычислителя.