Сейчас уже можно сказать, что работа над первой версией
компилятора Janus Forth подходит к завершению. В течение двух недель я
планирую выложить ее на сайте forth.ru. Как показывает опыт,
оценка сроков выпуска со стороны разработчиков продукта часто
немного страдает субъективностью, но реальный срок публикации
точно не будет отличаться от недели-двух на порядок! ;-)
Полное название компилятора - Janus Constructor, краткое -
"просто" Janus или Janus Forth. В отличие от SMAL32 (который можно
было использовать бесплатно лишь для некоммерческих проектов), в
лицензионное соглашение для Janus'а я планирую включить разрешение
и для бесплатного коммерческого использования этого продукта при
соблюдении некоторых условий. Итак...
- Janus будет бесплатен, если система используется для разработки
некоммерческих проектов, не перчисленных в пункте 3
- Janus будет бесплатен, если система используется для разработки
коммерческих проектов физическими или юридическими лицами, на
территории Росии или стран СНГ, кроме проектов, перечисленных в
пункте 3. Кроме того, я полагаю, разработчикам коммерческих
проектов не жалко будет поделиться со мной одной бесплатной
версией их продукта... ;-)
- Janus не будет "автоматически бесплатным" при его использовании
для разработки следующих типов проектов:
- Форт-систем или Форт-компиляторов
- Серверных Internet-приложений
- Систем тестирования (психологического, контроля знаний, etc...)
а также систем разрабтки тестов
- Возможно, условием использования Janus будет простая,
бесплатная регистрация копии системы на сервере forth.ru
Формулировки условий еще будут уточнены и дополнены.
Janus задумывался не как "самоценная" система. Целью разработки
для себя лично я ставил создание эффективного инструмента для
решения конкретной и достаточно масштабной прикладной задачи.
Кстати, название "Janus" было выбрано также, в значительной
степени, "с оглядкой" на этот самый масштабный проект. Что из
решения этой задачи получится - покажет время, если ничего не
выйдет, то останется "просто" Janus. :-)
Janus - заново переписанная система. Естественно, часть кода
мигрировала из предыдущих реализаций, но процент этого кода
относительно невелик и 100% этого кода были заново просмотрены и
переработаны.
Не существует нескольких версий Janus'а. Есть единый модуль,
запускаемый из различных операционных сред при помощи загрузчиков.
По скорости работы (прежде всего, из-за использования свернутого
шитого кода) Janus, по всей видимости, не будет столь быстрым как
SMAL32 или Small/32. Но, во-первых, он задумывался и не для
скоростных проектов, во-вторых везде, где это возможно, я попытался
скомпенсировать эту особенность эффективностью используемых
алгоритмов и оптимизацией, в меру своих возможностей, кода
наиболее часто используемых слов под процессор Pentium. И наконец,
в-третьих, остается перспектива перевода критичных секций в более
быстрые типы шитого кода.
Janus использует очень сложную структуру словаря, расчитанную,
прежде всего, на поддержку еще не реализованной в полной мере,
объектно-ориентированной модели. Все еще впереди... ;)
Из возможностей Janus'а:
- Многоплатформенность: Win32, Linux, DOS
- ANS - совместимость, в перспективе 100% слов из проекта стандарта
- Свернутый шитый код, отсутствие "статического" кода, не принадлежащего словарным статьям
- SMAL32 и Small/32 совместимость
- Исправлены все известные ошибки в коде, унаследованном со времен SMAL32
- Heap: эффективная динамическая память: ALLOCATE, FREE (использование AVL-деревьев
для быстрого поиска в списках, минимизация вызовов операционной системы, минимизация
запрашиваемой у операционной системы памяти).
- MASM-style ассемблер, набор команд Pentium, MMX, сопроцессор, макроопределения
- Локальные переменные разлчных типов, массивы, метки, переходы,
вложенные области действия локальных имен
- Обработка исключений: THROW-CATCH, дополнительный механизм TRY-TRAP-ENDTRY
- Управление контекстом выполнения слов: WITH-REPLACE-RESTORE
- Вызовы Linux-библиотек и Windows DLL-библиотек
- BUILD - построение компактных исполняемых модулей, настройка
на любой адрес, обработка множественной рекурсии
- Расширенный синтаксис управляющих контрукций, дополнительные управляющие конструкции
- Встроенные слова для работы с числами с плавающей запятой (множество слов FLOAT)
- Расширенная работа со строками, строки двух типов, длиной до 2^32-1 символов
- Работа с виртуальными экранами, запись в GIF-формате
- Условная компиляция, многострочные комментарии
- Быстрый поиск в словарях - использование HASH-функции
- Гибкая структура словаря, позволяющая реализовать большое количество схем работы слов
- Расширенное управление контекстом определения новых слов
- Функции расчета и обращения (подбора) CRC16 и CRC32
- Сортировка
- ...
Планируется реализовать в ближайшем будущем (в порядке убывания значимости):
- Berkley-style (или что-то более Forth-like) сокеты
- Объектно-ориентированная модель, классы
- Thread'ы - многозадачность, в перспективе все слова - реентерабельны
- Документация - ее не бывает мало
- Отладчик, дизассемблер. За основу, возможно, будет взят
Code Digger и дизассемблер Тома Циммера
- Графика, шрифты
- Встроенный макроязык для работы с текстом: TRAC - функционально
похож на Perl, структурно - на Lisp. В принципе, реализация
уже давно готова, осталось поработать над компенсацией
сложности синтаксиса за счет редактора, учитывающего
особенности языка.
- Отображение GPF и ситуации деления на 0 на механизм исключений
- Порт DOS-библиотек из SMAL32 (предже всего, графических),
некоторых возможностей из Small/32
- Страничка поддержки на forth.ru, FAQ-сервер, публикация удачных
авторских проектов для Janus и содействие их распространению по
договоренности с авторами
- Англоязычная документация (возможно, на основе текста Брюса Хойта)
- Доводка до соответствия стандарту
- Доводка текстового интерпретатора с целью отслеживания
различных ситуаций и генерации оптимального кода
- ...
Планируется реализовать в перспективе:
- BUILD - построение "быстрых" выполняемых модулей в стиле SMAL32
(на основе прямого шитого кода)
- Средства контроля ошибок
- Порт под BeOS, OS/2, Free BSD, Solaris (конечно, не все сразу ;-)
- ...
Самое "прогрессивное" в Janus'е - это динимическая память. Код,
обеспечивающий работу с "кучей" (heap'ом) по сложности вполне
может сравниться со сложностью BUILD'а. Как я понял благодаря
"приватным" консультациям, красивую и эффективную
объектно-ориентированную модель без продуманной реализации
динамической памяти написать просто не удастся, а написать ее для
развития проекта просто необходимо. При реализации слов ALLOCATE и
FREE я планировал решить одновременно несколько трудносовместимых
задач:
- Увеличение скорости работы за счет минимизации количества
системных вызовов и оптимизации поиска наиболее подходящего блока
из списка свободных.
- Минимизация количества памяти, запрашиваемой у системы.
- Минимизация памяти, отводимой для размещения системной
информации о состоянии "кучи".
- Унификация механизмов работы с целью сделать их независимыми от
операционного окружения.
Для рещения этих задач используются алгоритмы для работы с
двоичными сбалансированными деревьями (AVL-деревьями). Мне было
очень приятно узнать, что для решения аналогичных задач в ядре
Linux'а используются те же самые модели. ;)
Еще одна весьма удобная возможность в Janus'е - это управление
контекстом выполнения кода. Управляющая конструкция
WITH-REPLACE-RESTORE может использоваться в случаях, когда:
- Возникает необходимость временного, в пределах одного thread'а
и в пределах одного блока кода, переопределения действия уже
скомпилированного слова.
- Необходимо передать в слово параметры, не предусмотренные на
этапе программирования
- Установки временной "заглушки" на какое-то действие в уже
скомпилированной программе
- Использование разного контекста работы в разных thread'ах
(задачах) в рамках одной программы.
- ...еще не придумал. :)
Примеры:
\ Буферизация ввода/вывода
\ Возвращяет следующий символ из входного буфера
: GETCHAR ( --> char ) ... ;
\ Сохраняет выводимый символ символ в выходном буфере
: PUTCHAR ( char --> ) ... ;
: MAIN
...
WITH
KEY GETCHAR
EMIT PUTCHAR
REPLACE
... \ здесь ввод/вывод буферизирован
\ вместо KEY вызывается GETCHAR, вместо EMIT - PUTCHAR
RESTORE
... \ здесь KEY и EMIT опять работают "нормально"
;
\ Переопределение слова WORDS, заглушка на слово ?KEY
\ Новая версия слова не вызывает ?KEY для постраничной выдачи
: WORDS
WITH
?KEY FALSE
REPLACE
WORDS
RESTORE
;
|
Иными словами, эта конструкция временно подменяет все вызовы (как
прямые, так и косвенные) слов внутри блока REPLACE-RESTORE на
вызовы других слов. При этом заменяемое слово сохраняет
работоспособность, и может вызываться из других thread'ов (задач)
в неизменном виде. Замедления при вызове "подмененных" слов нет.
При помощи конструкции смены контекста выполнения можно
переопределять не только вызовы слов, но и, даже, обращения к
переменным при помощи слов TO
К примеру, конструкция:
WITH VAR1 VAR2 REPLACE FALSETO VAR1 RESTORE
|
заносит 0 в переменную VAR2
Пример определения ассемблерного макроса:
Macro testmacro param1,param2,param3
push param1
xchg param1,param2
call subroutine
pop param1
EndMacro
|
Вызывать этот макрос можно как из обычных ассемблерных
определений, так и из других макросов. В качестве фактических параметров
при вызове макроопределения сейчас могут выступать только выражения,
являющиеся простыми лексемами ассемблера.
Расширенная обработка исключений:
: MAIN
\ Ловим только 4 исключения с номерами -23 -2 -33 и 12
\ (Если оставить на стеке 0, то будем ловить все исключения)
-23 -2 -33 12 4
TRY
\ код, возможно, генерирующий исключение
TRAP
\ если исключение произошло, в этом блоке
\ сохранено состояние всех стеков в момент исключения
\ получим номер исключения (0 - исключения не было)
CAUGHT ( --> n )
\ если = -23, то генерируем исключение 12
\ его обработает обработчик на более высоком уровне
-23 = IF 12 THROW THEN
ENDTRY
\ здесь уже стеки находятся в исходном состоянии
;
|
Вызовы DLL-библиотек из ассемблерных определений:
Code DLLTEST ; ( mem_block_size --> mem_address|error_code )
pop eax
push 40h
push 1000h
push eax
push 0
wincall "VirtualAlloc","kernel32.dll"
; при последующих вызовах VirtualAlloc
; второй параметр (имя библиотеки)
; можно уже не указывать
or eax,eax
jz short get_err
push eax
next
get_err:
wincall "GetLastError","kernel32.dll"
push eax
next
EndCode
|
Linux shared-библиотеки вызываются таким же образом при помощи
макроса SHLCALL. Единственное отличие - параметры со стека
необходимо удалить после вызова (проанализировав?). Система
(ассемблер) отслеживает вызовы библиотек и предотвращает
повторное их подключение, если они уже подключены.
Наконец, локальные переменные, использование слов TO, FROM, PTR:
: MAIN ( param1 param2 param3 --> )
\ Обычное стандартное объявление переменных
FALSE LOCALS| PARAM4 PARAM3 PARAM2 PARAM1
\ Их использование
PARAM1 PARAM2 * PARAM3 + TO PARAM4
\ PARAM3 := PARAM3 + 4;
CELL+TO PARAM3
\ Или так:
PTR PARAM3 CELL+!
\ Обычные переменные
INT VAR1
INT VAR2
\ Массив длиной 4096 байт
[ 4096 ] MEMBLOCK ARRAY1
\ Его инициализация
ARRAY1 4096 ERASE
\ Присваивание
15 VAR1 !
\ или так:
VAR1 @ TO VAR2
\ или так:
FROM VAR1 TO VAR2
{
\ если объявить переменные здесь,
\ можно работать как с ними, так и с переменными
\ внешнего блока
\ Переход за пределы блока (объявление L1 - см. ниже):
\ все переменные, не существующие
\ в месте, в которое осуществляестся переход,
\ будут автоматически удалены
GOTO L1
100 0
DO
...
IF EXIT THEN \ EXIT выполнится нормально
...
15 TO I \ такое тоже возможно
16 TO J \ а вот это вызовет ошибку:
\ J пока еще не определено
LOOP
}
LABEL L1
;
|
Текстовый интерпретатор отслеживает использование локальных
переменных в слове, и если они отсутствуют, генерирует более
быстрый код, не требующий создания и удаления фрейма переменных.
Впрочем, сами локальные переменные тоже не должны быть слишком
медленными. Возможно, они будет работать, даже, быстрее переменных
глобальных, и уж точно быстрее локальных переменных в SMAL32.
И это, конечно же, не все, что есть в Janus'е...
И последнее - с выходом Janus'а я более не вижу смысла
ограничивать распространение исходных текстов SMAL32. Они будут
опубликованы. Возможно, в полном объеме.
Пока все!