Заполнение ассоциативных массивов: оператор With
Ещё оно средство от мозолей на пальцах.
Как известно, существует ограничение на количество формальных параметров у функций в 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 разберётся.
См также: