Еще немного о сжатии текстов

  Текстовая информация является одним из основных (а иногда и единственным)
способов передачи информации от программы к пользователю. Поэтому
неудивительно, что данному вопросу уделяется довольно много внимания. Различные
шрифты, процедуры печати, листалки и прочие программистские ухищрения
направлены на упрощение и улучшение этого самого процесса передачи.
  Сегодня мы с вами поговорим о вопросе уменьшения размера памяти, которое
требуется для хранения текстов.
  По этому вопросу существует несколько подходов. Попробую перечислить хотя бы
некоторые из них.

  1. Тексты хранятся "как есть" и являются частью общего кода программы.
Достоинства данного подхода очевидны- имеется статическая связь между
оперирующей программой и адресами в памяти, где лежат текстовые строки.
Применяется в подавляющем большинстве программ. В качестве ухищрений,
направленных на уменьшение занимаемого объема памяти, стоит отметить
использование старшего бита символов как ограничителя (например, в ПЗУ для
хранения ключевых слов бейсика). Разумеется, данный подход применим при
употреблении только ограниченного набора символов (с кодами <128), что
накладывает существенные ограничения на его использование. Также камнем в огород
является то, что подход провоцирует на смешивание "мух и котлет", сиречь кода и
данных, которым страдают подавляющее большинство (если не все) программ на
спектруме- код и данные перемешаны самым причудливым образом.

  2. Тексты хранятся отдельно и являются просто входными данными для остального
кода. Даный подход достаточно размыт, ибо под него попадает большой диапазон
разных способов и их специфических ответвлений. Для понимания сущности
достаточно представить любого представителя электронной прессы- существует
отдельная область памяти, отведенная под достаточно большой объем текста, а
остальной код просто занимается отображением этого текста и навигацией по нему
(навороченные оболочки, где сервисные функции перевешивают основные,
рассматривать не будем). Подобный способ оверлейного метода работы с текстами
был удачно применен в игре CSC:DejaVu, где данные подгружались в процессе игры.
Первое же приходящее на ум ухищрение, позволяющее уменьшить объем текста-
сжимать его любым из имеющихся в арсенале упаковщиков. Тонкости- использование
для адресации средств файловой системы (каждый текст- отдельный файл),
внутренней таблицы (актуально для полнодисковых журналов с пустыми каталогами
или монолоадеров), средств контейнера (при использовании архиваторов- zxzip,
hrip, zxrar).
  Но тем не менее, это ухищрение позволяет уменьшить объем данных исключительно
на носителе. В памяти в итоге будет занят тот же самый объем. Однако, тут стоит
упомянуть некоторые идеи, получившие воплощение в жизни. Например, хранить
тексты в памяти уже сжатыми. Простейший способ- использовать меньшее число бит
для хранения символов. Так делались первые номера журнала ZXTime. Метод не
идеален, но вполне может быть применен в некоторых ситуациях. Другой способ-
токенизация текста. Широко применяется в текстовых же играх жанра adventure (а
также в программах на встроенном бейсике). Дальнейшая статья будет посвящена
данному методу работы с текстами.

  Итак, что есть токенизация? Говоря простыми словами- замена некоторых
последовательностей символов ссылками, позволяющими восстановить исходные
данные. В качестве ссылки может служить как индекс в таблице, так и абсолютный
адрес фрагмента. В вышеупомянутом случае программы на встроенном бейсике
токенами служат ключевые слова языка, а ссылками- просто коды этих ключевых
слов. Различный синтаксис ключевых слов накладывает разные правила на
интерпретацию следующих за индексом токена байт в памяти- параметры, либо
следующий токен. В текстовых играх жанра adventure ситуация, в общем,
аналогичная- заранее составляется список "особых" слов и последовательностей
символов, а внутреннее представление текста состоит из ссылок на эти самые
токены.
  Достоинства и недостатки данного метода очевидны: предельная простота
