988 заметок с тегом

программирование

Три новые старые статьи

Свой собственный сайт появился у меня в ноябре 2000 года, кажется по адресу bolk.chat.ru. Позже мы познакомились с Алексом Экслером, который предложил хостится у него по адресу bolk.exler.ru, а в 2008-м году, после какого-то дискового сбоя, я переехал на свой домен bolknote.ru, где мой сайт обитает до сих пор. Недавно я перепечатал сюда часть своих бумажных дневников, начиная с 1989 года, и внизу, в подвале сайта у меня теперь в качестве старта сайта значится этот год.

Естественно моё присутствие в интернете началось не в 1989 году, но и до двухтысячного года что-то в интернет я писал. В частности, в 1999-м году опубликовал три статьи на «Цитфоруме» — «Хабре» того времени. В те времена это было очень круто, родители показывали бумажные распечатки своим знакомым.

Недавно, перевёрстывая некоторые старые заметки начала 2000-х, я обратил внимание, что все ссылки тех годов ведут в никуда — проекты или умерли, или сменили домен, или адресацию. Это подало мне идею перетащить сюда свои цитфорумские статьи, пока этот сайт ещё жив.

Точные даты публикаций, к сожалению, неизвестны, но я восстановил примерные, ориентируясь на дату появления моих заметок на сайте «Веб-архива»:

Что нового в JavaScript версии 1.3 от Netscape
Найди картинку. Неизведанные возможности Web-дизайна
Блоки try… catch… finally… в JScript 5

 Нет комментариев    137   1 д   программирование

Cookie в PHP 7.4

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

Выяснилось, что плюс появился сам собой, люди его и не думали ставить. Так я понял, что столкнулся с каким-то багом.

Слева как куки ставились до ПХП 7.4, справа — как ставятся теперь, пробел кодировался как «+», «плюс» — как «%2B»

В чём примерно дело, понятно стало почти сразу — в вебе пробел иногда кодируется как «+», видимо ошибка как-то связана с этим. Сначала я неправильно подумал, что «плюс» появляется в урле, но данные пользователей туда не попадают, потом сообразил заглянуть в «куки», провёл пару экспериментов и всё встало на свои места.

Проблема воспроизводится у тех комментаторов, которые оставили свой первый комментарий до того, как мой сайт перешёл на ПХП 7.4. В этой версии сменился способ кодирования пробела в «куках» (см. на скриншоте) — был +, стало %20.

В принципе, можно вернуть «статус-кво» следующим кодом:

if (strpos($_SERVER['HTTP_COOKIE'], '+') !== false) {
    parse_str(strtr($_SERVER['HTTP_COOKIE'], ['; ' => '&']), $_COOKIE);
}

Но хорошо бы внедрить в движок блога код, который будет перекодировать такие куки у пользователей.

 1 комментарий    150   1 д   php   php7   программирование   эгея

FFI: баг не будет исправлен?

В пхпешном FFI есть баг — можно, вызывав сишную функцию случайно изменить значение совершенно других переменных

Очень не хочется это признавать, но похоже тот баг, который я обнаружил в реализации FFI (интерфейса к языку Си) в ПХП, исправлен не будет. Собственно, на это мне давно указал один из читателей в комментариях к прошлой заметке на эту тему, но мне всё не верилось.

В тикете, который я завёл по проблеме, один из разработчиков языка написал, что придерживается мысли, что это преднамеренное проектное решение. Не думаю, что это действительно так, наверняка получилось случайно, но как всё поправить, не снизив существенно производительность модуля FFI, — неясно.

Видимо, придётся жить с тем, что есть.

В прошлый раз, в качестве временной меры, я предлагал использовать str_repeat (или эквивалент) для того, чтобы разорвать запутанность двух переменных и таким образом предотвратить неожиданное их изменение. Это плохой приём, но я его задумывал в качестве временной меры.

Но раз это навсегда, давайте избегать сторонние эффекты правильно. Тот же читатель предлагает более корректный способ:

