В настоящее время, проект перешел в фазу отладки активационной логики, предусматривающую гораздо более интенсивное изменение активационных скриптов. В таких условиях, наличие инструмента, позволяющего сформировать текстовое представление активационного скрипта, а также залить скрипт обратно в БД, после внесения изменений, позволит значительно увеличить продуктивность работы. Проблеме синтаксического разбора скрипта и загрузки его в AST-представление в БД и посвящена эта статья.
Напомню, что скрипт выглядит следующим образом:
[001420] target:ats.type; foreach (params) { [003101] platform:S-12; if (dou_off.dou = 'REDIRECT_NOANSWER') { [031010] < text:MODIFY-SUBSCR:DN=K'%s,CFWD=DEACT&CFWDNOR.; var_list:phone; [001041] > regexp:(.+); var_list:error_text; is_error:1; [001008] platform:M-200; var_list:is_redirect_param = 1; } [003111] platform:S-12; if (dou_off.dou = 'REDIRECT_BUSY') { [031110] < text:MODIFY-SUBSCR:DN=K'%s, CFWD=DEACT&CFWDBSUB.; var_list:phone; [001041] > regexp:(.+); var_list:error_text; is_error:1; [001008] platform:M-200; var_list:is_redirect_param = 1; } [003121] platform:S-12; if (dou_off.dou = 'REDIRECT_AUTOINF') { [031210] < text:MODIFY-SUBSCR:DN=K'%s, CFWD=DEACT&CFWDFIXA.; var_list:phone; [001041] > regexp:(.+); var_list:error_text; is_error:1; [001008] platform:M-200; var_list:is_redirect_param = 1; } [003131] platform:S-12; if (dou_off.dou = 'REDIRECT') { [031310] < text:MODIFY-SUBSCR:DN=K'%s, CFWD=DEACT&CFWDUVAR.; var_list:phone; [001041] > regexp:(.+); var_list:error_text; is_error:1; [001008] platform:M-200; var_list:is_redirect_param = 1; } [003071] platform:S-12; if (dou_off.dou = 'SET_ALARM_CLOCK') { [030710] < text:MODIFY-SUBSCR:DN=K'%s,ALMCALL=DEACT.; var_list:phone; [001041] > regexp:(.+); var_list:error_text; is_error:1; [001009] var_list:is_alarm_param = 1; } }
Каждая строка скрипта (за исключением закрывающихся процедурных скобок) определяет скрипт или команду. Команда определяется символами ‘<‘ и ‘>’, определяющими направление передачи данных (на оборудование и с него). Со скриптом или командой могут быть связаны настройки, определяемый следующей последовательностью:
<Имя настройки>:<Значение>;
Для настроек ‘if_condition’ и ‘foreach_var’, предусмотрены специальные синтаксические конструкции ‘if’ и ‘foreach’ похожие на аналогичные операторы привычных нам императивных языков.
Важной, но не обязательной частью скрипта являются числа в квадратных скобках. Это рекомендуемые значения ID для размещения скрипта или команды в БД. Задав одинаковое значение ID для команд или скриптов, можно добиться повторного использования фрагмента скрипта (при условии того, что помеченные фрагменты действительно идентичны), разместив этот фрагмент в БД однократно. Если значение ID не задано, оно назначается автоматически, при загрузке скрипта в БД.
Первым шагом в разборе скрипта будет его лексический анализ. Нам необходимо разработать процедуру-сканер, последовательно просматривающую поток символов, читаемых DBMS_LOB и формирующую на выходе последовательность лексем.
Если бы мы разрабатывали парсер на языке C, мы могли бы использовать Lex для генерации сканера. К сожалению, для PL/SQL, аналогичного средства не предусмотрено. Впрочем, задача эта более громоздкая, чем сложная. Наш сканер будет выглядеть следующим образом:
create or replace package body ae_scripting as g_init_state constant number default 0; g_id_state constant number default 1; g_ch_state constant number default 2; g_name_state constant number default 3; g_value_state constant number default 4; e_syntax_error EXCEPTION; pragma EXCEPTION_INIT(e_syntax_error, -20001); ... procedure load(p_id in number) as l_lob CLOB; l_str varchar2(1000) default null; l_len number default null; l_pos number default 1; l_ix number default 1; l_state number default g_init_state; l_ch varchar2(1) default null; l_lexem varchar2(1000) default null; l_lvl number default 0; l_isb number default 0; l_cmd number default 0; l_prev varchar2(1000) default null; begin select text into l_lob from ae_script_src where id = p_id for update; dbms_lob.open(l_lob, dbms_lob.lob_readonly); l_len := dbms_lob.getlength(l_lob); while l_pos <= l_len loop l_str := dbms_lob.substr(l_lob, 1000, l_pos); l_ix := 1; while l_ix <= length(l_str) loop l_ch := substr(l_str, l_ix, 1); if l_lvl > 0 then if l_lvl = 1 and l_ch = ')' then l_prev := ''; lexem(g_value_state, l_lexem); l_lexem := ''; l_lvl := 0; else if l_ch = '(' then l_lvl := l_lvl + 1; end if; if l_ch = ')' then l_lvl := l_lvl - 1; end if; l_lexem := l_lexem || l_ch; end if; elsif l_lvl = 0 and l_ch = '(' and l_state = g_init_state then l_prev := ''; l_lvl := 1; l_lexem := ''; elsif l_ch = '{' then if l_state <> g_init_state then RAISE_APPLICATION_ERROR(-20001, 'Syntax error'); end if; l_isb := 1; elsif l_ch = '}' and l_isb = 1 then if l_state <> g_init_state then RAISE_APPLICATION_ERROR(-20001, 'Syntax error'); end if; l_isb := 0; elsif l_ch = '<' or l_ch = '>' or l_ch = '}' or l_ch = chr(13) then if l_ch = '<' or l_ch = '>' then l_cmd := 1; end if; if l_state <> g_init_state then RAISE_APPLICATION_ERROR(-20001, 'Syntax error'); end if; if l_ch = chr(13) and l_isb = 1 then lexem(g_ch_state, '{'); l_prev := ''; l_isb := 0; l_cmd := 0; end if; if l_ch <> '}' or l_cmd = 0 then if l_prev is null or l_prev <> l_ch then lexem(g_ch_state, l_ch); end if; if l_ch = chr(13) then l_prev := l_ch; else l_prev := ''; end if; end if; if l_ch = '}' then lexem(g_ch_state, l_ch); end if; l_lexem := ''; l_state := g_init_state; elsif l_ch = '[' then if l_state <> g_init_state then RAISE_APPLICATION_ERROR(-20001, 'Syntax error'); end if; l_lexem := ''; l_state := g_id_state; elsif l_ch = ']' then if l_state <> g_id_state or l_lexem is null then RAISE_APPLICATION_ERROR(-20001, 'Syntax error'); end if; lexem(l_state, l_lexem); l_prev := ''; l_lexem := ''; l_state := g_init_state; elsif l_ch = ':' then if l_state = g_value_state then l_lexem := l_lexem || l_ch; else if l_state <> g_init_state then lexem(l_state, l_lexem); l_prev := ''; end if; l_lexem := ''; l_state := g_value_state; end if; elsif l_ch = ';' then if l_state <> g_value_state then RAISE_APPLICATION_ERROR(-20001, 'Syntax error'); end if; lexem(l_state, l_lexem); l_prev := ''; l_lexem := ''; l_state := g_init_state; elsif l_ch = ' ' or l_ch = chr(9) or l_ch = chr(10) then if l_state = g_value_state then l_lexem := l_lexem || ' '; else if l_state <> g_init_state then lexem(l_state, l_lexem); l_prev := ''; end if; l_lexem := ''; l_state := g_init_state; end if; else if l_state = g_id_state or l_state = g_name_state or l_state = g_value_state then l_lexem := l_lexem || l_ch; elsif l_state = g_init_state then l_lexem := l_ch; l_state := g_name_state; end if; end if; l_ix := l_ix + 1; end loop; l_pos := l_pos + 1000; end loop; dbms_lob.close(l_lob); if g_level <> 0 then RAISE_APPLICATION_ERROR(-20001, 'Syntax error'); end if; exception when others then if dbms_lob.isopen(l_lob) = 1 then dbms_lob.close(l_lob); end if; raise; end; ... end ae_scripting; /
Несмотря на устрашающие размеры, этот код довольно прост. Мы последовательно просматриваем все символы CLOB-поля, изменяя состояние нескольких переменных и вызывая процедуру lexem по мере обнаружения лексем.
На этом этапе, полезно убедиться, что сканер действительно находит в скрипте требуемые лексемы, в нужной нам последовательности. Сделать это просто. Создадим таблицу-лог:
create sequence ae_script_lex_log_seq; create table ae_script_lex_log ( id number not null, state_id number, text varchar2(2000) ); create unique index ae_script_lex_log_pk on ae_script_lex_log(id); alter table ae_script_lex_log add constraint pk_ae_script_lex_log primary key(id);
и определим процедуру lexem следующим образом:
create or replace package body ae_scripting as ... procedure lexem(p_state in number, p_value in varchar2) as begin insert into ae_script_lex_log(id, type_id, text) values (ae_script_lex_log_seq.nextval, p_state, p_value); end; ... end ae_scripting; /
Теперь мы можем разобрать скрипт в CLOB-поле процедурой load и визуально убедиться в том, что лексемы найдены в требуемом порядке и содержат ожидаемые нами значения. В следующей статье, мы рассмотрим как можно использовать полученный поток лексем для загрузки скрипта в базу данных.
ссылка на оригинал статьи http://habrahabr.ru/post/165101/
Добавить комментарий