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

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

декабря 30, 2011

ConnectBot

В догонку к моему недавнему посту про настройку линукса под андроидом. В программе ConnectBot, которая нужна для работы через консоль, можно назначить Esc на клавишу поиск, что очень полезно, т.к. на док. станции к моему планшету нет клавиши Esc. Это даже удобнее, чем если бы клавиша Esc была: она в виме используется весьма часто, и на обычной клавиатуре находится далеко от основных клавиш. А вот клавиша поиск находится слева от пробела. Теперь я даже при работе за обычным компьютером или ноутом применяю комбинацию Ctrl+C, потому что не хочу тянуться к Esc.


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

декабря 15, 2011

DEmbro в unix+ARM окружении!

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

 Портировал по принципу "что не пашет, то комментирую". Основные проблемы при портировании были из-за разделителя директорий, который отличается в windows и unix, из-за разных символов переносов строк. Из-за того, что ассемблерные вставки для i386 не работают в ARM, и из-за использования windows-специфичных функций серьёзных проблем не возникло. Может быть потому, что я был к этому готов.

Надо будет как-то получше организовать сборку с учётом платформ и зависимостей, а то сейчас всё в кашу превратилось. Может быть написать систему конфигурации на DEmbro? :) А то как-то лениво изучать существующие.


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

декабря 11, 2011

Устанавливаем на планшет Asus Eee Transformer tf101 в дополнение линукс

Недавно я стал счастливым обладателем планшета ASUS Eee Transformer tf101 16gb с док-станцией в комплекте и Android в качестве операционной системы. Мне, как программисту, хочется использовать девайс не только для чтения книг и интернета, но и для программирования. Поэтому я решил установить в дополнение к андроиду линукс.

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

На нижней стороне устройства находится его серийный номер. Если он начинается на B60 или меньше, то в инете можно найти кучу способов для получения root-доступа. Мне же досталась серия B80, для нее единственный известный способ -- использование программы Razorclaw. Программу нужно скачать, установить, запустить, и нажать на единственную в ней кнопку, после чего рут-доступ получен. Возможно, что перед этим потребуется обновить андроид до последней версии (это можно сделать в настройках).

Ок, после того, как рут-доступ получен, можно приступить к установке линукса. Для этого качаем с маркета программу Linux Installer, документацию для нее смотрите здесь. Авторы предупреждают, что неправильное использование программы может привести к порче устройства (если система будет достаточно серьезно повреждена, то восстановить ее не получится, и устройство можно будет выбрасывать).

Итак, запускаем Linux Installer, открываем в меню Setup. Нужно поменять только один параметр: путь к loop-файлу, я его установил в "/data/Linux.loop". На значение по умолчанию выдавался варнинг о том, что путь вроде как находится на съемном диске (несмотря на то, что это не так). По вкусу я еще поменял hostname и domainname на более короткие и приятные.

Возвращаемся из Setup, далее действуем по инструкции, которая выводится вверху экрана рядом с пингвином. А именно: жмем create target loop, format target loop, далее идем в Setup, ставим галку около "Allow write to /sys", возвращаемся, жмем mount loop, жмем install distribution in loop. Начались закачивание и установка линукса, нужно подождать какое-то время. Как только это закончилось, жмем Udpate launcher script, теперь для безопасности жмем Stop env loop, заходим в Setup, снимаем галку у "Allow write to /sys", возвращаемся и жмем mount loop. Ок, линукс готов к использованию.

Теперь нужно установить программу-консоль. У меня клавиатура внешняя (на док-станции) и сильно отличается от того, что обычно бывает на смартфонах, поэтому стандартный ConnectBot не подходит. Зато есть специальный ConnectBot для именно моего планшета. Устанавливаем, запускаем. В левом нижнем углу выбираем тип соединения local, вводим какое-нибудь название для сессии (doj, например), жмем Enter. Сейчас мы находимся в псевдолинукс системе самого андроида, в нем мы мало что можем делать. Набираем linuxchroot, должен запуститься установленный ранее линукс с bash в качестве оболочки.

Устанавливем базовый комплект для программирования, для этого набираем:
apt-get install vim
apt-get install make
apt-get install gcc
apt-get install g++

Ок, теперь у нас есть vim (версии 7.2, т.е. со всеми основными вкусностями вима) и компилятор g++. Можем приступить непосредственно к программированию:
mkdir /root/hello
cd /root/hello
vim hello.cpp

Отмечу на счет вима вот что: на клавиатуре нет клавиши Esc, вместо нее нужно использовать комбинацию Ctrl+C. Набираем минимальную прогу
#include <stdio.h>

int main(int argc, char* argv[]) {
  printf("Hello from Android!\n");
  return 0;
}

Выходим, выполняем
g++ hello.cpp -o hello
./hello

Видим, что прога работает.


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

ноября 23, 2011

Меняем шрифты в vim'е

Недавно по рекомендации Darthman'а решил сменить используемый шрифт. Всегда раньше использовал FixedSys, но попробовать другие стало интересно. В посте я попытался описать как настраивать шрифты в виме.

Если vim запущен в консольном режиме, то отображаемый шрифт полностью зависит от программы-оболочки, реализующей консоль. Поэтому всё, что написано ниже, имеет отношение к gVim'у.