декодера, поддерживающего как блочное, так и посимвольное декодирование; из
недостатков- невысокая степень сжатия (80-90% от объема исходного текста) и
определенные трудности с процессом, собственно, токенизации. Способу упростить
этот процесс была посвящена одна моя работа, выполненная по просьбе Alex Xor'а.
Результатом ее выполнения стала программа textator (далее просто- программа),
призванная облегчить работу с текстами в программах и автоматически выполнить
токенизацию достаточно оптимальным, на мой взгляд, способом. Получить эту
программу можно свободно по адресу http://textator.googlecode.com - доступны
бинарные сборки для платформы Win32 и исходные тексты, позволяющие собрать
программу на платформах linux/windows (на других платформах не пробовал, но
проблем возникнуть не должно).
  Несколько слов о возможностях программы:
  - простой макроязык, ориентированный на работу с текстовыми переменными для
формирования результирующих строк;
  - возможность организации выборки текстовых строк по ключам, облегчающая
организацию переводов текстов на другие языки;
  - экспорт результата работы в форматах языков assembler и C/C++ и
промежуточный текстовый формат, являющийся также входным;
  - два режима токенизации текста (только для режима assembler и текстового).
  
  Краткую справку по ключам и режимам работы программы можно получить, запустив
ее без параметров (поскольку она имеет интерфейс командной строки, делать это
лучше в сеансе оболочки или командного интерпретатора, в зависимости от
используемой ОС).

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

#simple test

< TEXT1
> "This is the first text string"

< TEXT2
> "This is the second text string"

  Здесь объявляются две текстовые строки. После обработки, на выходе получим:

textator --process --asm test1.txt
; This file was automatically generated
TEXT1:  db "This is the first text string",0
TEXT2:  db "This is the second text string",0

(по умолчанию результат выполнения направляется на стандартный вывод, можно
определить имя файла с помощью ключа --output).

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

= BEGIN
> "This is the "

= END
> " text string"

< TEXT1
> BEGIN "first" END

< TEXT2
> BEGIN "second" END

  Добавились два новых идентификатора (но специальным образом оформленные), а
старые идентификаторы стали базироваться на основе новых двух. Результат
обработки будет полностью совпадать с предыдущим примером- программа просто
подставила строки и выполнила вывод. Стоит только заметить, что специальным
образом (начиная со знака '=') оформленные новые строки не попали на выход.
Будем называть такие идентификаторы локальными, тогда как обычные- глобальными.
  Еще усложним пример:
#simple test

= BEGIN
en> "This is the "
ru> "Это "

= END
en> " text string"
ru> " строка"

< TEXT1
en> BEGIN "first" END
ru> BEGIN "первая" END

< TEXT2
en> BEGIN "second" END
ru> BEGIN "вторая" END

  У каждого идентификатора добавились метки. Это ключи, благодаря которым можно
реализовать разные локализации для программ. Теперь запуск программы со старыми
параметрами вернет ошибку:
textator --process --asm test3.txt
Failed to find value of BEGIN by keys even by default

Необходимо уточнить, по каким ключам будет происходить выборка. Результат
выполнения команды:
textator --process --asm --keys en test3.txt

будет полностью совпадать с вышеупомянутым, а вот запуск команды:
textator --process --asm --keys ru test3.txt

даст результат:
; This file was automatically generated
TEXT1:  db "Это первая строка",0
TEXT2:  db "Это вторая строка",0

  Ключей может быть несколько, выборка происходит до первого удачного
совпадения. Если ни одно совпадение не было найдено, происходит проверка пустого
ключа по умолчанию.
  Выше был продемонстрирован один из режимов работы программы- обработка.
Другой режим- оптимизация- активируется по умолчанию и предназначен для
проверки на правильность синтаксиса:
textator --keys en test3.txt
# This file was automatically generated
# Local entries
= BEGIN
> "This is the "

= END
> " text string"
# Global entries
< TEXT1
> BEGIN "first" END

< TEXT2
> BEGIN "second" END

  Просто произошла выборка по ключу и вывод данных в текстовом формате. Тот же
самый процесс для вывода в ассемблерном формате выглядит следующим образом:
textator --asm --keys en test3.txt
; This file was automatically generated
; Local entries
        DEFTOKEN BEGIN
        db "This is the ",0
        DEFTOKEN END
        db " text string",0
