Pull to refresh
0
InterSystems
InterSystems IRIS: СУБД, ESB, BI, Healthcare

Макросы в InterSystems Caché

Reading time 7 min
Views 3.5K
Monet tulips in holland

Введение


Хочу рассказать про использование макросов в InterSystems Caché. Макрос — это символьное имя, заменяемое при компиляции исходного кода на последовательность программных инструкций. Макрос может «разворачиваться» в различные последовательности инструкций при каждом вызове, в зависимости от сработавших разветвлений внутри макроса и переданных ему аргументов. Это может быть как статический код, так и результат выполнения COS. Рассмотрим, как их можно использовать в вашем приложении.

Компиляция



Для начала, чтобы понять где используются макросы, несколько слов о том, как происходит компиляция ObjectScript кода:
  • Компилятор классов использует определение класса для генерации MAC кода
  • В некоторых случаях, компилятор использует классы в качестве основы для генерации дополнительных классов. Можно посмотреть на эти классы в студии, но не надо их изменять. Это происходит, например, при компиляции классов, которые определяют веб сервисы и веб клиенты
  • Компилятор классов также генерирует дескриптор класса. Caché использует его во время выполнения кода
  • Препроцессор (иногда называемый макро-препроцессор, MPP) использует INC файлы и заменяет макросы. Кроме того, он обрабатывает встроенный SQL в рутинах ObjectScript
  • Все эти изменения происходят в памяти, сам пользовательский код не изменяется
  • Далее компилятор создаёт INT код для рутин ObjectScript. Этот слой известен как промежуточный код. Весь доступ к данным на этом уровне осуществляется через глобалы
  • INT код компактен и человекочитаем. Для его просмотра нажмите в студии Ctrl+Shift+V или кнопку
  • INT код используется для генерации OBJ кода
  • Виртуальная машина Caché использует этот код. После того, как он сгенерирован CLS/MAC/INT код больше не требуется и может быть удален (например, если мы хотим поставлять решения без исходного кода)
  • Если класс — хранимый, то компилятор SQL создаст соответствующие SQL таблицы

Макросы


Как уже было сказано, макрос — это символьное имя, заменяемое при обработке препроцессором на последовательность программных инструкций. Определяется он с помощью команды #Define за которой следует сначала название макроса (возможно — со списком аргументов) а затем значение макроса:
#Define Macro[(Args)] [Value]
Где могут определятся макросы? Либо непосредственно в коде, либо в отдельных INC файлах, содержащих только макросы. Подключают необходимые файлы к классам командой Include MacroFileName в самом начале определения класса — это основной и предпочтительный метод подключения макросов к классу, присоединив таким образом макросы их можно использовать в любой части определения класса. К MAC рутинам или коду отдельных методов класса можно присоединить INC файл макросов командой #Include MacroFileName.

Примеры


Пример 1


Перейдём к примерам использования, начнём с вывода строки «Hello World». COS код:  Write "Hello, World!"
Теперь напишем макрос HW, выводящий эту строку: #define HW Write "Hello, World!"
Достаточно написать в коде $$$HW ($$$ для вызова макроса, затем следует имя макроса):

ClassMethod Test()
{
     
#define HW Write "Hello, World!"
     
$$$HW
}

И при компиляции он преобразуется в следующий INT код:

zTest1(public {
     Write "Hello, World!" }

В терминале при запуске этого метода будет выведено:
Hello, World!

Пример 2


В следующем примере используем переменные:

ClassMethod Test2()
{
     
#define WriteLn(%str,%cnt) For ##Unique(new)=1:1:%cnt { ##Continue
         Write %str,! ##Continue
     }
     
     $$$WriteLn
("Hello, World!",5)
}

Тут строка %str выводится %cnt раз. Названия переменных должны начинаться с %. Команда ##Unique(new) создает новую уникальную переменную в генерируемом коде, а команда ##Continue позволяет продолжить определение макроса на следующей строке. Данный код преобразуется в следующий INT код:

zTest2
(public {
     For %mmmu1=1:1:5 { 
         Write "Hello, World!",
     } }

В терминале при запуске этого метода будет выведено:
Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!

Пример 3


Перейдём к более сложным примерам. Оператор ForEach бывает очень полезен при итерации по глобалам, добавим его:

ClassMethod Test3()
{
    
#define ForEach(%key,%gn) Set ##Unique(new)=$name(%gn) ##Continue
    Set %key=
"" ##Continue
    For { ##Continue
        Set %key=$o(@##Unique(old)@(%key)) ##Continue
        Quit:%key=
""
    
    
#define EndFor    }
    
       Set 
^test(1)=111
       
Set ^test(2)=222
       
Set ^test(3)=333
       
       
$$$ForEach(key,^test)
           
Write "key: ",key,!
           
Write "value: ",^test(key),!
       
$$$EndFor
}

Вот как это выглядит в INT коде:

zTest3
(public {
       Set ^test(1)=111
       Set ^test(2)=222
       Set ^test(3)=333
       Set %mmmu1=$name(^test) 
       Set key="" 
       For { 
           Set key=$o(@%mmmu1@(key)
           Quit:key=""
           Write "key: ",key,!
           Write "value: ",^test(key),!
       } }

Что происходит в этих макросах?
  • На вход принимается переменная %key в которую будет записываться текущий ключ (subscript) итерируемого глобала %gn
  • В новую переменную записываем имя глобала (функция $name)
  • Ключ принимает первоначальное, пустое значение
  • Начинаем цикл итерации
  • С помощью индирекции и функции $order присваиваем ключу следующее значение
  • С помощью постусловия проверяем не принял ли ключ значение "", если да, то итерация завершена, выходим из цикла
  • Выполняется произвольный пользовательский код, в данном случае вывод ключа и значения
  • Цикл закрывается

В терминале при запуске этого метода будет выведено:
key: 1
value: 111
key: 2
value: 222
key: 3
value: 333

Если вы используете списки и массивы — наследников класса %Collection.AbstractIterator то можно написать аналогичный итератор уже для него.

Пример 4


Ещё одной возможностью макросов является выполнение произвольного COS кода на этапе компиляции и подстановка результата выполнения вместо макроса. Создадим макрос со временем компиляции:

ClassMethod Test4()
{
      
#Define CompTS ##Expression("""Compiled: " _ $ZDATETIME($HOROLOG) _ """,!")
      Write $$$CompTS
}

Который преобразуется в следующий INT код:

zTest4
(public {
      Write "Compiled: 05/19/2015 15:28:45",}

В терминале при запуске этого метода будет выведено:
Compiled: 05/19/2015 15:28:45

Выражение ##Expression выполняет код и подставляет результат, на входе могут быть следующие элементы языка COS:

Пример 5


Директивы препроцессора #If, #ElseIf, #Else, #EndIf используются для выбора исходного кода при компиляции в зависимости от значения выражения после директивы, например этот метод:

ClassMethod Test5()
{
    
#If $SYSTEM.Version.GetNumber()="2015.1.1" && $SYSTEM.Version.GetBuildNumber()="505" 
        
Write "You are using the latest released version of Caché"
    
#ElseIf $SYSTEM.Version.GetNumber()="2015.2.0"
        
Write "You are using the latest beta version of Caché"
    
#Else 
        Write 
"Please consider an upgrade"
    
#EndIf
}

В Caché версии 2015.1.1.505 скомпилируется в следующий INT код:

zTest5() public {
    
Write "You are using the latest released version of Caché"
}

И в терминале выведет:
You are using the latest released version of Caché

В Caché скачанной с бета-портала скомпилируется уже в другой INT код:

zTest5() public {
    
Write "You are using the latest beta version of Caché"
}

И в терминале выведет:
You are using the latest beta version of Caché

А прошлые версии Caché скомпилируют следующий INT код с предложением обновиться:

zTest5() public {
    
Write "Please consider an upgrade"
}

И в терминале выведут:
Please consider an upgrade

Эта возможность может использоваться, например, для сохранения совместимости клиентского приложения между старыми версиями и новыми, где может быть использована новая функциональность СУБД Caché. Этой цели также служат директивы препроцессора #IfDef, #IfNDef которые проверяют существование или отсутствие макроса соответственно.

Выводы


Макросы могут как просто сделать ваш код понятней, упрощая часто повторяющиеся в коде конструкции, так и реализовывать на этапе компиляции часть логики приложения, уменьшая таким образом нагрузку в рантайме.

Что дальше?


В следующей статье расскажу о более прикладном примере использования макросов — системе логирования.

Ссылки


О компиляции
Список директив препроцессора
Список системных макросов
Класс с примерами
Часть II. Система логирования

Автор выражает благодарность хабраюзерам Daimor, Greyder и еще одному очень компетентному инженеру, пожелавшему остаться неназванным, за помощь в написании кода.
Tags:
Hubs:
+6
Comments 17
Comments Comments 17

Articles

Information

Website
www.intersystems.com
Registered
Founded
1978
Employees
1,001–5,000 employees
Location
США