Опция gVim'а, настраивающая шрифт, называется guifont. Если в качестве параметра ей передать звёздочку, то появится диалоговое окошко (проверено под Windows, в других ОС не гарантируется)
:set guifont=*


Этот способ запуска полезен для перебора разных вариантов, но неудобен для настроек (не поместить в vimrc файл, например). Вместо звёздочки можно передать название шрифта с дополнительными опциями. Вот что написано в хелпе о формате дополнительных опций под windows:
        For the Win32 GUI                                       *E244* *E245*
        - takes these options in the font name:
                hXX - height is XX (points, can be floating-point)
                wXX - width is XX (points, can be floating-point)
                b   - bold
                i   - italic
                u   - underline
                s   - strikeout
                cXX - character set XX.  Valid charsets are: ANSI, ARABIC,
                      BALTIC, CHINESEBIG5, DEFAULT, EASTEUROPE, GB2312, GREEK,
                      HANGEUL, HEBREW, JOHAB, MAC, OEM, RUSSIAN, SHIFTJIS,
                      SYMBOL, THAI, TURKISH, VIETNAMESE ANSI and BALTIC.
                      Normally you would use "cDEFAULT".

          Use a ':' to separate the options.
        - A '_' can be used in the place of a space, so you don't need to use
          backslashes to escape the spaces.
        - Examples: >
            :set guifont=courier_new:h12:w5:b:cRUSSIAN
            :set guifont=Andale_Mono:h7.5:w4.5


Вот несколько моих примеров со скриншотами:
set guifont=consolas:h11:cRUSSIAN


set guifont=DejaVu_Sans_Mono:h11:cRUSSIAN


Некий обзор шрифтов для программирования можно почитать в этой статье.

Если не передавать опцию cRUSSIAN, то могут начаться проблемы с отображением русских букв в разных ситуациях. Если же всё же нет возможности передать такую опцию, то ИМХО самым простым способом решения проблемы является изменение кодировки ввода и языка сообщений:
set guifont=consolas:h11
set encoding=utf-8
lan mes en


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

ноября 03, 2011

Vim'у сегодня исполняется ровно 20 лет!

А мы с ним одногодки, оказывается :)


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

октября 25, 2011

Язык разметок на DEmbro

Я пытаюсь написать свой универсальный язык разметок. Использование M4 сопровождается множеством неудоств, в частности, долгой отладкой. Из-за этого прогресс с языком разметок немного встал.

Единый язык разметок удобен тем, что можно писать один текст, и из него генерировать много текстов в различных форматах (html, tex, wiki, simple). В частности, такая возможность мне очень необходима при написании файлов помощи DEmbro — как минимум планируется документация на вики и в REPL режиме.

Недавно я придумал, как этот язык разметок можно организовать на DEmbro. В качестве основы я взял свои идеи по созданию аналога функции printf в DEmbro, и... реализовал всё самое главное в течении нескольких минут (единственная большая функция занимает 8 строк: она тут). Осталось только написать нужный набор команд, но это не составит труда (пример того, что уже начато для googlecode wiki тут).

Опишу идею и её реализацию. Размеченный текст представляет из себя просто обычный текст, в котором иногда встречаются специальные команды, влияющие на отображение этого текста. Почему бы мне не написать команду на DEmbro, которая читает размеченный текст, обычную составляющую просто выводит, а специальные фрагменты выделяет и выполняет их так, как будто это код на DEmbro? Сразу придумался синтаксис: фигурные скобки выделяют в тексте специальные фрагменты. Получается следующее
variable *doing?
: \page *doing? off ;
: {page}
    *doing? on
    begin *doing? @ while
      " {" source-cut str.
      " }" source-cut evaluate
    repeat ;





Команда {page} выделяет в цикле фрагменты исходника, зажатые между фигурными скобками, и выполняет их. Команда \page обрывает этот цикл. Как этим пользоваться: допустим, мы хотим добавить в язык разметок команду b для генерации жирного шрифта в формате HTML. Тогда пишем следующее:
: b " <b>" str. ;
: \b " </b>" str. ;


(Разница слешей в последней команде нелепа, но в DEmbro я больше привык закрывающие команды писать именно с таким наклоном, потому что другой «занят» под деление.)

Теперь можно смело использовать:
{page}
  Это просто текст.
  {b}А это уже жирный текст{\b}
{\page}

(Это программа на DEmbro, которая выведет текст, преобразованный в html-форматированный.)

Если есть желание использовать вместо фигурных скобок другие, легко написать другую page-команду, например:
: [page]
    *doing? on
    begin *doing? @ while
      " [" source-cut str.
      " ]" source-cut evaluate
    repeat ;


При этом каким-либо образом переопределять команды \page, b, \b и другие не нужно: можно смело писать
[page]
  Это просто текст.
  [b]А это уже жирный текст[\b]
[\page]


В зависимости от удобства можно дописать вариант page с любыми ограничителями (не обязательно даже односимвольными).

Между фигурными/квадратными скобками не обязательно писать лишь одну команду, можно сразу несколько. Например,
: i " <i>" str. ;
: \i " </i>" str. ;
: u " <u>" str. ;
: \u " </u>" str. ;
[page]
  Это просто текст.
  [b i u]А это уже жирный курсивный подчёркнутый текст[\u \i \b]
  [b i][u] И это тоже, несмотря на непарность конструкций[\u \i \b]
