logo
TurboProlog / Документация / TOM_2

Интерпретатор Пролога

Мы уже полностью познакомились с интерпретатором правил. Для показа

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

создании сердца интерпретатора Пролога.

Хотя это выходит далеко за пределы того, что требуется для интерпре-

тации обычных баз правил, мы надеемся, что сможем дать некоторые сообра-

жения по адаптации механизмов вывода. Кроме того, многие (если не все)

встроенные предикаты, которые могут вам потребоваться при самостоятельной

работе, могут непосредственно копироваться с предлагаемых.

Нам потребуется некоторый интерфейс с внешним миром, включая правила

ввода и вывода результатов, но это будет изменяться в зависимости от кон-

кретного применения интерпретатора. Однако, в случае создания механизма

вывода для интерпретатора Пролога связь с внешним миром выглядит очень

простой - она состоит в одной строчке с подсказкой.

Такой интерфейс традиционного Пролога весьма слаб, он непосредствен-

но лежит в сердце машины. Для разработок, ориентированных на конечного

пользователя, таких как оболочки экспертных систем, интерпретатор обычно

скрыт от глаз внутри программы, которая посылает ему сканнирумые и транс-

лируемые правила базы, которые пользователь хочет выполнить.

UNIFY_TERM

unify_term выглядит также как и раньше:

unify_term (ATerm, var(ID), Env):-

free(ID), free(ATerm),!,

createVar(ATerm, Env, ID).

unify_term(_, STerm,_):-

bound(STerm), STerm = var("_"),1.

unify_term(Term, var(ID), Env):-

bound(ID),!,

member(e(ID, Term1), Env), Term1=Term.

unify_term(int(I), int(I),_):-!,

unify_term(atom(A), atom(A),_):-!.

unify_term(str(S), str(S),_):-!.

unify_term(char(C), char(C), _):-!.

unify_term(list(H1,T1), list(H2,T2), Env):-!,

unify_term(H1, H2, Env), unify_term(T1, T2, Env).

unify_term(nill, nill,_):-!.

unify_term(cmp(ID, LI), cmp(ID, L2), Env):-!,

unify_term(L1< L2, Env).

Сейчас мы более внимательно рассмотрим первое предложение

unify_term. Оригинал этого предложения с различными шаблонами на поток

параметров предиката, используется в случаях, когда необходимо заложить

все в обширную базу данных.

Наверное, вы помните, что работа unify_term заключалась в унификации

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

закладывании информации мы должны действовать другим способом. Например,

если вы сделаете так:

.., Name=banana, Fondness=150, assert(favourite(Name,Fondness))), ...

то, вероятно, ожидаете, что будет заложено favourite(fruit (banana,150)),

а не favourite(Name, Fondness)). Другими словами, нам надо преобразовать

из действительных термов в статические, и именно это делает другой пото-

ковый шаблон unify_term. Однако, возникает проблема, что делать со сво-

бодными переменными. Рассмотрим следующее:

..., assert(member(X, [X|_])), ...

Предположим, мы хотим заложить в базу такой факт:

member(X, [X|_]).

Но это не так просто сделать, как кажется. Запомните, что во время

выполнения Х является не именем "Х", а связывается со свободной перемен-

ной. Решением станет создание имен для свободных переменных в форме

_<number>.

Обратите внимание, что мы не могли помещать свободную переменную

просто в виде знака подчеркивания. Это вызвано тем, что второе предложе-

ние для unify_term

unify_term(_, STerm,_):- bound(STerm), STerm = var("_"),1.

не позволит ей оказывать какое-либо действие при дальнейшем использовании

заложенного терма. Чтобы unify_term мог делать различные связывания, имя

свободных переменных должно отличаться от "_". Рассмотрим строку:

..., Food=fruit(_,_), Snack=Food, Snack=fruit(banana,150), ...

Вы можете ожидать, что Food должна иметь значение fruit(banana,150),

несмотря на то, что она была определена как имеющая только анонимные пе-

ременные. На боле высоком уровне это будет результатом того, что Snack и

Food разделяющие переменные, а по этой причине их подтермы также должны

быть разделены.

Такое разделение переменных может быть предотвращено только в случае

передачи в базу данных путем создания отдельных внутренних имен для ано-

нимных переменных, которые позволяют полноправно ввести их в среду в слу-

