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

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

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


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

апреля 06, 2011

Оператор switch

Добавил оператор switch. До сих пор, правда, был лишь один случай, когда он был нужен (обработка windows-сообщений).

Трудно писать документацию на команды (вообще и на switch в частности). Не лениво, а непонятно как. Я без проблем могу объяснять как это использовать на практике, но что это и как это устроено, описать в несколько понятных абзацев не получается. А это в документации нужно описать.


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

Локальные переменные

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

Простые локальные переменные (типы int, uint, bool, ptr) объявляются так
(внутри команды):
| l1 l2 ... ln|
Они сразу снимают со стека начальные значения, к примеру после
1 2 3 | i j k|
в i окажется значение 1, в j окажется значение 2, в k окажется значение 3.

Чтение/запись переменных стилизовано под классический фортовский стиль. Если
name — название локальной переменной, то для неё определяются следующие команды


  1. name@ — прочитать значение переменной, положить его на стек
  2. name! — записать в name значение со стека


Помимо этих двух примитивных операций, определяются следующие сахарные:

  1. name1+ — увеличить значение name на 1
  2. name1- — уменьшить значение name на 1
  3. name++ — положить значение name на стек и увеличить name на 1
  4. ++name — увеличить name на 1 и положить результат на стек
  5. name-- — положить значение name на стек и уменьшить name на 1
  6. --name — уменьшить name на 1 и положить результат на стек
  7. name+ — прибавить name к вершине стека
  8. name- — вычесть name из вершины стека
  9. name* — умножить вершину стека на name
  10. name+! — прибавить к переменной name значение с вершины стека
  11. name-! — вычесть из вершины стека name и положить результат в name
  12. name*! — умножить name на значение с вершины стека


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

Несколько примеров использования локальных переменных:
// Функция Result = (a + b) * (b + c) * (c + a)
: func | a b c| a@ b+ b@ c+ c@ a+ * * ;
// Команда, печатающая числа от 100 до 1
: test 101 | i| begin --i while i@ . repeat ;


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

апреля 02, 2011

Контекстные параметры

Часто в программе существуют объекты-менеджеры. Например, гуи-менеджер — объект,
при помощи которого осуществляется управление кнопками, окошками, полями ввода
и т.д. Чтобы создать кнопку, нужно указать в каком именно гуи-менеджере создаём.
Чтобы установить кнопке стиль, нужно этот стиль получить из гуи-менеджера.
  Button := GuiManager.CreateButton;
  Button.SetStyle(GuiManager.GetStyle('glamur'));

При этом в 99% программ название переменной (или свойства), содержащей ссылку на
менеджера, будет называться именно GuiManager.

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

Самое тупое решение — создать глобальную переменную GuiManager и пусть к ней
обращаются функции CreateButton, GetStyle и прочие. А что если в программе
нужно работать с несколькими менеджерами? Нужно просто в нужные моменты
менять значение глобальной переменной. Что делать, если нужно работать с
разными менеджерами из разных потоков? Переписать GuiManager с глобальной
переменной под зависящую от потока.

Хоть этот метод и решает поставленную задачу, он мне не нравится. Вместо него
напрашивается концепция «контекстных параметров». Её мне не удалось реализовать
на паскале, но я надеюсь, что получится реализовать в Lisp'е и DEmbro.

Идея вот в чём: у функции мы выделяем некоторый набор параметров и объявляем их
контекстными. Например:
  function CreateButton(context GuiManager: TGuiManager): TGuiButton;
Здесь мы указали, что параметр GuiManager контекстный. От этого мы не теряем
возможностей, функцию можно вызвать обычным образом:
  Button := CreateButton(GuiManager);
при этом выполнится она так же, как и обычная функция.

Отличие заключается в возможности опустить передачу параметра:
  Button := CreateButton();
При этом компилятор автоматически подставит GuiManager внутрь, обратившись
к идентификатору в текущей области видимости. Т.е.

  procedure DoSomething(GuiManager: TGuiManager);
    procedure DoSomethingDeep;
    var
      GuiManager: TGuiManager;
    begin
      Button := CreateButton(); // в CreateButton передастся локальная переменная
                                // функции DoSomethingDeep
    end;
  begin
    Button := CreateButton(); // в CreateButton передастся параметр
                              // функции DoSomething
  end;


Может даже так получится, что GuiManager — это функция, сама содержащая
контекстный параметр:
  var
    App: TApp;

  function GuiManager(context App: TApp): TGuiManager;
  begin
    Result := App.GuiManager;
  end;

  ....

  Button := CreateButton(); // эквивалентно CreateButton(GuiManager(App))



Теперь несколько слов о возможном синтаксисе в паскаль-семействе. Контекстные
параметры поведением очень похожи на параметры со значением по умолчанию. Чтобы
подчеркнуть это, возможно, что имеет смысл заменить синтаксис, использованный выше, на
такой:
  function CreateButton(GuiManager: TGuiManager = context): TGuiButton;


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

марта 30, 2011

SetPixelFormat 3221684230

Пытаюсь написать на DEmbro OpenGL-приложение.

Функция SetPixelFormat возвращает ошибку, GetLastError сообщет, что это ошибка C0070006 (или 3221684230 в decimal), а после чтения форумов узнал, что эта ошибка возникает в системе с nVidia видеокартой, если handle окна не принадлежит процессу (или процесс не имеет должных прав).

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

Самое обидное, что эта ошибка возникает именно в OpenGL и именно с nVidia-видеокартой. Т.е. если бы я использовал DirectX или ATI-видеокарту, то такой ошибки не возникло бы.

[UPDATE] Ура, справился с этим. Всегда так получается, что пока я наедине с собой ищу ошибку несколько часов, ничего не выходит. Как только опишу её кому-то — сразу осеняет и проблема решается. Пора спать.


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

марта 20, 2011

DEmbro: документация, плавающая точка, и конфигурируемость

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

Пока что с самой документацией бардка, не успел ещё всё оформить.

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

Осуществил свою главную задумку: теперь при создании DEmbro-машины доступно около
четырёх команд. Две из них служат для того, чтобы подключить остальные встроенные
команды. Это позволяет программисту самому решить какое подмножество команд DEmbro
выбрать для программирования. По умолчанию подключается файл «system.de», который
подключает весь функционал ядра.

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


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

марта 01, 2011

Простейшие примеры работы со строками

Всё перечисленное уже работает, за исключением unicode->cp1251.


" expe" " rience" str+ // сложили две строки, получили experience
2 4 str-cut // оставили четыре символа, начиная со второго, получили peri (нумерация с нуля)
bdup str+ // сложили строку с самой собой, получили periperi
" rip" str^ // определяем символ, начиная с которого идёт первое вхождение rip в строке, получаем число 2
str# // узнали длину строки rip, т.е. 3
// Итого, на обычном стеке: 2 3
// На стеке строк: " periperi" " rip"
bdrop // скидывем строку rip со стека
str-del // удаляем три символа, начиная со второго, получаем peeri
utf8->unicode unicode->cp1251 // перекодировываем строку из utf8 в cp1251
" peeri" str= // проверяем равны ли строки, в данном случае получаем true
." Hello world!"cr // печатаем на консоль строку


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

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

Обо мне

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