; Global entries
TEXT1:
        TOKEN BEGIN
        db "first"
        TOKEN END
        db 0
TEXT2:
        TOKEN BEGIN
        db "second"
        TOKEN END
        db 0

  Локальные идентификаторы оформились особым образом с помощью макроса
DEFTOKEN, а их использование- с помощью макроса TOKEN. В комплекте с программой
идут примеры реализации этих макросов и сопутствующие им ограничения, а также
примеры посимвольного и блочного декодеров.
  В простейшем случае, макрос DEFTOKEN выглядит так (в формате SjAsm):

        macro DEFTOKEN id
id      equ $
        endm

  Т.е. для каждого макроса будет объявлена метка в текущем файле. Реализация
макроса TOKEN несколько сложнее и может варьироваться. Например, реализация:

TKNBASE equ 14
TKNEND  equ 31

        macro TOKEN id
        assert (high id)-(high LocalBase) <= TKNEND-TKNBASE
        db TKNBASE+(high id)-(high LocalBase),low id
        endm

кодирует каждый токен двумя байтами- первый содержит старший байт смещения
относительно метки LocalBase (с которой должны начинаться все токены),
закодированного в определенный диапазон значений (чтобы его можно было отличить
от обычных символов), второй байт содержит младший байт смещения. Очевидны
ограничения данного метода- на токены отводится около 4 килобайт (в большинстве
случаев этого достаточно).
  Более прямолинейная реализация, требующая 3 байта на кодирование ссылки на
токен выглядит очевидно:

TKNCODE equ 16

        macro TOKEN id
        db TKNCODE,high id,low id
        endm

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

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

; Print tokenized string
; In: hl - text address
; Function StrOutChar should be defined
StrPrint
        xor a
        push af ;limiter
StrCycle
        ld a,(hl)
        inc hl
        and a
        jr z,TestEnd
        TESTTOKEN StrOut
        push hl
        ld l,a
        ex af,af'
        ld h,a
        jr StrCycle
StrOut  call StrOutChar ;external
        jr StrCycle
TestEnd
        pop hl
        ld a,h
        and a
        jr nz,StrCycle
        ret

  Как можно заметить, абсолютно бесплатно достается поддержка вложенных токенов,
т.е. содержащих ссылки на другие токены. Макрос TESTTOKEN зависит от
используемого способа кодирования ссылок и выглядит как:

        macro TESTTOKEN jpto
        cp TKNBASE
        jr c,jpto
        cp TKNEND+1
        jr nc,jpto
        add a,(high LocalBase)-TKNBASE
        ex af,af'
        ld a,(hl)
        inc hl
        endm

для двухбайтовых ссылок и как:

        macro TESTTOKEN jpto
        cp TKNCODE
        jr nz,jpto
        ld a,(hl)
        inc hl
        ex af,af'
        ld a,(hl)
        inc hl
        endm

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

;In: HL- initial text
;Out: A- char. 0 if end
;HL is updated, should be used for next iterations until end
StrGetChar
        ld a,(hl)
        inc hl
        and a
        jr z,PopCtx
        TESTTOKEN_CHAR
        push de
        ex de,hl
StrSP   equ $+1
        ld a,0
        inc a
        ld (StrSP),a
        dec a
        add a,a
        add a,low StrStack
        ld l,a
        adc a,high StrStack
        sub l
        ld h,a
        ld (hl),e
        inc l
        ld (hl),d
        pop hl
        jr StrGetChar
PopCtx  ld a,(StrSP)
        and a
        ret z   ;real end
        dec a
        ld (StrSP),a
        add a,a
        add a,low StrStack
        ld l,a
        adc a,high StrStack
        sub l
        ld h,a
        ld a,(hl)
        inc l
        ld h,(hl)
        ld l,a
        jr StrGetChar
StrStack
        ds 16           ;assume that 8-level-deep is enough

  Здесь приходится прилагать некоторые усилия для поддержки вложенных токенов.
