Ошибка "Too many variables" + препроцессор

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

Текущая реализация языка QPILE имеет неприятное ограничение на количество переменных. Всего их может быть не более 1000. Долгое время я не добирался до этого ограничения путём оптимизации библиотечных функций, но все когда-то случается впервые и приходится искать пути решения.

При подсчете переменных компилятор QPILE использует не только сами имена переменных, но также и имена формальных параметров при вызове функций. Таком образом, фрагмент

func f(parameter1,parameter2)
    result = parameter1 + parameter2
end func
а = f(3,4)

создаст ни много ни мало 4 переменные: a, parameter1, parameter2, result.

Неудивительно, что ограничение в 1000 переменных не выглядит недостижимым.

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

Может быть в разных функциях следует давать одинаковые имена формальным параметрам, чтобы не плодить переменные? Увы и ах, это не помогает. Два формальных параметра разных функций с одним именем все равно породят 2 переменные. Замкнутый круг. Неужели прекращать использование формальных параметров и пользоваться глобальными переменными для передачи значений в функции? Способ кажется единственно возможным, чтобы не упираться в ограничение в 1000 переменных, но слишком уж он неприятный.  Отладчик qpile станет мне главным другом, но куда-нибудь подальше таких друзей...

Как известно, все давно украдено до нас. В нашем примере приведена довольно простая функция, коорую можно без каких-либо зазрений совести написать inline, без вызова функции. Обойдемся без формальных параметров и переменной result. Все бы хорошо, но как быть с простейшей функцией

 ' Пребразует время из внутреннего int ЧЧММСС в текстовый HH:MM:SS
FUNC TimeIntToStr(return)
     result = add_zero(Floor(return/10000)) & ":" & add_zero(Mod(Floor(return/100),100)) & ":" & add_zero(Mod(return,100))
END FUNC

Тоже каждый раз писать руками все эти скобочки?

Сформулируем задачу.

  • найти средство для снятия остроты проблемы с "Too many variables"
  • не изменять принятому стилю программирования (разбиение кода на независимые части)
  • не перенапрягать при этом глаза и пальцы

Решение задачи имеется - универальный макропроцессор. Среди универсальных наилучшим и наиболее распространённым является макропроцессор M4. Существует давно, лет 25, никак не меньше. Все ошибки давно найдены, вдоль и поперек изучен.  Попробуем использовать его для решения нашей задачи.

Можно ли применить макропроцессор для замены вызова функции TimeIntToStr() на inline код?

Запросто! Вот что получается. Вместо самой функции пишем макрос.

define(TimeIntToStr,add_zero(Floor($1/10000)) & ":" & add_zero(Mod(Floor($1/100),100)) & ":" & add_zero(Mod($1,100))

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

Теперь в любом месте нашего qpile портфеля делаем вызов этого макроса как обычной функции

StrTime = TimeIntToStr(20120714)

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

StrTime = add_zero(Floor(20120714/10000)) & ":" & add_zero(Mod(Floor(20120714/100),100)) & ":" & add_zero(Mod(20120714,100)))

который является 100%-но готовым к исполнению в терминале QPILE.

Здесь опять фигурируют вызовы других функций. Например, функция Mod(), вычисляющая остаток от деления. Может, с ним поступить точно также? Можно ли делать вложенные макросы? Попробуем. 

' Вычисление остатка от деления первого параметра на второй
FUNC Mod(min_max_x,result)
               result = min_max_x - Floor_To_Step(min_max_x,result)
END FUNC

 

Заменяем текст функции на макрос для макропроцессора М4: 

define(Mod,$1- Floor_To_Step($1,$2))

итого исходный текст:

define(Mod,$1- Floor_To_Step($1,$2))
define(TimeIntToStr,add_zero(Floor($1/10000)) & ":" & add_zero(Mod(Floor($1/100),100)) & ":" & add_zero(Mod($1,100)))
StrTime = TimeIntToStr(20120714)

будет преобразован препроцессором в

StrTime = add_zero(Floor(20120714/10000)) & ":" & add_zero(Floor(20120714/100)- Floor_To_Step(Floor(20120714/100),100)) & ":" & add_zero(20120714- Floor_To_Step(20120714,100))

Довольно зубодоробительно, но все честно. Может add_zero() и Floor_To_Step() переписать в виде макроса? В принципе, нет проблем. Однако я этого делать уже не буду. Края стакана видеть нужно, с горкой не нальёшь.

Что мы имеем в сухом остатке?

  • съэкономлено 5 переменных
  • исходный текст попрежнему вполне читаемый и внятный
  • глаза видят, пальцы не стёрты.

Таким образом, макропроцессор M4 является довольно приятным способом для ухода от проблемы переполнения списка переменных в интерпретаторе qpile.

См. также Расширение синтаксиса QPILE

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

Библиотека