Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MUMPS СУБД. Практика применения и опыт программирования.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
1.14. ИНТЕРПРЕТАТОР 161
Здесь две последних альтернативы определены рекурсивно, и парсер сначала должен определить есть ли символ унарного оператора, запом­нить его, и перейти к парсингу последующего выражения как expratom. При его разборе считается что парсер выполняет кодогенерацию для этого элемента. Поэтому после возврата из парсинга expratom останется добавить к байткодам байткод унарной операции.
Например, при парсинге выражения
+$H
Сначала выполняется запоминание унарной операции плюс (+), за­тем кодогенерация для вычисления $H, затем добавляется байткод для вычисления унарной операции плюс. При выполнении байткода система исполнения уже выполнит действия в правильной последовательности, а не так как это написано - сначала оператор плюс а потом вычисление $H.
То же правило относится к альтернативе (expr) - эта синтаксическая конструкция может целиком заменять конструкцию expritem.
Определение конструкции exprtail дано так:
exprtail := | | binaryop | expratom |
| | [’]truthop | | | [’] ? pattern |
К синтаксичесим конструкциям binaryop относятся бинарные опера­торы, например +, -, #. К синтаксическим конструкциям truthop отно­сятся логические операторы, например >, =, <.
Таким образом, в языке MUMPS при трансляции выражения
a + b * c
Сначала выполняется вычисление a, затем вычисление b, затем сло­жение, затем вычисление c, затем умножение, а при трансляции выра­жения
a + ( b * c )
сначала вычисляется a, затем b, затем c, затем произведение b * c, затем сложение.
Определение синтаксических конструкций языка, таким образом, не содержит приоритетов операций для операторов.
В полный набор операций виртуальной машины входит кроме вы­числения имен, меток и значений, также множество дополнительных служебных операций, например при трансляции кода
162 ГЛАВА 1. СРЕДА ИСПОЛНЕНИЯ
write:a+b c+d,! set ...
виртуальная машина должна сначала вычислить потсусловие (a+b), а затем, если оно не выполнилось, пропустить в байткоде выполнение кода для
c+d,!
для команды write и перейти сразу к вычислению аргумента следующей за ней команды set (или его постусловия, если оно было задано).
Набор таких дополнительных служебных операций полностью опре­деляется архитектурой виртуальной машины, каким образом она была разработана производителем MUMPS системы.
Современные интерпретаторы MUMPS систем зачастую поддержи­вают кроме предопределенного функционала также возможность вызо­ва внешних модулей. Обычно это выполняется динамической загрузкой динамических библиотек. В этом случае DLL (или SO в Linux) должна реализовать определенный интерфейс для того, чтобы интерпретатор мог выполнить вызов.
При выходе последующих версий MUMPS систем традиционно при­нято соблюдать правила совместимости используемых форматов байт­кодов. Если в следующей версии MUMPS системы производятся из­менения, например добавляются операторы или системные функции с соответствующими байткодами для них, то они добавляются так, чтобы предыдущие компилированные байткоды для этой версии MUMPS мог­ли исполняться без изменений. Это называется совместимостью снизу вверх.
Современные реализации MUMPS систем, поддерживающие компи­ляцию исходных текстов в байткод, также поддерживают возможность отдельного экспорта и импорта компилированного байткода. Такой спо­соб позволяет компилировать исходный текст MUMPS рутины на одном сервере и исполнять его на другом без исходных текстов.
Такие меры по переносу только компилированного байткода могут приниматься, если действительно необходимо скрыть исходный текст рутин или при сокращении времени переноса программ при большом количестве кода. В случае переноса только байткода значительно со­кращается объем действий на целевом сервере, и выполняется только сохранение байткода вместо сохранения исходного текста, компиляции и все равно сохранения байткода.
Одновременно с тем, разработчики должны учитывать, что часть ис­ходных текстов может не допускать такого переноса, если программе
1.15. ГОЛАЯ ССЫЛКА 163
требуется читать строки кода функцией $TEXT, или если при компиля­ции макрокода производится генерация кода рутин, зависимая от конфи­гурации сервера, версии MUMPS системы, или времени трансляции.
Кроме того, для многих современных интерпретаторов MUMPS си­стем возможно выполнение декомпилятора байткода с получением исход­ного текста. Возможно, исходный текст не будет в точности повторять оригинальный, например декомпилятор может не определить в каком регистре была написана команда и была ли использована полная или со­кращенная форма. Как правило, от версии к версии производители могут добавлять к определению байткода дополнительные элементы и деком­пиляторы, тем самым, устаревают. Но в каких-то случаях они могут быть полезны для восстановления оригинального текста.
Можно отметить, что перенос только компилированного байткода в реальной практике применяется очень редко и такой код составляется с особенным вниманием к перечню версий MUMPS системы, на которой он должен работать. Если какие-то из использованных функций поддер­живаются не во всех версиях, то их обычно вызывают через команду XECUTE и при выполнении соответствующих проверок.