чае использования терма. Порождение внутреннего имени порождается следую-

щим механизмом:

database - varno

determ current_var(INTEGER)

predicates

reset_vargenerator

createVar(TERM,ENV,STRING)

lookup_termid(TERM,ENV,STRING)

get_next_unused(ENV,INTEGER,INTEGER,STRING)

vid_exist(STRING,ENV)

clauses

reset_vargenerator:-

retractall(current_var(_)),

assert(current_var(0)).

createVar(TERM,ENV,ID):-

lookup_termid(TERM,ENV,ID),!.

createVar(TERM,ENV,NEWID):-

retract(current_var(NO)),

NO1=NO+1,

get_next_unused(ENV,NO1,NO2,NEWID),

member(e(NEWID,TERM),ENV),

assert(current_var(NO2)).

lookup_termid(_,ENV,_):-free(ENV),!,fail.

lookup_termid(TERM,[e(ID,TERM1)|_],ID):- eeq(TERM,TERM1),!.

lookup_termid(TERM,[_|ENV],ID):-

lookup_termid(TERM,ENV,ID).

get_next_unused(ENV,NO,NO,NEWID):-

str_int(ID,NO),concat("_",ID,NEWID),

not(vid_exist(NEWID,ENV)),!.

get_next_unused(ENV,NO1,NO3,ID):-

NO2=NO1+1,

get_next_unused(ENV,NO2,NO3,ID).

vid_exist(_,ENV):-free(ENV),!,fail.

vid_exist(VID,[e(VID,_)|_]):-!.

vid_exist(VID,[_|L]):-vid_exist(VID,L).

Смелее! Здесь нет ничего особенно запутанного, хотя и может таким

показаться.

- Задачей create Var является возвращение ID для ATerm, который

является свободной переменной - усли бы он ею не был, то create

Var не был бы вызван. Он выполняет свою задачу, пытаясь найти в

среде терм с помощью lookup_termid; если терм введен в среду,

то нам захочется получить имя, под которым он известен.

- Предикат eeq - это реализация проверки на истинность, извест-

ная из традиционного Пролога. Мы ее опишем позже.

- Если тармин не найден в среде, то create Var должнен создать

ему имя. Это делается с помощью вызова get_next_unused, который

просто создает имя в форме _<number>, удостоверившись, что в

среде такого имени еще нет.

UNIFY_BODY

unify_body использует большое количество обработчиков, не все из ко-

торых являются равно понятными. Они требуются для интерпретации полного

Пролога. Эти специальные разработчики также показывают, как Пролог раз-

личными способами отодвигается от своих теоретических предпосылок. Полная

версия unify_body выглядит так:

unify_body(cmp(",",[ATerm1,ATerm2]),Env,BTOP):-!,

unify_body(TERM1,Env,BTOP),unify_body(TERM2,Env,BTOP).

unify_body(atom("!"),_,BTOP):-!,cutbacktrack(BTOP).

unify_body(cmp(";",[TERM,_]),Env,BTOP):-

unify_body(TERM,Env,BTOP).

unify_body(cmp(";",[_,TERM]),Env,BTOP):-!,

unify_body(TERM,Env,BTOP).

unify_body(cmp("not",[TERM]),Env,_):-

getbacktrack(BTOP),unify_body(TERM,ENV,BTOP),!,fail.

unify_body(cmp("not",_),_,_):-!.

unify_body(cmp("call",[TERM]),ENV,_):-!,

getbacktrack(BTOP),unify_body(TERM,ENV,BTOP).

unify_body(cmp("assert",[TERM]),ENV,_):- !,

handle_assert('0',TERM,ENV).

unify_body(cmp("asserta",[TERM]),ENV,_):-!,

handle_assert('a',TERM,ENV).

unify_body(cmp("assertz",[TERM]),ENV,_):-!,

handle_assert('z',TERM,ENV).

unify_body(cmp(PID,TERML),ENV,_):-

unify_terml(CALL,TERML,ENV),trace_call(PID,CALL).

unify_body(var(ID),ENV,_):-!,

member(e(ID,TERM),ENV),bound(TERM),

TERM=cmp(PID,TERML), trace_call(PID,TERML).

unify_body(atom(PID),_,_):-

trace_call(PID,[]).

