Добро пожаловать!

Я Дож, программирование — моё хобби. По мере того, как я осваиваю что-то новое, стараюсь об этом написать пост.
На текущий момент в блоге затронуты следующие темы: vim, free pascal, lisp, forth, m4
Занимаюсь разработкой своего языка под названием DEmbro, подбробней: wiki и svn
Для постов, не связанных с программированием, у меня есть отдельное жж.

июня 15, 2011

m4: временные макросы

Часто при использовании m4 может возникнуть необходимость использования локальных определений. Например, в случае языка разметки часто нужно оформлять какие-то списки. Списки бывают разных типов — по оформлению: нумерованный, с кружочками; по применению: оглавление, список литературы. И хочется иметь возможность быстро сменять один тип на другой, т.е. было бы удобно записывать как-то так:

LIST(
  ITEM(Ананас)
  ITEM(Банан)
  ITEM(Кокос))
REFERENCES(
  ITEM(Энциклопедический словарь Брокгауза и Ефрона: В 86 томах (82 т. и 4 доп.). — СПб.: 1890—1907.)
  ITEM(Перевод главы о бананах из книги Julia F. Morton «Fruits of Warm Climates» 1987)
  ITEM(Кокосовая пальма — статья из Большой советской энциклопедии))


Здесь в обоих конструкциях используется макрос ITEM, но в разных смыслах: в первом случае он будет раскрыт в нумерованный элемент, во втором — в элемент списка литературы.

