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

Я Дож, программирование — моё хобби. По мере того, как я осваиваю что-то новое, стараюсь об этом написать пост.
На текущий момент в блоге затронуты следующие темы: 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;


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

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

Обо мне

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