День 10. Макет справки

День 10. Макет справки

Регистро-нечуствительный поиск. Проектирование библиотеки работы со списками.

Сегодня решил сделать поиск справки нечуствительной к регистру. Идея проста: если слово не найдено в словаре, перевести его имя в верхний регистр (ИмяСлова -> ИМЯСЛОВА) и опять попытаться найти. Если и на это раз не будет найдено, выдать сообщение. Примерно так же реализован поиск встроенной помощи SP-Forth – файл lib\ext\help.f

Нужно написать слово, которое переводило бы строку в верхний регистр. То что предлагалось в help.f в слове TO-UPPER имело один недостаток – не конвертило русские буковки. Идея там была проста, известно, что в кодовой таблице ASCII коды строчных и заглавных латинских букв отстоят 32 позиции. Соответсьвенно, нужно либо добавлять смещение, либо отнимать. В оптимизированном варианте, это делалось битовыми операциями, смещение-то 32!

Я выбрал другой путь. Более универсальный. А может и более привычный. По аналогии как это делается в Perl, через оператор перекодирования tr:

  $str =~ tr/abcdef/ABCDEF/ ;

Первый вариант был реализован как совокупность слов, выполняющих относительно простые операции – поиск адреса указанного символа в подстроке; выполняющее замену одного символа на симводл в верхнем регистре, если таковой для него существует; и главное слово – выполняющее замену всех сивмолов подстроки.

: FindChar ( char c-addr u – index TRUE | FALSE )
   0 DO          ( char c-addr )
 DUP I + C@  ( char c-addr c1 ) \ Извлек очередной символ подстроки
     2 PICK      ( char c-addr c1 char )
     = IF   \ Символы совпали
       2DROP I TRUE LEAVE \ выход
 THEN
 LOOP ( char c-addr | ind )

DUP 10000 > IF \ если число меньше 10000, то это адрес строки 2DROP FALSE \ и значит число найдено не было THEN ;

: UCaseChar { chr } \ Переводит в верхний регистр и русские буквы chr S" qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъфывапролджэячсмитьбюё" FindChar IF \ Есть индекс S" QWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ" DROP SWAP + C@ ELSE chr THEN ;

: UCase { c-adr u } u 0 DO c-adr I + C@ UCaseChar c-adr I + C! LOOP CR c-adr u ;

\ Тестирование : test S" ТестИРОвание" UCase TYPE ; test

Здесь промучался с отладкой довольно долго. Где-то ошибался с распределением данных на стеке. Если добавить относительно долгий процесс полуинтерактивного диалога в Far-е из-за многочисленных вспомогательных действий руками, то становилось грустным. Когда вдоволь намучавшись я перешел к использованию локальных переменных, то все заработало со второго раза. Эффект был потрясающим. Думаю, это надолго у меня отбило охоту вручную манипулировать стеком.

Но мне хотелось сделать таки Перловский вариант. И я написал слово tr по аналогии с перловским. Здесь я уже использовал вовсю локальные переменные и все получилось просто здорово. Несмотря на большой роазмер слова, его почти не пришлось отлаживать.

: tr { c-str ustr c-from ufrom c-to u-to \ chr }
  \ Циклом прохожу по всем символам оригинальной подстроки  
  ustr 0 DO
  c-str I + C@  -> chr \ символ подстроки
                       \ ищу его индекс в подстроке from
 FALSE             \ кладу на стек флаг, что символ не был найден
     ufrom 0 DO
       c-from I + C@
       chr = IF       \ нашел его
 DROP I TRUE \ поменял на стеке флаг и положил индекс найденного 
 LEAVE THEN
 LOOP

IF \ Если символ был найден, нахожу ему замену и заменяю его DUP u-to 1+ < IF \ на случай разных длин подстрок c-to + C@ \ взял символ вместо c-str I + C! \ и записал его вместо текущего THEN THEN LOOP c-str ustr ;

: test S" AS-90-90 RT привет!" S" qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъфывапролджэячсмитьбюё" S" QWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ" tr TYPE ; test

Зато здесь наступил на другие грабли. Фрагмент тестирвоания был запсиан просто как последовательность слов:

 S" AS-90-90 RT привет!"
 S" qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъфывапролджэячсмитьбюё"
 S" QWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ"
 tr TYPE

И оно конечно не работало. Потому, что слово S" в интерактивном режиме (а именно в нем и находился) размещает подстроку во временном буфере, который тут же затирает новой. Зато, если завернуть этот фрагмент в определение слова, что и было сделано, то слово S" рассматривает подстроки как некие контсанты и размещает их по разным адресам. Вспомнил я это быстро, но все равно было досадно.

