программирование
Ещё про инженерный пароль от «Войи»
Ну началось 😅 Теперь меня не отпускают мысли как можно доработать приложение для получения инженерного пароля, которое я написал вчера.
Как я говорил, у «Флиппера» есть проблема — он ничего не знает о часовом поясе. В каждом приложении, где нужно его знать, приходится
Я пока не стал заморачиваться, просто показываю три кода из трёх разных дней. Это, конечно, не совсем правильно. Учитывая, что часовые пояса у нас бывают от
В общем, решил сделать как все — окошко с настройкой часового пояса. Если пояс задан, он будет показан на кнопке и выводиться будет только один код, если нет, на кнопке будет написано
Инженерный пароль от «Войи»
Чтобы совсем не позабыть как писать и компилировать программы для «Флиппера», написал небольшую программу, которая выводит инженерный пароль для моей машины.
Пароль там можно даже на калькуляторе вычислять, но это неудобно — надо сложить поциферно
День берётся по часовому поясу Китая, а поскольку «Флиппер» ничего про часовые пояса не знает, поэтому приходится показывать три числа — для разных дней.
Полезное упражнение, подзабыл некоторые особенности, как самой сборки, так и наличествующего АПИ.
The Sensor Watch
Одна из вещей, которых я сильно боюсь, это однажды навсегда потерять желание
Я несколько раз попадал в такой водоворот. Чувствуешь памятью о личности, которой было
Описанное — та причина по которой я тщательно пестую любую искру интереса
В общем, очередная моя хотелка — Sensor Watch, маленькая платка, стоящая 39 долларов и вставляемая вместо оригинальной электроники в ряд часов «Касио».
Эта платка реагирует на те же кнопки, выводит данные на то же табло и пищит тем же динамиком, но самое главное — у неё на борту армовский процессор с возможностью программирования. В прошивку, которая туда заливается, можно записывать свои небольшие приложения, если придумать как выводить данные на весьма ограниченный дисплей часов!
Поскольку в прошлой жизни я был электронщиком и программистом на ассемблере, такие ограниченные ресурсы всегда возбуждают моё любопытство. Платку я себе заказал, а пока написал в эмуляторе порт знаменитой в свои годы игрушки «Hunt The Wumpus», я про неё уже
На скриншоте видно как приходилось изворачиваться. Поскольку не все буквы получается вывести в сегментах часов как есть, приходится проявлять фантазию. Например, справа внизу я пытался изобразить букву «W» (это значит что
Описание получившейся игры и инструкцию, можно найти на моём гитхабе. Там
На то, чтобы разобраться как всё утроено и написать саму игру, ушло несколько часов. Начал я вечером позавчерашнего дня, потом почти весь день проектировал в голове, когда были паузы не работе, и дописал всё вчерашним вечером. Устроено всё просто, хотя в некоторых местах пришлось вставлять костыли —
Находкой считаю, что смерть игрока сопровождается красной подсветкой, а выигрыш — зелёной (часы так умеют).
Теперь дождаться бы часов и платы «во плоти», чтобы попробовать поиграть не в эмуляторе, а на самой железке.
Brainfuck в шрифте
Я писал уже про язык программирования в шрифтах. Там я, в качестве примера, написал программу, которая переводит вводимые буквы в азбуку Морзе. Внедрение всякой всячины в шрифт оказалось таким весёлым занятием, что вчера не удержался и воткнул в шрифт интерпретатор языка программирования Brainfuck. Язык внутри языка внутри шрифта.
Поскольку никакого ввода, кроме строки, которую нужно написать шрифтом, тут не предусмотрено, но некоторые программы требуют ввод (для этого в Брейнфаке есть конструкция «запятая»), пришлось выкручиваться.
Я сделал так: программа и входные данные у меня записаны в одну строку и разделены слешем. Часть после слеша считается тем, что введено в программу с клавиатуры.
99 бутылок: bootBASIC
Очередная «песня о пиве», в последнее время я
78. bootBASIC — интерпретатор простенького диалекта Бейсика, занимающий 512 байт. В наше время, когда некоторые программы занимают гигабайты, удивительно видеть
А тут целый язык, со строками, поддержкой математики, ветвлением и даже небольшим редактором кода. Правда я уже писал «песню о пиве» на миниатюрном Си. Он занимал чуть меньше, но и возможности там были
Конечно, при таком размере есть свои ограничения — арифметика только целочисленная, до 65535 и нет операций сравнения, — можно проверить только, что число не равно нулю.
Кроме того, строки кода ограничены по длине,
Обидно, что нет gosub для перехода к подпрограмме, вместо этого приходится использовать goto на переменную. Ладно хоть такая возможность есть. Комментариев нет, но можно использовать для этого команду print""; — дальше неё интерпретатор в строке ничего не видит.
1 print""; Written by Evgeny Stepanischev https://bolknote.ru
10 b=99
20 r=30
21 goto 200
30 print"of beer on ";
31 print"the wall, ";
40 r=50
41 goto 200
50 print"of beer."
60 b=b-1
70 print"Take one do";
71 print"wn and pass";
72print" it around, ";
80 r=90
81 goto 200
90 print"of beer on ";
91 print"the wall."
100 if b goto 20
110 goto 900
200 if b goto 203
201 print"no";
202 goto 210
203 print b;
210 print" bottle";
220 if b-1 goto 250
230 print" ";
240 goto r
250 print"s ";
299 goto r
900print"No more bott";
901print"les of beer ";
902print"on the wall,";
903print" no more bot";
904print"tles of beer."
905print"Go to the st";
906print"ore and buy ";
907print"some more, 9";
908print"9 bottles of";
909print" beer on the";
910print" wall.
Библиотека itertools для PHP
В качестве развлечения написал тесты к своей старой библиотеке itertools для ПХП и выяснил, что в ней
Пример использования теперь стал выглядеть вот так:
use Itertools\Itertools as it;
foreach (it::islice(it::cycle('ABC'), 10) as $element) {
echo $element;
}99 бутылок: mkdir + find
77. mkdir + find — тут некий японец придумал как писать программы, используя только утилиты mkdir и find. Мне такое очень нравится, поэтому и я решил
Программу вы можете видеть на экране, а как она работает расскажу чуть ниже.
# Written by Evgeny Stepanischev https://bolknote.ru
rm -rf bottles; mkdir bottles
find bottles -maxdepth 98 -execdir mkdir bottles/bottles \;
find bottles -empty -name bottles -execdir mkdir bottles/bottle \;
find bottles \
-empty -printf "1 bottle of beer on the wall, 1 bottle of beer.
Take one down and pass it around, no bottles of beer on the wall.
No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.\n" -o \
-regex '.*/bottles/bottles' \
-execdir find . \
-empty -printf "%d bottles of beer on the wall, %d bottles of beer.\n" \; \
-execdir find bottles \
-empty -printf "Take one down and pass it around, %d %f of beer on the wall.\n\n" \;
# Clean up
rm -rf bottlesПрежде всего — что за две команды тут используются? rm не в счёт, она для очистки мусора, можно без неё.
Команда mkdir, наверное, многим знакома — это создание директория, а find — утилита для поиска файлов и директориев по различным критериям. Причём с найденным она может делать некоторые простые действия.
Чтобы писать программы, автор оригинальной статьи использует несколько находок.
Параметр запускает указанную команду в директории, который сейчас просматривает find. Эти вложенные директории в дальнейшем используются как конечный цикл для следующей команды find. Вложенность при этом можно регулировать параметром -maxdepth.
Мне пришлось дополнительно решить ещё две проблемы.
На самом деле на каждой внешней итерации я пускаю не один, а два внутренних цикла, поскольку мне нужны два числа — текущее значение считаемого «пива» и это же значение, уменьшенное на единицу. Сделать это было просто — ещё один find надо запускать во вложенном директории.
Но в строке перед этим текстом у меня сделано более честно. Как видно, структура директориев у меня такая: 98 раз созданы вложенные bottles, а конечная директория называется bottle.
Это мне позволяет в одном из «циклов», огранизованных через find, использовать аргумент %f параметра -printf, который выводит имя текущего директория. Таким образом в самом конце я получаю bottle вместо bottles.
Добавлено: мне подсказали, что обратный цикл можно огранизовать проще, через ключ -depth, переделал программу под него:
# Written by Evgeny Stepanischev https://bolknote.ru
find start -delete 2>&-
mkdir -p start/bottle/bottles
find start/bottle -maxdepth 97 -name bottles -execdir mkdir bottles/bottles \;
find start -mindepth 1 -depth \
-empty -printf "%d %f of beer on the wall, %d %f of beer.\n" -o \
! -empty -printf "Take one down and pass it around, %d %f of beer on the wall.\n\n" \
-printf "%d %f of beer on the wall, %d %f of beer.\n" \
-name bottle -printf "Take one down and pass it around, no bottles of beer on the wall.
No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.\n"
rm -rf start
Интернет-время «Своч»
Интересно насколько
Последняя форма записи подтолкнула мои размышления к тому, какой у нас, в сущности, странный способ измерения времени.
«8.35» похоже на десятичную дробь, но на самом деле основание дробной части тут — 60, а у целой — 12 или 24, в зависимости от обстоятельств. Наверняка все об этом думают время от времени.
Я знаю, что было несколько попыток предложить
Швейцарская компания «Своч» (Swatch) занимается наручными часами. Не знаю как у неё сейчас с известностью, а конце
Видимо на волне этой популярности,
Идея была в том, чтобы разделить сутки на тысячу «битов» — от нуля до 999, каждый «бит» (beat) равнялся 86,4 секунды, при этом отсчёт начинался с часового пояса UTC+01:00.
Время записывалось, начиная с символа «@» (он тогда прочно ассоциировался с интернетом) и добивалось нулями до трёх разрядов. Например: «@007», «@013» и так далее.
Вот небольшая программа на «Расте» для вывода текущего времени в этом формате:
use chrono::{Utc, Timelike, Duration as D};
use std::thread;
use std::time::Duration;
fn main() {
const SECS_IN_BEAT: f64 = 86.4;
loop {
let seconds = (Utc::now() + D::hours(1)).num_seconds_from_midnight() as f64;
let beat = (seconds / SECS_IN_BEAT).floor();
print!("🕰️ @{}\n\x1b[A\x1b[G", format!("{:03}", beat));
let duration = (1. + beat) * SECS_IN_BEAT - seconds;
thread::sleep(Duration::from_secs_f64(duration));
}
}Идея свою минуту славы получила, — я помню, компания «Эрикссон» выпустила телефон, который умел отображать время в этом формате, в языке ПХП добавили поддержку этого формата в функции работы со временем, а «Своч» выпустила часы, где в первой строке было традиционное время, а ниже — время
Не дожив даже до середины
Сейчас для видеосозвонов
К настоящему моменту, я считаю, поезд
Немного о WASM'е в шрифтах
Когда я вчера в небольшом кругу рассказал о том как работает WASM в шрифтах, оказалось, что никто не понял некоторые базовые вещи, которые мне казались очевидными. Поэтому я хочу их расписать и тут тоже.
Давайте опять немного о том, почему язык программирования в шрифте — это удобно.
Шрифты сейчас устроены довольно сложно. Если не приглядываться, то ничего и не заметишь, но если разглядывать как взаимодействуют буквы между собой, окажется, что даже в отображении привычных нам кириллических и латинских букв есть особенности — в хороших шрифтах расстояния между различными парами букв тщательно выверено и записано внутрь шрифта.
Это делается для красоты, чтобы шрифт казался однородным. Буквы не квадратные, одни шире, другие у́же, «о» круглая, а «ж» похожа на причёску Альберта Эйнштейна. Если писать буквы как есть, то будет выглядеть неаккуратно, поэтому в местах их прилегания расстояния подбирают так, чтобы смотрелось хорошо.
Но вот нам хочется добавить в Юникод египетские иероглифы, а там имя фараона пишется в продолговатом овале — картуше. Как это решить в шрифте? Да никак. Мы можем ввести символы начала картуша и конца, а линии, которыми они должны соединяться, запрограммируем в движке, который рисует нам символы. Причём дизайнер шрифта никак не будет влиять на то, как эти линии рисуются.
Может показаться, что это не так уж и важно, наверное правда в этом есть, но это всего лишь пример. Есть и более актуальные проблемы, например, в арабском письме, но там их объяснить сложнее. По крайней мере, сложнее для меня.
Вместо того чтобы писать каждый раз новую логику в библиотеке, есть другое решение — внедрить этот кусок кода в сам шрифт, тогда с каждым шрифтом будет идти «инструкция» как его рисовать. Это удобно, потому что не нужно ждать, пока библиотека обновится во всех программах, где нам хочется, чтобы картуши рисовались правильно.
Как это работает?
Библиотека, если сильно упрощать, получает на вход от приложения место на экране (координаты) и строку, которую надо написать в этом месте. Строка, перед тем как начнётся отрисовка, засовывается в программу, которая хранится внутри шрифта. Эта программа производит с символами
Внутри программы можно менять символы, сдвигать их на другие позиции и так далее. Самое главное — программе доступен контекст в виде целой строки. Таким образом можно обнаружить (в случае египетских иероглифов), что слово начинается с символа начала картуша и нарисовать линии до признака его конца.
WASM в шрифтах
Ну вот мы и дожили до момента, когда в шрифтах появился язык программирования. В harfbuzz, лидирующей библиотеке для отрисовки шрифтов появилась встроенная поддержка WASM.
В принципе, ожидаемо — даже мне, далёкому от проблем шрифтовиков человеку, было очевидно, что сложности соединения символов, используемых на письме, не решить только декларативным образом.
В общем, в шрифтах появился свой язык программирования.
Естественно, все побежали делать на нём всякую весёлую дичь. Внутрь шрифтов уже засунули «Тетрис» и даже
Я узнал об этом развлечении поздновато, но вчера вечером тоже немного развлёкся — сделал шрифт, который из букв «на лету» делает символы азбуки Морзе.
В принципе,
И опять чинил PogodaStatusBar
Прям заметно, что эпоха открытого веба закончилась. Было время вокруг были открытые сервисы, используй сколько влезет. Потом всё
В общем, прошла неделя и мне снова прикрыли лавочку — урл «Гисметео», который я использовал, чтобы определять айди населённого пункта по названию вдруг меня забанил. На любой запрос выдаётся «доступ запрещён». Да что ж такое!
Полез я снова смотреть погодный плагин, смотреть как сделано там. Он же
Координаты у меня уже есть, я их могу забрать у «Яндекса», надо только распарсить:
@functools.cache
def _getRegionData(self):
try:
url = "https://yandex.ru/tune/geo/"
content = urllib.request.urlopen(url).read().decode('utf-8')
region_data = re.search(r'data-bem="([^"]+coords[^"]+)"', content).group(1)
parsed_region_data = json.loads(html.unescape(region_data))
# {'id': xx, 'region': 'XXXXX', 'coords': ['XX.XXXXX', 'XX.XXXXX'], 'accuracy': 'XXXXXX'}
return parsed_region_data['checkbox']['auto']
except (IOError, AttributeError):
return NoneВ итоге, получилась такая схема: в «Яндексе» получаем координаты и айди населённого пункта у «Яндекса», по этому айди получаем информацию по пробкам. Далее по координатам получаем айди населённого пункта у «Гисметео», по которому получаем погоду.
Опять чинил PogodaStatusBar
Сегодня участвовал в довольно скучной видеоконференции. Моё участие там было хоть и необходимое, но небольшое. А чтобы не одуреть от безделья, пока я слушал всю эту скуку, решил
Плагин в строке статуса показывает текущую погоду и пробки в вашем городе. Много лет данные для этого я брал с неофициального урла «Яндекса», но компания решила коммерциализировать погоду и, сколько я не искал новый способ получить её бесплатно, так и не нашёл.
Пробки, кстати, тоже перестали нормально работать.
Я перебрал несколько малоизвестных сервисов, пока, наконец, не увидел, что страница yandex.ru/tune/geo/ содержит в себе необходимый номер. Этого хватило, чтобы починить показ пробок.
С «Гисметео» пришлось повозиться. Не нашёл официальную информацию
Определить номер города в формате «Гисметео» помогает сервис www.gismeteo.ru/rmq/search/{название}/1/, куда я подставляю название, полученное от «Яндекс.Пробок» — там он возвращается вместе с остальными данными.
Оставалось только расшифровать иконку погоды. В документации есть часть описания (остаток можно понять, скачав с «Гисметео» значки погоды), выглядит всё несложно. Например, d.r3 означает «день» (day) и «дождь максимальной интенсивности» (rain 3).
Поскольку у меня Юникод, в нём есть не все необходимые значки, я сделал вот такой объект, содержащий признаки, которые позволяют мне выбрать какую иконку показать:
_icons = {
"🌞": {"d"}, # day
"🌙": {"n"}, # nite
"☁️": {"n", "c"}, # night, cloud
"🌧": {"rs", "c", "r"}, # rain+snow, cloud, rain
"🌤": {"d", "c"}, # day, cloud
"🌦": {"d", "r"}, # day, rain
"🌩️": {"c", "st"}, # #day, storm
"⛈️": {"c", "st", "r"}, # cloud, storm, rain
"🌨": {"c", "s"}, # cloud, storm
"💨": {"mist"}, # mist
"⚡️": {"st"}, # storm
}И вот такой простой алгоритм для выбора иконки:
def _getStatus(self, coded_weather):
codes = {x.strip("0123456789") for x in coded_weather.split(".")}
max_icon = None
max_score = 0
for icon, code in self._icons.items():
result = codes & code
score = sum(len(x) for x in result)
if score > max_score:
max_icon, max_score = icon, score
return max_iconИдея простая — чем больше значков совпало, тем лучше; если совпал значок с более длинным названием (st, mist), считаем его более конкретным и важным.
Посещал ли пользователь сайт?
Атака не работает — в браузерах давно реализована защита, у меня идея «сработала» в силу того, что я не выполнил все необходимые проверки.
В прошлом было достаточно много методов определить ходил ли пользовать
Это считалось проблемой, поэтому свойство изменили так, чтобы никакой утечки информации о посещении в ДжаваСкрипт не происходило. Все современные браузеры уже много лет от этого защищены.
Но недавно меня осенило, что есть способ обойти эту защиту!
Ведь если пользователь посещал целевой сайт, то скорее всего
Я попробовал сделать это с «Гитхабом». Самый большой файл, который загружается там с первой страницей — vendors-node_modules_github_mini-throttle_dist_index_js-node_modules_primer_octicons-react_di-b40d97-9b98c5140e22.js. Занимает он 136 килобайт, вполне подходящий размер.
Как же определить момент когда он загрузится? Если посмотреть внутрь, видно, что он после скачивания создаёт свойство webpackChunk в объекте globalThis. Если перехватить обращение к этому свойству, можно понять, что файл загружен:
const t = new Date();
Object.defineProperty(globalThis, 'webpackChunk', {
set: function() {
console.log(new Date() - t);
}
});Ниже этого кода подключаем загрузку указанного файла прямо с «Гитхаба» через тег <script> и дело в шляпе.
Я проверил, если очистить кеш браузера, то файл у меня загружается примерно за 100—400 миллисекунд, если берётся из кеша — это 0—12. Легко отличить одно от другого.
Графики в стиле xkcd
xkcd — культовая серия
У xkcd очень узнаваемый стиль рисовки и, когда я их вдруг встречаю, они
Вот код:
import matplotlib.pyplot as plt
import numpy as np
t = np.linspace(0, 2*np.pi, 1000)
x = 16 * np.sin(t)**3
y = 13 * np.cos(t) - 5 * np.cos(2*t) - 2 * np.cos(3*t) - np.cos(4*t)
plt.xkcd()
plt.figure(figsize=(8, 6))
plt.plot(x, y, color='b')
plt.title('cos(…) = ?')
plt.show()А вот результат:
Сложности проверки версий
Случайно обратил внимание
Дело в том, что в движке, который я использую для своего дневника, для проигрывания аудио используется библиотека «Жуэль», а её текущая версия, в свою очередь использует старую версию библиотеки «howler.js», в которой есть вот такой код:
self._navigator = (typeof window !== 'undefined' && window.navigator) ? window.navigator : null;
var checkOpera = self._navigator && self._navigator.userAgent.match(/OPR\/([0-6].)/g);
var isOldOpera = (checkOpera && parseInt(checkOpera[0].split('/')[1], 10) < 33);Проблема в регулярном выражении OPR\/([0-6].) и в версии «Оперы». Недавно версия перевалила за 100 и эта регулярка, вместо корректного номера, стала возвращать «10».
Заменил у себя на сайте howler.js на актуальную версию и всё заработало. Заодно предложил патч в «Жуэль», надеюсь примут оперативно.
cmd.sh ★
Я тут случайно в спор ввязался — можно ли переписать
Речь шла о вполне определённом файле, там запрограммировано небольшое меню и в зависимости от выбранного пункта запускается та или иная, одинаковая для всех ОС, последовательность команд.
Мой собеседник мне не верил, когда я утверждал, что это вполне возможно, и требовал доказательств.
Что ж. Основная моя идея была в том, чтобы заменить команды батника на вызовы
То, что мне предстояло адаптировать начиналось вполне стандартно:
@ECHO off
CHCP 1251
CLSЧто тут происходит? Выключается вывод команд на экран (при адаптации это можно игнорировать), ставится однобайтовая кодировка CP1251 (это хорошо бы учесть) и очищается экран.
Поскольку «баш» этих команд не знает, надо определить три функции, спрятав их от cmd.exe. Сделать это несложно. Команду «двоеточие» cmd.exe трактует как начало метки и на дальнейшее не реагирует, а «баш» считает её пустой командой, которую можно точкой с запятой отделить от строки, которую мы хотим спрятать:
:; @ECHO() { :;}; CLS() { clear; }; CHCP() { ENC="$1"; }Следующее, чему надо научиться — выводить текст в указанной в батнике кодировке. Это просто:
:; ECHO.() { echo $@ | iconv -f "CP$ENC"; }Если теперь сделать файл запускаемым и добавить впереди #!/bin/bash (и пренебречь тем, что cmd.exe успевает ругнуться на эту строку до очистки экрана), у нас получится следующий файл, работающий во всех трёх операционках (кодировка должна быть
#!/bin/bash
:; @ECHO() { :;}; CLS() { clear; }; CHCP() { ENC="$1"; }
@ECHO off
CHCP 1251
CLS
:; ECHO.() { echo $@ | iconv -f "CP$ENC"; }
ECHO. Всем приветА вот дальше сложнее. Само меню организовано в оригинале так:
SET /p opt=Введите цифру:
IF %opt%==1 GOTO dns_auto
IF %opt%==2 GOTO dnschange
IF %opt%==3 GOTO exitВ «баше» goto (переход к метке) отсутствует и это проблема. Аналогов тоже нет. Единственное, что тут можно сделать — использовать вызов функций, но синтаксис в «бате» и «баше» сильно различается.
Как быть?
:; SET() { shift; ECHO. -n "${@#*=}"; read -n1 "${1%%=*}"; echo; }
:; CALL() { eval "$(sed 's/%\(.*\)%/$\1/g' <<< "$1")" 2>&-; }
SET /p opt=Введите цифру:
CALL :menu_%opt%Первая функция парсит команду SET выводит строку после равно, ждёт ввода и записывает значение в имя переменной, указанной слева от равно.
Вторая — заменяет CALL. В ней %variable% заменяется на $variable, выполняется подстановка переменной и получившееся имя выполняется как команда или функция «баша». Конструкция 2>&- нужна, чтобы избежать вывода ошибки в ситуации, если пользователь введёт
Теперь надо
:function_1
ECHO это якобы функция
GOTO :EOFВ «баше» то же самое могло бы выглядеть, например, так:
function_1() {
echo это функция
}Как это объединить? Я придумал следующий подход:
:; GOTO() { :; };
GOTO ;#start
:function_1 (){
ECHO. это [якобы] функция
GOTO :EOF
:; }
:;#startВ первой строке определяется пустая функция для «баша», потому что эта часть синтаксиса
Ниже идёт строка GOTO ;#start. С точки зрения «баша» тут две конструкции — GOTO, которая вызывает определённую выше пустую функцию и #start — строка комментария, так как в«баше» с «решётки» начинаются комментарии.
В «батнике» же эта же строка будет означать переход к метке с именем ;#start. Этот переход нужен нам, чтобы «обогнуть» строки, которые определены ниже, иначе cmd.exe начнёт сразу их выполнять, а нам этого не нужно. Их нельзя «спрятать» ниже основной программы, так как в «баше» функции должны определены раньше их вызова.
Что происходит дальше? С точки зрения cmd.exe ниже расположена метка :function_1 (как показывают мои эксперименты, часть после пробела просто отбрасывается), потом тело функции, команда её завершения GOTO :EOF и метка с именем :;}.
С точки зрения «баша» там определяется функция с именем :function_1, ниже идут вызовы уже определённых мною функций ECHO. и GOTO, а ещё ниже — уже знакомая нам пустая команда : и фигурная скобка, завершающая тело функции.
Соединяем всё вместе и получается следующая программа:
#!/bin/bash
:; @ECHO() { :;}; CLS() { clear; }; CHCP() { ENC="$1"; }
@ECHO off
CHCP 1251
CLS
:; GOTO() { :; };
:; ECHO.() { echo $@ | iconv -f "CP$ENC"; }; PAUSE() { read; };
:; SET() { shift; ECHO. -n "${@#*=}"; read "${1%%=*}"; }
:; CALL() { eval "$(sed 's/%\(.*\)%/$\1/g' <<< "$1")" 2>&-; }
GOTO ;#start
:menu_1 (){
ECHO. Пункт первый
GOTO :EOF
:; }
:menu_2 (){
ECHO. Пункт второй
GOTO :EOF
:; }
:;#start
SET /p opt=Введите цифру:
CALL :menu_%opt%Мой оппонент признал, что в споре я победил.
Юникод во «Флиппере»: глубока ли кроличья нора
Юникод во «Флиппере» неисчерпаем, как атом.
Сначала мне стало просто интересно почему для «Флиппера» нет ни одного русскоязычного приложения, потом я выяснил что мешает это сделать и научился обходить эти ограничения, потом написал русскоязычную игру, после чего залез в прошивку и начал добавлять в неё поддержку Юникода, погружаясь всё глубже и глубже.
К моим коммитам отнеслись довольно благосклонно, но в один момент дело застопорилось — авторы прошивки решили обновить сторонний фреймворк для вывода на экран, что могло повлиять на мои изменения, поэтому работу над ними я отложил. Но уже тогда у меня возникла мысль, что надо бы добавить Юникод и в библиотеку работы со строками, которую использует «Флиппер Зеро».
Пока я ждал обновления, занял себя векторизацией функции подсчёта длины строки в кодировке
И вот сегодня я получил новый комментарий к моему коммиту от авторов прошивки — мне порекомендовали заняться тем, о чём я
Размер доработок всё ширится, а результат всё дальше. Интересно, насколько глубока кроличья нора?
Управление «Алисой»
Хочу попробовать решить одну прикладную задачу, пока не буду описывать какую. Для этого надо научиться управлять яндексовской «Алисой» из локальной сети. В идеале — проговаривать произвольную фразу. Почитал интернет, посмотрел как это делается.
Официальный документации нет, но хорошие люди провели обратную разработку и задача выглядит как будто бы не очень сложно. Получаем токен, в сети через mDNS по имени _yandexio._tcp.local. ищем нужную колонку, находим, команды отдаём по протоколу WebSocket.
Так как я сейчас стараюсь больше времени проводить за книгой, чем за компьютером, всё, что успел — освоить модуль zeroconf для Гоу, чтобы по имени колонки получать её
Получившийся код выглядит так:
import "github.com/grandcat/zeroconf"
// …тут остальные импорты…
const Service = "_yandexio._tcp"
const Domain = "local."
func getAlisaAddr(instance string) (*zeroconf.ServiceEntry, error) {
resolver, err := zeroconf.NewResolver(nil)
if err != nil {
log.Fatalln("Failed to initialize resolver:", err)
}
entries := make(chan *zeroconf.ServiceEntry)
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
err = resolver.Lookup(ctx, instance, Service, Domain, entries)
if err != nil {
return nil, err
}
entry := <-entries
cancel()
<-ctx.Done()
if entry == nil {
return nil, fmt.Errorf(
"Failed to lookup for service instance '%s'",
instance
)
}
return entry, nil
}Теперь надо разобраться где взять токен и как выглядят команды.
Конец ускоренной strlen на «Флиппере»
В итоге, эпопея с ускорением функции замера длины строки в кодировке
У меня с самого начала этой затеи были сомнения. Я понимал, что для того, чтобы решить
Тем не менее, я с удовольствием повозился инструкциями SIMD. До этого
Моя функция, кстати, претерпела некоторые изменения:
size_t furi_string_utf8_length(FuriString* str) {
size_t len = string_size(str->string);
const char * cstr = string_get_cstr(str->string);
const char* end = cstr + len;
if(len >= sizeof(uint8x4_t)) {
const char* vend = end - sizeof(uint8x4_t);
int8x4_t zero = 0x00000000;
int8x4_t one = 0x01010101;
int8x4_t threshold = -1077952577; // -65, -65, -65, -65
do {
int8x4_t vec = *(int8x4_t*)cstr;
__ssub8(threshold, vec);
uint8x4_t result = __sel(one, zero);
len -= __usada8(result, zero, 0);
cstr += sizeof(uint8x4_t);
} while(cstr <= vend);
}
while(cstr < end) {
signed char c = *cstr++;
len -= c < -64;
}
return len;
}Оказалось, что тип FuriString хранит в себе бинарную длину строки, поэтому я переделал код, чтобы он не пытался читать чужую память. В векторизованой части это было вероятно, но своих рассуждений и дискуссии по коммиту я сделал вывод, что в случае «Флиппера» это не имеет
Тем не менее, если использовать бинарную длину, код получается более аккуратным и немного упрощается, так что грех не воспользоваться. Интересно бы, кстати, измерить наивный алгоритм без векторной части, но с использованием бинарной длины, наверное займусь этим в ближайшее время.
В итоге, решая эту, возможно несуществующую, проблему, я опробовал на практике векторные инструкции расширений NEON и DSP SIMD, научился делать два параллельных запроса на вливание в «Гитхабе» и ещё раз вспомнил как компилировать и заливать свою прошивку во «Флиппер Зеро».
Сделал PR с новой strlen во «Флиппер»
Спасибо Шурику Бабаеву за помощь, добавил свой коммит с векторизованной функцией для измерения длины строки в кодировке
Попутно выяснилось, что «Флиппер» использует функцию замера длины из библиотеки M*LIB. Можно было бы закоммитить новую версию в неё, но не стал этого делать по двум причинам.
Кстати, сделал итоговые замеры. Миллион итераций с четырьмя короткими строками (от ЯБ12341 до ЯБ12341234) моя версия делает за шесть секунд, тогда как прежняя — за 39.