[\page]


Более того, т.к. в управляющем коде допустимо писать любой DEmbro-код, можно прямо в нём объявлять команды, переменные, вычислять время и т.д.
{page}
  {: _ "  " ; ( Тут мы объявили команду, печатающую пробел)
   : int int->str DOC-GENERATOR << ^ ; ( Тут мы объявили команду int, которая печатает число)}
  Значение таймера на момент генерации страницы:  {timer int}
  Числа от {8 dup int} до {0 dup int}: {
      :noname  swap begin 2dup <= while dup int _ 1- repeat ; execute
  }
{\page}


Последний пример (объявление noname-функции) наталкивает меня на идею компилируемых размеченных текстов. Т.е. сейчас это всё работает только в режиме интерпретации, но, возможно, было бы удобно создавать команды, в которые вкомпилированы генераторы размеченных страниц, зависящие от переданных параметров и окружения.

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

Пара слов по поводу кода, который находится сейчас среди модулей DEmbro (ссылки были выше). В нём используется та же идея, что описана тут, только слова разложены по пространствам имён, и сделана возможность сменить команду вывода на свою. В будущем конвертирование из моего формата в необходимый будет происходить как-то так:
HTML " sample.dep" evaluate-file ^
TEX " sample.dep" evaluate-file ^
GOOGLECODE " sample.dep" evaluate-file ^
SIMPLE " sample.dep" evaluate-file ^


Текущий пример для googlecode можно посмотреть тут
sample.dep -> test.wiki -> так это выглядит в итоге


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

октября 16, 2011

Вставка кода с подсветкой в блог при помощи вима

Уже не первый год я вставляю сюда фрагменты кода. Для того, чтобы сгенерировать код с подсветкой, я использую команду :TOhtml вима, которая генерирует html-код с подсветкой текущего файла или выделенного фрагмента. Код html появляется в отдельном окне, содержит теги head и body, содержит теги br, которые здесь в блоггере не нужны, содержит кучу лишних переносов строк.

Поэтому создание кода с подсветкой занимало у меня некоторое время: сгенерировать, выполнить команду для удаления тегов br, удалить ненужные переносы строк, выделить фрагмент html-кода без тегов head, body, скопировать, закрыть окно, и, наконец, вставить html в нужное место.

Сегодня мне это окончательно надоело, и я автоматизировал этот процесс, сведя всё к одной кнопке.




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

Пространства имён в DEmbro

Недавно в DEmbro появилась конструкция, которую я называл «короткое пространство имён», и я решил написать о пространствах имён в DEmbro и словарях вообще.


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

октября 13, 2011

Условные переходы

Сессия сдана, пришло время продолжить разработки.

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

Вот пример цикла:


:asm-noname PREFIX
  *auto-integers? on
    mov eax 555
    mov ebx 111
  @> LOOP
    sub eax 2
    sub ebx 1
    jnz @@ LOOP
    mov d[ *out disp ] eax
    ret
  all-resolve
\PREFIX asm-noname; execute


Технические детали: при помощи :asm-noname ... asm-noname; execute код на асме транслируется и сразу запускается. Блок PREFIX ... \PREFIX указывает на то, что мы будем использовать префиксную запись инструкций (т.е. инструкции перед операндами). Вызов *auto-integers? on делает так, что целые числа автоматически считаются операндами команд. (Без этой опции после чисел 555, 111, 2, 1 нужно бы было просто вызывать команду imm. Это полезный режим, если мы хотим операнды вычислять, например, в этом режиме можно писать  mov eax 64 1024 * imm, если не хочется в уме перемножать 64 и 1024. Кроме того, код может зависеть от чего-нибудь внешнего: например, тут mov eax timer imm заносится состояние таймера в момент трансляции, операнд в явном виде не указан.)

Теперь по коду. Сперва в соответствующие регистры кладётся 555 и 111. Далее, создаётся метка с именем LOOP. После этого в цикле из eax вычитается 2, из ebx вычитается 1 до тех пор, пока ebx не обнулится. То, что останется от eax, переносится в *out — глобальную переменную DEmbro, которую нужно определить выше, например, так:

variable *out


Но так делать вовсе необязательно, вместо *out может быть любой указатель. Как нетрудно догадаться, в *out будет занесено 333, далее с переменной можно работать из кода на DEmbro, или из следующей ассемблерной вставки. Таким образом ассемблер может обмениваться данными с DEmbro. Планируется более детальный механизм взаимодействия: из ассемблера можно будет получать доступ к стекам и другим структурам DEmbro-машины.

Команда all-resolve разрешает все метки. Дело в том, что на метку могут быть ссылки выше метки, ниже метки, или одновременно и так, и так. Нет возможности сразу это предугадать, поэтому метки и ссылки собираются в две таблицы, а в конце кода командой all-resolve ссылкам ставятся в соответствие метки и нужные адреса проставляются. Из-за этой муторности условные переходы и были написаны самыми последними среди прочих инструкций. Теперь, я надеюсь, мне осталось только перебить таблицы из документации к ia-32 в свой транслятор.