1.15 Голая ссылка

Голая ссылка (naked indicator) - это сокращенное обозначение обращения к последнему имени глобала, к которому было обращение. Обозначается опусканием имени глобала и добавлением индексов:
set ^Data(id,"root","rel","sub","color")="Синий" set ^("figure")="Квадрат" ; use naked set ^("count")=15
С точки зрения выполнения кода это полный эквивалент более мно­гословного варианта:
set ^Data(id,"root","rel","sub","color")="Синий" set ^Data(id,"root","rel","sub","figure")="Квадрат" set ^Data(id,"root","rel","sub","count")=15
Само значение голой ссылки хранится как часть текущего состояния процесса. При обращении к глобалам это значение меняется на имя гло­бала, к которому было обращение, включая значения индексов. При этом не имеет значения контекст такого обращения, это могут быть команды присваивания, системные функции, или контекст вычисления постусло­вия.
164 ГЛАВА 1. СРЕДА ИСПОЛНЕНИЯ
При старте процесса и при смене текущей базы данных значение очищается и равно пустой строке. У каждого процесса значение голой ссылки собственное, независимое от действий других процессов.
В примере выше первая строка обращается к глобалу
^Data(id,"root","rel","sub","color")
и значение голой ссылки принимает значение этого имени.
Узнать значение голой ссылки, согласно стандарта языка напрямую нельзя. Но большинство современных реализаций поддерживает специ­альную расширенную системную переменную $ZREFERENCE, которая возвращает имя голой ссылки.
В рекомендации стандарта (но не в сам стандарт) входит поддержка системной переменной $REFERENCE с именно таким поведением, но многие MUMPS системы могут не поддерживать такую переменную.
Обращение к голой ссылке во многом похоже на ключевое слово WITH в языке Pascal, допускающее сокращение записи для обращения к элементам структуры или объекта, отличающиеся лишь именем поля структуры. В сокращенной записи опускаются полные операции доступа к объекту или структуре и указываются лишь последние, отличающиеся друг от друга, имена.
При доступе к глобалу значение голой ссылки может принять как индексированное, так и неиндексированное значение. При использовании голой ссылки система выполнения берет имя голой ссылки, отбрасывает последний индекс и дописывает к полученному имени указанные после символа (ˆ) индексы.
Эта операция не может быть корректно выполнена если значение голой ссылки пустая строка или неиндексированное имя. В этом случае MUMPS система генерирует стандартную ошибку (M1).
Использование голой ссылки дает существенное преимущество при записи кода, обращающегося к глобалам, имена которых отличаются лишь последними индексами. Например, типовой код добавления записи:
Add(Color,Figure,Count)
n id s id=$i(^DATA) s ^DATA(id,"Color")=Color s ^DATA(id,"Figure")=Figure s ^DATA(id,"Count")=Count q id
в реальных программах может быть намного большим. Использование голой ссылки может существенно сократить как объем кода, так и ко­личество возможных опечаток.
1.15. ГОЛАЯ ССЫЛКА 165
При этом значение голой ссылки меняется каждый раз при доступе к глобалу и является побочным эффектом многих функций и команд. В отношении порядка взведения голой ссылки и порядка вычисления выражений и имен в книге есть отдельные статьи, их также нужно изу­чить подробнее. При практическом использовании голой ссылки имен­но возможность побочного эффекта при иных операциях может создать проблему модификации кода на MUMPS.
Например, если имеем код
Add(Color,Figure,Count)
n id s id=$i(^DATA) s ^DATA(id,"Color")=Color s ^("Figure")=Figure s ^("Count")=Count q id
и необходимо добавить дополнительные действия
s ^("Figure")=$$Check(Figure)
то после возврата из функци $$Check и перед присваиванием значе­ние голой ссылки может измениться и запись произойдет уже не в ту глобаль, которая предполагалась до модификации кода, более того, про­межуточный код может взвести значение голой ссылки в значение пу­стой строки или неиндексированного имени. В целом, все эти варианты приводят к ошибке выполнения кода с голой ссылкой после возврата из фунции $$Check.
По требованиям совместимости с имеющимися программами разра­ботанными на языке MUMPS в стандарт уже нельзя ввести стекование голой ссылки. Поэтому в некоторых случаях необходимо иметь меха­низм восстановления значения голой ссылки в подпрограммах. Это мож­но выполнить стандартными средствами языка, если перед выполнением подпрограммы запомнить значение ссылки, а затем, при выходе, восста­новить его. Например, если есть код:
Check(Figure)
n ret s ret=Figure_$d(^ANYGLOBBAL) q ret
то к нему можно добавить сохранение и восстановление голой ссылки:
166 ГЛАВА 1. СРЕДА ИСПОЛНЕНИЯ
Check2(Figure)
n saveref ; save naked s saveref=$na(^("any")) s ret=Figure_$d(^ANYGLOBBAL) ; reset naked i $d(@saveref) q ret
Здесь сохраняется не само имя голой ссылки, а имя с затертым зна­чением последнего индекса. В любом случае, при использовании голой ссылки, это значение не имеет применения, поскольку будет автомати­чески замещаться на первый индекс указанный после символа голой ссылки.
Но в этом коде есть проблема с сохранением значения. Если значение голой ссылки было пустой строкой или неиндексированым именем то операция обращения к имени
s saveref=$na(^("any"))
генерирует ошибку.
Для компенсации такой проблемы реализации MUMPS поддержи­вают системную переменную $ZREFERENCE или ее аналог, которая возвращает имя голой ссылки как есть.
Многие современные реализации MUMPS систем также поддержи­вают присваивание этой системной переменной или ее синониму $RE­FERENCE. В этом случае код подпрограммы можно выполнить более устойчивым к ошибке доступа к имени голой ссылки:
Check3(Figure)
n saveref ; save naked s saveref=$zr s ret=Figure_$d(^ANYGLOBBAL) ; reset naked s $zr=saveref q ret
Чтение и возможность присваивания системным переменным $ZRE­FERENCE и $REFERENCE не входят в настоящее время в стандарт и поддерживаются производителями различных MUMPS систем само­стоятельно. Можно отметить, то вероятность что в используемой Ва­ми системе поддерживается переменная $ZREFERENCE, очень высока. Присваивание таким переменным поддерживают меньшее число систем. В любом случае необходимо проверить в документации, поддерживается ли системная переменная возвращающая имя голой ссылки как есть и как именно.
1.16. ОЧЕРЕДНОСТЬ ВЫПОЛНЕНИЯ 167

