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

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

мая 31, 2010

dpp: лямбда-функции

Занялся написанием лямбда-функций.

Когда нужно написать очень небольшую функцию, в одну-две строчки, и вызвать ее только в одном месте, то не хочется прописывать метод у класса, потом реализовывать код в implementation-части, и только после этого его вызывать. Хотелось бы эту жалкую строку с кодом прописать сразу в месте вызова.

Именно эту проблему и решают мои лямбда-функции:
FButton.OnClick := #l(Button: TButton) FWindow.Close;

Конструкция #l создает метод у класса, внутри которого она вызывается. Так записывается вызов процедуры и функции соответственно:
ObjectUpdate := #l(Obj: TMyObject) Obj.Update;
ObjectFilter := #l(Obj: TMyObject): Boolean = Obj is TSuperObject;

Внутри можно писать и несколько операторов, разделенных символом ;

Есть так же возможность создать лямбду на классовый метод или даже просто функцию, делается это заменой l на lsimple.

lsimple уже реализовано и работает.

При реализации сразу же я столкнулся с множеством проблем.

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

Во-вторых, есть более серьезная проблема при использовании локальных переменных. Например, сейчас такой код не заработает:
for I := 0 to 3 do
    FButtons[I].OnClick := #l(Button: TButton) FWindows[I].Show;
хотя очень хотелось бы.

Даже если отбросить тот факт, что это сложно реализовать технически (нужно создавать промежуточный класс, который запомнит значение переменной I в момент вызова, и у этого класса могут начаться трудности в получении доступа к приватному полю FWindows, да и вообще к FWindows нужно будет каким-то макаром приписать что-то типа FOwner.FWindows), даже отбросив - совершенно непонятно сколько этот класс должен жить в памяти. Узнать когда кнопка перестанет ссылаться на метод этого класса невозможно.