Сейчас транслятор поддерживает уже 84 инструкции. Подробнее о том, как я делал этот транслятор и о том, как сделать свой транслятор, напоминаю, планируется большой цикл статей.


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

сентября 20, 2011

Полнота по Тьюрингу — насколько это хорошо?

Часто любят говорить, что такой-то язык программирования полон по Тьюрингу. Насколько хорошо полнота по Тьюрингу характеризует язык с точки зрения практического применения?


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

сентября 12, 2011

Новости с разработок DEmbro

Сейчас у меня много дел, и на DEmbro остаётся мало времени.

Недавно начал писать заголовок к windows sockets, вроде как самое необходимое объявлено. Написал при помощи него для примера простенькие клиент и сервер. Кроме того, написал hello world http-ответом.

Но основное направление деятельности сейчас — написание транслятора ассемблера. Планируется полная поддержка ia32 ассемблера (он же x86). Сейчас уже реализована 51 инструкция. До юзабельности не хватает системы меток и инструкций переходов, а до полного счастья не хватает всяких там sse :)

В целом, транслятор мне нравится. Вот пример кода на нём:

mov eax h100 imm
mov ecx d[ *in2 disp ]
xor eax d[ *in4 disp ]
imul ecx eax
lea eax d[ ecx 2* eax 256 disp ]
and d[ *out6 disp ] eax


Конкретные примеры использования асма на DEmbro можно посмотреть здесь.

О трансляторе подробно будет целый цикл статей, если руки дойдут.


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

августа 17, 2011

Таблицы различия диалектов ЯПов

Наткнулся на отличный сайт о различиях схожих языков программирования
http://hyperpolyglot.org/

В частности, там есть очень полезная таблица сравнения лиспов.


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

августа 16, 2011

Скрытие раздела «Добро пожаловать»

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

<b:widget id='Text1' locked='false' title='Добро пожаловать!' type='Text'>
<b:includable id='main'>
<b:if cond='data:blog.pageType != &quot;item&quot;'>
    ....
</b:if>
</b:includable>
</b:widget>



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

DEmbro уже год

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

Поначалу у меня не было никакого опыта программирования на фортах, а было теоретическое, чисто математическое понимание того, чем они хороши и удобны. Спустя несколько месяцев DEmbro был развит до того, что я начал писать какие-то библиотеки и программы, и время от времени у меня возникал вопрос «Как на таком можно писать??». Но со временем привык, и теоретическое понимание перешло в практическое.

Многое в DEmbro было заложено в самом начале разработки, пока я ещё не до конца разобрался с устройством форта, и потому некоторые фундаментальные вещи написаны неправильно и требуют рефакторинга. Рефакторинг висит на мне уже несколько месяцев, мешая дальнейшей разработке.

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

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

Вот график количества изменений DEmbro по месяцам:



Первый провал графика связан с зимней сессией, которая была с декабря 2010ого по явнварь 2011ого. Второй провал связан с летней сессией, начавшейся в мае (пятый месяц). Провал графика между ними, скорее всего, связан с жизненными проблемами, когда мне было не до DEmbro. Сейчас на свне уже более 550 коммитов.

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


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

августа 10, 2011

Добавление команд в ядро DEmbro

Упростил процедуру добавления команд в ядро DEmbro.


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

Официальные каты

А официальные каты таки существуют!


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

июля 30, 2011

Ещё одна игра на DEmbro

Написал ещё одну игру, опять на конкурс IGDC. Темой конкурса была змейка.



Писал в последний день конкурса семь с половиной часов подряд, с перерывом на ужин.

В основном, переделывал предыдущую игру (Maze Journey). Изначально у меня была
идея ради эксперимента не трогать основные модули, а только написать специальные
уровни для Maze Journey, в которых реализуется змейка. Но передумал, как минимум
управление хотелось сделать по-нормальному.

Что забавно, в получившейся змейке можно подгрузить уровень из Maze Journey,
и он будет благополучно работать. (Чем я и воспользовался при изготовлении
«титров» — делал в последний момент в спешке, просто переделав соответствующие
титры из Maze Journey. Плохо, конечно, но ИМХО лучше абы какие титры, чем
никаких.)

Забыл проверить размер перед отправкой, а зря — оказалось, что релиз весит 1,99 МБ.
Большой размер связан с тем, что я отправил дебаг версию DEmbro, весящую более
полутора мегабайта, в то время как релиз версия весит всего 76 КБ.


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

июля 18, 2011

Бажные каты

Предыдущий пост на главной странице выглядит некорректно — каты сработали неправильно, рррррр. Тег table включает отображение текста, даже если сам тег вложен внутрь скрытого span'а. Странное поведение. Пришлось отключить кат.

Когда же наконец появится что-нибудь официальное для катов вместо этой глючной поделки?

[updated] Нашёл официальные каты.


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

Пара слов о том, как делался Maze Journey

Недавно участвовал в конкурсе IGDC, писал игру на DEmbro. Это был достаточно интересный опыт использования на практике своего языка.

Сразу оговорюсь, что в DEmbro полно недоделок, он глючный, и я плохо на нём программирую :)

Средства разработки

DEmbro и текстовый редактор Vim. Выглядит это как-то так:


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

Коротко о языке

