Qt-Forth - учебный forth с графикой Qt для Windows и Linux
Учиться никогда не поздно,
тем более классическим алгоритмам.
Главная
Учебник
SPF 4.20 и QT
Алгоритмы
Исходники
DownLoad
Об авторе

  1. Запуск консоли.
  2. Стек.
  3. Целые числа и стековые операции.
  4. Строки.
  5. Определение новых слов (функций).
  6. Программа (графическая форма) "Здравствуй мир!".
  7. Основной шаблон программы.
  8. Работа с API . На примере Win32 API.
  9. Выполнение слов форта по вызову из DLL/SO.
  10. if=L, if=W - условная компиляция.
  11. Создание окна (формы). Класс CQWidget.
  12. Стандартные потоки stdin, stdout

Запуск консоли Qt-Forth.

Для начального ознакомления с фортом будем использовать консольный вариант Qt-Forth, коротко - консоль. Консоль, это обычная, программа на Qt-Forth, использующая визуальные компоненты графической библиотеки Qt. Итак, перейдите в каталог (сделать текущем), содержащим qtforth.exe и файлы *.zim. Обычно в Linux выполняемые файлы не имеют расширения EXE. Исходный текст консоли находится в файле console.zim и запуск консоли осуществляется следующей командой:

./qtforth -s console.zim

Ключ -s означает запуск Qt-Forth с исходным файлом console.zim, содержащим исходный (forth) текст программы нашей консоли. Команды языка forth в консоле вводятся в строке "Команда". Ввод команды заканчиваем нажатием Enter. Так как наша консоль в общем то тоже учебная программа (около 300 строк), то она не предполагает какой либо защиты от ошибок. По этому вводя команды будьте внимательны и не удивляйтесь, если консоль просто "развалиться" после очередной вашей команды. Можно использовать буфер обмена для ввода и коррекции информации в строке ввода команд консоли.

Стек.

Поддерживаются обычные фортовские операции на стеке. Например введем три цифры через пробел:

1 2 3

Внизу в строке состояния печатается текущее содержимое стека. Там мы и увидим наши значения. Они будут в том же порядке, в котором мы их ввели (1 2 3). Крайнее с права число лежит на вершине стека, а крайнее слева в глубине стека. Полностью сбросить (очистить) стек можно введя непонятное для Qt-forth команду (слово), например любую случайную последовательность букв.

Рассмотрим несколько классических команд (слов) форта. В каждом примере предпологаем, что на стеке лежит три числа (1 2 3).

dup - дублировать вершину стека. После выполнения данного слова на стеке будет ( 1 2 3 3 )

drop - сбросить число с вершины стека. После выполнения данного слова на стеке будет ( 1 2 )

swap - обменять местами два верхних значения на стеке. Результат ( 1 3 2 )

over - обменять местами два верхних значения на стеке. Результат ( 1 2 3 2 )

rot - "прокрутить" значения на стеке. ( 2 3 1 )

Это классика форта.

Целые числа и стековые операции.

Форт есть форт. Поддерживаются обычные фортовские операции на стеке. Например введем следующий текст :

2 3 + .

Обратите внимание, что все слова и числа разделены хотя бы одним пробелом. Если все введено правильно увидим ответ:

==> 2 3 + . это повторена строка ввода, для удобства

5 - это вычисленное выражение

Таким образом, введенное выражение было вычислено и результат отображен. На стеке ни чего не осталось. Допустимы операции + сложение, - вычитание, * умножение, / деление. Адреса (указатели) на стеке воспринимаются как числа. По этому легко сместить указатель просто прибавив или отняв от него число. Смещение всегда на количество байт. Единица = один байт. Три = три байта.

Таким же образом обрабатываются отрицательные числа.

Строки.

Строка при вводе записывается во временную область памяти, а её начало выкладывается на стек. Команда w вывести строку на экран консоли. Обратите внимание, после первой кавычки нет пробела (как в обычном форте):

==> "Привет мир" w

Будет напечатано "Привет мир". Строка храниться с первым длины строки и последним байтом равным нулю. Таким образом прощен передавать строки в функции API. Достаточно обойти первый байт. Пример строки в памяти:

==> "ABC" dump

000000019203 | 003 065 066 067 000 004 068 085 077 080 000 032 109 083 116 114 075 032 032 032 |

Вот так выглядит строка в памяти. Команда dump печатает 20 байт в десятичном формате начиная с адреса лежащего на стеке. Для совместимости с обычным фортом существует и слово кавычка. Его реализацию можно посмотреть в файле stdlib.zim