Предполагается, что восьмикратной вложенности будет достаточно. Макрос
TESTTOKEN_CHAR выглядит как:

        macro TESTTOKEN_CHAR
        cp TKNBASE
        ret c
        cp TKNEND+1
        ret nc
        add a,(high LocalBase)-TKNBASE
        ld d,a
        ld e,(hl)
        inc hl
        endm

и

        macro TESTTOKEN_CHAR
        cp TKNCODE
        ret nz
        ld d,(hl)
        inc hl
        ld e,(hl)
        inc hl
        endm

для двух- и трехбайтовых ссылок соответственно.
  Использовать данный способ декодирования приходится когда процедура вывода
символа нетривиальна:

        ld hl,text
        call myprint
        ...
myprint call StrGetChar
        ret z
        call MyOutputChar
        jr myprint

  Как использовать созданный программой код разобрались. Теперь можно и
рассмотреть оставшуюся часть функционала программы, ради которой она и
писалась- автоматическая токенизация.
  Ручное выделение токенов имеет довольно ограниченное применение- разве что
для выделения ключевых слов, которые могут меняться в процессе разработки
(имена героев игры, название пунктов меню и т.д.). В то же время, в
результирующем файле налицо избыточность данных, которую можно сократить.
  Дадим такое задание программе, предварительно уложнив пример:
  
textator --tokenize --minsize 4 --keys en test4.txt
# This file was automatically generated
# Local entries
= TOK0
> "This"

= TOK5
> "text"

= TOK6
> "string"
# Global entries
< TEXT1
> TOK0 " is the first " TOK5 " " TOK6

< TEXT2
> TOK0 " is the second " TOK5 " " TOK6

< TEXT3
> TOK0 " is the third " TOK5 " " TOK6

< TEXT4
> TOK0 " is the fourth " TOK5 " " TOK6

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

textator --tokenize --fragments --keys en test4.txt
# This file was automatically generated
# Local entries
= TOK0
> "This is the "

= TOK1
> " text string"
# Global entries
< TEXT1
> TOK0 "first" TOK1

< TEXT2
> TOK0 "second" TOK1

< TEXT3
> TOK0 "third" TOK1

< TEXT4
> TOK0 "fourth" TOK1

  Уже гораздо лучше! Программа смогла определить, что во всех подстроках есть
довольно большие общие части и выделила их в токены.
  Еще усложним пример. Добавим в него дублирующие строки. Например, строку
TEXT5 равную строке TEXT4 и посмотрим что будет на выходе:
#simple test
...
< TEXT4
en> BEGIN "fourth" END
ru> BEGIN "четвертая" END

< TEXT5
> TEXT4

Результат выполнения:

textator --tokenize --fragments --keys en test5.txt
# This file was automatically generated
# Local entries
= TOK0
> "This is the "

= TOK1
> " text string"
# Global entries
< TEXT1
> TOK0 "first" TOK1

< TEXT2
> TOK0 "second" TOK1

< TEXT3
> TOK0 "third" TOK1

< TEXT4
> TOK0 "fourth" TOK1

< TEXT5
> TEXT4

  Программа не стала обрабатывать строку, целиком состоящую из ссылки на другую
строку и сохранила ее "как есть". В ассемблерном виде это будет выглядеть так:

textator --tokenize --fragments --keys en --asm test5.txt
; This file was automatically generated
; Local entries
        DEFTOKEN TOK0
        db "This is the ",0
        DEFTOKEN TOK1
        db " text string",0
; Global entries
TEXT1:
        TOKEN TOK0
        db "first"
        TOKEN TOK1
        db 0
TEXT2:
        TOKEN TOK0
        db "second"
        TOKEN TOK1
        db 0
TEXT3:
        TOKEN TOK0
        db "third"
        TOKEN TOK1
        db 0
TEXT4:
        TOKEN TOK0
        db "fourth"
        TOKEN TOK1
        db 0
TEXT5 equ TEXT4

  Строка представлена наиболее лаконичным из возможных вариантов!

  Как это все работает. Алгоритмы парсинга входных данных, "сборки" строк путем
