Парсеры
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). % …
Упражнение. Завершите определение вычислителя.