Я использовал (за исключением нескольких незначительных мест) только три типа: целое число, указатель и строка. Хотя типов как таковых в DEmbro нет. Все операции производятся на стеке, для чисел и указателей один стек, для строк — отдельный. Круглые скобки используются для многострочных комментариев, а для однострочных — общепринятое «//».

Несколько примеров:


1 // положить на стек число 1
2 // положить на стек число 2
3 4 5 // положить на стек числа 3, 4 и 5. Теперь стек выглядит так: 1 2 3 4 5
+ // сложить два верхних числа. Теперь стек выглядит так: 1 2 3 9
* // умножить два верхних числа. Теперь стек выглядит так: 1 2 27
swap // поменять два верхних числа местами, 1 27 2
div // нацело поделить два верхних числа, 1 13
max // из двух верхних элементов оставить максимальный, 13
1+ // увеличить верхнее число на 1, получится 14
drop // скинуть верхний элемент со стека, теперь стек вернулся в начальное состояние


Можно писать свои команды при помощи конструкции

: name ..... ;


где name — имя команды.

(Именем может быть любая последовательность символов без пробелов. Никаких ограничений на имя нет (можно переопределять стандартные слова).)

Например, в программе всё измеряется в миллесекундах. Но иногда приходится задавать время в минутах. Чтобы задать пять минут, нужно 5 умножить на 60*1000, и получится соответствующее число миллисекунд. Чтобы делать это вычисление автоматически, можно написать команду

: mins 60000 * ;


Теперь можно просто записать

5 mins


и получить время в миллесекундах для пяти минут.

Команды можно выполнять косвенно. При помощи символа апостроф, за которым идёт название команды, можно положить указатель на эту команду. А при помощи команды execute выполнить указатель с вершины стека. Т.е.

5 ' mins execute


эквивалентно коду «5 mins».

Можно создавать анонимные команды при помощи команды «:noname»:

:noname ." Hello world" cr ;


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

:noname ." Hello world" cr ; execute



Пара примеров на работу с указателями и переменными:

variable x // создаёт область памяти под целое число или указатель
// теперь вызов команды x кладёт на стек указатель на эту область памяти
5 x ! // записать число 5 в переменную x
x @ // положить в стек значение переменной x
. // распечатать на консоль полученное число
x off // записать в переменную x ноль


Ещё есть условный оператор и циклы.

: printbool if ." TRUE" else ." FALSE" then cr ; // создаём команду printbool
// она снимает со стека верхний элемент, и если он ноль, то печатает FALSE
// иначе TRUE
false printbool // напечатает FALSE
true printbool // напечатает TRUE
100 10 > printbool // напечатает TRUE
100 10 < printbool // напечатает FALSE
100 0<> printbool // напечатает TRUE

// печатает числа от 0 до 10
:noname 0 begin dup 10 <= while dup . 1+ repeat drop ; execute


Условные операторы и циклы являются компилирующими командами, и потому их можно использовать только внутри описания команд.

Сказанного должно быть достаточно для понимания того, что будет ниже. Чтобы не затягивать вступление до бесконечности, дам напоследок несколько ссылок
  1. Пишущееся вики
  2. Файл с реализацией условного оператора и простых циклов
  3. Файл с реализацией оператора switch
  4. Файл с реализацией работы с пространствами имён
Практически никакого контроля нет ни на стадии компиляции, ни на стадии интерпретации. Что лежит на стеке или в памяти узнать нельзя. Можно написать на свой страх и риск почти любую фигню с непредсказуемым исходом.

Кратко об игре
Лучше один раз увидеть, чем сто раз услышать, так что отсылаю читателя самому сыграть в игру (в конкурсном архиве запускать надо Doj\mazejourney\release\run.bat). Я написал на DEmbro создание окна и инициализацию OpenGL достаточно давно. Т.к. числа с плавающей точкой в DEmbro немного сыроваты (в частности, нет нормального способа передать double в виде параметра в dll функцию), я решил для упрощения жизни их не использовать вообще. Кроме того, я не писал ни поддержки текстур, ни каких-то хитрых примитивов. Единственное, что я использовал при рендере — цветные квадраты. Игру я писал с девизом «каждый уровень имеет свой уникальный геймплей». Поэтому нужно было иметь удобные конструкции для описания уровней.

Как описывается уровень
Чтобы не томить любителей ковыряться самостоятельно, рекомендую посмотреть файл «game\levels\big.de» — он является очень простым примером того, как описывается уровень. Кстати, по-поводу этого уровня, в нём можно усмотреть букву O — это чит, позволяющий пройти в определённом месте сквозь стену и оказаться у выхода. Итак, первым делом я написал команду lab, которая позволяет в наглядном текстовом виде описывать уровни. Например, так:

lab
##########
#S       #
#        #
#        #
#        #
#        #
#       F#
##########
\lab

