Приведу три довольно полезных трюка с макросами в Cache'.
Первый, довольно стандартный для программистов привыкших к языкам С и С++, защищает исходные тексты от повторного включения инков. Для этого во включаемых инках текст обрамляем в операторные скобки:
Несмотря на классичность трюка, входящие в дистрибутив Cache системные включаемые файлы им почему-то не пользуются. Возможно, это связано с тем, что два определения одного символа в Cache в отличие от С ошибкой не являются и при повторной трансляции того же включаемого файла проблем обычно не происходит.
Второй трюк немного напоминает сишный макрос ASSERT. В зависимости от того, определен ли специальный символ при трансляции, определенное имя определяется либо как исполняемый код, либо как пустой оператор.
Примерное использование
Приведенные макросы монопольно используют глобаль и пишут одновременно в одно и то же место из различных процессов. Для того чтобы использовать трассировку двух или более одновременно работающих процессов, следует модифицировать макросы чтобы они использовали уникальное для процесса значение, наприемер $j. Как вариант может быть использовано:
Делаем отдельную служебную рутину, в которой составляем функцию, вызываемую в период компиляции. В ее задачу входит просмотреть используемые компилятором структуры и выдать строку, которую он вставит в компилируемый код. Трюк основан на возможности макрокоманды ##function.
Все использованные в данной заметке возможности, которые Вы не смогли найти в документации, являются недокументированными.
Первый, довольно стандартный для программистов привыкших к языкам С и С++, защищает исходные тексты от повторного включения инков. Для этого во включаемых инках текст обрамляем в операторные скобки:
#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То есть примерная наиболее близкая замена
Все использованные в данной заметке возможности, которые Вы не смогли найти в документации, являются недокументированными.
Комментариев нет:
Отправить комментарий