В памяти строка состоит из первого байта длины строки, самой строки и завершающего байта равного нулю (нечто среднее между строками PASCAL и C++). Длина строки 256 байтов. Завершающий 0 нужен для работы внешних функций API, которые как правило все на С.

==> "ABC" 1+ strlen

На стеке 3. Строка на стеке представлена указателем, который увеличивается на 1 при помощи слова 1+ тем самым обходя байт длины строки. Далее вызов strlen стандартной С функции, которая вернет на стек длину строки.

Определение новых слов (функций).

Новые слова определяются при помощи стандартных слов форта. Пример:

==> : hello "Привет мир" w ;

==> hello

Будет напечатано "Привет мир". Вот мы и сделали новое слово (функцию). Слово hello не изменяет значение стека, ни чего не забирает и ни чего не возвращает.

Имя (через пробел после начального двоеточия) может быть задано латинскими буквами (регистр не учитывается) или русскими (регистрозависимое). Внутри определения нового слова можно применять структурные операторы (определены в stdlib.zim). Кстати чем интересен Qt-Forth, что его интерпретатор написан на форте и может быть изменен в любое время. Структурные операторы:

if ... else ... then - Ветвление по условию

do .. loop - Цикл со счетчиком

begin ... while ... repeat - Цикл по условию.

case( ... of( ... of) ...case) - оператор case

и т.д. и т.п. Если чего то не хватает, всегда можно это дописать используя слова организации переходов в шитом коде, такие как <mark, >resolve и т.д. Пример в stdlib.zim.

Программа (графическая форма) "Здравствуй мир!".

Создадим простую программу. Описание Qt начинается с этого примера. Привожу текст программы, как он должен быть набран в консоле без комментариев (можно перенести копированием, правда построчно). Комментарии, естественно, можно не набирать. Обращаю внимание, что перед комментарием необходимы пробелы и за комментарием // необходим хотя бы один пробел, т.к. коментарий, это слово форта.

==> def qs1:CQStr // определяем объект qs1 класса CQStr (QString)

==> 0 0 def label1:CQLabel // определяем объект label1 класса CQLabel (QLabel)

==> "Привет мир!" 1+ qs1.set // присваиваем строку qs1 обходя начальный байт длины строки

==> qs1.% label1.setText // устанавливаем текст в label1.

==> : привет label1.show ; // определяем новое слово, которое выполнит метод show класса CQLabel

Вот программа и готова. Набираем привет и видим форму с приветствием.

Всё же удобнее писать программы в редакторе. Сохраним в файле hello.zim следующий текст:


// Наша программа
def qs1:CQStr            // опред объект qs1 класса CQStr (QString)
0 0 def label1:CQLabel   // опред объект label1 класса CQLabel (QLabel)
"Привет из нашей программы!" 1+ 
qs1.set                  // присв строку qs1 обходя начальный байт длины строки
qs1.% label1.setText     // устанавл текст в label1.
: привет label1.show ;   // опред новое слово

Теперь в консоли выполним команду

==> load hello.zim

Произойдет загрузка файла и слово привет станет доступно для выполнения. Набираем в консоли привет и видим форму с приветствием.

Основной шаблон программы.

При написании программы необходимо использовать следующий шаблон.

// Описание программы

Редактор kwrite читает первую строку и включает подсветку синтаксиса. Это значительно упрощает редактирование исходного текста.

load stdlib.zim

Грузим стандартную библиотеку. В ней собраны все определения. Изначально в ядре определены только самые необходимые слова, а всё остальное, циклы, условия, переменные и т.д. определены в stdlib.zim

ncodec "UTF-8" sz_:= // Имя кодека кодовой страницы

Скопируем строку "UTF-8" в переменную ncodec, тем самым сказав QtForth, что исходный текст в кодировке Linux. Для Windows "Windows-1251"

def myApp:CQApplication // Инициализация класса Приложение

Библиотека Qt имеет класс QApplicatin. Вот тут мы его как раз и создадим. Это позволяет по разному организовать программу, например как MDI.

........... сама программа .............

Здесь определяем новые слова, которые будут составлять нашу программу. Тело так сказать.

: main // Основная функция

.... инициализация, подготовительные действия ...

myApp.exec // главный цикл, ожидание графических событий

;

Таким образом определили слово (функцию) которая является основной функцией запуска.

main // Запускаем на выполнение основную программу