(Из-за бага в ядре дембро, после слова «lab» нужно ставить пробел.) Команда lab использует функцию ядра «source-next-line», которая читает следующую строку исходника. По первой строке определяется ширина уровня, а дальше создаёт двумерный массив, содержащий ascii коды символов. После выполнения этой конструкции можно при помощи команды last-lab-x получить ширину лабиринта, при помощи last-lab-y высоту лабиринта, а при помощи last-lab-maze указатель на двумерный массив. Далее я написал команду «level», которая создаёт уровень. Напрямую она практически не используется, но в файле «game\levels\list.de» определяется надстройка над ней — newlevel, которая внутри себя вызывает level, выполняет файл, имя которого следует после «newlevel», и связывает уровни в последовательность. В том же файле list.de можно обнаружить сам список уровней. Как только мы создали уровень (командой level) можно приступать к его наполнению. Команда «maze!» установит на текущем уровне последний лабиринт, созданный командой «lab». При помощи команд «cell-x!» и «cell-y!» можно установить размер в пикселях одной ячейки лабирнта текущего уровня, например так: 16 cell-x! 16 cell-y! При помощи команд «offset-x!» и «offset-y!» задаётся положение лабиринта на экране. Т.к. всегда мне нужно было центрировать лабирит, я написал команду «centrize», которая автоматически вызывает «offset-x!» и «offset-y!», вычисляя смещение при помощи размера лабиринта и размера ячейки. При помощи команд «player-x!» и «player-y!» можно установить координаты ячейки, в которой будет появляться игрок в начале уровня. Перейдём к более нетривиальным элементам уровня — событиям. Можно описать команды, которые будут выполняться при старте уровня — при помощи этого механизма запускается звук гонга перед началом уровня:

:noname pchar" data\sounds\newlevel.wav" sound ;init

Тут просто вызывается команда sound, которая принимает один параметр — указатель на pchar-строку, содержащую имя звукового файла. Менее тривиальный примеры — события, когда игрок входит на какую-то ячейку. Можно установить на каждый тип (т.е. ascii-символ) ячейки свой обработчик. Обработчику на стеке передаются x,y координаты ячейки, на которую пытается войти игрок. Если их скинуть со стека, то игрок не сможет зайти в ячейку, т.е. можно описать обработчик для стены:

:noname drop drop ;enter_ #

Есть команда passed, которая, наоборот, считывает x,y и смещает в них игрока. Можно написать

:noname passed ;enter_ O

Тогда игрок сможет заходить в букву O. Чтобы буква O выглядела как обычная стена, для неё нужно определить такой же обработчик рисования (который тоже принимает координаты x,y), как и для стены:

: draw_# COLOR 2dup PURPLES GEN[]2 ^ set-color draw-cell-rect ;
:noname  draw_# ;draw_ #
:noname  draw_# ;draw_ O

Есть ещё один тип обработчиков для каждой ячейки — обработчик инициализации. Обработчики инициализации нужно установить перед вызовом команды «lab». Тогда при считывании каждой ячейки внутри конструкции «lab .... \lab» будет вызван соответствующий заданному ascii-символу обработчик инициализации. Пример использования: всегда неудобно задавать положение игрока координатами при помощи «player-x!» и «player-y!». Гораздо удобнее пометить нужную ячейку буквой S. Для этого перед выполнением «lab» достаточно добавить обработчик инициализации ячейки S:

:noname  player-y! player-x! ;oninit_ S

При входе на букву F по умолчанию задан переход к следующему уровню командой «next». При желании аналогичной функциональностью можно наделить другие буквы, и сделать переходы на другие уровни. Таковы общие принципы создания уровней. Теперь я разберу особенности разработки некоторых уровней. Часто, из-за копипасты в описнии уровней можно увидеть много ненужного кода, — я не успел его удалить до дедлайна.  

Уровень в темноте (dark.de)
На этом уровне игрок видит только соседние с собой клетки. Нужно просто при рисовании стены посчитать расстояние игрока до стены, и если оно больше одного, то не рисовать стену:

:
  2dup player-y @ - abs swap player-x @ - abs max 1 > if drop drop exit then
  COLOR 2dup PURPLES GEN[]2 ^ set-color draw-cell-rect
;draw_ #


Уровень-цикл (loop.de)
Тоже уровень в темноте, в котором если ходить по часовой стрелке, то будешь бегать по кругу, а если пойти против часовой стрелки, то прийдёшь к выходу. Реализация простая: описываются при помощи команды «lab» три лабиринта — тот, который изначально, тот, который с циклом, и тот, который с выходом. Изначальный нужен, чтобы закрутить игрока идти по часовой стрелки, его мы устанавливаем в уровне командой «maze!». Остальные два сохраняем в переменные. Вот, например, цикличный лабиринт:

lab
     #######
     #1    #
###### ### #
#      # # #
# ###### # #
# #      # #
# #      # #
# ######## #
#     2    #
############
\lab
  last-lab-x value 1maze-x
  last-lab-y value 1maze-y
  last-lab-maze value 1maze-ptr

На карте есть ячейки 1 и 2. При наступании на 1 включается лабиринт с циклом, при наступании на 2 — лабиринт с выходом. Вот их обработчики:

:noname passed 1maze-x maze-x ! 1maze-y maze-y ! 1maze-ptr maze-ptr ! ;enter_ 1
:noname passed 2maze-x maze-x ! 2maze-y maze-y ! 2maze-ptr maze-ptr ! ;enter_ 2

Проверка видимости сделана так же, как и в dark.de, только расстояние берётся не 1, а 3 ячейки.  