подстановки макросов, выборки по ключу и кодогенерации достаточно тривиальны и
не представляют ничего интересного. Аналогичное можно сказать и про алгоритм
токенизации по словам- суть его работы была изложена в одном предложении выше.
Алгоритм токенизации по фрагментам гораздо сложнее, посему стоит остановиться на
нем подробнее.
  Перво-наперво, все входные данные проходят контроль на уникальность. Таким
образом отсеиваются явные дубли, облегчая дальнейшую работу. Пример такого
контроля приведен чуть выше (к слову сказать, он также работает и при
токенизации по словам).
  Затем начинается разбитие входных данных на всевозможные фрагменты. Из всех
подстрок выделяются все возможные подпоследовательности символов, длины которых
лежат в задаваемом диапазоне (параметры --minsize/--maxsize). Очевидно, что дело
пахнет комбинаторным взрывом. Так и есть. К концу выделения
подпоследовательностей программа входит в пик по выделению оперативной памяти.
Для крупных текстов размером в несколько десятков килобайт этот пик может
достигать 300-400 мегабайт (поскольку это проверялось на синтетических примерах,
в реальности цифра в несколько раз меньше).
  Следующий, самый важный, этап- выделение тех подпоследовательностей, которые
в результате станут токенами. Выделение происходит в два этапа. Сначала
отсеиваются все подпоследовательности, которые не проходят по критерию
ссылочности- на них слишком мало ссылок чтобы они стали токенами (параметр
--minrefs). Таких последовательностей достаточно много и в результате список
кандидатов на становление токенами изрядно прореживается. После этого
итеративно происходит поиск наихудшего из кандидатов для его последующего
удаления. Для оценки "худшести" вводится понятие "цена" для фрагмента и его
"вредность". "Ценой" фрагмента считается квадрат произведения его длины на
число его ссылок. Зависимость от длины и числа ссылок очевидна- чем больше
фрагмент и чем больше раз на него ссылаются, тем он лучше как токен. Откуда
квадрат? А это заставляет программу отдавать приоритет более длинным токенам
перед набором коротких подстрок его составляющих. Предположим, фрагмент
"ПРЕДЛОЖЕНИЕ" встречается в тексте 10 раз:

ПРЕДЛОЖЕНИЕ вес = (11 символов х 10 раз) ** 2 = 110 ** 2 = 12100
а также в кандидатах есть подстроки данного фрагмента с теми же параметрами:
ПРЕД        вес = (4 символа х 10 раз) ** 2 = 40 ** 2 = 1600
    ЛОЖЕНИЕ вес = (7 символов х 10 раз) ** 2 = 70 ** 2 = 4900

 Суммарная "цена" подфрагментов будет 1600 + 4900 = 6500, что почти в два раза
ниже "цены" большого фрагмента, который и будет иметь больше шансов стать
токеном. "Вредность" же фрагмента означает "сколько кандидатов должны будут
исчезнуть чтобы данный фрагмент стал токеном", т.е. сумма "цен" всех
фрагментов, с которыми данный пересекается. Далее из всего списка кандидатов
выбираются те, у кого минимальная по значению разница между "ценой" и
"вредностью" и из этого набора удаляется кандидат с минимальной "ценой" как
самый бесполезный. После этого происходит переход на начало поиска "худшего"
фрагмента (пересчет весов). Это происходит из-за того, что фрагменты влияют друг
на друга и при удалении одного из них картина может достаточно сильно
поменяться. Процесс продолжается до тех пор, пока есть "мешающие" друг другу
фрагменты. После этого остается чисто техническая задача разбора полученных
результатов и формирования списка токенов.
  Оценка качества сжатия. Как и упоминалось выше, качество сжатия не особо
высокое ввиду предельной простоты алгоритма кодирования повторяющейся
информации. Для примера были взяты тексты из игры Вера (19329 байт). Сводная
таблица размера результирующих бинарных данных для разных параметров
токенизации приведена ниже. Основными параметрами выступали минимальный размер
токена и минимальное число ссылок. Поскольку токены занимали меньше 4к,
использовались двухбайтовые ссылки.

r - минимальное число ссылок (--minref)
s - минимальный размер токена (--minsize)