Здесь запускаем на выполнение основную программу.

mstop // Стоп виртуальная машина, ожидаем событий графического интерфейса

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

Пример:

Оформим предыдущий пример в стиле самостоятельной программы, без участия консоли. Сохраните текст в файле hello.zim


// Наша программа. Имя hello.zim
// Запуск ./qtforth -s hello.zim

load stdlib.zim
ncodec "UTF-8" sz_:=     // Имя кодека кодовой страницы
def myApp:CQApplication  // Инициализация класса Приложение

def qs1:CQStr            // опред объект qs1 класса QString
: str-->qstr             // Переобразовать строку в QString
     1+ qs1.set qs1.% ;
0 0 def label1:CQLabel   // опред объект label1 класса QLabel
"Привет из нашей программы!" str-->qstr label1.setText   // устанавл текст в label1.
: привет label1.show ;   			// опреденовое слово
: main                   							// основная программа
    "Внимание!" str-->qstr label1.setWindowTitle         // Поставим заголовок на форму 
    привет 
    myApp.exec // главный цикл, ожидание графических событий
    ;
main
mstop                    // Остановить виртуальную машину и ждать графических событий

Запуск командой:

./qtforth -s hello.zim

Работа с API . На примере Win32 API.

Работа с API основана на динамической загрузке библиотек. Для Windows - *.DLL, а для Linux - *.so. Рассмотрим в качестве примера небольшую прогу, которая выводит в консоль список всех зарегистрированных в Windows экземпляров окон верхнего уровня.


 // Пример работы с API Windows

// GetWindow() Constants из winuser.h
0 const GW_HWNDFIRST
1 const GW_HWNDLAST
2 const GW_HWNDNEXT
3 const GW_HWNDPREV
4 const GW_OWNER
5 const GW_CHILD

// Предпологаеться, что USER32.DLL уже загружена ( смотри stdlib.zim ) и h_user32 не пустой.
h_user32 @  "GetDesktopWindow" 1+ gpaddres : GetDesktopWindow 0 1 lit, acall ; // -- hwnd
h_user32 @  "GetWindow"        1+ gpaddres : GetWindow        2 1 lit, acall ; // -- hwnd
h_user32 @  "GetWindowTextA"   1+ gpaddres : GetWindowText    3 1 lit, acall ; // hwnd str Ndlstr -- hwnd
h_user32 @  "GetClassNameA"    1+ gpaddres : GetClassName     3 1 lit, acall ; // hwnd str Ndlstr -- hwnd

256 const$ СтрокаЗаголовка    // Строка char[256] для Заголовка
256 const$ СтрокаКласса       // ---- " ------ "  для Имени Класса
256 const$ СтрокаВывода       // ---- " ------ "  для результата

: g->       //   hwnd -- hwnd Дай описатель соседа
    GW_HWNDNEXT GetWindow ;
: g-V      //    hwnd -- hwnd Дай описатель ребенка
    GW_CHILD GetWindow ;
: g-title  //   ( hwnd -- ) заголовок окна по HWND
    dup СтрокаЗаголовка 255 GetWindowText drop ;
: g-class  //   ( hwnd -- ) класс окна по HWND
    dup СтрокаКласса 255 GetClassName drop ;

: СписокОкон   // ( -- ) Печатает  в консоли список окон первого уровня
    GetDesktopWindow g-V
    begin
       dup >r g-title g-class     // по HWND найдем имя класса и загол. окна и сохраним в СтрокаКласса и СтрокаЗаголовка
       СтрокаВывода "%d-->[%s]-->%s" 1+ r> СтрокаКласса СтрокаЗаголовка 
       5 6 sprintf drop           // sprintf(СтрокаВывода, "%d-->[%s]-->%s", hwnd,  СтрокаКласса СтрокаЗаголовка);
       СтрокаВывода 1- w          // Напечатаем СтрокаВывода в консоли.
       g-> dup not                // Пока указатель след окна не нулевой, крутим цикл 
    until drop ;
 