Первый параметр memcpy должен быть указателем на отдельно выделенную память достаточного размера. И в FFI есть метод для выделения памяти: FFI::new. Его и надо использовать вместо str_repeat.

Таким образом правильная работа с указателями должна выглядеть примерно вот так:

function memcpy(?string &$dst, string $src):void
{
    $len = strlen($src);
    // изготавливаем тип, в котором уместится копируемая строка
    $type = FFI::arrayType(FFI::type('char'), [$len]);
    // выделяем место под изготовленный тип
    $destination = FFI::new($type);

    FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
       ->memcpy($destination, $src, $len);
    // преобразуем область памяти в строку
    $dst = FFI::string($destination, $len);
}

$rock = "ROCK";
var_dump($rock); // «ROCK»
memcpy($rock, "SOCK");
var_dump($rock); // «SOCK»

Что тут происходит?

Мы хотим вызвать сишную функцию для копирования одной строки в другую. В месте, куда мы собираемся копировать, должно быть выделено достаточное количество памяти, мы её выделяем методом FFI::new, передавая ему на вход специально изготовленный тип — указатель на массив однобайтовых символов длиной c копируемую строку.

После копирования преобразуем область памяти в пхпешную строку. Память, выделенная при помощи FFI::new, освободится автоматически.

 3 комментария    158   2 дн   php   php7   программирование

UnknownPlatformer

В одном из чатов по ПХП кинули файлом демку платформера, который использует для отрисовки OpenGL через FFI. Файл называется UnknownPlatformer.zip, это название я и вынес в заголовок.

В ней пока мало что есть — лес с эффектом параллакса, персонаж на первом плане, который управляется стрелками на клавиатуре, но всё это уже очень интересно.

Демка сделана на авторском микродвижке «Bic» (Because I Can), выглядит хорошо и я надеюсь на продолжение, к счастью автор забрасывать (пока?) его не намерен — следующим шагом собирается добавить камеру, нормальный viewport и внедрить паттерн «Entity component system». Движок мультиплатформенный, работает под всеми тремя основными операционными системами — «Виндоуз», «МакОС», «Линукс».

Если тоже захотите полюбопытствовать, то просто скачайте архив, распакуйте его куда-нибудь, в этой же папке в консоли запустите следующие команды:

composer install
:; [[ $(uname) == "Darwin" ]] && brew install sdl2_image
php app.php
 Нет комментариев    282   19 дн   php   программирование

PHP 7.4 vs. PHP 8.0a

Резюме — в моём частном случае стало ощутимо быстрее, PHP 7.4 работал 63,9 секунд, 8.0a — 42,6.

Как многие знают, основной фишкой восьмой версии интерпретатора ПХП будет JIT. Обещают ускорение вычислительных задач, что немного не про веб, где в основном используется этот язык, но всё равно выглядит интересно.

В официальном репозитории альфа восьмой версии лежит уже давно, стало интересно сравнить её с «семёркой» — много ли даёт JIT приросту. Для подобных тестов я давно использую свой оптимизирующий транслятор «Брейнфака», с загруженной в него задачей вычисления числа Пи.

Восьмой ПХП собрался на моём «Макбуке» следующей последовательностью команд:

PATH=/usr/local/Cellar/bison/3.5.3/bin/:$PATH ./buildconf
./configure --prefix=/opt/php-bin/ --without-iconv --disable-xml \
--without-libxml --disable-simplexml --disable-xmlwriter --disable-dom --disable-xmlreader
make -j4
make install

Версию 7.4 ставил через «брю», сборочную конфигурацию оставил как есть, ничего не менял.

После небольшой коррекции php.ini в информации об интерпретаторе появились OPcache и JIT:

Скомпилированная версия PHP 8.0a со включенным JIT

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

Сравнение PHP 7.4 и 8.0a, со включённым и выключенным opcache (меньше — лучше)