\r|     |     |     |     |     |     |     |     |
s\|  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  |
--+-----+-----+-----+-----+-----+-----+-----+-----+
3 |16877|16124|16147|16370|16418|16521|16723|16854|
4 |16589|16092|16307|16632|16841|17127|17401|17587|
5 |16632|16474|17044|17487|17846|18142|18357|18473|
6 |16852|17023|17700|18006|18298|18474|18685|18813|
7 |17060|17528|18077|18332|18529|18742|18920|18954|
8 |17529|18126|18558|18769|18930|18984|19128|19167|
9 |17913|18502|18825|18999|19139|19203|19248|19248|

  Наилучший результат получился с параметрами --minref 3 --minsize 4: 83.25% от
исходного файла. Это говорит о том, что параметры по умолчанию не всегда дают
оптимальный результат, стоит "поиграться" ими до получения нужного эффекта.
Для сравнения- сжатие с помощью hrust дало 11230 байт (58.1%). Но работа с hrust
требует те же почти 19кб памяти, тогда как токенизированный текст в
дополнительных буферах не нуждается.
  Время, затраченное на обработку файла примера колеблется в пределах 20-40
секунд в зависимости от скорости машины.

  Расширенное использование программы. Для того, чтобы показать работу
некоторых из неупомянутых выше ключей и возможностей, расширим пример:
  
< __HEADER__
> "Some user-defined text in header "
> "It also may use multiple\nlines"

< __RAWHEADER__
__asm> ";and this one is"
__asm> " raw header\n"
__asm> "  org 24576\n"
__cpp> "//and this one is for C++ source\n"
__cpp> "#include <stdio.h>\n\n"

+ test1.txt

< EXTERNAL
> +test1.txt+

<__RAWFOOTER__
__asm> ";raw footer for asm\n"
__cpp> "/* raw footer for C++ */\n"

< __FOOTER__
> "It is footer"

  Рассмотрим по порядку все перечисленные сущности. __HEADER__ - специальный
макрос, описывающий заголовок для созданных файлов. Он заменит собой
стандартный текст "This file was automatically generated". Следующий макрос
__RAWHEADER__ гораздо более интересен. Он позволяет описать заголовок (в
дополнение к предыдущему макросу) наиболее гибким образом. Специальные ключи
__asm и __cpp используются для определения текущего режима генерации исходного
текста. Они определяются автоматически и рассматриваются в последнюю очередь,
непосредственно перед выборкой по пустому ключу (см. описание процесса парсинга
и "сборки" строк).
  Строка "+ test5.txt" сообщает о том, что подключается внешний файл и
обрабатывается в соответствии с общими правилами синтаксиса. По метке EXTERNAL
этот же файл подключается и обрабатывается как обычная строка, только заданная
не непосредственно в тексте, а в другом файле.
  Попробуем выполнить генерацию исходного текста (без токенизации) для
ассемблера:

textator --process --asm test6.txt
; Some user-defined text in header It also may use multiple
; lines
;and this one is raw header
  org 24576
EXTERNAL:
        db "#simple test",10,10,"< TEXT1",10,"> ",34,"This is the first text "
        db "string",34,10,10,"< TEXT2",10,"> ",34,"This is the second text st"
        db "ring",34,10,0
TEXT1:  db "This is the first text string",0
TEXT2:  db "This is the second text string",0
; It is footer
;raw footer for asm

и для С/С++:

textator --process --cpp test6.txt
// Some user-defined text in header It also may use multiple
// lines
//and this one is for C++ source
#include <stdio.h>

const char EXTERNAL[] = {
        '#','s','i','m','p','l','e',' ','t','e','s','t','\n',
        '\n',
        '<',' ','T','E','X','T','1','\n',
        '>',' ','\"','T','h','i','s',' ','i','s',' ','t','h','e',' ','f','i',
        'r','s','t',' ','t','e','x','t',' ','s','t','r','i','n','g','\"','\n',
        '\n',
        '<',' ','T','E','X','T','2','\n',
        '>',' ','\"','T','h','i','s',' ','i','s',' ','t','h','e',' ','s','e',
        'c','o','n','d',' ','t','e','x','t',' ','s','t','r','i','n','g','\"',
        '\n',
        0
};
const char TEXT1[] = {
        'T','h','i','s',' ','i','s',' ','t','h','e',' ','f','i','r','s','t',' ',
        't','e','x','t',' ','s','t','r','i','n','g',0
};
const char TEXT2[] = {
        'T','h','i','s',' ','i','s',' ','t','h','e',' ','s','e','c','o','n','d',
        ' ','t','e','x','t',' ','s','t','r','i','n','g',0
};
// It is footer
/* raw footer for C++ */

  Сразу в глаза бросается разница между способами подключения внешнего файла.