Такой порядок предложений будет оптимальным не для всех случаев. Для

некоторых баз правил более эффективным окажется иной порядок, не говоря

уже о том, что многие конкретные случаи могут оказаться просто ненужными.

Первое новое предложение обрабатывает отсечения. Как уже указывалось

раньше, отсечение - это конструкция совершенно не типичная для Пролога.

Нет способа создать ее с помощью интерпретатора правил, не используя не-

которые нетипичные для Пролога конструкции; т.е то, что в Турбо Прологе

делает динамическое отсечение (предикат cutbacktrack).

Реальное отсечение в Турбо Прологе использовать нельзя, потому что

он составляет часть докомпиляционного анализа исходного кода, и, следова-

тельно, в принципе не вызывает действия в процессе выполнения. Во всяком

случае, когда в выполняемой базе правил встречается отсечение, то уста-

навливается заранее найденный указатель backtrack.

Пока мы еще не выяснили, где это делается, но для обычных случаев

этот указатель находится в call, откуда бы не вызывалось предложение оп-

ределенного пользователем предиката. Предложение, обрабатывающее функтор

call, просто отвечает за вызов в стандартном Прологе, в то время как

предложения для включения термов обрабатывают включение термов. Они дела-

ют это с помощью вызова:

handle_assert(Poscode,TERM,ENV):-

unify_term(CALL,TERM,ENV),

reset_vargenerator,

unify_term(CALL,STERM,ENV),

assertclause(Poscode,STERM),

fail.

handle_assert(_,_,_).

handle_assert создает действительный терм, затем отмечает его как

статический терм, генерируя внутренние имена для свободных переменных.

assertclause - это предикат, обрабатывающий простые термы, не имею-

щие тела.

Следующее за ним fail является программистской хитростью. Он уничто-

жит поправки, внесенные в среду при создании внутренних имен и восстанав-

ливает требуемое состояние среды. Неиспользование этого способа приведет

к ошибке только в случае, когда внесение чего-нибудь, содержащего аноним-

ные переменные было сделано на уровне цели, например вот так:

..., assert(fruit(_,_)), ...

Распечатка среды укажет на наличие двух переменных, имеющих одинако-

вое имя _<number>, хотя они очевидно не появлялись в цели.

Почему assert обрабатывается из unify_body, а не из call? Теорети-

чески, это результат того, что assert изначально не является частью тео-

рии решений. Практическое объяснение заключается в том, что используемый

handle_assert unify_term просто не имеет доступа в среду через call.

Можно обрабатывать внесение и без среды, но тогда все свободные пе-

ременные, включая те, которые имеют собственные имена, должны вноситься с

внутренне сгенерированными именами. Это необходимо, потому что без среды

невозможно найти имя терма. Такой выбор сделан для многих разработок на

традиционном Прологе, но для забавы мы покажем реализацию, идущую нес-

колько дальше.

/*Демонстрируем, как внесение обрабатывается интерпретатором*/

goal: L=[_,_], assert((p(X, L):- member(X, L)))

L=[_,_]

1 solution

goal: list(p/2)

p(X, [_1,_2]):- member(X, [_1, _2]).

Yes

1 solution

Остальная часть предложений самоочевидна. Предложение для случая var

(ID) позволяет вам назначить вызываемому терму переменную, а затем выз-

вать эту переменную (некоторые интерпретаторы Пролога запрещают такие

действия). Например:

..., Pred = fruit(banana,150), Pred, ...

выполнит fruit (снабженный базой данных с нужными предложениями).

В этом способе не делается ничего такого, что бы не могло быть сде-

лано предложением call из unify_body, которое мы рассмотрели раньше.

На этом заканчивается версия unify_body, необходимая для интерпрета-

ции Пролога; традиционный Пролог значительно отошел от лежащей в его ос-

нове теории, что наиболее заметно на "плоских" глобальных базах данных и

на отсечениях.

CALL

Если бы мы захотели подробно рассмотреть все стандартные предикаты,

встроенные в call, то нам пришлось бы слишком углубиться. Их главной

целью, помимо разрешения вам взаимодействовать с традиционным Прологом,

является возможность дать вам источник идей, которые вы можете включать в

собственные механизмы вывода. Не обращайте на это слишком много внимания,