Кроме того, хочется в дальнейшем делать встроенные лямбы, а не только в конце строк:
RegistHandler($10, $32, #l(Packet: TPacket) DoSomething#);
А в этом плане символ # очень неудачен, трудно определить кто из них открывающий, а кто закрывающий, и при вложенностях получится путаница.

Поэтому, наверно, будет введена конструкция [# ... #] для встроенных в строки команд.


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

мая 19, 2010

dpp: линейные команды #assert и #return

Только что написал две новые команды, которые может обработать dpp:

#assert булево выражение
Пример вызова:
          #assert (2 + 2 = 4) and (0 = Cardinal(nil))
Проверяет истинность утверждения, если оно не выполняется - выводит в лог информацию о том в каком файле, в какой строке, когда, и какое утверждение не верно.

#return результат
Пример:
          #return ERROR_CANNOT_DO_ANYTHING
Возвращает из функции указанный результат, и выходит из функции

Это весьма простые конструкции, и добавление их поддержки требует написания парочки методов. В коде dpp есть глобальный класс PreProcessor, в котором нужно зарегистрировать необходимые обработчики. В настоящий момент поддерживаются только построчные обработчики:


  // Line - текущая обрабатываемая строка в файле
  // Fragment - часть строки, идущая после приставки
  //            (см. StartWord в RegistLineProcessor)
  // через S возвращается то, что нужно вставить
  // вместо приставки и Fragment'а
  TLineProcessor = procedure (
                         PreProcessor: TPreProcessor;
                         const Line, Fragment: TString;
                         out S: TString
                       ) of object;


К примеру, обработчик конструкции #assert выглядит так:


procedure TDebugProcessor.AssertProcess(
      PreProcessor: TPreProcessor;
      const Line, Fragment: TString;
      out S: TString
    );
begin
  S := 'Debug.Assert(' + Fragment + ', ''' +
        PreProcessor.CurrentFileName +
        '(' + IntToStr(PreProcessor.CurrentLine) + ') ' +
        Stringalize(Fragment) + ''');';
end;


Единственное, что я прокомментирую в этом коде, - функция Stringalize преобразует строку к печатному виду. А именно, если написать
          #assert S = 'hello'
то чтобы сконструировать строку, содержащую "S = 'hello'" для печати в лог, мы должны удвоить кавычки по правилам паскаля. Именно это и делает Stringalize.

Для полного счастья осталось зарегистрировать функцию в обработчике:


PreProcessor.RegistLineProcessor('#assert', '*', AssertProcess);


Первый параметр (я его называю приставкой или StartWord) - с чего должна начинаться конструкция.

Второй параметр - паттерн, задающий как должна выглядет строка после приставки. В данном случае может быть что угодно, но, например, конструкция
          #(6, 7, 2, 'hello')-> Notifier
не имеет опознавательных знаков в приставке, и потому задается паттерном
          (*)->*
(на самом деле он другой, но это не важно).

Совершенно аналогично написан #return.


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

мая 18, 2010

Математические символы

Сегодня искал коды математических символов для HTML. Чтобы хранить их всех где-то в одном месте, буду складывать их в этот пост.

символописаниеhtmlunicode
стрелка влево ←
стрелка вверх ↑
стрелка вправо →
стрелка вниз ↓
стрелка влево и вправо ↔
¬ Знак отрицания ¬
логическое И∧U+2227 ISOtech
логическое ИЛИ∨U+2228 ISOtech
для всех∀U+2200 ISOtech
существует∃U+2203 ISOtech
пустое множество/диаметр
U+2205 ISOamso
не равно≠U+2260 ISOtech
совпадает с
U+2261 ISOtech
перпендикулярно⊥U+22A5 ISOtech
истина ⊤
выводимо ⊦
не выводимо ⊬
двойное выводимо ⊩
двойное не выводимо ⊮
тройное выводимо ⊪
⊨ ⊧ верно ⊨
не верно ⊭
двойное верно ⊫
двойное не верно ⊯
входит∈U+2208 ISOtech
не входит∉U+2209 ISOtech
является подмножеством⊂U+2282 ISOtech
является надмножеством⊃U+2283 ISOtech
является подмножеством или совпадает ⊆U+2286 ISOtech
является надмножеством или совпадает ⊇U+2287 ISOtech
двойная стрелка вправо⇒ U+21D2 ISOtech
двойная стрелка влево-вправо⇔U+21D4 ISOamsa
n-арное произведение = знак произведения∏ U+220F ISOamsb prod
n-арное суммироание∑U+2211 ISOamsb sum
бесконечность∞U+221E ISOtech
вертикальная палка ∣
незакрашенный маленький квадрат□
закрашенный маленький квадрат ■
закрашенный квадрат
квадрат с точкой ⊡
квадрат с квадратом ▣
квадрат с горизонтальной полосой ⊟
квадрат с вертикальной полоской ◫
квадрат с плюсом ⊞
квадрат с горизонтальными полосами ▤
квадрат с вертикальными полосами ▥
лево полузакрашенный квадрат ◧
право полузакрашенный квадрат ◨
закрашенный квадрат с откусанным правым нижним уголком ◩
закрашенный квадрат с откусанным левым верхним уголком ◪
интеграл ∫
μ мю μ

◊ вытянутый ромб (◊) ◊
◇ ромб ◇
◆ закрашенный ромб ◆
◈ ромб с точкой ◈

○ маленький кружочек ○
● маленький закрашенный кружочек ●
◌ маленький пунктирный кружочек ◌
▢ ?кружочек ▢
⊝ кружочек с минусом ⊝
⊖ кружочек с горизонтальной чертой ⊖
⊕ кружочек с плюсом ⊕
⊜ кружочек с двумя полосами ⊜
◍ кружочек с вертикальной полосатостью ◍
⊘ кружочек с косой чертой ⊘
⊗ кружочек с крестом ⊗
⊙ кружочек с точкой ⊙
◉ кружочек с жирной точкой ◉
⊚ кружочек с кружочком ⊚
◎ кружочек с большим кружочком ◎
⊛ кружочек со звездочкой ⊛
◐ кружочек левозакрашенный ◐
◑ кружочек правозакрашенный ◑
◒ кружочек нижнезакрашенный ◒
◓ кружочек верхнезакрашенный ◓
◔ кружочек с закрашенным верхним правым углом ◔
◕ закрашенный кружочек с незакрашенным верхним левым углом ◕


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

мая 10, 2010

Собственный препроцессор (aka dpp)

Все больше и больше утыкаюсь в ограничения возможностей Free Pascal'я. Сегодня решил начать осуществлять свою давнюю задумку - препроцессор.

Итак, в чем заключается задумка. Есть файлы, написанные на моем расширенном паскале, имеют расширение dpp. Препроцессор просматривает папки, ищет такие файлы, и на их основе генерирует pas-файлы, которые уже можно просто скомпилировать free pascal'ем.

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

Сделано это странное ограничение для того, чтобы компилятор Free Pascal когда сообщал об ошибках, указывал бы номера строки и столбца ошибки совпадающие с соответствующими строками и столбцами в dpp-файла.

Моя утилитка dpp будет open source и ей сможет пользоваться любой желающий.

Это все была теория использования. О том, как утилитка действует, я расскажу в следующий раз, а сейчас перечислю некоторые фишки, которые хочу с помощью нее реализовать:

1) Событийную систему. К сожалению, мне не удалось на чистом fpc написать красиво отправку сообщения, поэтому ввожу такую конструкцию
#(5, 3, 'hello', Obj)-> Notifier
Она отправляет сообщение в Notifier с параметрами 5, 3, 'hello', Obj.

2) Автоматические создание и освобождение классов. Две конструкции

#autoclass(FMyClass: TMyClass) TMyExtendedClass.Create('hello')
Описывается внутри описания класса. Создает у класса поле FMyClass, которое будет автоматически создаваться во всех конструкторах вызовом TMyExtendedClass.Create('hello') и автоматически освобождаться в каждом деструкторе. По сути, время жизни поля привязывается к времени жизни хозяина - частоиспользуемая схема.

#stackclass(MyClass: TMyClass) TMyExtendedClass.Create('hello', Param1)
Описывается внутри описания метода. Автоматически создает в начале метода, автоматически уничтожает в конце

3) Система сериализации классов (для генерации кода по загрузке/сохранению). Тут немного сложнее, основные конструкции примерно такие:

#streaminterface
#streamimplementation(TMyClass)
Создает все необходимые функции по сохранению/загрузке класса TMyClass

#streamfield(FImportantValue: Integer)
Создает поле у класса, которое необходимо сохранять

4) Продвинутое RTTI.
#RTTIclass(MyRTTIVar) from TMyClass
Записывает в MyRTTIVar RTTI-информацию о классе TMyClass

5) Вставка в код название текущего файла/текущего метода/содержимого текущей строки/номера текущей строки. Очень удобно для логов.
#currentfilename_______________
#currentmethodname_____________
#currentline___________________
#currentlinenumber_____________

6) Собственно, печать в лог с указанием всей нужной инфы из пункта 5
#log 'error ' + IntToStr(ErrorId) + '!'

Пока что готов только пункт 1 :)


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

апреля 15, 2010

RTTI

Тут я расскажу про некоторые возможности RTTI (run-time type information, информация о типах во время выполнения), которыми сам активно пользуюсь.

Самое простое и всем хорошо известное - это оператор is для проверки принадлежности класса определенному типа, и функция ClassName, возвращающая строку с именем класса.

Чуть по-сложнее - метаклассы:


type
  TMyClass = class
    constructor Create; virtual;
    class function GetValue: Integer;
  end;

  constructor TMyClass.Create;
  begin
  end;

  class function TMyClass.GetValue: Integer;
  begin
    Result := 5050;
  end;

type
  TMyClass2 = class(TMyClass)
  end;

  CMyClass = class of TMyClass;

var
  MyClass: CMyClass;

begin
  // присваиваем наш базовый класс
  MyClass := TMyClass;
  // можем обращаться ко всем классовым функциям
  Writeln(MyClass.GetValue);
  // можем создать экземпляр класса TMyClass
  Writeln(MyClass.Create.ClassName);
  // можем присвоить наследника
  MyClass := TMyClass2;
  // т.к. конструктор сделан виртуальным, следующий вызов приведет к созданию
  // экземпляра TMyClass2
  Writeln(MyClass.Create.ClassName);
end.


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

В классах помимо секций private, protected и public есть еще и четвертая - published. Все, что в ней содержится (методы и свойства), при компиляции сохраняется вместе с названиями. (Модуль для этого нужно компилировать с ключем $M+, иначе published не будет отличаться от public.) Для доступа к этой информации предусмотрено два метода (взято из док-ции на fpc):


public function TObject.MethodAddress(const name: shortstring): pointer;
public function TObject.MethodName(address: pointer): shortstring;


Указатель, который возвращает первая функция, нельзя сразу использовать, нужно сперва заполнить им структуру TMethod:


{$M+}
type
  TMyClass = class
   published
    function GetValue: Integer;
  end;

  TGetValue = function (): Integer of object;

function TMyClass.GetValue: Integer;
begin
  Result := 5050;
end;

var
  I: Integer;
  Obj: TMyClass;
  Method: TMethod;

begin
  Obj := TMyClass.Create;
  Method.Code := Obj.MethodAddress('GetValue');
  Method.Data := Obj;
  I := TGetValue(Method);
  Writeln(I);
  Obj.Free;
end.


Этих двух функций мне оказалось недостаточно. Представим себе, что нужно прочитать xml-файл и на каждый тэг мы прописали метод чтения, и хочется, чтобы эти методы регистрировались автоматически. Для этого нужно как-то извлечь массив со всеми published-методами и их названиями. После изучения модуля System, я написал следующий код


type
TMethodName = record
  Name: TString;
  Method: TMethod;
end;
TArrayOfMethodName = array of TMethodName;

{$IFDEF FLAG_FPC}
  // Версия для Free Pascal (2.2.4)
  // Отличается от Delphi только форматом хранения RTTI
  function GetMethodArray(Obj: TObject): TArrayOfMethodName;
    type
      tmethodnamerec = packed record
        name : pshortstring;
        addr : pointer;
      end;
      tmethodnametable = packed record
        count : Cardinal;
        entries : packed array[0..0] of tmethodnamerec;
      end;
      pmethodnametable =  ^tmethodnametable;
      var
        I: Integer;
        Table: PMethodNameTable;
  begin
    Table := pmethodnametable((Pointer(Obj.ClassType) + vmtMethodTable)^);
    SetLength(Result, Table^.Count);
    for I := 0 to High(Result) do begin
      Result[I].Name := Table^.entries[I].name^;
      Result[I].Method.Code := Table^.entries[I].addr;
      Result[I].Method.Data := Obj;
    end;
  end;
{$ELSE}
  // Версия для Delphi (2009)
  function GetMethodArray(Obj: TObject): TArrayOfMethodName;
    type
      TMethodtableEntry = packed Record
        len: Word;
        addr: Pointer;
        name: ShortString;
      end;
    var
      pp: ^Pointer;
      pMethodTable: Pointer;
      pMethodEntry: ^TMethodTableEntry;
      i, numEntries: Word;
  begin
    pp := Pointer(Integer(Obj.ClassType) + vmtMethodtable);
    pMethodTable := pp^;
    if pMethodTable = nil then begin
      SetLength(Result, 0);
      Exit;
    end;
    SetLength(Result, PWord(pMethodTable)^);
    pMethodEntry := Pointer(Integer( pMethodTable ) + 2);
    for I := 0 to High(Result) do begin
      Result[I].Name := pMethodEntry.name;
      Result[I].Method.Code := pMethodEntry.addr;
      Result[I].Method.Data := Obj;
      pMethodEntry := Pointer(Integer( pMethodEntry ) + pMethodEntry^.len);
    end;
  end;
{$ENDIF}


Функция GetMethodArray возвращает список из уже почти готовых для вызова методов и их названий. После этого можно, например, найти все те, которые содержат в начале слово XML и использовать их для обработки xml-файла.

TString - это псевдокод на обычную ascii-строку (не unicode).


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

апреля 11, 2010

Подписка и события (часть 1)

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

Зачем оно

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

Все, наверно, сталкивались с чем-то вроде

Button.OnClick := MyClickProc;


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

Но, во-первых, часто бывает необходимость не одному, а нескольким обработчикам получать сообщения. Во-вторых, в приведенном коде не все понятно с освобождением - ведь если класс, который содержит метод MyClickProc, высвободиться из памяти, нажатие на кнопку приведет к ошибке.

Итак, задача: придумать удобную, изящную и безопасную реализацию на паскале системы событий, решающую задачи в предыдущем абзаце.

Архитектура

Событие - это набор данных, описывающих то, что произошло. Подписчик - тот, кто зарегистрировался на получение определенного события.

Есть два архитектурных направления:

1) Глобальное. Есть глобальный класс, в котором зарегистрированы все подписчики и события программы. При отправке сообщения он фильтрует подписчиков, выбирая тех, кому может быть интересно это событие. В операционных системах используется глобальная система сообщений.
2) Локальное. На каждое событие в программе создается отдельный экземпляр специального класса, хранящий всех подписчиков на это событие. При этом этот класс никак не зависим от других, и не обращается ни к чему глобальному.

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

Проектируем

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

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

До сих пор под событием понималась некоторая абстракция. Что же может быть событием в паскале?
1) Наследники специального базового класса TEvent. К примеру, можно написать TEventButtonClick и задать в нем поля Name, X, Y, Button, в которых передавать параметры. Тогда все методы-обработчики нажатия на мышь будут иметь вид

procedure OnMyClick(Ev: TEventButtonClick);

2) Набор параметров функции. Например, в выражении

procedure OnMyClick(const Name: String; X, Y, Button: Integer);

событие описывают параметры функции.

Разница этих двух методов лишь в том, как объявляется описание события. И первый способ мне не нравится: для описания класса потребуется описать private-поля, public-свойства (чтобы никто не изменил поля и ничего не испортил), и реализовать конструктор. Это гораздо больше, чем

type
  TButtonClickHandler = procedure (
                              const Name: String;
                              X, Y, Button: Integer
                            ) of object;

а самое неприятное - содержит в себе много дублирований.

Итак, после всего этого, мы приходим к следующему варианту (псевдокод):


type
  // так описываем тип нотифера
  TClickNotifier: specialize TNotifier<procedure (
                                                 const Name: String;
                                                 Button, X, Y: Integer
                                               ) of object>;
 
TButton = class
 private
  // его необходимо создать в конструкторе и удалить в деструкторе
  FClickNotifier: TClickNotifier;
 public
  ...
  property ClickNotifier: TClickNotifier read FClickNotifier;
end;

...

procedure TButton.OnClick(Button: Integer);
begin
  // так отправляем
  FClickNotifier.Send(Self.Name, Button, Self.X + 5, Self.Y + 5);
end;

...

procedure TMyObject.SomeAction(const Name: String; Button, X, Y: Integer);
begin
  // тут обрабатываем нажатие на кнопку
end;

...

// так происходит регистрация подписчиков
Button.ClickNotifier.Subscribe(A.SomeAction);
Button.ClickNotifier.Subscribe(B.SomeAction);


Ну, а о том, какие у этого есть проблемы, я расскажу в следующей статье цикла.


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

марта 29, 2010

Неиспользуемые переменные

У меня проверки при компиляции стоят на самом высоком уровне - т.е. компиляция валится и от хинтов, и варингов. Очень часто при отладке, когда комментируешь участки кода - появляются неиспользуемые переменные и это мешается. Мне это очень надоело и я написал простенькую до неприличия функцию


procedure TDebug.UnusedVar(var V);
begin
end;

Теперь могу написать:
Debug.UnusedVar(S)
и никакой ругани не будет.


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

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

Обо мне

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