воскресенье, 10 апреля 2016 г.

Три трюка с макросами

Приведу три довольно полезных трюка с макросами в Cache'.


Первый, довольно стандартный для программистов привыкших к языкам С и С++, защищает исходные тексты от повторного включения инков. Для этого во включаемых инках текст обрамляем в операторные скобки:
#ifndef INCNAMEINCLUDED
#define INCNAMEINCLUDED

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

#endif INCNAMEINCLUDED
Здесь используется имя (INCNAMEINCLUDED), производное от имени включаемого файла. В первой строке проверяется что этот символ не определен. Если он не определен, то определяется и транслятор использует остальные конструкции включаемого файла. Если символ определен, то транслятор пропускает все строки до последнего #endif.

Несмотря на классичность трюка, входящие в дистрибутив Cache системные включаемые файлы им почему-то не пользуются. Возможно, это связано с тем, что два определения одного символа в Cache в отличие от С ошибкой не являются и при повторной трансляции того же включаемого файла проблем обычно не происходит.

Второй трюк немного напоминает сишный макрос ASSERT. В зависимости от того, определен ли специальный символ при трансляции, определенное имя определяется либо как исполняемый код, либо как пустой оператор.
 ; специальный символ, определяемый где-то ранее.
#define DEBUG
 
 ; если транслируемся в режиме "с отладкой"
#ifdef DEBUG
 ; определяем имя глобали для трассировки
#define TRACEGLB ^zAugustDBG
 ; макрос очистки трассировочной глобали
#define BEGINTRACE k $$$TRACEGLB
 ; добавить следующее сообщение
#define TRACE(%s) s $$$TRACEGLB($I($$$TRACEGLB))=$G(%s)
 ; добавить значения заданной переменной с подиндексами
#define TRACEVAR(%s) m $$$TRACEGLB($I($$$TRACEGLB),$na(%s))=%s
 ; добавить текст с автоматическим удвоением кавычек
#define TRACETEXT(%s) s $$$TRACEGLB($I($$$TRACEGLB))=##quote(%s)
 ; добавить вычисляемое выражение и его значение 
#define TRACEEXPR(%s) s $$$TRACEGLB($I($$$TRACEGLB),##quote(%s))=%s
 ; если отладочный символ не определен
#else
 ; определяем все использованные символы на пустые операторы
#define TRACEGLB
#define BEGINTRACE
#define TRACE(%s)
#define TRACEVAR(%s)
#define TRACETEXT(%s)
#define TRACEEXPR(%s)
#endif
В приведенном тексте два макроса, использующие макроподстановку ##quote, специфичны для Cache 5 и старше, в младших версиях не поддерживаются.

Примерное использование
 ; начали трассировку
 $$$BEGINTRACE
 s err=$$func^fROU(params)
 ; трассируем сообщения с возможным форматированием значений
 $$$TRACE("err = "_err)
 ; трассируем состояние переменной
 $$$TRACEVAR(src)
 ; трассируем произвольно заданный текст
 $$$TRACEVAR(попали "сюда")
 ; трассировка выражения
 $$$TRACEEXPR($lg(var,8))
Макросы довольно удобны для полной трассировки хода выполнения программы с просмотром что и в каком месте было. При сборке окончательного варианта достаточно закомментировать строку #define DEBUG и перекомпилировать программу.

Приведенные макросы монопольно используют глобаль и пишут одновременно в одно и то же место из различных процессов. Для того чтобы использовать трассировку двух или более одновременно работающих процессов, следует модифицировать макросы чтобы они использовали уникальное для процесса значение, наприемер $j. Как вариант может быть использовано:
#define TRACEGLB $na(^zAugustDBG($j))
#define BEGINTRACE k @$$$TRACEGLB
#define TRACE(%s) s @$$$TRACEGLB@($I(@$$$TRACEGLB))=$G(%s)
#define TRACEVAR(%s) m @$$$TRACEGLB@($I(@$$$TRACEGLB),$na(%s))=%s
#define TRACETEXT(%s) s @$$$TRACEGLB@($I(@$$$TRACEGLB))=##quote(%s)
#define TRACEEXPR(%s) s @$$$TRACEGLB@($I(@$$$TRACEGLB),##quote(%s))=%s
Третий трюк создает видимость существования "инициализирующей" формы оператора new. Как если бы программист мог написать
 new a,b=123,c="",d=b
И совместить в одном операторе как объявление переменной, так и ее присваивание в начальное значение. В Cache в настоящее время такой возможности нет, и в стандарте языка М тоже такой возможности не предусмотрено. Но на мой взгляд такая возможность полезна по крайней мере в методических целях. Вот как ее можно самостоятельно создать с использованием макроса.

Делаем отдельную служебную рутину, в которой составляем функцию, вызываемую в период компиляции. В ее задачу входит просмотреть используемые компилятором структуры и выдать строку, которую он вставит в компилируемый код. Трюк основан на возможности макрокоманды ##function.
MakeNew()
 q:$ll(%literalargs)=0 ""
 n i,retn,rets,var,value
 s retn="n ",rets=""
 f i=1:1:$ll(%literalargs) d
 . s value=$lg(%literalargs,i)
 . s var=$p(value,"=",1)
 . s retn=retn_var_","
 . i $l(value)'=($l(var)) s rets=rets_value_","
 s $e(retn,$l(retn),$l(retn))=""
 i $l(rets)>2 s $e(rets,$l(rets),$l(rets))=""
 i rets'="" s retn=retn_" s "_rets
 q retn
После чего или в отдельном включаемом файле или в том же маке в начале прописываем макрос
#def1arg NEW(%value) ##function($$MakeNew^initnew())
Этот макрос определен через директиву #def1arg, что позволяет ему принимать произвольное число аргументов через запятую (что довольно тяжело организовать на С) и в период компиляции по месту применения вызывает функцию MakeNew. Здесь нужно заменить initnew на имя рутины, в которой Вы сохраните эту функцию. Результат выполнения функции будет подставлен компилятором вместо использованного макроса $$$NEW. Например вместо приведенного примера
 new a,b=123,c="",d=b
нужно будет написать немногим от него отличающийся код:
 $$$NEW(a,b=123,c="",d=b)
Что при компиляции будет трансформировано в
 n a,b,c,d s b=123,c="",d=b
То есть примерная наиболее близкая замена

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

Комментариев нет:

Отправить комментарий