хотя как и для многих встроенных предикатов традиционного Пролога, здесь

приходиться иметь дело с побочными эффектами или преобразованиями между

представлениями данных, информационное содержание которых остается одним

и тем же.

Еще раз посмотрите на последнее предложение call

call(ID, ATermList):-

getbacktrack(Btop),

clause(cmp(ID, STermList1), Body),

free(Env),

unify_terml(ATermList, STermList1, Env),

unify_body(Body, Env, Btop).

Оно идентично тому, которое было приведено раньше в дискуссии об об-

щем механизме вывода, но сейчас мы обратим внимание на реализацию динами-

ческого отсечения.

В интерпретаторах этого типа не сложно отсечь возврат внутри предло-

жения определенного пользователем предиката. Проблема заключается в более

общей природе отсечения, означающей, что действие распространяется на

весь предикат, а не только на текущее выполняемое предложение.

Без динамического отсечения в этом предложении call возникнет следу-

ющая ошибка, которая проявится как ошибка в вызове unify_body в этом

предложении, и приседет в предикат базы данных clause и к возможному на-

хождению нового предложения для преликата. Однако, поместив указатель

возврата, содержащийся getbacktrack обратно в unify_body, отсечение вы-

полняемой базе правил установит указатель возврата интерпретатора в ука-

затель, предшествующий вызову clause. Это предотвращает дальнейшую интер-

претацию предиката, содержащего предложение с отсечением.

Другими словами, если вы потерпите неудачу при вызове в следующем

фрагменте:

p :- a, b, !, c.

p :- d, e.

который проявится как невозможность вызова call во фрагменте:

unify_body(cmp(PID, ATermList), Env,_):-

unify_terml(Call, ATermList), Env,_),

call(PID, Call).

который опять будет отражен как невозможность выполнения unify_body в

предложении call, то без динамического отсечения интерпретатор во всех

случаях опять обращается к предикату clause и возвращает исходный код для

предложения р.

Если вы посмотрите на код unify_body, то вы увидете, что предотвра-

тить такое решение нельзя. Однако, когда встречается отсечение, вызов

cutbacktrack в unify_body устанавливает указатель возврата выше вызова

clause, и интерпретаор не будет искать для р другие предложения.

Если вы просмотрите предложения для call, то заметите, что большинс-

тво традиционных предикатов Пролога выполняются тем или иным способом.

Многие из них интересны только при интерпретации Пролога.

Давайте начнем с рассмотрения одного из волшебных предикатов, изоб-

ражаемого =.. и по некоторым историческим причинам читаемого как "univ".

univ совершает преобразования между сложными объектами и списками, где

функтор сложной системы становится первым элементом списка. С univ можно

вести следующий диалог:

Goal: fruit(banana,150) =.. [fruit, banana,150]

yes

Goal: GOAL =.. [member, X,[1,2,3]], call(GOAL)

GOAL=member(1, [1,2,3]), X=1

GOAL=member(2, [1,2,3]), X=2

GOAL=member(3, [1,2,3]), X=3

3 solution

Непосвящяенным это покажется чистой магией, потому что списки и

сложные структуры обычно представляются полностью отличными друг от дру-

га. Однако, если совпадает их информационное содержание, то между ними

легко устанавливается соответствие с помощью вспомогательного предиката

list_terml:

call("=..",[cmp(ID, ATermList), list(atom(ID), LIST)]):-!,

list_terml(LIST, ATermList).

predicates

list_terml(term, termlist)

clauses

list_terml(nill,[]):- !.

list_terml(list(H, T), [H|TT]):-

list_terml(T, TT).

Что же делает list_terml? Он передвигает заголовок списка в заголо-

вок списка термов или наоборот, а затем повторяет работу до конца спис-

ков.

Вторым магическим предикатом, который мы использовали в методах зап-

роса, является строгое равенство (==):

call("==",[T1, T2]):- !,

eeq(T1, T2).

Этот предикат выполняется в том случае, когда его аргументы являются

идентичными термами или разделяемыми переменными. Две свободные перемен-

ные не равны, если они не являются разделяемыми переменными. Это противо-

речит унификации (=), при выполнении которой две свободные переменные де-

лаются разделяемыми.

/* иллюстрирует функцию истинного равенства */

Goal: free(X), free(Y), call("==",[X, Y])