1.16 Очередность выполнения

Одна из основных особенностей языка MUMPS и его отличия от других состоит в недостаточно точном понимании программистами очередности вычисления выражений и выполнения операций в языке. В других язы­ках точно так же присутствует модель вычисления выражений и опреде­ленные соглашения, которые могут поставить программиста в тупик при смене языка. Например, выражение
int i = 5; i = ++i + ++i;
в разных языках может дать различные результаты:
C++ 14 cl 14 bcc 14 lcc 13 gcc 13 php 13 C# 13
С языком MUMPS тоже не все так просто, как кажется на пер­вый взгляд. Но, в отличие от многих других языков, в языке MUMPS полностью отсутствует undefined behavior, или поведение на усмотрение реализации.
Очередность выполнения операций и вычисления выражений в языке MUMPS строго определены стандартом. К очередности выполнения и вычисления могут быть отнесены два основных пункта:
1. Очередность вычисления выражений.
2. Очередность вычисления имен.
И различные дополнительные
1. Очередность вычисления параметров функций.
2. Очередность применения команд к аргументам.
3. Очередность определения альтернатив в функции $SELECT.
Если дополнительным правилам вычисления всегда соответствует по­рядок слева - направо, то первые два нужно рассматривать детально, по­скольку при их применении или, что не менее важно, при модификации, могут возникнуть затруднения.
168 ГЛАВА 1. СРЕДА ИСПОЛНЕНИЯ

1.16.1 Очередность вычисления выражений