Уровень с убегающим выходом (runningexit.de)
Алгоритм убегания выхода простой. Если игрок приблизился на близкое расстояние по обеим координатам, то выход смещается. Выбирается координата, по которой расстояние до игрока больше, после чего выход смещается от игрока по этому направлению. Если это не удаётся (стена), пытаемся сместить выход в перпендикулярном направлении. Поэтому, например, если игрок идёт на выход по узкому коридору, то выход будет убегать по этому коридору от игрока, и свернёт в бок, как только упрётся в стену. Там используется событие after-update для всех этих расчётов и рендера выхода (рендер сделан отдельный, потому что выход при смещении с ячейки на ячейку должен смещаться плавно). Когда выход смещается, в лабиринте перезаписывается его расположение, поэтому событие попадания в выход обрабатывается корректно обычным образом.

Уровень с двигающимися стенами (movingwalls.de)
Идея аналогична уровню-циклу. Я создал два лабиринта: главный прямоугольник с выходом, и лабиринт со штуковиной. И сохранил его в переменные (moving-x, moving-y, moving-ptr). Далее, я переопределил события рендера и входа для пробела:

: draw#brown COLOR 2dup BROWNS GEN[]2 ^ set-color draw-cell-rect ;
:noname  2dup moving-x * + player-y @ + cells moving-ptr + @
         35 = if draw#brown else 2drop then ;draw_
:noname  2dup moving-x * + player-y @ + cells moving-ptr + @
         35 = if 2drop else passed then ;enter_  

(Там в конце, каждого определения должно быть по два пробела.) Число 35 — это ascii код для символа решётки (который я получил при помощи команды «ga» в виме). Тут всё просто: чтобы определить нужно ли рисовать на пустом месте что-то, берётся значение из вспомогательного лабиринта со штуковиной, с прибавлением y-координаты игрока.  

Заключение
 В целом, разрабатывать игру мне очень понравилось.

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


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

июня 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. Пока что они не полные и скорее полигон для экспериментов, чем что-то, что я использую в боевых условиях, но со временем эти файлы будут пополняться и совершенствоваться.


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

мая 26, 2011

Реализация полиморфизма

Наткнулся на статью C++ всегда быстрее Smalltalk?, в которой в частности утверждается:

«<…> в C++ виртуальные вызовы происходят косвенным образом через таблицу диспетчеризации виртуальных методов. А, как известно, косвенный вызов является очень дорогостоящим на нынешнем поколении процессоров.

В динамических языках, в которых полиморфизм не привязан к иерархии классов/интерфейсов, реализация полиморфного вызова осуществляется совершенно другим образом, намного более эффективным.»

Мне вот теперь интересно, что это за более эффективные реализации, ибо в голову совершенно не приходит никаких идей.


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

мая 19, 2011

Метапаскаль: dquotes

Т.к. программировать на паскале приходится часто, чтобы автоматизировать многие вещи я решил по полной задействовать m4.

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

Использование m4 с паскалем имеет серьёзную проблему: ограничитель ' (апостроф) используется в m4 для ограничения цитат, и любая строковая константа паскаля сбивает его с толку.

