Заполнение ассоциативных массивов: оператор With

Опубликовано в M4

Ещё оно средство от мозолей на пальцах.

Как известно, существует ограничение на количество формальных параметров у функций в qpilе. Их не может быть более 8. Однако нередко возникают ситуации, когда таких параметров требуется значительно больше. Это касается как пользовательских функций, так и встроенных.

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

Процесс упаковки очевиден. Для каждого параметра исполняется вызов set_value:

transaction_parameters = set_value(transaction_parameters,"OPERATION","S")
transaction_parameters = set_value(transaction_parameters,"PRICE",100)
transaction_parameters = set_value(transaction_parameters,"TRANS_ID","12345")
transaction_parameters = set_value(transaction_parameters,"ACCOUNT","Sht45WG")

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

Количество таких строк в иных случаях достигает двух десятков и даже больше. Кроме очевидной бестолковой работы пальцами текст принимает невразумительный вид.

Как упростить ситуацию? Применим проверенный способ - макросы.

Идея следующая. Создадим конструкцию

With(массив)
   Key(поле1,значение1)
   Key(поле2,значение2)
   Key(поле3,значение3)
   Key(поле4,значение4)
EndWith

Таким образом, создаются 3 псевдо-оператора. With объявляет название ассоциативного массива. Key заполняет определённое поле в этом массиве. EndWith завершает конструкцию

Каждый из псевдо-операторов реализуется одним макросом препроцессора M4. Для простоты я приведу простейший вариант этих макросов, не содержащих проверки на корректность и правильность синтаксиса:

define(`With',`pushdef(`m4$with_map',$1)')
define(`EndWith',`popdef(`m4$with_map')')
define(`Key',`indir(m4$with_map) = set_value(indir(m4$with_map),$1,$2)')

Теперь из текста

With(transaction_map)

   Key("ACTION","NEW_ORDER")
   Key("CLASSCODE",classcode)
       .. какие-то операторы ....
   Key"SECCODE",seccode)
   Key("ACCOUNT",account)
   Key("TRANS_ID",45)
   Key("OPERATION","S")
      ... еще какие-то операторы ...
   Key("PRICE",Price*1.01)
   if clientcode != ""
       Key("CLIENT_CODE",clientcode)

EndWith

Получаем код QPILE:


res = Send_transaction(30,transaction_map)

transaction_map = set_value(transaction_map,"ACTION","NEW_ORDER")
transaction_map = set_value(transaction_map,"CLASSCODE",classcode)
.. какие-то операторы ....
transaction_map = set_value(transaction_map,"SECCODE",seccode)
transaction_map = set_value(transaction_map,"ACCOUNT",account)
transaction_map = set_value(transaction_map,"TRANS_ID",45)
transaction_map = set_value(transaction_map,"OPERATION","S")
... еще какие-то операторы ...
transaction_map = set_value(transaction_map,"PRICE",Price*1.01)
if clientcode != ""
transaction_map = set_value(transaction_map,"CLIENT_CODE",clientcode)

res = Send_transaction(30,transaction_map)

Никаких инноваций-модернизаций-сколковых, исключительно глаз удобства для Улыбаюсь.

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


Можно добавить проверки. Директивы Key должны следовать за With, EndWith должен быть последним. В конце программы (END_PROGRAM) можно проверить соответствие количества With и EndWith. Можно также проверить нахождение конструкции целиком внутри границ функции. В оператор With можно добавить второй параметр, определяющий необходимость инициализации массива посредством Create_Map. Того же эффекта можно добиться, создав альтернативный макрос начала конструкции

define(`WithInit',`With($1) $1 = create_map()')

Неплохо также проверить, чтобы параметр, переданный макросу With (или WithInit) был переменной, а не выражением. Ничего страшного - получим синтаксическую ошибку на этапе исполнения скрипта QPILE, но лучше сразу её проверить. Достигается это элементарным поиском регулярного выражения.

После минимальных исправлений ситуация может выглядеть как-то так:

define(`m4_error',`err`print'(Error __file__ line __line__: $1
)')

dnl ------- WITH ----------
define(`With',`pushdef(`m4$with',$1)')
define(`WithInit',`With($1) $1 = create_map()')
define(`EndWith',`ifdef(m4$with,,m4_error(ENDWITH without WITH)) popdef(`m4$with')')
define(`Key',`ifdef(m4$with,,m4_error(KEY without WITH)) indir(m4$with) = set_value(indir(m4$with),$1,$2)')

 

Update 20/09/2012

Как известно, предела совершенству нет. Если происходит последовательное заполнение многих полей в ассоциативном массиве, более оптимально будет объединить все эти set_value в одну строку. Да и GetKey тоже не помешает.

define(`With',`ifelse(m4_is_variable($1),True,,m4_error(Parameter WITH/WITHINIT may be variable only))pushdef(`m4$with',$1)')
define(`WithInit',`With($1) $1 = create_map()')
define(`GetKey',`ifdef(m4$with,,m4_error(GETKEY without starting WITH)) get_value(indir(m4$with),$1)')
define(`IncKey',`ifdef(m4$with,,m4_error(INCKEY without starting WITH)) indir(m4$with) = Inc_Value(indir(m4$with),$1,$2)')
define(`EndWith',`ifdef(m4$with,,m4_error(ENDWITH without starting WITH)) popdef(`m4$with')')

define(`$setkey',`ifelse($1,,indir(m4$with),`set_value(indir($setkey,shift(shift($*))) ,$1,$2)')')
define(`SetKey',`ifdef(m4$with,,m4_error(SETKEY without starting WITH)) indir(m4$with) = indir($setkey,$*)')

Любимая моя рекурсия Улыбаюсь  Пробуем:

WithInit(transaction_map)

SetKey("ACTION",       "NEW_ORDER",
          "CLASSCODE",  classcode,
          "SECCODE",     seccode,
          "ACCOUNT",    account,
          "TRANS_ID",    45,
          "OPERATION", "S",
          "PRICE",          Price*1.01)
if clientcode != ""
      SetKey("CLIENT_CODE",clientcode)
EndWith

Получаем:

transaction_map = create_map()

transaction_map = set_value(set_value(set_value(set_value(set_value(set_value(set_value(transaction_map ,"PRICE",Price*1.01) ,"OPERATION","S") ,"TRANS_ID",45) ,"ACCOUNT",account) ,"SECCODE",seccode) ,"CLASSCODE",classcode) ,"ACTION","NEW_ORDER")
if clientcode != ""
     transaction_map = set_value(transaction_map ,"CLIENT_CODE",clientcode)

Весело и радостно, даже без обфускатора  Крутой. Однако интерпретатор qpile разберётся.

См также:

Недостаточно прав для комментирования

Библиотека