Выражения в MUMPS вычисляются слева направо, приоритеты опера­ций отсутствуют. Если нужно указать иную очередность вычисления выражения, то следует поставить скобки. Это обычно первая проблема, с которой могут столкнуться программисты при прочтении кода на языке MUMPS.
Например:
>w 1+2*3 9
Если при написании программы программист обычно при первом же тестовом прогоне обнаруживает, что он неправильно записал выраже­ние, то при внесении исправлений в имеющийся код могут возникнуть ошибки. Типичная ситуация - пусть имеется код
>s a=1,b=2 i a=1 w "#",! #
Со временем может возникнуть необходимость усложнить условие, например a=1 и b=2 одновременно. Обычно первое что пишет програм­мист, это:
>s a=1,b=2 i a=1&b=2 w "#",!
И условие уже не выполняется. Потому что здесь применяется оче­редность вычисления выражения слева направо, и сначала выполняется сравнение a и 1, результат 1. Потом вычисляется операция & с получен­ным результатом (1) и значением b, получаем 1, и этот результат уже сравнивается с значением 2. Получаем 0.
Решение конфликта состоит в расстановке приоритетов явным зада­нием порядка вычисления скобками:
>s a=1,b=2 i (a=1)&(b=2) w "#",! #
Теперь все работает нормально. Вывод: нужно внимательно отно­ситься как к написанию сложных выражений, так и к их изменению. Поскольку автор исходного выражения может использовать отсутствие приоритетов для расположения операций для правильного вычисления выражения. Бояться тут нечего, надо соблюдать принятые в языке со­глашения.
1.16. ОЧЕРЕДНОСТЬ ВЫПОЛНЕНИЯ 169

1.16.2 Очередность вычисления имен

Имена вычисляются слева направо, значения индексов имени вычисля­ются в том порядке, в котором они следуют, и вычисляются как выра­жения.
Например:
>k s a($i(a))=$i(a) w a=2 a(1)=2
Здесь видно, что сначала было вычисление значения индекса (по­лучили 1), потом значения выражения стоящего справа (получили 2). Если нужно явно задать порядок вычисления "сначала присваиваемое значение, потом имя", то для гарантирования следует явно разнести эти операции на две:
>k s tmp=$i(a) s a($i(a))=tmp w a=2 a(2)=1 tmp=1
Все на первый взгляд просто, но тут тоже есть нюанс, на который необходимо обратить внимание. Это голая ссылка (naked indicator) и по­рядок ее взведения. При вычислении имени, образованном голой ссыл­кой, сначала вычисляются записанные программистом индексы, потом команда использующая имя вычисляет полное имя непосредственно пе­ред использованием, используя текущее значение голой ссылки (naked indicator).
Например, при вычислении имени
^($h)
Сначала вычисляется выражение $h. И лишь при непосредственном использовании имени выполняется подстановка голой ссылки. Пример:
; очищаем экспериментальную глобаль >k ^a
; взводим индикатор голой ссылки >i $d(^a(1))
; проверяем чему он равен >w $zr ^a(1)
170 ГЛАВА 1. СРЕДА ИСПОЛНЕНИЯ
; выполняем присваивание с использованием ; naked indicator >s ^(123,$zr)=$d(^(2,3))
; смотрим результат >zw ^a ^a(2,123,"^a(1)")=0
В этом примере четко видно, в какой последовательности реально бы­ло вычислено имя глобала для присваивания. Если бы вычисление имени выполнялось слева направо, то сначала был бы взят naked indicator (зна­чение ˆa(1)), и от него было бы образовано имя
^a(123,"^a(1)")
Но, в действительности, стандарт языка MUMPS предписывает вы­числять слева направо лишь значения индексов имени, и применять го­лую ссылку непосредственно при использовании имени.
В нашем примере выполняется сначала вычисление индексов, и имя присваивания развертывается с значения
^(123,$zr)
до
^(123,"^a(1)")
После чего вычисляется правая часть выражения - берется голая ссылка (ˆa(1)) и от нее вычисляется новое имя (ˆa(2,3)). По этому имени берется выражение ($d() в нашем случае возвращает 0). Функция $d() меняет индикатор голой ссылки на новое значение (ˆa(2,3)). Команда set выполняет присваивание. При этом получает на вход имя с голой ссылкой. Голая ссылка со значения
^(123,"^a(1)")
используя текущее значение naked indicator равный
^a(2,3)
развертывается до полного имени
^a(2,123,"^a(1)")