Однако, подобный синтаксис реализовать в m4 невозможно. В любых конструкциях сперва вычисляются аргументы (т.е. ITEM'ы), и только после этого вызываются макросы, в которые эти аргументы передаются, т.е. в LIST и REFERENCES. Поэтому LIST и REFERENCES не могут никак повлиять на то, как будут вычисляться ITEM'ы. Как можно было бы обойти это ограничение? Самый простой способ — отказаться от локальных объявлений, а сделать глобальные, но с разными именами:
LIST(
  ITEM(Ананас)
  ITEM(Банан)
  ITEM(Кокос))
REFERENCES(
  RITEM(Энциклопедический словарь Брокгауза и Ефрона: В 86 томах (82 т. и 4 доп.). — СПб.: 1890—1907.)
  RITEM(Перевод главы о бананах из книги Julia F. Morton «Fruits of Warm Climates» 1987)
  RITEM(Кокосовая пальма — статья из Большой советской энциклопедии))


Этот метод не очень красив, не очень удобен, и может привести к трудностям, если требуется делать вложенные списки. Вместо этого способа мне больше нравится разделение LIST и REFERENCES на открывающий и закрывающий макросы:

LIST
  ITEM(Ананас)
  ITEM(Банан)
  ITEM(Кокос)
_LIST
REFERENCES
  ITEM(Энциклопедический словарь Брокгауза и Ефрона: В 86 томах (82 т. и 4 доп.). — СПб.: 1890—1907.)
  ITEM(Перевод главы о бананах из книги Julia F. Morton «Fruits of Warm Climates» 1987)
  ITEM(Кокосовая пальма — статья из Большой советской энциклопедии)
_REFERENCES


Теперь уже можно внутри REFERENCES ввести своё собственное объявление ITEM'а, а внутри _REFERENCES восстановить старое.

Создание временных макросов в m4 имеет несколько тонкостей. Во-первых, вместо обычного define следует использовать пару pushdef и popdef. Первый макрос определяет новый макрос, сохраняя все предыдущие, popdef удаляет последнее определение, восстанавливая предыдущие.

Во-вторых связано с использованием локальных переменных: если записать
define(`LIST', `<OL>pushdef(`ITEM', `<LI>$*</LI>')')
то вместо $* будет подставлен параметр макроса LIST, а не макроса ITEM. Более того, даже если заключить параметр в сколь угодно большое кол-во символов цитирования, всё равно оно будет считаться параметром макроса LIST. Я придумал решение, быть может не очень красивое, но рабочее: нужно сгенерировать текст «$*» вычислением, самый простой способ — конкатенация:
define(`CONCAT', `$1$2')
define(`_DS', `CONCAT($,*)') 
define(`LIST', `<OL>pushdef(`ITEM', `<LI>_DS</LI>')')
define(`_LIST', `popdef(`ITEM')</OL>')

(DS — аббревиатура для Dollar Star).

Можно аналогичным образом определить макросы _D1, _D2, ..., _D9 для, соответственно, $1, $2, ..., $9.

Ещё есть красивое решение, которое заключается в написании макросов _S, _1, _2, ..., _9, которые заменяются на, соответственно, *, 1, 2, ..., 9. Тогда «локальные параметры» подмакросов можно будет записывать как $_S, $_1, $_2, ..., $_9.

Составные конструкции можно ещё усовершенствовать: вместо пар LIST/_LIST, REFERENCES/_REFERENCES можно задавать всего по одному макросу LIST и REFERENCES, внутри которых будет определяться макрос END, определённый аналогичным ITEM'у способом. Этот способ реализован в моём языке разметок, в файлах util.m4 и html.m4 (см. списки и таблицы). В файле examples.m4 можно посмотреть примеры использования всего этого, а в файле examples.html то, что из этого сгенерировано: как видно, все списки и таблицы благополучно раскрываются в то, что нужно.


Читать дальше......

июня 14, 2011

Починил каты

Напомню, что когда-то я настроил в блоге каты, а когда сменил дизайн, они сломались: даже если пост не содержит ката, то на странице со списком всё равно писалось «Читать дальше......».

Сегодня, спустя более года после поломки, я решил всё починить. Оказалось, что после смены дизайна блоггер удалил всё, что было добавлено в теге head, и после добавления каты снова работают корректно.


Читать дальше......

Менеджер памяти

Известно, что если передавать паскалевские строки в/из длл, и ни в чём себя не ограничивать, то прога может упасть. Поэтому при взаимодействии с длл большинство использует строки в стиле Си.

Наткнулся на забавную статью, предлагающую решение проблемы в случае, если и прога, и длл пишутся вместе. Автор утверждает, что если и у длл, и у программмы установить один менеджер памяти, то проблем не будет. Это стоит ещё проверить, но звучит правдоподобно.


Читать дальше......

DEmbro: классы для исходного кода

Выносил из центрального объекта обработку исходного кода. Перемудрил с архитектурой. С первого раза написать хороший код не получилось, вышла большая каша, желание переделывать которую не было. Писать другие части DEmbro не представлялось возможным без этого, поэтому почти полтора месяца (!) я был деморализован и боялся всё переписывать. Недавно, наконец, нашёл в себе силы и осуществил задуманное.

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

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

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

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

У этого класса есть два вспомогательных — TSpaceSkipper и TNameReader. Первый, как нетрудно догадаться, нужен для того, чтобы пропускать пробелы. Пока что пробелами считаются символы в диапазоне от 0 до 32, но при необходимости можно будет унаследовать своё поведение. Кроме того, в TSpaceSkipper можно будет добавить функциональность по подсчёту числа пробелов, и этим воспользоваться в DEmbro (например, в языке программирования питон для выделение блоков вместо ограничителей begin...end или {...} используются отступы — что достаточно удобно и наглядно. При записывании уровня игры символами в виде псевдографики проще пользоваться пробелами, чем какими-то другими символами). TNameReader, в свою очередь, читает имена. В имя могут входить только символы не из диапазона от 0 до 32, но можно будет унаследовать класс, который, например, будет воспринимать слова с пробелами, если слово заключено в одинарные кавычки.

Наконец, есть дополнительный класс — TLineReader. Он выделяет строки, считает номер текущей строки, и позицию в строке. Если это нужно, конечно, — класс можно будет отключить для ускорения работы.

Вроде как получилось расширяемо и ортогонально, но немного муторно.


Читать дальше......

июня 02, 2011

m4: Универсальный язык разметок

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

Я весьма часто пишу тексты, и для разметки приходится использовать довольно разный
синтаксис: то html, то LaTex, то google wiki, то bbCode (upd ещё vkontakte разметку бывает
приходится использовать). Даже html бывает разного вида (например, некоторые блоги
сами вставляют тег br в конце каждой строки).

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

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

Рассмотрим это на примере HTML и LaTeX. Я создал два файла: html.m4 и tex.m4.
Когда требуется из файла изготовить текст на HTML, то следует вызывать
m4 html.m4 file.m4 > file.html
Если же требуется получить LaTeX, то нужно вызывать
m4 tex.m4 file.m4 > file.tex

Этот вызов сначала выполнит html.m4 или tex.m4, потом file.m4, сольёт результат вместе и
запишет в file.out. При этом в файлах html.m4 и tex.m4 находится исключительно служебная
информация, и мы не хотим, чтобы из них что-то печаталось. Поэтому в начало
каждого такого файла добавляется строка
divert(-1)
которая весь дальнейший вывод выбрасывает, а в конце файла пишем
divert
после чего вывод будет происходить нормально.

Приступим, наконец, к написанию макросов. Самое простое — перевод строки.
html.m4:
define(`BR', `<BR>')
tex.m4:
define(`BR',`\\')

Я решил, что все макросы будут писаться большими буквами, чтобы было по-меньше
конфликтов имён.

Перейдём к более сложному макросу: жирный шрифт. Использование такого макроса
будет выглядеть примерно так: BOLD(жирный шрифт). Для реализации нужно
в теле макроса написать $* — вместо него будет подставлено всё, что находится
между скобочек:
html.m4:
define(`BOLD', `<strong>$*</strong>')
tex.m4:
define(`BOLD', `\textbf{$*}')

Далее, запись списков. Я выбрал такой синтаксис:
LIST(
  ITEM(Ананас)
  ITEM(Манго)
  ITEM(Кокос)
)


Оно создаст соответствующий нумерованный список. Реализация:
html.m4:
define(`LIST',`<OL>$*</OL>')
define(`ITEM',`<LI>$*</LI>')

tex.m4:
define('LIST', `\begin{enumerate}$*\end{enumerate}')
define('ITEM', `\item $*')


Перейдём к более сложному примеру: ссылки. Ссылки уже требуют двух параметров:
куда ссылается и что отображается. Для получения отдельных параметров вместо $*
следует использовать $1, $2, и т.д. Поэтому реализацию ссылки можно бы было
записать так:
define(`LINK', `<a href="$1">$2</a>')

Но эта конструкция имеет проблему: если написать
LINK(google.com, Используй гугл перед тем, как задать вопрос, пожалуйста)
то m4 будет думать, что тут четыре параметра, и второй — «Используй гугл перед тем»,
а не вся фраза целиком. Для решения проблемы следует использовать макрос «shift»:
он возвращает все свои параметры, кроме первого.
html.m4
define(`LINK', `<a href="$1">shift($*)</a>')
tex.m4
define(`LINK', `\href{shift($*)}{$1}')

Теперь указанный выше пример сработает корректно.

Далее — дело техники, уже перечисленного достаточно для реализации многого.
В случае, если при наборе какого-то текста критично то, что макросы для разметки слишком длинные, то легко можно вставить где-то в тексте строки вида:
define(`B',`BOLD')
и использовать краткие варианты макросов, ничего при этом не потеряв.

Вот мои файлы: html.m4 и tex.m4. Пока что они не полные и скорее полигон для экспериментов, чем что-то, что я использую в боевых условиях, но со временем эти файлы будут пополняться и совершенствоваться.


Читать дальше......

Постоянные читатели

Обо мне

Моя фотография
Мой e-mail: vitek_03(at)mail(dot)ru