Часто в программе к некоторым переменным класса удобно выдать прямой доступ (только на чтение или и на чтение, и на запись). Если в языке нет механизма для автоматической генерации такого доступа, то приходится писать 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 я записал в несколько строк. Однако, на практике бывает полезно, чтобы компилятор выдавал строку с ошибкой в изначальном файле (а не в сгенерированном), для этого полезно записать определение этих макросов в одну строку.
Добро пожаловать!
На текущий момент в блоге затронуты следующие темы: vim, free pascal, lisp, forth, m4
Занимаюсь разработкой своего языка под названием DEmbro, подбробней: wiki и svn
Для постов, не связанных с программированием, у меня есть отдельное жж.
апреля 10, 2011
m4: добавление префиксов и автогенерация setter/getter методов
апреля 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 — название локальной переменной, то для неё определяются следующие команды
- name@ — прочитать значение переменной, положить его на стек
- name! — записать в name значение со стека
Помимо этих двух примитивных операций, определяются следующие сахарные:
- name1+ — увеличить значение name на 1
- name1- — уменьшить значение name на 1
- name++ — положить значение name на стек и увеличить name на 1
- ++name — увеличить name на 1 и положить результат на стек
- name-- — положить значение name на стек и уменьшить name на 1
- --name — уменьшить name на 1 и положить результат на стек
- name+ — прибавить name к вершине стека
- name- — вычесть name из вершины стека
- name* — умножить вершину стека на name
- name+! — прибавить к переменной name значение с вершины стека
- name-! — вычесть из вершины стека name и положить результат в name
- 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;
Читать дальше......