Дальше определил два нужных мне слова

: CASE ( c-adr u ) \ Переводит слово в верхний регистр
 S" qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъфывапролджэячсмитьбюё"
 S" QWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ"
 tr
;      ( c-adr u )

: case ( c-adr u ) \ Переводит слово в нижний регистр S" QWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ" S" qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъфывапролджэячсмитьбюё" tr ; ( c-adr u )

вот где регистрозависимость SP-Forth кстати!

Появилось одно, довольно интересное соображение. Классический подход, рекомендуемый в литературе по Forth сводится к тому, что бы разбиваться всю задачи на очень мелкие поздачи, буквально четыре-пять строчки и по одиночке их отлаживать. Думаю, что это требование возникло из-за сложности ручного манипулирования данными на стеке. В случае маленьких фрагментов – это делать довольно просто. Но если же использовать технику именнованных локальных перемеенных, то подобное требование становится лишним. По кол-ву имеенованных объектов оба подхода примерно одинаковые, различие в типах этих объектов. В первом случае, это сплошняком слова, а вот втором это слова и локальные переменные.

Таким образом, макет справочной системы приобрел вид

VOCABULARY HELPS
ALSO HELPS DEFINITIONS

: DUP ." = stack (n - n n) " CR ; : DROP ." = stack (n - ) " CR ;

PREVIOUS DEFINITIONS

: manman ." Справка. Использование: man слово" CR ;

: tr { c-str ustr c-from ufrom c-to u-to \ chr } \ Циклом прохожу по всем символам оригинальной подстроки ustr 0 DO c-str I + C@ -> chr \ символ подстроки \ ищу его индекс в подстроке from FALSE \ кладу на стек флаг, что символ не был найден ufrom 0 DO c-from I + C@ chr = IF \ нашел его DROP I TRUE \ поменял на стеке флаг и положил индекс найденного LEAVE THEN LOOP

IF \ Если символ был найден, нахожу ему замену и заменяю его DUP u-to 1+ < IF \ на случай разных длин подстрок c-to + C@ \ взял символ вместо c-str I + C! \ и записал его вместо текущего THEN THEN LOOP c-str ustr ;

: CASE S" qwertyuiopasdfghjklzxcvbnmйцукенгшщзхъфывапролджэячсмитьбюё" S" QWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮЁ" tr ;

: man ALSO HELPS NextWord \ Беру следующее слово после man ( с-addr u )

DUP 0= IF 2DROP manman EXIT THEN \ если ничего не ввели

SFIND \ ищу это слово в словаре \ если слово не найдено, то (с-addr u 0) 0= IF CASE SFIND \ Регистро-нечувствит: перевожу его в верхний регистр 0= IF 2DROP ." Слово не найдено" CR PREVIOUS EXIT THEN THEN \ Если слово было найдено то (ИДслова 1|-1) DUP ['] HELPS \ слова из HELPS будут иметь ИД больше самого ИД словаря, \ т.к. были определены позже него > IF EXECUTE PREVIOUS \ если слово из этого словаря, то выполняю его ELSE DROP ." Don't find" CR PREVIOUS EXIT \ в противном случае -- выход THEN ;

Удобный символ для строчного комментария, одинаково набирается при любой раскладке, безумно удобно.

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

Предпринял попытку сделать поиск словарей не через механизм словарей, а через обычный связанный список. Для этого нужно спроектировать библиотеку для работы со списками строк. Нашел таковую devel\~ac\lib\list\STR_LIST.F В принципе идея понятна, лежащая в ее основе – для каждого узла выделяем память, куда записываем служебное поле, где храним указатель на предыдущий узел.

Я решил что так как это будет структура чтение, сделать выделение единоразово, и в линейный участок памяти уложить плотно все пары "имя – данные". И по ним перемещаться. Структура обрисовалась быстро. Набор оперторов тоже. Начал реализовывать. Очень сильно не достает механизма работы со структурами. Пошли большие ошибки из-за ошибок в укаазтелях – т.к. приходиться в ручную иммитирвоать работу с отдельными полями структуры. Можно конечно писать отдельные слова... Но когда набор слов превышает десяток становится грустновато...

Выяснилось еще одно, очень полезное свойство именнованных локальных переменных – если я принял неудачное размещение данных на стеке для проектируемого слова, то в дальнейшем оно очень просто меняется, путем редактирвоания нужного порядка в скобках { ... } без необходимости перепроектирования всего слова.

<<< Предыдущий Начало   Следующий >>>
Copyright © Alex Furashev 2004

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

Hosted by uCoz