нет решения

Goal: free(X), Y=X, call("==",[X, Y])

X=_, Y=_

1 решение

В действительности такой проблемы в Турбо Прологе не возникает. В

нем можно создать следующее:

predicates

eeq(TERM,TERM)

eeqterml(TERML,TERML)

clauses

eeq(T1,T2):-free(T1),free(T2),T1=int(0),T2=int(1),!,fail.

eeq(T1,T2):-free(T1),free(T2),!.

eeq(T1,T2):-free(T1),!,fail; free(T2),!,fail.

eeq(cmp(ID,TERML1),cmp(ID,TERML2)):-!,eeqterml(TERML1,TERML2).

eeq(list(H1,T1),list(H2,T2)):-!,eeq(H1,H2),eeq(T1,T2).

eeq(X,X).

eeqterml([],[]):-!.

eeqterml([H1|T1],[H2|T2]):-

eeq(H1,H2),eeqterml(T1,T2).

В основном данная задача заключается в проверке на равенство без вы-

зова какого-либо согласования термов в том случае, когда они не согласо-

ваны.

Первое предложение для eeq пытается привести первый терм к како-

му-либо произвольноу значению, в данном случае int(0).

- Если происходит последующая унификация второго терма, то тер-

мы не являются разделяемыми переменными (В противном случае

второй терм также будет установлен в int(0).). Затем неисполне-

ние cut уничтожает установку и eeq не выполняется.

- С другой стороны, если термы являются свободными разделяемыми

переменными, то не сможет выполниться попытка унификации Т2 и

int(1) (потому что Т2 будет иметь то же самое значение, что и

Т1, называемое int(0)). Разворот уничтожит установку и направит

выполнение ко второму предложению, где выполнится eeq.

Следующие предложения для eeq более или менее понятны, хотя важно

заметить, что свободные переменные никогда не могут проникнуть дальше

третьего предложения, если же это произойдет, то при дальнейшей оценке

предиката они будут установлены.

Предикат retract, также как assert, выходит за официально разрешен-

ные в Прологе границы действия, но, по сравнению с assert, вызывает мень-

ше сложностей:

call("retract",[ATerm]):- !, handle_retract(ATerm).

retract управляется с помощью handle_retract:

predicates

nondeterm handle_retract(TERM)

clauses

handle_retract(cmp(":-",[cmp(ID,TERML),BODY])):-

bound(ID),!,

clause(cmp(ID,STERML),SBODY),

free(ENV),

unify_terml(TERML,STERML,ENV),

unify_term(BODY,SBODY,ENV),

retractclause(cmp(ID,STERML),SBODY).

handle_retract(cmp(":-",[HEAD,BODY])):-free(HEAD),!,

clause(SHEAD,SBODY),

free(ENV),

unify_term(HEAD,SHEAD,ENV),

unify_term(BODY,SBODY,ENV),

retractclause(SHEAD,SBODY).

handle_retract(cmp(ID,TERML)):-

clause(cmp(ID,TERML1),atom(true)),

free(ENV),

unify_terml(TERML,TERML1,ENV),

retractclause(cmp(ID,TERML1),atom(true)).

Первое предложение для handle_assert является некоторой оптимизаци-

ей, дающей значительно более быстрое действие retract в случае, когда из-

вестно имя (ID) того предложения, от которого надо отказаться. Почти

всегда это является делом случая; редко имеет смысл отказываться от того,

что происходит на вершине базы данных.

Предложения для handle_retract на самом деле делают немногое. Вызовы

unify_term и unify_terml служат двойной цели в случае унификации:

1. Гарантируют соответствие термов предложения, находящегося в

базе данных, термам, определенным в вызове retract.

..., retract(fruit(banana,_)), ... %взять назад вход banana

2. Они объединяют переменные в вызове retract с соответствующи-

ми термами в предложении, от которого надо отказаться

..., retract(fruit(banana,Fondness)), ...

%взять назад и проверить вход banana

Следуя традиции этого приложения Fondness будет объеденен с целым

термом 150.

Благодаря многочисленным унификациям, возможно объединенным с не-вы-

полнить-и-возвратиться-и-взять-снова-другое-предложение, retract оказыва-

ется весьма медленным. (Не забывайте, что это те самые действия, которые

традиционный Пролог делает во всех ситуациях).

