Вверх ↑
Этот топик читают: Гость
Администрация
Ответов: 15295
Рейтинг: 1519
#1: 2011-06-04 14:11:20 ЛС | профиль | цитата
Что такое блоки?
   В рамках *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.
   Следует внимательно посмотреть на последний код: в нем оказалось два блока BODY. Однако вспоминаем, что кодогенераторы *TCG в каждый момент времени могут находится к контексте только одного элемента и выполнять только один его метод, в котором ничто не мешает временно запомнить ссылку на текущий блок BODY (из переменной blk), назначить новый, и продолжить выполнение процесса кодогенерации. После чего восстановить ссылку обратно. Благодаря этому для всех элементов всегда существует только один набор блоков, куда они и печатают свой код не заботясь о том, что и куда попадет на самом деле. На схеме ниже представлены все этапы этого процесса в более наглядном виде:

Графическое представление процесса формирования кода схемы в *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
   Из приведенного кода видно, что все те действия, которые идут после блока onClick на схеме делаются вызовом всего лишь одного метода add_event и разработчику достаточно иметь только общее представление о его назначении без необходимости разбираться в деталях. Именно в этом и заключается первоначальная сложность при проектировании нового (а в последствии при изучении уже существующего) пакета - нужно хорошо продумать технологию генерации и реализовать минимальный набор методов, который позволит в соответствии с ней быстро и просто создавать элементы без необходимости каждый раз вспоминать как и на что подменять блоки и как потом из них собрать нужный кусок кода конечного приложения.

Редактировалось 1 раз(а), последний 2015-12-12 16:27:43
карма: 27
3
файлы: 1rtcg-code-graph.png [47.6KB] [971]
Голосовали:login, Netspirit, CriDos
Ответов: 16884
Рейтинг: 1239
#2: 2011-06-04 14:43:55 ЛС | профиль | цитата
Выделить бы "Пакет Lazarus" в отдельную тему.
карма: 25
Немного терпения! Дежурный экстрасенс скоро свяжется с Вами!
0
Ответов: 4641
Рейтинг: 334
#3: 2011-06-04 15:35:54 ЛС | профиль | цитата
Tad писал(а):
Выделить бы "Пакет Lazarus" в отдельную тему.

так и так в отдельной теме. может имелось ввиду в отдельный раздел?
карма: 1
Время верстки: %cr_time% Текущее время: %time%
0
Ответов: 16884
Рейтинг: 1239
#4: 2011-06-04 15:51:23 ЛС | профиль | цитата
Ravilr писал(а):
так и так в отдельной теме
Сейчас отдельными темами.
Часть 1: Вступление
Часть 2: Блоки

Хотелось бы или одной темой (статьёй) или отдельным разделом.
карма: 25
Немного терпения! Дежурный экстрасенс скоро свяжется с Вами!
0
Разработчик
Ответов: 4698
Рейтинг: 426
#5: 2011-06-04 20:29:07 ЛС | профиль | цитата
Dilma писал(а):
blk_body = block.reg("class_body").inc()

А вот что в данном примере делает inc()?
карма: 10
0
Администрация
Ответов: 15295
Рейтинг: 1519
#6: 2011-06-04 20:51:43 ЛС | профиль | цитата
Assasin писал(а):
А вот что в данном примере делает inc()?

Управление блоками inclvl ()
карма: 27
0
Разработчик
Ответов: 4698
Рейтинг: 426
#7: 2011-06-04 20:57:51 ЛС | профиль | цитата
Dilma писал(а):
inclvl()

Другой вопрос (детали реализации): а почему это сделано в инициализации? ИМХО, гораздо понятнее и нагляднее было бы делать это в том месте, где все блоки сливаются и образуют конечный код.
карма: 10
0
Администрация
Ответов: 15295
Рейтинг: 1519
#8: 2011-06-04 21:02:51 ЛС | профиль | цитата
уровень отступа печатается в блок в тот момент, когда вызываются методы print, поэтому выставлять его надо до формирования кода, а не после
карма: 27
0
Разработчик
Ответов: 4698
Рейтинг: 426
#9: 2011-06-04 21:08:06 ЛС | профиль | цитата
Подожди, в пакете Lazarus существует функция, которая сливает все блоки в один (на уровне скрипта, а не кодогенератора)?
карма: 10
0
Администрация
Ответов: 15295
Рейтинг: 1519
#10: 2011-06-04 22:04:20 ЛС | профиль | цитата
ф-ция нет, код да
карма: 27
0
Разработчик
Ответов: 4698
Рейтинг: 426
#11: 2011-06-04 22:35:48 ЛС | профиль | цитата
Dilma писал(а):
ф-ция нет, код да

Так ведь можно перед слитием все в один код сделать вот так: (приведу пример на FTCG)
#hws
block.select(BLK_RESULT)
block.inclvl()
block.copyhere(BLK_BODY)
block.declvl()
//... и т д с каждым блоком, и не нужно писать .inc() для каждого блока при инициализации

карма: 10
0
Администрация
Ответов: 15295
Рейтинг: 1519
#12: 2011-06-04 23:00:21 ЛС | профиль | цитата
чего-то я не очень понимаю кто и что выигрывает от размещения вызовов не в одном файле, а в другом?
карма: 27
0
Разработчик
Ответов: 4698
Рейтинг: 426
#13: 2011-06-04 23:20:19 ЛС | профиль | цитата
Assasin писал(а):
ИМХО, гораздо понятнее и нагляднее

Т.е выигрывает другой программист, решивший изучить код элементов пакета. При этом никто не проигрывает.
карма: 10
0
Администрация
Ответов: 15295
Рейтинг: 1519
#14: 2011-06-05 01:23:16 ЛС | профиль | цитата
Assasin, когда "ИМХО" предлагается в качестве решения по умолчанию ожидаются какие-то более объективные аргументы, чем
Assasin писал(а):
гораздо понятнее и нагляднее


Завтра придет дядя Вася и скажет, что ему "ИМХО" гораздо понятнее и нагляднее, если этих функций вообще не будет - опять переделывать все будем?
карма: 27
0
Ответов: 4641
Рейтинг: 334
#15: 2011-06-05 11:08:13 ЛС | профиль | цитата
[offtop]
Dilma писал(а):
Завтра придет дядя Вася

дядя придет из молодого поколения. так что можно может и прислушаться. У молодого поколения свои взгляды [/offtop]
карма: 1
Время верстки: %cr_time% Текущее время: %time%
0
Сообщение
...
Прикрепленные файлы
(файлы не залиты)