Их можно поменять, но подобрать другие удобные символы для m4 мне не удалось (нужно два различных ограничителя слева и справа, по умолчанию используются ` и '). Кроме того, стандартный (и немаленький) комплект библиотек для m4 написан с использованием ` и '.

Поэтому я решил, что нужно в коде на паскале использовать для строковых констант " вместо ', а перед самой компиляцией конвертировать в исходниках двойную кавычку на одинарную. Собственно, это и делает написанная мною небольшая утилита dquotes.

Она содержит множество параметров на многие случаи жизнь. Например, такой вызов
dquotes -r .\ D*.pasq *.pas
рекурсивно обходит все файлы, начиная с текущей директории, и файлы с названием, начинающимся на D и расширением .pasq сконвертирует и сохранит в файл с таким же названием, но расширением .pas

Можно организовать и обратное преобразование:
dquotes -qq -aq -r .\ D*.pas *.pasq

Из того, что ещё предстоит сделать: обработка стандартных ввода и вывода (для организации цепочки вычислений наподобии «m4 file.pas4 | dquotes -i > file.pas»).

В качестве бонуса, утилитка позволяет задать преобразование любого символа в любой другой, задав их хекс-коды. Поэтому попутно можно решить ещё одну проблему: символ $ (доллар) используется в m4 для получения параметров, и конфликтует с $ из паскаля. Поэтому я решил, что хекс-константы в метапаскале будут записываться начиная с &, а при помощи dquotes конвертироваться в стандартные, начинающиеся с $.


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

мая 05, 2011

DEmbro: разнесение команд по файлам

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

Начинаю разбивать монолитную DEmbro-машину на классы.


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

мая 03, 2011

DEmbro: начало глобального рефакторинга

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

Весь код ядра практически находится в одном файле, содержащем 6600+ строк. После раскрытия макросов при помощи m4 получается 12400+ строк чистого паскаля, некоторые из которых напоминают спрессованный мусор:

     procedure uint64_divmod (Machine: TForthMachine; Command: PForthCommand); begin with Machine^ do begin TUInt64((Pointer(TUInt(Machine.WP) + (0))^)) := TUInt64((Pointer(TUInt(Machine.WP) + (-2*SizeOf(TUInt64)))^)) mod TUInt64((Pointer(TUInt(Machine.WP) + (-SizeOf(TUInt64)))^));
                                                     TUInt64((Pointer(TUInt(Machine.WP) + (-2*SizeOf(TUInt64)))^)) := TUInt64((Pointer(TUInt(Machine.WP) + (-2*SizeOf(TUInt64)))^)) div TUInt64((Pointer(TUInt(Machine.WP) + (-SizeOf(TUInt64)))^));

                                                     TUInt64((Pointer(TUInt(Machine.WP) + (-  SizeOf(TUInt64)))^)) := TUInt64((Pointer(TUInt(Machine.WP) + (0))^));
                                                end; end;
     procedure uint64_shl (Machine: TForthMachine; Command: PForthCommand); begin with Machine^ do begin Dec(WP, SizeOf(TUInt64));
                    TUInt64((Pointer(TUInt(Machine.WP) + (-SizeOf(TUInt64)))^)) := TUInt64((Pointer(TUInt(Machine.WP) + (-SizeOf(TUInt64)))^)) shl TUInt64((Pointer(TUInt(Machine.WP) + (0))^))  end; end;


Я уже достаточно многое понял, и потому назрел глобальный рефакторинг. Первое, что я сделал, -- вынес из этого файла некоторые реализации команд DEmbro, раскидав их по тематическим файлам (20 получилось пока что). В результате число строк в файле удалось сократить с 6600+ до 3900+. После раскрытия макросов размер не сильно изменился, потому что я выносил команды, слабо использующие макросы.

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


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

апреля 10, 2011

m4: добавление префиксов и автогенерация setter/getter методов

Часто в программе к некоторым переменным класса удобно выдать прямой доступ (только на чтение или и на чтение, и на запись). Если в языке нет механизма для автоматической генерации такого доступа, то приходится писать getter и setter методы, которые обеспечивают доступ к переменной.
public class Student {
    private String name ;
    public String getName() {
      return name;
    }

    protected int age = 18;
    public int getAge() {
      return age;
    }

    protected CatOrDogName favoriteCatOrDog ;
    public CatOrDogName getFavoriteCatOrDog() {
      return favoriteCatOrDog;
    }
    public void setFavoriteCatOrDog(CatOrDogName newFavoriteCatOrDog) {
      favoriteCatOrDog = newFavoriteCatOrDog;
    }

}


Писать для всех переменных такие getter/setter методы может быть весьма утомительно. Предлагаю решение для автоматизации этого процесса при помощи макропроцессора m4.

Первое, что нужно сделать, — написать макрос для добавления префиксов к идентификатору. Поля и методы в данном случае именуются слитными фразами, первое слово пишется маленькими буквами, у каждого следующего заглавная только первая буква. Чтобы к такому идентификатору добавить префикс, нужно поднять первую букву и сложить две строки.

Объявим макрос для сложения двух его параметров:
define(`CONCAT', `$1$2')

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

define(`UPCASE', `translit($1, `a-z', `A-Z')')


Теперь напишем макрос, который поднимает только первый символ входящего параметра. Для этого задействуем ещё три макроса: len(s) возвращает длину переданной строки, substr(s, first, count) извлекает из строки фрагмент длиной count, начинающийся с символа номер first (нумерация символов с нуля), и, наконец, eval вычисляет арифметическое выражение.
define(`FIRSTUP',   `CONCAT(UPCASE(substr($1,0,1)),
                            substr($1,1,eval(len($1) - 1)))')


Например, если теперь написать
FIRSTUP(veryLongName)
, то оно будет заменено на
VeryLongName

Осталось совсем немного для макроса, добавляющего префикс:
define(`ADDPREFIX', `CONCAT($1,FIRSTUP($2))')

Теперь перейдём к основной задаче. Я решил ввести следующие макросы:
PRIVATE(type, name, after) — поле приватное, для него определяется публичный геттер
PROTECTED(type, name, after) — доступ к полю наследуется, для него определяется публичный геттер
PUBLIC(type, name, after) — доступ к полю наследуется (но не публичен), для него определяются публичные геттер и сеттер.

Третий параметр after всех этих макросов нужен для написания явной инициализации полей. Реализация этих макросов технически проста:

define(`PRIVATE',
`  private $1 $2 $3;
    public $1 ADDPREFIX(`get', $2)() {
      return $2;
    }
')
define(`PROTECTED',
`  protected $1 $2 $3;
    public $1 ADDPREFIX(`get', $2)() {
      return $2;
    }
')
define(`PUBLIC',
`  protected $1 $2 $3;
    public $1 ADDPREFIX(`get', $2)() {
      return $2;
    }
    public void ADDPREFIX(`set', $2)($1 ADDPREFIX(`new', $2)) {
      $2 = ADDPREFIX(`new', $2);
    }
')


Самый первый пример в этом посте теперь можно сгенерировать следующим кодом:

public class Student {
  PRIVATE(String, name)
  PROTECTED(int, age, = 18)
  PUBLIC(CatOrDogName, favoriteCatOrDog)
}


Для красоты макросы PRIVATE, PROTECTED и PUBLIC я записал в несколько строк. Однако, на практике бывает полезно, чтобы компилятор выдавал строку с ошибкой в изначальном файле (а не в сгенерированном), для этого полезно записать определение этих макросов в одну строку.


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

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

Архив блога

Обо мне

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