Будучи поключенным через директиву, он передал свои текстовые строки
"родительскому" файлу (TEXT1, TEXT2). В случае подключения в виде макроса, его
содержимое было проинтерпретировано в сыром виде (обратите внимание на коды
перевода строки- они тоже были интерпретированы как данные). Также произошла
соответствующая выборка заголовков и окончаний файлов.
  В режиме --inline для генерации ассемблерного текста получается следующий
результат:

textator --asm --inline test6.txt
; Some user-defined text in header It also may use multiple
; lines
;and this one is raw header
  org 24576
; Local entries
; Global entries
        macro EXTERNAL
        db "#simple test",10,10,"< TEXT1",10,"> ",34,"This is the first text "
        db "string",34,10,10,"< TEXT2",10,"> ",34,"This is the second text st"
        db "ring",34,10,0
        endm

        macro TEXT1
        db "This is the first text string",0
        endm

        macro TEXT2
        db "This is the second text string",0
        endm

; It is footer
;raw footer for asm

  Как видно, каждая результирующая строка стала оформляться отдельным
макросом. Такой способ генерации применим в процедурах вывода строк, когда
адрес строки берется со стека:
  call print
  db "Text to print",0
  ... ;continue

  Этот флаг оказывает влияние только на режимы --analyze и --tokenize.
  
  В конце можно также упомянуть о диагностических возможностях программы. Для
режима токенизации и генерации ассемблерного файла можно включить отображение
статистики:
textator --tokenize --fragments --asm --statistic --keys en --output result.asm
test5.txt
Input data statistic:
        Processed strings: 5
        Total symbols: 148
Aliasing statistic:
        Detected aliases: 1
        Total symbols saved: 30
Tokenizing statistic:
        Extracted tokens: 2
        Tokens symbols: 24
        Tokens references: 8
        Tokenized symbols: 96
Compression statistic:
        Source size: 153 bytes
        Result size: 60 bytes (ratio 39%) for reference size 1
        Result size: 68 bytes (ratio 44%) for reference size 2
        Result size: 76 bytes (ratio 49%) for reference size 3

  Данный вывод сообщает следующую информацию:
  - во входных данных содержалось 5 независимых строк общим размером в 148
символов;
  - из этих 5 строк найдена одна повторяющаяся строка размером в 30 символов,
которая была выделена в алиас (как мы помним, это TEXT5);
  - из всего объема текста было выделено 2 токена общим размером в 24 символа.
На эти токены было 8 ссылок, которые заменили 96 символов.
  - ниже идет статистика сжатия текста для разных размеров ссылок при условии
представления текста в формате ASCIIZ.

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

textator --verbose --tokenize --fragments --minsize 2 --keys en --output
result.txt test5.txt
Warning! Inappropriate tokenize parameters for reference size 2
Warning! Inappropriate tokenize parameters for reference size 3
Searching for fragments
Analyze fragments
Extracting result

  Например, использование токенов размером от 2 символов (--minsize 2) может
неблагоприятно сказаться на процессе токенизации- памяти на хранение токенов и
ссылок на них может потребоваться больше, нежели сэкономится на их выделении.

  Подробности использования можно получить либо на вики проекта (адрес см.
выше), либо опытным путем. Примеры параметров для генерации текстов на C++
можно найти в файлах исходных текстов (да, программа подготавливает свои
собственные тексты). Также вы всегда можете задать мне вопросы на форуме, на
сайте проекта либо по электронной почте.

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

Гаврилов Виталий aka Vitamin/CAIG/2001

vitamin_caig@mail.ru
vitamin.caig@gmail.com
