В рамках *TCG блок это массив кусков кода целевого языка. При первом знакомстве может показаться, что блок это всего лишь другое название привычного инструмента StringList (списка строк), однако это не так. Разница как раз и заключается в способе хранения данных внутри блока и методах их извлечения наружу, о чем подробно будет рассказано в одном из следующих разделов. В данной же части будут рассмотрены основные блоки, применяемые в пакете Lazarus (и в пакетах вообще), а так же методы работы с ними и их использование для генерации синтаксически верного и достаточно оптимального кода целевого языка проекта.
Создание и подготовка блоков к работе
В предыдущем разделе уже приводилось то место, где лучше всего создавать блоки (да и вообще любые объекты, используемые в пакете). Это метод create модуля sys:
#hws
func create(entry)
gvar(blk_vars, blk_lvars, blk_init, blk_proc_decl, blk_proc_imp, blk_body, blk)
// create blocks
blk_body = block.reg("class_body").inc()
blk_vars = block.reg("class_vars").inc()
blk_lvars = block.reg("class_lvars").inc()
blk_init = block.reg("class_init").inc()
blk_proc_decl = block.reg("class_proc_decl").inc()
blk_proc_imp = block.reg("class_proc_imp")
// set current block
blk = blk_body
end
1) объявляет набор глобальных переменных blk_XXX, которые доступны из любой части проекта и всегда содержат ссылки на текущие блоки, отвечающие за тот или иной сегмент кода конечного приложения (либо как в пакете Lazarus эти блоки содержат код не всей программы целиком, а только элементов схемы для текущего уровня вложенности)
2) создает собственно объекты блоков (названия блоков по большому счету могут быть любыми, т.к. в дальнейшем элементы должны использовать глобальные переменные для доступа к ним, а не их непосредственные имена)
3) указывает, что текущим блоком(переменная blk) является блок blk_body
Что такое текущий блок и зачем он нужен?
Текущий блок это тот сегмент кода в конечном приложении, который выполняет основную работу программы и в зависимости от того, в какой "событийной" ветки схемы находится элемент, обрабатываемый кодогенератором в данный момент, этот блок может ссылаться на разные участки кода. Посмотрим на примере как это происходит.
Пример 1: в схеме два элемента - MainForm и Label, при этом событие onStart формы соединено с doCaption надписи (т.е. при старте приложения текст надписи должен стать пустым)
Имеем следующий шаблон кода(все лишнее скрыто), из которого лепиться конечное приложение и который был рассмотрен в предыдущей части:
#pas
Unit hiMainForm1;
interface
uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, StdCtrls;
type
TMainForm1 = class(TForm)
protected
procedure DoCreate; override;
...
public
// --- начало блока PRIVATE VARS
{ ... }
// --- конец блока PRIVATE VARS
// --- начало блока METHODS INT
{ ... }
// --- конец блока METHODS INT
end;
implementation
...
procedure TMainForm1.DoCreate;
// --- начало блока LOCAL VARS
{ ... }
// --- конец блока LOCAL VARS
begin
Caption := 'Form1';
// --- начало блока INIT
{ ... }
// --- конец блока INIT
// --- начало блока BODY
{ ... }
// --- конец блока BODY
end;
// --- начало блока METHODS IMP
{ ... }
// --- конец блока METHODS IMP
end.
#pas
Unit hiMainForm1;
interface
uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, StdCtrls;
type
TMainForm1 = class(TForm)
protected
procedure DoCreate; override;
...
public
// --- начало блока PRIVATE VARS
Label2:TLabel; // <-------- добавилась собственно надпись
// --- конец блока PRIVATE VARS
end;
implementation
...
procedure TMainForm1.DoCreate;
begin
Caption := 'Form1';
// --- начало блока INIT
Label3 := TLabel.Create(self); // <--------- создание надписи и добавление на форму
addWidget(Label2, 60, 30, 40, 30);
Label2.Caption := 'Label'; // <---------- инициализация ее свойств
// --- конец блока INIT
// --- начало блока BODY
Label2.Caption := ''; // <--------- это та самая полезная работа, которую и выполняет наше приложение
// --- конец блока BODY
end;
end.
Пример 2: теперь же усложним схему еще одним элементом - Кнопкой, и надпись будем устанавливать не в событии onStart формы, а в событии onClick кнопки. Конечный код должен быть примерно таким:
#pas
Unit hiMainForm1;
interface
uses Classes, SysUtils, FileUtil, LResources, Forms, Controls, StdCtrls;
type
TMainForm1 = class(TForm)
protected
procedure DoCreate; override;
...
public
// --- начало блока PRIVATE VARS
Button2:TButton; // <-------- добавилась новая кнопка
Label3:TLabel;
// --- конец блока PRIVATE VARS
// --- начало блока METHODS INT
procedure onClick2(Sender:TObject); // <-------- появился обработчик события onClick кнопки
// --- конец блока METHODS INT
end;
implementation
...
procedure TMainForm1.DoCreate;
begin
Caption := 'Form1';
// --- начало блока INIT
Button2 := TButton.Create(self); // <--------- создание кнопки и добавление на форму
addWidget(Button2, 35, 20, 55, 30);
Button2.Caption := 'Push'; // <---------- инициализация ее свойств
Button2.onClick := @onClick2;
Label3 := TLabel.Create(self);
addWidget(Label2, 60, 30, 40, 30);
Label2.Caption := 'Label';
// --- конец блока INIT
// --- начало блока BODY
{ ... } // <---------- этот BODY остался пуст
// --- конец блока BODY
end;
// --- начало блока METHODS IMP
procedure TMainForm1.onClick2(Sender:TObject);
begin
// --- начало блока BODY
Label3.Caption := ''; // <---------- а этот BODY теперь содержит основную полезную часть приложения
// --- конец блока BODY
end;
// --- конец блока METHODS IMP
end.
Графическое представление процесса формирования кода схемы в *TCG пакетах
Благодаря такому подходу вызовы процедур и функций целевого языка могут быть не ограничено вложенными друг в друга и их код никогда не будет перепутан в процессе кодогенерации. В тоже самое время представление всей этой кухни ввиде простых методов объекта sys делает разработку прозрачной и не требует от программиста каждый раз заниматься ее реализацией. Например, типичный код GUI элементы выглядит так:
#hws
include("WinControl-proc") // общие ф-ции для всех визуальных элементов
func init
addWidget('TButton') // добавление нового виджета на форму (или любой другой GUI контейнер) с классом TButton
include("WinControl-init") // общий код инициализации всех визуальных элементов
sys.set_undef_field('Caption') // установка св-ва Caption текущего GUI элемента
sys.add_event('onClick', this.props("Data").value) // а это вызов того самого механизма создания процедуры в целевом языке, который на схеме выше представлен блоком onClick
end
func doCaption(text) // метод установки текста надписи
blk.println(this.codename + '.Caption := ', text, ';')
end