В начале формируем константы для функций GetWindow. Эта функция перебирает список окон, доступных в данный момент в системе Windows. Т.к. каждая форма, зарегистрированная в системе, может в свою очередь состоять из различных областей (окон) вложенных друг в друга, то обходим в цикле только окна уровня следующие за уровнем Desktop. GetDesktopWindow g-V получает HWND экрана и по нему вычисляет первый и единственный указатель на список окон первого уровня. Ну а дальше цикл перебора по списку, пока не найдем HWND==0. Для Каждого найденного HWND формируем строку при помощи API функции sprintf, которой в качестве параметров передаем аргументы строки, для формирования результирующей строки. Обратите внимание, что перед вызовом sprintf мы указываем ей, что будет 6 аргументов на стеке, причем 5 аргументов для переменного количества аргументов самой sprintf. Это пример вызова функции API с переменным числом аргументов. Выводимая информация показывает HWND, ИмяКлассаОкна и ЗаголовокОкна. То же самое можно увидеть в утилите SPY++ из состава компилятора C++.

Выделите и сохраните текст программы в файле a.zim в каталоге с файлами *.zim. Теперь в консоли выполним команду

==> load a.zim

Произойдет загрузка файла и слово СписокОкон станет доступно для выполнения.


if=L, if=W - условная компиляция.

Правильнее назвать эту конструкцию - управляемый комментарий. Фактически это комментарий (определен в stdlib.zim) и сделан он на базе hard слова OsName, которое выкладывает на стек код символа. W - для windows, L - для linux.

Создание окна (формы). Класс CQWidget.

Для определения объектов используется слово def. Синтаксис следующий:

[параметры если есть] def ИмяОбъекта:ИмяКласса

Для создания окон используется класс CQWidget. Слово def - это макрос (stdlib.zim), который разварачиваеться в большое определение. В связи с этим используеться только в режиме интерпретации. Желательно использовать один def на одной строке.

def  Окно1:CQWidget    // Определим объект Окно1 класса CQWidget
Окно1.show   // Вызовем метод show
: Окно Окно1.show ;      // Методы можно компилировать

Стандартные потоки stdin, stdout.

И в Linux и в Windows возможно перенаправление входных и выходных потоков.

программа.exe > файл.txt // Пример запуска программы с перенаправлением выходного потока в файл

Такое же действие можно выполнить и в Qt-Forth, даже когда он работает в графическом режиме.

qtforth.exe -s console.zim > log.txt // Пример запуска Qt-Forth с перенаправлением выходного потока в файл

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

putchsd // ( N -- ) Вывести байт в поток. По умолчанию в stdout

getchsd // ( -- N ) Читать байт из потока. По умолчанию из stdin

print // ( Aстроки -- ) Вывести строку в поток, причем без CR и LF

cr // ( -- ) Вывести в поток CR и LF

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

 -1 const EOF        // getchsd выдаёт -1, если достигнут конец файла
: ЗадвоениеПотока   // Читать входной поток и выводить в выходной задвоенную информацию
   begin
     getchsd               // Читаем из входного потока байт
     dup EOF = not         // Пока не дойдем до конца файла
   while
     dup putchsd putchsd   // Выводим задвоенный байт
   repeat drop
   cr "Вот и задвоили всё ..." print cr  ;     
  

Запустим на выполнение консоль, перенаправив входной поток с файла console.zim а выходной на файл con22.zim:

qtforth -s console.zim < console.zim > con22.txt

Так как закрытие входных потоков у нас в программе отсутствует, то для правильного формирования выходного файла нужно выйти из Qt-Forth. Произойдет закрытие всех файлов, в том числе и стандартных потоков. Результат работы программы можно посмотреть в файле con22.txt.

Getchsd и putchsd - работают изначально с s stdin и stdout. Однако их можно переключить на работу с файлами, открытыми стандартными файловами функциями open. В Qt-Forth определены две переменных, dev_i и dev_o, которые содержат указатель на структуру FILE (C++). Переприсваивая им новый файловый дискриптор, можно перенаправить ввод или вывод в файл, непосредственно из программы.

"w" "f.txt" openf dev_o !   // Переключим вывод на f.txt, записав новый дискриптор в dev_o
"Строка для записи в файл" print cr    // Запишем в файл строку
dev_o @ closef  // Закроем файл
 

dev_o // ( -- Afile ) Адрес *FILE с которой работает stdout

dev_i // ( -- Afile ) Адрес *FILE с которой работает stdin

openf // (Aстр_шаблона Aстр_имяфайла -- Nфайловыйдескриптор) Открыть файл на запись или чтение (см описание формата fopen C++)

closef // ( Nфайловыйдескриптор - 1/0) Закрыть файл (см описание fclose C++)

Более подробно о файловых функциях можно почитать в документации на C++, особенно об режимах открытия. Можно подключить другие функции ввода вывода, используя загрузку динамических библиотек.

 

Hosted by uCoz