И, наконец, мы рассмотрим, вероятно самый запутанный предикат - пре-

дикат write:

call("write", ATermList):- !,

writeterml("write", ATermList).

Он использует ту же группу предикатов, что и display, который печа-

тает термы в префиксной форме (т.е. без учета объявлений операторов):

writeterml(_,[]):-!.

writeterml(DISPLAY,[H|T]):-

wterm(DISPLAY,H),

writeterml(DISPLAY,T).

wterm(_,TERM):-

free(TERM),!,write('_').

wterm(_,int(X)):-!,

write(X).

wterm("write",str(X)):-!,

write(X).

wterm(_,str(X)):-!,

write('"',X,'"').

wterm("write",char(X)):-!,

write(X).

wterm(_,char(X)):-!,

write('`',X).

wterm(_,atom(X)):-!,

write(X).

wterm(_,var(X)):-!,

write(X).

wterm(_,nill):-!,

write("[]").

wterm(DISPLAY,list(HEAD,TAIL)):-!,

write('['),

wlist(DISPLAY,list(HEAD,TAIL)),

write(']').

wterm(DISPLAY,cmp(FID,ATERML)):-

wcmp(DISPLAY,FID,ATERML).

wcmp(DISPLAY,FID,ATERML):-

DISPLAY><"display",

OP=FID,

op(PRIOR,ASSOC,OP),

wop(DISPLAY,PRIOR,ASSOC,OP,TERML),!.

wcmp(DISPLAY,FID,TERML):-

write(FID,'('),

wterml(DISPLAY,TERML),

write(')').

При печати термов могут возникнуть некоторые сложности, т.к. эта

процедура противоположна трансляции. Однако, трансляция - это механичес-

кий процесс, который мы уже поняли. То же относится к печати. Действия

wterm совершенно очевидны, и мы не будем волноваться из-за предиката, пе-

чатаемого списком, потому что его содержимое весьма понятно. wcmp приво-

дит к неприятностям, когда пытаются операторы; значительно проще действо-

вать, когда установлена опция display, т.к. дело касается только написа-

ния списка термов для каждого терма.

Предположим, что мы нашли оператор, вызвав предикат ор в первом

предложении wcmp, и переходим к вызову wop:

prefix(fx,x). prefix(fy,y).

suffix(xf,x). suffix(yf,y).

infix(xfx,x,x). infix(xfy,x,y). infix(yfx,y,x). infix(yfy,y,y).

wop(DISPLAY,PRIOR,ASSOC,OP,[ATERM]):-

prefix(ASSOC,XY),!,

write(OP),

wright(DISPLAY,XY,PRIOR,ATERM).

wop(DISPLAY,PRIOR,ASSOC,OP,[ATERM]):-

suffix(ASSOC,XY),!,

wleft(DISPLAY,XY,PRIOR,ATERM),

write(OP).

wop(DISPLAY,PRIOR,ASSOC,OP,[TERM1,TERM2]):-

infix(ASSOC,LEFT_XY,RIGHT_XY),

wleft(DISPLAY,LEFT_XY,PRIOR,TERM1),

write(OP),

wright(DISPLAY,RIGHT_XY,PRIOR,TERM2).

brackets_needed(_,PRIOR,ATERM):-

bound(TERM),TERM=cmp(FID,_),

OP=FID,

op(PRIOR1,_,OP),

PRIOR1>PRIOR,!.

brackets_needed(x,PRIOR,ATERM):-

bound(TERM),

ATERM=cmp(FID,_),

OP=FID,

op(PRIOR,_,OP),!.

wright(DISPLAY,XY,PRIOR,ATERM):-

brackets_needed(XY,PRIOR,ATERM),!,

write(" ("),

wterm(DISPLAY,ATERM),

write(')').

wright(DISPLAY,_,_,ATERM):-

write(' '),

wterm(DISPLAY,ATERM).

wleft(DISPLAY,XY,PRIOR,ATERM):-

brackets_needed(XY,PRIOR,ATERM),!,

write('('),

wterm(DISPLAY,ATERM),

write(") ").

wleft(DISPLAY,_,_,ATERM):-

wterm(DISPLAY,ATERM),

write(' ').

