Программные файлы,
написанные на языке JavaScript,
часто бывают очень объемными, что неудобно при их мобильном использовании и
хранении. Кроме этого, использование длинных мнемонических имен идентификаторов
и обширных комментариев несомненно облегчает работу
программиста при создании программы, но делает программный код достаточно
“прозрачным” и хотелось бы убрать этот эффект. В силу этого возникла
необходимость уменьшить объем программного кода за счет следующих действий:
- |
удаление отдельной
строки-комментария и комментария, располагающегося непосредственно в строке
за оператором; |
- |
удаление пустых
строк; |
- |
сокращение числа смысловых
пробелов в строке (например, для отделения зарезервированных ключевых слов
языка) до одного; |
- |
удаление пробелов
между операндами и операциями в операторах; |
- |
замена формальных параметров
и локальных переменных в функциях, написанных на языке JavaScript
короткими идентификаторами, причем во вложенных функциях заменяются также их
имена; |
- |
замена
идентификаторов, используемых в операторах функции, на их новое
представление; |
- |
“сжатие” файла за
счет удаления кода разбивки программного кода на строки. |
Основное соглашение
принято для генерируемых коротких идентификаторов замены, которые
представляются на каждом уровне доступа в виде _n, где n целое
число больше или равное 1 (например, _1,_25,_326 ).
Номер n
соответствует порядку встретившегося первый раз идентификатора текущего уровня,
подлежащего замене. Первый уровень составляют список формальных параметров и
локальные параметры головной функции, а также имена вложенных в неё функций,
второй уровень – список формальных параметров и локальные параметры вложенной
на первом уровне функции, а также имена вложенных в эту функцию функций второго
уровня и т.д. Каждый уровень доступа начинает отсчет номеров переменных от 1,
т.е. идентификаторы замены для разных уровней доступа могут совпадать.
Глобальные переменные, используемые в текущем файле и определяемые вне
описываемых в нем функций, не изменяются.
Для составных имен
переменных в операторах языка, представляющих собой последовательность
идентификаторов, разделенных точкой (например, UW.h.document), контролю и при
необходимости замене подлежит только первый идентификатор сложного имени (UW).
Для указания на то,
что идентификатор не должен контролироваться и изменяться введены специальные
обрамляющие комментарии. Например, идентификатор MIDS всегда не изменится, если написать /*##B*/MIDS/*##E*/
Строки, заключенные в последовательность двойных
и/или одинарных кавычек анализу и изменению не подлежат .
Реализация
поставленной задачи производилась с использованием разрабатываемого в НИВЦ МГУ
инструментального комплекса TeConv.
Этот комплекс позволяет производить контекстную замену фрагментов текста на
базе специальных средств, которые обеспечивают создание сложных сценариев
контекстного поиска/замены. Сценарий для выполнения поставленной задачи
содержит три этапа редактирования файла (его имя с полным путем доступа берется
в сценарии из левого списка ListFile,
который создаётся перед запуском сценария):
!Подготовительный этап: чистка архива,
ячейки которого используются как:
! {{1}} - список идентификаторов замен в текущий
момент времени … _3 _2 _1
! {{2}} - список имён текущих переменных, подлежащих
замене
! {{3}} - список имён глобальных переменных, не
подлежащих замене
! {{4}} -
счётчик числа глобальных переменных
! {{5}} - счётчик числа элементов списка {{2}}
! {{6}} - счётчик параметров вложенной функции и её локальных переменных
! {{7}} - признак блока комментариев
!
{{8}} - счётчик открывающих фигурных
скобок '{'
!
{{9}} - счётчик закрывающих фигурных
скобок '}'
! {{10}} -
признак открытых двойных или одинарных кавычек
! {{11}} -
признак структуры
! {{12}} -
признак тега
! {{13}} -
признак обработки списка переменных в операторе var
! {{14}} -
признак обработки списка параметров функции
! {{15}} -
счётчик двойных кавычек
! {{16}} -
счётчик одинарных кавычек
#[РАБОТА С
АРХИВОМ] {1}
>ОЧИСТИТЬ
АРХИВ
!Первый этап:
ликвидация комментариев и лишних пробелов
#[ЗАМЕНА
КОНТЕКСТОВ] {2}
/1/входной
текст (индекс): 3
/2/выходной
текст (индекс): 0
/3/входной
каталог: LeftList
/4/шаблоны
входных файлов: *.*
/5/искать в
подкаталогах (признак): 0
/6/без учета
регистра (признак): 0
/7/режим
замены (индекс): 0
/8/входная
кодировка (индекс): 3
/9/выходной
каталог: ListFiles (путь к каталогу в списке)
/10/рабочий
каталог:
/11/сводный
файл строк редакции (признак): 0
/12/только
протокол (признак): 0
/13/вид
протокола (индекс): 0
/14/статистика
для протокола (признак): 0
/15/файл
запроса: d:\javar\javcon100s.txt
[ПАРАМЕТРЫ
ВЫХОДНОГО КАТАЛОГА]
/1/запись
измененных файлов (индекс): 1
/2/выходной
каталог: ListFiles (путь к каталогу в списке)
/3/сохранять
копии (признак): 1
/4/рабочий
каталог:
/5/копировать
дерево файлов исходного каталога (признак): 0
/6/копировать
в рабочий каталог (индекс): 0
>НАЧАТЬ:
12
На первом этапе таблица контекстов для
ликвидации комментариев, пустых строк и лишних
пробелов загружается из файла javcon100s.txt и содержит следующие контексты:
Контекст
поиска: |
Контекст
замены: |
Комментарий: |
{
*/@*##B@*/}|{ */@*##E@*/} |
#[{1}|~1..-1//{1}] |
начальное
обнуление ячеек |
{
*/@*?*@*/} |
"" |
удаление
комментария |
{%/@*?*}|{%//##Begin} |
#["true"//{{7}};
BlockRepl(%)] |
признак начала блока комментария и удаление строки |
{%@*/?*}|{%//##End} |
#[""//{{7}};
BlockRepl(%)] |
снятие признака блока комментария и удаление строки |
|[{{7}}=true]|{%?*} |
% |
удаление строки комментария внутри блока |
{%
*function *} |
#[""//{{10}};
""//{{15}}; ""//{{16}}; {1}|~1..-1~ & "
"//{1}] |
начало описания функции: очистка ячеек общего архива,
выделение из группы ключевого слова без обрамляющих пробелов и добавление
следом одного пробела |
{"} |
#[{{15}} +
1//{{15}}; max({{15}} mod 2, {{16}} mod 2)//{{10}}] |
коррекция счетчика двойных кавычек и установка признака
открытых кавычек какого-либо вида |
{'} |
#[{{16}} +
1//{{16}}; max({{15}} mod 2, {{16}} mod 2)//{{10}}] |
коррекция счетчика одинарных кавычек и установка признака
открытых кавычек какого-либо вида |
|[{{10}}
mod 2 <> 0]|{?} |
|
пропуск символа, если он находится внутри кавычек |
{% +} |
"" |
удаление
первых пробелов |
{ *//?*} |
"" |
удаление комментария, расположенного в строке за
оператором |
{%\0d\0a} |
"" |
удаление
пустой строки |
{
*[=!,<>/()&;:^@~@?@|@+ @-@*@[@]] *} |
#[{1}|~1..-1~//{1}] |
удаление окружающих операции пробелов |
{ +} |
"
" |
сжатие смысловых пробелов в операторе до одного |
!Второй этап:
замена параметров внешних функций, локальных переменных
! а также имён и
параметров вложенных функций
#[ЗАМЕНА
КОНТЕКСТОВ] {3}
/1/входной
текст (индекс): 3
. . . . . . . . . . . . . . .
/15/файл
запроса: d:\javar\javcon200s.txt
[ПАРАМЕТРЫ
ВЫХОДНОГО КАТАЛОГА]
. . . . . . . . . . . . . . .
>НАЧАТЬ:
12
!Третий этап:
ликвидация символов перехода на новую строку
! (представление файла
единой строкой)
#[ЗАМЕНА
КОНТЕКСТОВ] {4}
/1/входной
текст (индекс): 3
. . . . . . . . . . . . . . .
/15/файл
запроса: d:\javar\javcon300s.txt
[ПАРАМЕТРЫ
ВЫХОДНОГО КАТАЛОГА]
. . . . . . . . . . . . . . .
>НАЧАТЬ:
12
На втором этапе производится
замена параметров функции согласно принятым соглашениям. При этом контроль
вложенности функций обеспечивает подсчёт поступивших открывающих и закрывающих
фигурных скобок, а контроль текстовых констант
- подсчёт поступивших двойных и одинарных кавычек. В каждый момент
анализа текущего идентификатора анализируется созданный к этому моменту
динамический список глобальных и локальных параметров данного уровня, после
чего возможна коррекция этого списка и замена идентификатора.
Организация списков и
стеков осуществляется в виде последовательности лексем, помещаемых в ячейку
архива. При этом для стека доступ к верхнему элементу (лексеме) в n-ой ячейке архива осуществляется
посредством конструкции {{n}}|-1''-1
. Алгоритм анализа вложенности функций строится на подсчете и сравнении числа
открывающих и закрывающих фигурных скобок текущего уровня, которые организуют
стеки в ячейках 8 и 9. Список идентификаторов, подлежащих замене, формируется в
ячейке 2, а соответствующий ему список идентификаторов замены в ячейке 1,
причем новые элементы добавляются всегда в начало списков. Для подсчета числа
идентификаторов, подлежащих замене на каждом текущем уровне вложенности, в
ячейке 6 строится стековый счетчик. Последнее значение лексемы этого счетчика
содержит число лексем на которое усекается начало
списков 1 и 2 при совпадении числа открывающих и закрывающих скобок текущего
уровня, подсчитываемых с помощью стеков в ячейках 8 и 9.
Таблица контекстов загружается из файла
javcon200s.txt и содержит следующие основные контексты:
Контекст
поиска: |
Контекст
замены: |
Комментарий: |
|1[{{8}}=""]|%%{% *function }{[_A-Za-z][_A-Za-z0-9]*
*(} |
#[""//{{5}}; "0"//{{6}};
"0"//{{8}}; "0"//{{9}}; ""//{{7}};
"0"//{{10}}; "0"//{{15}}; "0"//{{16}};
""//{{13}}; "true"//{{14}}; {2}|1..-2~ & "
"//{{3}}/b; loop({{3}}|*''*)/a//sysvar1] |
начало блока внешней функции: установка начальных значений
счётчиков в ячейках общего архива, добавление имени функции в качестве первой
лексемы в начало списка глобальных имён {{3}} и запись списка полексемно в
системную переменную sysvar1, {{14}} - признак функции |
|1[{{8}}<>""]|{% *function
}{[_A-Za-z][_A-Za-z0-9]* *(} |
#[{{6}}|-1''-1 + 1//{{6}}|-1''-1; {{6}} & "
0"//{{6}}; {{8}} & " 0"//{{8}}; {{9}} & "
0"//{{9}}; "true"//{{14}}; {{5}} + 1//{{5}}; {2}|1..-2~ &
" "//{{2}}/b; "_" & {{5}} & "
"//{{1}}/b; {{1}}|1''1//{2}|1..-2~] |
начало блока вложенной функции: начальные значения
счётчиков добавляются как лексемы следующего уровня в ячейки архива,
добавление в начало списка {{2}} найденного имени функции, в начало списка
{{1}} добавление имени замены и замена на него имени функции |
|1|{/@*##B@*/} |
#["true"//{{7}}] |
начало блока пропускаемых последующих символов (установка
признака) |
|1|{/@*##E@*/} |
#[""//{{7}}] |
конец блока
(снятие признака) |
|1[{{7}}=true]|{?} |
|
пропуск
символа |
1[{{14}}=true]|{[_A-Za-z][_A-Za-z0-9]* *,} |
#[{{5}} + 1//{{5}}; {1}|1..-2~ & "
"//{{2}}/b; "_" & {{5}} & " "//{{1}}/b;
{{6}}|-1''-1 + 1//{{6}}|-1''-1; {{1}}|1''1//{1}|1..-2] |
непоследний параметр функции: увеличивается счетчик
параметров текущего уровня (последняя лексема в списке {{6}}), корректируются
списки {{1}} и {{2}}, производится замена параметра |
|1[{{14}}=true]|{[_A-Za-z][_A-Za-z0-9]* *)} |
#[{{5}} + 1//{{5}}; {1}|1..-2~ & "
"//{{2}}/b; loop({{2}}|*''*)/a//sysvar; "_" & {{5}} &
" "//{{1}}/b; ""//{{14}}; {{6}}|-1''-1 + 1//{{6}}|-1''-1;
{{1}}|1''1//{1}|1..-2] |
последний параметр функции: дополнительно производится
перепись списка {{2}} полексемно в системную переменную sysvar и снятие признака функции (очистка
{{14}}) |
|1|{var *[_A-Za-z][_A-Za-z0-9]* *,} |
#[{{5}} + 1//{{5}}; {1}|~5..-2~ & "
"//{{2}}/b; loop({{2}}|*''*)/a//sysvar; "_" & {{5}} &
" "//{{1}}/b; "true"//{{13}}; {{6}}|-1''-1 + 1//{{6}}|-1''-1;
{{1}}|1''1//{1}|5..-2] |
описание локального параметра через
оператор var: {{13}} - признак анализа переменных из оператора var |
|1[{{13}}=true]|{ *[_A-Za-z][_A-Za-z0-9]* *,} |
#[{{5}} + 1//{{5}}; {1}|1..-2~ & "
"//{{2}}/b; loop({{2}}|*''*)/a//sysvar; "_" & {{5}} &
" "//{{1}}/b; {{6}}|-1''-1 + 1//{{6}}|-1''-1;
{{1}}|1''1//{1}|~1..-2] |
описание локального параметра с присваиванием через var (элемент списка var) |
|1[{{13}}=true]|{;} |
#[""//{{13}}] |
снятие
признака оператора var |
|1|{.[_A-Za-z][_A-Za-z0-9]*}+ |
#[BlockOper(setcn,0,0,1)] |
пропуск до конца сложного идентификатора и возврат на
начало таблицы контекстов |
|1[{1}|~1..-2~=sysvar]|{[_A-Za-z][_A-Za-z0-9]*[:<>!,/)@]@?@[@*@|@+@-&^@~]} |
#[{{1}}|r*''r*//{1}|~1..-2~] |
поиск и замена идентификатора: поиск производится по
списку лексем sysvar (аналог
списка {{2}}) и если идентификатор найден (номер лексемы совпадения
определяется r*),
то он заменяется на
соответствующую лексему из списка {{1}} |
|1[{1}|~1..-2~=sysvar]|{[_A-Za-z][_A-Za-z0-9]*.} |
#[{{1}}|r*''r*//{1}|~1..-2~;
BlockOper(setst,0,0,-1); BlockOper(setcn,0,0,1)] |
поиск и замена начала сложного имени, после замены возврат
по строке на символ назад (перед точкой) и на начало таблицы |
|1|{@{} |
#[{{8}}|-1''-1
+ 1//{{8}}|-1''-1] |
подсчет открывающих фигурных скобок |
|1[{{8}}|2''2="" :: {{8}}|-1''-1 <> {{9}}|-1''-1 + 1 ::
sysLval=sysRval]|{@}} |
#[BlockOper(setst) :: {{9}}|-1''-1 + 1//{{9}}|-1''-1 ::{{8}}|1''-2//{{8}};
{{9}}|1''-2//{{9}}; {{6}}|-1''-1//{{20}}; {{1}}|{{20}}+1''-1//{{1}};
{{2}}|{{20}}+1''-1//{{2}};
loop({{2}}|*''*)/a//sysvar; {{5}}-{{20}}//{{5}}; {{6}}|1''-2//{{6}}] |
подсчет закрывающих фигурных скобок для вложенных функций:
при равенстве числа открывающих и закрывающих фигурных скобок текущего уровня
списки {{1}} и {{2}} усекаются от начала на число лексем, равное количеству
переменных текущего уровня |
|1[{{8}}|1''1
<> {{9}}|1''1 + 1]|{@}} |
#[{{9}}|1''1
+ 1//{{9}}|1''1] |
скобок для невложенной функции подсчет закрывающих
фигурных |
|1[{{8}}|1''1
= {{9}}|1''1 + 1]|$${@}} |
#[""//{{8}};
""//{{9}}; ""//{{10}}; ""//{{2}};
""//{{1}}; ""//sysvar] |
конец блока функции при равенстве открывающих и закрывающих
фигурных скобок первого уровня |
На третьем этапе из файла удаляются
символы перевода строк. Таблица контекстов загружается из файла javcon300s.txt
и содержит следующий контекст:
Контекст
поиска: |
Контекст
замены: |
Комментарий: |
\0d |
% |
удаление
символа перевода строки \0d |
Прогон файлов через инструментальный комплекс TeConv дал следующие результаты:
Имя файла ======== |
Исходный размер (kb) ================= |
Выходной размер (kb) ================= |
K_Alib1 |
12 |
10 |
K_Alib2 |
24 |
19 |
K_Alib3 |
27 |
21 |
K_Alib4 |
71 |
60 |
K_Alib5 |
32 |
25 |
K_Alib6 |
31 |
27 |
K_Alib7 |
14 |
11 |
K_Alib8 |
5 |
4 |