Каждую версию интерпретатора я тестировал со включенным и выключенным OPcache десять раз, а результаты усреднил. Как видите, JIT существенно ускорил выполнение программы в моём случае (там огромное количество циклов и ветвлений) — 63,9 секунды против 42,6.

Что интересно — PHP 8.a без OPcache обогнал версию 7.4 со включенным opcache, это примечательный результат— значит сам «голый» интерпретатор тоже оптимизируют.

 Нет комментариев    319   19 дн   brainfuck   php7   php8   программирование

Комментарии в Си

Из документации к языку Си на сайте «Микрософта», секция про комментарии, описывается нестандартное расширение спецификации:

Comments beginning with two forward slashes (//) are terminated by the next newline character that is not preceded by an escape character. […] the newline character is preceded by a backslash (\), creating an «escape sequence». This escape sequence causes the compiler to treat the next line as part of the previous line. (For more information, see Escape Sequences.)

The default for Microsoft C is that the Microsoft extensions are enabled. Use /Za to disable these extensions.

То есть в примере ниже, если его скомпилировать в «Визуал Студио», выражение i++; на самом деле будет частью комментарий и не выполнится:

// мой комментарий \
    i++;

Счастливой отладки!

Добавлено позднее: в компиляторе gcc тоже работает, причём раскрашивается как комментарий в редакторах vim и «Саблайм».

 9 комментариев    268   25 дн   microsoft   программирование   си

Юзерпики в «Эгее»

Во второй раз занимаюсь разовой конвертацией блогов в формат «Эгеи», надо бы записать кое-какие вещи, а то забывается. Авось придётся в третий, опять придётся разбираться.

В основном обращаться приходится к трём таблицам, имеющим довольно говорящие названия: Notes — сами заметки, Comments — комментарии к ним (там же хранятся и авторы комментариев, единого профиля у них нет) и Aliases — адреса, по которым доступны страницы (в моей схеме в этой таблице нет поля Alias, куда и записывается сам адрес).

Основные поля основных таблиц «Эгеи» версии 2.9

Назначения полей, в основном, понятны из названий, кроме разве что поля Stamp — это временная метка создания чего либо в секундах (timestamp) и поля с префиксом GIP, об этом чуть ниже.

Если вам интересны метки (теги), то понадобятся ещё две таблицы: Keywords — где хранится русское название метки и её урл и NotesKeywords, таблица, связывающая метки и заметки.

Теперь про GIP. Если флаг IsGIPUsed в таблице Comments установлен в ноль, то это прежняя схема хранения автора комментарий — без юзерпика, чтобы юзерпик появился, надо выставить IsGIPUsed в единицу, в поле GIP записать сервис, через который была проведена аутентификация (facebook, vk или twitter), а в GIPAuthorID записать некий идентификатор пользователя во внешнем сервисе.

Юзерпик нужно положить в pictures/avatar/ с именем, составленным из полей GIP и GIPAuthorID, записанных через дефисоминус. Например: vk-1.jpg — это был бы юзерпик Дурова, создателя «Вконтакте», если бы он зачем-то зашёл хоть на одну «Эгею».

Тут есть одна интересная деталь. Дело в том, что у меня в блоге, когда он ещё работал на самописном движке, уже были юзерпики, были они и у Олега Петровича, блог которого я переносил сегодня ночью. Причём и у меня, и у Олега авторизация была своя собственная, не обязательно через те сервисы, которые поддерживает «Эгея». Как же мне удалось перенести юзерпики?

«Эгея» в этом месте имеет простой механизм расширений — в принципе в неё несложно добавить любой внешний механизм аутентификации, что я и сделал. Подобрал в папке system/theme/images/, где движок ищет картинки внешних сервисов, подходящую картинку (email.svg) и написал фейковый плагин для сервиса email, который положил в файл system/gips/email.php:

<?php

class E2GIPEmail extends E2GIP {
  protected $type = 'email';

  private function _get_instance() {
    return (object) [];
  }

  public function get_auth_url() {
    return '';
  }

  public static function get_profile_url($id, $link) {
    return false;
  }

  public function callback() {
    return true;
  }
}

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

 4 комментария    285   27 дн   php   программирование   эгея

AppleScript Tetris

Я очень люблю «ненормальное программирование» — где вместо получения результата (такого мне и на работе хватает), приходится преодолевать нетривиальные трудности, придумывать какие-то нестандартные ходы и решать нерешаемые задачи.

Недостатка в таких задачах у меня в голове не бывает, но руки приложить к ним получается нечасто. «Тетрис» на ЭплСкрипте — один из примеров такого «приложения рук», прошло три года, но он до сих пор мне так нравится, что я решил рассказать о нём ещё раз.

Для начала немного об ЭплСкрипте. Это скриптовый язык для автоматизации, который встроен в МакОС в незапамятные времена — в «Википедии» написано, что он появился аж в 1993 году, то есть широкоизвестный ПХП его младше аж на два года!

Несмотря на то, что язык уже несколько лет не обновляется, заброшенным его не назовёшь, скорее всего в нём просто нечего обновлять — все задачи, которые он призван решать, он решает, в МакОСи есть даже встроенный визуальный редактор и я знаю ребят, которые делают на нём потрясающие вещи в плане создания каких-либо удобств в операционной системе!

Естественно можно программировать и в обычном текстовом редакторе, программы в этом случае выглядят как текст на немного необычном английском языке:

tell application "Microsoft Word" to quit
display dialog "Hello, world!"

print pages 1 thru 5 of document 2

Сильно популярным его не назовёшь, но в своей нише он хорош и продвинутые «маководы» о нём знают. До «Тетриса» я писал на нём «песню о пиве» и интерпретатор Брейфака.

За всё историю существования на ЭплСкрипте никто никогда не писал графических интерактивных игр. Причина проста: в языке нет поддержки графики и опроса клавиатуры. 😭, расходимся!

Тем не менее идея создать на нём что-то подобное «Тетрису» мучала меня несколько лет, видимо интуиция жужжала где-то в фоне, что это возможно.

Первым придумался способ вывода графики — я подумал, что фигуры можно рисовать свёрнутыми окошками какой-нибудь программы, например обычного маковского текстового редактора. В моём воображении это выглядело примерно так:

Для этого, в припципе, всё есть — ЭплСкрипт умеет запускать программы и управлять размерами и расположением окон:

tell application "TextEdit"
    set the bounds of 1st window whose id is wid to {x, y, (x + |size|), (y + |size|)}
end

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

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

В первом же прототипе появилась ещё одна проблема — наверху у «МакОСи» есть строка меню, приложения не могут на неё залезать, при попытке вывести в эти координаты окно, оно «сползает» вниз. В итоге первые несколько точек выводились в других координатах и всё рушилось. Было два решения — центрировать ещё и по вертикали или как-то измерить размер меню (кодировать его высоту константой мне показалось плохим решением — вдруг изменится).

В итоге, способ придумался. Сразу после запуска я вывожу окно в нулевые координаты и смотрю на сколько пикселей оно съехало, это и будет высотой меню:

-- Get height of menu bar
tell application "TextEdit"
	activate
	make new document at the front
	set the bounds of 1st window to {0, 0, 0, 0}
	set coords to bounds of 1st window
	close 1st window
end

set minimalY to coords's item 2

С графикой разобрались, а что с клавишами? В ЭплСкрипте, как я уже говорил, нет способов опросить клавиатуру, существуют плагины и сторонние решения, но их использование рушит всю идею. Хотелось чистого решения.

Голову пришлось ломать очень долго, пока в один момент я не додумался заиспользовать под это опрос значения громкости — уменьшение и увеличение громкости как кнопки влево/вправо, а отключение динамика — для поворота фигуры.

Сейчас на моём «Макбуке» аппаратных кнопок громкости уже нет, но «Тетрис» слушается и нарисованных на тачбаре

Для этого на пуске я устанавливаю громкость на середину и в цикле опрашиваю текущее значение. После каждого изменения значение опять сбрасывается на среднее:

on levelReset()
    set volume output volume volumeMiddle without muted
end

on checkDirection()
    set level to output volume of (get volume settings)
    levelReset()
    _sign(level - volumeMiddle)
end

on checkRotate()
    return output muted of (get volume settings)
end

Тут _sign — моя собственная функция взятия знака, у ЭплСкрипта нет встроенной.

В получившийся вариант вполне можно играть — я пробовал. Немного странное управление через несколько минут уже перестаёшь замечать, единственное, чего не хватает — кнопки сброса фигуры, но тут уж ничего не попишешь.

Если вдруг кому-то станет интересно запустить игру, то убедитесь, что всё, что у вас есть во встроенном текстовом редакторе (TextEdit) сохранено, — приложение создаст целую кучу мелких дополнительных окон, которые не будут очищены после остановки программы, их придётся закрыть вручную, в этом случае легко потерять что-нибудь нужное.

Полный исходный код есть на гитхабе.

 1 комментарий    415   1 мес   applescript   программирование

99 бутылок: V

71. V — один из языков, за развитием которых я пристально наблюдаю. Искренне желаю ему развития и процветания — под влиянием «Раста», «Гоу» и чего-то вроде Пе́рла, у авторов получается довольно интересный язык, который пока, правда, находится в стадии альфы.

Сил авторам не занимать — работает (за редким исключением) почти весь заявленный синтаксис, есть компиляция, трансляция в Си и ДжаваСкрипт, а так же обратная трансляция из Си в Ви (на гитхабе есть оттранслированный в Ви «Дум»).

Сильнее всего язык, кажется, похож на Гоу — есть даже синтаксически такой же оператор go, позволяющий запускать что-нибудь в отдельном потоке (позднее обещают сделать корутины), но и уши «Раста» торчат даже в моей небольшой программе: совпадение с образцом, условные операторы-конструкции, константные переменные (если не указан mut).

Есть обобщённое программирование (которого так не хватает в Гоу), интерполяция переменных в строках (привычная ребятам, программирующим на ПХП, Перле и шеллах) и ограниченная перегрузка операторов (как в Си++).

Насколько я понял из сайта и блога, язык нацелен на безопасное программирование, скорость, как выполнения (проигрывает Си всего на три процента), так и выполнения и простоту кода. Повторюсь, выглядит очень интересно.

// 99.v by Evgeny Stepanischev Feb 2020

fn bottles(beer byte) string {
    return match beer {
        0 {
            'no bottles'
        }
        1 {
            '1 bottle'
        }
        else {
            '$beer bottles'
        }
    }
}

fn main() {
    mut b := bottles(99)

    for i := 98; i >= 0;  {
        println('$b of beer on the wall, $b of beer.')
        b = bottles(i--)
        println('Take one down and pass it around, $b of beer on the wall.\n')
    }

    println('No more bottles of beer on the wall, no more bottles of beer.')
    println('Go to the store and buy some more, 99 bottles of beer on the wall.')
}
 1 комментарий    339   1 мес   99   программирование

Забавный баг в FFI (PHP 7.4.2)

В последнее время очень заинтересовался FFI в ПХП — интерфейсом к языку Си, появившимся в версии 7.4. Очень полезное, как по мне, нововведение, позволяющее расширять язык, обходясь минимальными знаниями о других языках программирования.

Очевидно, потребность в чём-нибудь таком давно назрела, появлялся даже специальный язык для написания расширений — «Зефир», очень похожий на ПХП, принципиальных отличий нет, порог входа — низкий. При этом он транслируется в Си, благодаря чему достигается почти нативная производительность.

К несчастью, модуль FFI в ПХП только-только появился и содержит по меньшей мере один очень неприятный баг, который может привести к труднопонимаемым ошибкам в программах.

Небольшой код для иллюстрации проблемы:

const IRRELEVANT = "Hello ";

echo IRRELEVANT;  // Выведет «Hello»

FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
    ->memcpy("Hello ", "world!", 6);

echo IRRELEVANT; // Выведет «world!»

В чём суть? Мы объявляем константу IRRELEVANT, убеждаемся, что она содержит присвоенное ей значение, потом вызываем через FFI копирование одной строки в другую, и константа внезапно меняет своё значение. Как это произошло? Чтобы понять, немного модифицируем программу.

function memcpy(string $dst, string $src):void
{
    FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
         ->memcpy($dst, $src, strlen($src));
}

$rock1 = str_repeat('ROCK', 1);
$rock2 = str_repeat('ROCK', 1);

var_dump($rock1, $rock2); // Выведет «ROCK» два раза

memcpy($rock1, "SOCK");
memcpy($rock2, "LOCK");

var_dump($rock1); // выведет «SOCK»
var_dump($rock2); // выведет «LOCK»

Что у нас тут? Вызов memcpy обёрнут в функцию, которая ничего не возвращает и ничего не должна модифицировать — параметры передаются по значению, а не ссылке. Тем не менее, если вызвать объявленную функцию и передать в неё переменные, их значение будет изменено.

В ПХП для обеспечения производительности используется механизм копирования при записи — если куда-то переменную нужно передать по значению, то интерпретатор для скорости передаёт её по ссылке, а если требуется модификации переданного значения, копирует его. Модификацию отслеживает сам интерпретатор, но в случае FFI этого, по всей видимости, не происходит.

В силу этой особенности, переменные остаются ссылочными, попадая в таком виде в сишный код, где модифицируются — вызов memcpy меняет первую переданную в него переменную, копируя в неё значение второго параметра.

Кстати, если изменить строку инициализации переменных на вот такую:

$rock1 = $rock2 = str_repeat('ROCK', 1);

то после вызовов memcpy обе переменные получат значение «LOCK» — работает тот же самый механизм.

Почему же в первом листинге изменилось значение константы? Сейчас разберёмся, осталось совсем немного.

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

Таким образом константа IRRELEVANT и строка «Hello » в первом параметре — суть одна и та же область памяти. Когда функция memcpy модифицирует эту область, меняются сразу все значения всего, что, для оптимальности, ссылается на это же место. Естественно, константа изменяется тоже.

Поскольку интернирование не работает во время выполнения, результат str_repeat('ROCK', 1); не интернируется и во втором листинге такого эффекта не создаётся.

Как же разорвать эту мистическую связь и заставить работать код так как задумывалось? Для этого нужно, чтобы интерпретатор создал новую область памяти, которую будет портить FFI, не затрагивая нашу переменную. Это можно сделать, например, при помощи уже упомянутой функции str_repeat:

function memcpy(string $dst, string $src):void
{
    // Создаём новое место, которое испортит memcpy
    $dst = str_repeat($dst, 1);
    FFI::cdef('char *memcpy(char *dst, const char *src, size_t len);')
        ->memcpy($dst, $src, strlen($src));
}

$rock = 'ROCK';
var_dump($rock); // Выведет «ROCK»

memcpy($rock, "SOCK");
var_dump($rock); // Так же выведет «ROCK»

Внимание! Это некорректный способ, корректный описан в более поздней заметке.

Естественно, подойдут и substr, и sprintf, и вообще любые функции, в общем случае возвращающие модифицированную строку. Интересно, что implode([$dst]) от бага не защищает, видимо для этого случая внутри работает какая-то оптимизация, возвращающая значение $dst по ссылке до первой модификации.

Всё описанное верно для версии актуальной на текущий момент — 7.4.2. Надеюсь, в скором времени этот довольно забавный баг устранят и необходимость в таких ухищрениях исчезнет.

 5 комментариев    384   1 мес   php   php7   программирование
Ранее Ctrl + ↓