Нет, само по себе это работать не будет. wop определяет (с помощью

небольшой группы infix, prefix и postfix предложений), как будет осущест-

вляться управление оператором и его связывание с другими. Потом он вызы-

вает один из двух операторов:

- или wright для выписывания терма под управлением префиксного

оператора

- wleft - для выписывания терма под управлением постфиксного

оператора.

В случае бинарного оператора используются обе формы.

Связывание имеет большое значение в случае, когда несколько операто-

ров, имеющих одинаковое старшинство, появляются друг за другом. Будем

считать, что знак "^" обозначает степень, тогда выражение 2^3^4 получит

значение 2^(3^4), потому что "^" - оператор, начинающий связывание спра-

ва. Это выглядит несколько необычно, ведь большинство операторов выполня-

ются слева направо. Рассмтрим деление: 2/3/4 означает (2/3)/4, а не

2/(3/4).

Дело становится совсем плохим, когда операторы вне зависимости от

наличия скобок разрешают иметь вокруг себя только операторы меньшего

старшинства. Такая ситуация возникает крайне редко, но если вы скомпили-

руете и запустите интерпретатор, то попытайтесь выполнить что-либо типа:

X='='('=(1,2),'='(1,2))

В префиксной форме это совпадает с:

X=((1=2)=(1=2))

(Никакого смысла в этом выражении не предполагается). Вы заметите,

что ответ интерпретатора включает скобки, хотя они и отсутствовали в уни-

фикации, использующей префиксную нотацию. Это вызвано тем, что объявление

оператора "=" определяет его как xfx, что означает, что он не будет при-

нимать расположенные вокруг него операторы того же или большего уровня

старшинства, если же они присутствуют, то должны быть заключены в скобки.

Второе предложение brackets_needed, проверяющее на ввод аргумента x, уп-

равляет этой ситуацией, в то время как первое предложение brackets_needed

за обычнми случаями старшинства операторов, требующими скобок, например

(1+2)*3.

Вот последнее замечание о wcmp. В предложении:

wcmp(DISPLAY,FID,ATERMList):-

DISPLAY><"display",

OP=FID,

op(PRIOR,ASSOC,OP),

wop(DISPLAY,PRIOR,ASSOC,OP,ATERMList),!.

соблазнительно поместить отсечение сразу после вызова ор. Ведь после вы-

яснения того, что идентификатор функтора (Functor IDentifir) явлется опе-

ратором (OPerator), других выборов не остается, не так ли? Нет, не так.

Оператор может одновременно иметь и унарное и бинарное объявление, как

это происходит в случае минуса, например:

op(500, yfx, "-"). op(500, fx, "-"). op(400, yfx, "*").

В этом случае, как и в других случаях бинарности, wop не выполнится

и вернется назад для проверки, не совпадает ли первое объявление, возвра-

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

возможность возвращения в предикат ор с попыткой найти другое предложе-

ние.

На этом заканчивается наш обзор встроенных предикатов интерпретато-

ра. Если вам будет сопутствовать удача, то предикаты, которые могут вам

потребоваться в своих разработках, окажутся более или менее сходными с

ними. Особенно это относится к функции eval, для дерева выражений, ранее

описанной в данном приложении, и к предикату печати термов.

Возможно, вы захотите, чтобы предикат печати термов возвращал стро-

ку, а не отображал вывод на экране или в файле. Это несложно реализовать.

Вам потребуется только добавить еще один аргумент для строки и использо-

вать concat или format для ассемблирования предполагаемой строки. Расс-

мотрим следующий случайный пример:

wleft(DISPLAY,XY,PRIOR,ATERM):-

brackets_needed(XY,PRIOR,ATERM),!,

write('('),

wterm(DISPLAY,ATERM),

write(") ").

wleft(DISPLAY,_,_,ATERM):-

wterm(DISPLAY,ATERM),

write(' ').

Считая, что wterm и другие предикаты были изменены подобным образом,

этот случайный пример можно преобразовать в:

wleft(DISPLAY,XY,PRIOR,ATERM, OS):-

brackets_needed(XY,PRIOR,ATERM),!,

wterm(DISPLAY,ATERM, S1),

format(OS,"(%) ", S1).

wleft(DISPLAY,_,_,ATERM, OS):-

wterm(DISPLAY,ATERM,S1),

concat(S1," ", OS).