[Из песочницы] Стрелочные часы на CMake
Когда я устроился на новую работу, пришлось в ускоренном темпе осваивать новые для меня технологии, которые используются в данной компании. Одной из таких технологий стала система сборки cmake, с которой мне раньше не приходилось сталкиваться.
Эта система имеет свой встроенный язык для написания сборочных скриптов. Этот самый язык меня и заинтересовал. Вскоре я выяснил, что в нем есть возможность вычисления математических выражений, запись и чтение из файлов, запуск внешних процессов и другие интересные возможности, что навело меня на мысль воспользоваться этим языком в качестве основного ЯП и написать на нем что-нибудь осязаемое. Речь пойдет о том, как я писал стрелочные часы на языке cmake 2.8.Честно говоря, сначала мне в голову пришла идея проверить cmake на возможность ввода-вывода из стандартных потоков. Хотелось научиться считывать нажатые клавиши, ну или, на худой конец, события мыши, что позволило бы сделать какую-нибудь интерактивную программу, написать, к примеру, тетрис. С выводом оказалось все довольно просто:
file (WRITE /dev/stdout «blabla») А вот считывать стандартный поток cmake напрочь отказывался, читать напрямую из ивентов (/dev/input/event4 или /dev/input/mice) также не удавалось. Так что идея сделать тетрис была отброшена и я решил поиграться с выводом, всё-таки способность выводить напрямую в stdout меня привлекала больше, чем стандартная команда message ().Я решил, раз уж могу писать напрямую в stdout, то надо попробовать писать туда escape-последовательности. Это бы дало богатые возможности: цветной вывод, перемещения курсора, очистка экрана и другие. К счастью, в cmake оказалась возможность вывода непечатаемых символов — это операция ASCII функции string, так я написал функцию очистки экрана:
string (ASCII 27 ESCAPE) function (clrscr) file (WRITE /dev/stdout »${ESCAPE}[2J») endfunction (clrscr) Раз уж escape-коды заработали, то я решил следующим шагом научиться выводить текст в произвольных координатах: function (textXY X Y MSG) file (WRITE /dev/stdout »${ESCAPE}[${Y};${X}H${MSG}») endfunction (textXY) Ну и следующим логичным продолжением этого родилась мысль написать функцию рисования линии. Здесь уже пришлось столкнуться с первыми трудностями: cmake вычисляет выражение записанное в строку и результат его целочисленный; функции в cmake не возвращают значение; хочется иметь универсальный алгоритм рисования линии, не зависящий от расположения концов относительно друг друга; Решать эти трудности я начал с конца. Во-первых, был придуман алгоритм рисования линии: Найти разницу координат концов Dx=x2-x1 и Dy=y2-y1 с учетом минуса (он будет нужен для направления); Найти максимальную по модулю дельту Dmax = max (abs (Dx), abs (Dy)); Пробежать циклом i = 0…Dmax, на каждом шаге вычисляя текущие координаты по формулам: x = x1 + i * Dx / Dmax y = y1 + i * Dy / Dmax Во-вторых, нужны были функции поиска максимума и абсолютного значения. Так как функции в cmake значения не возвращают, то пришлось воспользоваться макросами. В макросы можно подставлять как переменные, так и значения. Мне показалось, что переменные везде подставлять красивее, но макрос получается слишком «волосатым», так что в дальнейшем я стал использовать подстановку переменной только для результата.Код макросов macro (max a b m) if (${a} LESS ${${b}}) set (${m} ${${b}}) else (${a} LESS ${${b}}) set (${m} ${${a}}) endif (${a} LESS ${${b}}) endmacro (max)
macro (abs a res) if (${a} LESS 0) string (LENGTH ${a} len) math (EXPR l1 »${len} — 1») string (SUBSTRING ${a} 1 ${l1} ${res}) else (${a} LESS 0) set (${res} ${a}) endif (${a} LESS 0) endmacro (abs) Для поиска абсолютного значения используется тот факт, что cmake оперирует строками и просто «откусывается» минус, если он есть.Когда макросы были готовы, то при попытке вычислять выражения для координат, используя команду
math (EXPR
Для реализации тригонометрических функций применяется их разложение в ряд Маклорена. И снова трудности:
Не хочется использовать слишком высокие степени и факториалы в ряде Маклорена; При использовании 2–3 первых членов ряда хорошие приближения получаются только в интервале [-pi/2; pi/2]. Мне же хотелось иметь ОДЗ хотя бы в интервале [-pi; 2*pi], для этого было решено угол в радианах переводить в правую полуплоскость, делая поправку на знак функции. Технически тут геометрический смысл и формулы приведения, поэтому сильно не «разжевываю». Итоговый код тригонометрических функций получился довольно «страшненьким»: Код синуса и косинуса set (PI1000 3142) set (PI500 1571) set (_PI500 -1571) set (_2PI1000 6283)
macro (m_rad1000_4sin x res) math (EXPR rad1000 »(0${x}) * ${PI1000} / 180») if (rad1000 GREATER ${PI1000}) math (EXPR rad1000_ »${PI1000} — ${rad1000}») else (rad1000 GREATER ${PI1000}) set (rad1000_ ${rad1000}) endif (rad1000 GREATER ${PI1000}) if (rad1000_ GREATER ${PI500}) math (EXPR rad1000__ »${PI1000} — ${rad1000_}») else (rad1000_ GREATER ${PI500}) if (rad1000_ LESS ${_PI500}) abs (${rad1000_} abs_rad1000_) math (EXPR rad1000__ »${abs_rad1000_} — ${PI1000}») else (rad1000_ LESS ${_PI500}) set (rad1000__ ${rad1000_}) endif (rad1000_ LESS ${_PI500}) endif (rad1000_ GREATER ${PI500}) set (${res} ${rad1000__}) endmacro (m_rad1000_4sin)
macro (m_rad1000_4cos x res) math (EXPR rad1000 »(0${x}) * ${PI1000} / 180») if (rad1000 GREATER ${PI1000}) math (EXPR rad1000_ »${rad1000} — ${_2PI1000}») else (rad1000 GREATER ${PI1000}) set (rad1000_ ${rad1000}) endif (rad1000 GREATER ${PI1000}) set (${res} ${rad1000_}) endmacro (m_rad1000_4cos)
macro (sin1000 x res) m_rad1000_4sin (${x} r1000) math (EXPR ${res} »0${r1000} — (0${r1000}) * (0${r1000}) / 1000 * (0${r1000}) / 1000 / 6 + (0${r1000}) * (0${r1000}) / 1000 * (0${r1000}) / 1000 * (0${r1000}) / 1000 * (0${r1000}) / 1000 / 120») endmacro (sin1000)
macro (cos1000 x res) m_rad1000_4cos (${x} r1000) unset (sign) if (r1000 GREATER ${PI500}) math (EXPR r1000_ »${PI1000} — ${r1000}») set (r1000 ${r1000_}) set (sign »0-») endif (r1000 GREATER ${PI500}) if (r1000 LESS ${_PI500}) math (EXPR r1000_ »${PI1000} + (0${r1000})») set (r1000 ${r1000_}) set (sign »0-») endif (r1000 LESS ${_PI500}) math (EXPR ${res} »${sign}(1000 — (0${r1000}) * (0${r1000}) / 1000 / 2 + (0${r1000}) * (0${r1000}) / 1000 * (0${r1000}) / 1000 * (0${r1000}) / 1000 / 24 — (0${r1000}) * (0${r1000}) / 1000 * (0${r1000}) / 1000 * (0${r1000}) / 1000 * (0${r1000}) / 1000 * (0${r1000}) / 1000 / 720)») endmacro (cos1000) После этого остальное уже было делом техники — нарисовать 12 чисел по кругу, крутиться в цикле и спрашивать у системы время; когда оно изменилось, стирать старые стрелки и рисовать новые под нужными углами. Время получаем через запуск внешнего процесса: execute_process (COMMAND «date» »+%H%M%S» OUTPUT_VARIABLE time) выделить подстроки из time и вычислить углы — в рамках школьной математики.Полный код можно посмотреть на гитхабе.Тестировалось на cmake version 2.8.12.2, Ubuntu 12.04, 14.04.