СЖАТИЕ ФАЙЛОВ, НАПИСАННЫХ НА ЯЗЫКЕ JAVASCRIPT

 

Постановка задачи.

Программные файлы, написанные на языке 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