FORTH: POP3 наноклиент. Часть 2

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

Что достаточно знать из POP3 протокола. Пара команд USER PASS нужна для логина, STAT чтобы получить количество писем в ящике и RETR для получения самого письма. Мы не будем удалять письма командой DELE. Команду QUIT будем отдавать из вежливости.
Посмотрим на команды повнимательнее. Можно заметить, что их можно разделить на две группы: команды без аргументов NOOP QUIT STAT RSET и команды, имеющие аргумент: DELE LIST RETR TOP APOP USER PASS. Причем последние три в качестве аргумента требуют текстовых строк. Строго говоря, все команды требуют строкового аргумента. В одном случае это пустая строка, в другом строка состоит из десятичных цифр, в третьем из произвольных символов.

Рассмотрим самый простой случай, безаргументные команды. Можно написать в лоб

S" NOOP" sockt WriteSocketLine THROW и так 4 раза. 
S" DELE" sockt WriteSocket THROW     S>D (D.) sockt WriteSocketLine THROW 
тоже 3 раза

( TOP APOP не будем реализовывать, а с USER PASS пока не будем мудрить. )


Но мы же пишем на Форте, а в нем есть изящный механизм создания определяющих слов. Используем его.
Для начала вынесем sockt WriteSocketLine THROW и buf bufsize sockt ReadSocket THROW TO bytes_read в отдельные определения. Лень уже выписывать столько букв.
: >sockt   
              sockt WriteSocketLine THROW ;

: sockt>
              buf bufsize sockt ReadSocket THROW TO bytes_read ;



И начнем создавать создающие слова

: pop3_noarg 
                     CREATE LATEST , 
                     DOES> @ COUNT  >sockt   ; 

: pop3_noargs 
                    0 DO pop3_noarg LOOP ;

4 pop3_noargs NOOP QUIT STAT RSET 


Таким образом, тремя строчками мы определили четыре однотипных команды. Поясню немного поподробнее, что же здесь происходит.
Через двоеточие определено новое слово pop3_noarg, со следующим действием: выбрать из входного потока литеры, ограниченные пробелом, использовать их для создания на вершине словаря слова с действием VARIABLE. Это все делает слово CREATE, в момент исполнения слова pop3_noarg. Следующее слово LATEST кладет на стек адрес поля имени определяемого слова, например NOOP. Запятая компилирует значения со стека на вершину словаря. Если бы далее не было части DOES>, вызов NOOP положил бы на стек некий адрес, разыменовав который мы получили бы адрес строки со счетчиком, превратив который в адрес строки счетчик словом COUNT мы могли бы напечатать ее словом TYPE.
В результате увидели бы NOOP. Словами выглядит очень сложно, быстрее попробовать самостоятельно в командной строке форт-системы.
Что же далее? Далее у нас присутствует самое загадочное слово Форта DOES>. Достаточно осознать, что это слово меняет действие по-умолчанию слова, созданного через CREATE, на действие, которое мы пожелаем. Все, что написано после DOES> является общим действием для всех слов, определенных через pop3_noarg. Здесь оно заключается в получении адреса и счетчика строки — имени слова и отправки его в сокет.

: pop3_digiarg 
               CREATE LATEST , 
               DOES>    @ >R       <#    S>D  #S     BL HOLD     R> COUNT HOLDS #>   >sockt   ;

: pop3_digiargs  0 DO pop3_digiarg LOOP ;

3 pop3_digiargs  DELE LIST RETR 


Очень похоже на предыдущую группу слов с заметным отличием в части DOES>. Понятно, что что-то отправляется в сокет. А вот что?
Здесь мы используем еще одну замечательную возможность Форта. У нас есть прекрасное слово (D.) преобразующее число двойной точности (64 бит) со стека в строку. И мы могли бы его легко использовать, но для этого нам бы пришлось написать что-то вроде

S" LIST" sockt WriteSocket S>D (D.) >sockt


Не страшно, но как-то уныло.

Раз уж мы решили сделать красиво, почему бы не поискать другие решения. Если заглянуть в исходники Форта, и найти там определение (D.) можно увидеть что оно состоит из <# #S #>. Эти три слова изобретены еще Чаком Муром и раскладывают преобразование числа в строку на три этапа. Первый — подготовка преобразования, второй — собственно преобразование, и третий — выдача результата. Из-за того, что преобразование числа происходит с младших разрядов, а печать идет со старших разрядов — для преобразования нужен некоторый временный буфер, куда будут складываться цифры и откуда будет забираться результат. Размер буфера берется с хорошим запасом. Слово HOLD как раз и убирает очередной символ со стека в буфер. HOLDS, множественное число от HOLD, делает то же самое с несколькими символами ака строкой. Пара слов >R R> используется как легкая подручная реализация локальных переменных, когда надо некоторое значение со стека ненадолго отложить.
Подобная эквилибристика позволяет не заводить дополнительные буфера, не копировать одно и тоже в кучу мест, не возиться с библиотеками для слияния строк, а сделать все в системном буфере практически предназначенном для этой цели.

Теперь посмотрим на ответы сервера. Их всего два: +OK или -ERR, за которыми могут идти, а могут не идти какой-нибудь текст или числа. Было бы прелестно, если бы ответы сервера как-нибудь сами делали всю работу. Чтобы нам не приходилось скакать по бесконечным ветвлениям. И Форт может нам это позволить.
Представим буфер, в котором сохранен ответ сервера как входной поток для интерпретации. Словом EVALUATE мы можем интерпретировать произвольную строку. Используем эту возможность.
Ответ сервера всегда начинается с +OK или -ERR. Вложим же смысл в эти ответы.

Проще всего с -ERR. В случае ошибки неплохо бы вывести остаток строки на терминал и прервать исполнение программы.

 : -ERR     1 PARSE TYPE CR ABORT ; 


PARSE служит для получения хвоста интерпретируемой строки.

+OK не так прост. Он всегда выскакивает, если команда обработана успешно. Говорит нам все хорошо, но каков контекст этого хорошо?
Первый +OK сообщает об успешном подключении к серверу. Нам надо принять этот факт и вывести на терминал приветствие сервера.
Второй и третий +OK возникают в процедуре логина. Просто игнорируем остаток строки.
Четвертый +OK появляется в ответ на STAT и к нему, к +OKею, прилагаются два числа. Первое — количество сообщений в ящике. Второе — число попугаев, занимаемых ними. Попугаи нам не нужны. Значит нам надо будет превратить текстовое число из ответа сервера в число на стеке, а остаток строки игнорировать.
Пятый +OK сообщит что следом за ним будет нужное нам письмо. Тут есть один нюанс в протоколе. Во-первых, пауза между ответом сервера и передачей письма. Во-вторых, два числа следом за +OK — номер сообщения и его размер в попугаях.
Шестого +OK, можно не ждать, а можно обработать также, как и первый.
Итак, +OK должен уметь три разных действия.
Форт позволяет обойтись без флагов и сложной логики. В нем есть так называемые векторные слова. Слова, действие которым можно назначить в любой нужный момент.

VECT +OK

: do_type       1 PARSE TYPE CR ;
: do_ignore    1 PARSE 2DROP ;
: do_number     1 PARSE ?SLITERAL ;
: do_msg         
                sockt>                 
                BEGIN   buf bytes_read  TYPE CR 
                bytes_read  bufsize - 0< 0= WHILE  sockt>  REPEAT do_ignore ;



Теперь у нас есть все, чтобы собрать код воедино.

Код

~nn/lib/sock2.f  

0 VALUE sockt
0 VALUE bytes_read
0 VALUE num_of_msgs

8192 CONSTANT bufsize
bufsize ALLOCATE THROW CONSTANT buf
 
MODULE:

: >sockt   
              sockt WriteSocketLine THROW ;

: sockt>
              buf bufsize sockt ReadSocket THROW TO bytes_read ;

: response  
                    sockt>
                     buf bytes_read EVALUATE ;


: pop3_noarg 
                     CREATE LATEST , 
                     DOES> @ COUNT  >sockt   response ; 

: pop3_noargs 
                    0 DO pop3_noarg LOOP ;

4 pop3_noargs NOOP QUIT STAT RSET


: pop3_digiarg 
               CREATE LATEST , 
               DOES>    @ >R       <#    S>D  #S     BL HOLD     R> COUNT HOLDS #>   >sockt  response  ;

: pop3_digiargs  0 DO pop3_digiarg LOOP ;

3 pop3_digiargs  DELE LIST RETR


: user              S" USER username"  >sockt response  ;
: pass             S" PASS password"  >sockt  response ;


VECT +OK

: do_type           1 PARSE TYPE CR ;
: do_ignore         1 PARSE 2DROP ;
: do_number    1 PARSE ?SLITERAL do_ignore ;
: do_msg         
                sockt>                 
                BEGIN   buf bytes_read  TYPE CR 
                bytes_read  bufsize - 0< 0= WHILE  sockt>  REPEAT do_ignore ;

: -ERR    do_type ABORT ;


: start_sockets   
                       SocketsStartup THROW
                       CreateSocket THROW TO sockt ;

: connect_to_host
                      GetHostIP THROW 
                     110 sockt ConnectSocket THROW 
                      response  ;

\   Ниже исполняемый код. Выше - необходимые определения.

start_sockets

' do_type TO +OK 

S" pop.server.com" connect_to_host 

' do_ignore TO +OK

user  pass  

' do_number TO +OK 

STAT   TO  num_of_msgs

' do_msg TO +OK 
num_of_msgs RETR  

' do_type TO +OK
QUIT


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

FORTH: наносервера и наноклиенты. Часть 1

PS: При написании топика Хабр несколько пострадал.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 12

    +2
    Как я люблю все это волшебство определений слов!
    Почему то это ложится куда приятнее чем всяческие темплейты и фабрики. Возможно ностальгия, а, возможно, что-то большее.

    Мы сейчас очень пристально рассматриваем вариант разработки наших алгоритмов sense cognition на forth.

    Единственное, что пока вызывает взрыв не очень конструктивной фантазии, это хорошая форт-архитектура работы с базой данных. Нет ли у Вас каких-нибудь идей по этому поводу?
      +1
      Для работы с базами данных есть наработки еще у Андрея Черезова и Дмитрия Якимова. То-есть какие-то библиотеки в комплекте есть. Разбираться нужно с этим. С другой стороны, если хорошо знать предметную область, можно наработать необходимые абстракции. Собственно, хорошо поставленное задание уже является решением на Форте. Вполне возможно, что решение Вашей проблемы с базой данных лежит в иной плоскости.
        0
        А какие из реализаций Форта сегодня наиболее актуальны? Раньше вот sp-forth Андрея Черезова очень нравился, его и под Линукс портировали, а потом все заглохло.
          0
          Это и есть SP-Forth. В предыдущем топике давали ссылку на GForth.
            0
            Да, я видел. Интересно, есть ли развитие в форт-машинах. К примеру sp-forth практически 5 лет не движется вперед, есть отдельные форки (64битная версия spf, к примеру), но особого комьюнити у них нет.
              0
              Непонятно, какое развитие требуется от форт-машин. Сама по-себе форт-машина реализуется для любой железки за несколько рабочих дней. Тот же GForth портирован на кучу платформ. Надо просто пользоваться языком. Никто ведь не мешает в SP-Forth определить, например, команду CRC-32, которая есть в системе команд современных процессоров. Очень мало народу пишет на форте. И платить деньги за разработку на нем никто не хочет. Комьюнити организовать или найти не проблема. Часть его вот она, прямо здесь.
                0
                Чрезвычайно интересное применение Форта на микропроцессорах. Сам использовал прошивку AmForth на AVR Atmega. Показывая другим, вызывал легкий шок, как это, используя только отладочную плату, удается написать, «откомпилировать» и выполнить программку. Даже простую, помигать диодиками. И с хорошим таймингом. И там поле не паханное для развития: полноценная диагностика на работающем устройстве, целевая компиляция, разработка IDE.
                  0
                  Конечно. В такие штучки Форт ложится очень хорошо. Форт-машина, по хорошему, штука слоистая.
                  1-й уровень — опкод-ассемблер
                  2-й уровень — ассемблер-низкоуровневое слово Форта, всякие + @ и т.п., причем совсем необязательно стараться написать их все, что есть в стандарте.
                  3-й уровень — высокоуровневые определения через: CREATE VARIABLE и т.п. Вот определяющие слова, двоеточие, СREATE DOES> реализовывать обязательно. Это фундамент следующего,
                  4-го уровня — уровня приложений.
            +1
            Внезапно, а обратите внимание на FACTOR
              0
              В предыдущем топике было дело. Внешний вид похож, да. Идеология иная. Стеки, словарь, синтаксис — это часть, антураж.
              Главное в Форте — возможность создания собственных определяющих слов. Все языки похожи на чемодан с инструментами, или кладовку где лежат чемоданы инструментов. В Форте есть один инструмент, с помощью которого можно сделать любой нужный инструмент. Этакая звуковая отвертка.
                0
                Да, то, что мне нравится в философии Форта — это инструмент создания инструментов, маленький инструментальный цех :)
                То есть по сути это инструмент для создания под каждую задачу своего диалекта (если уж не языка) программирования. Форт определяет минимальный исходный синтаксис.

                И вот для нашего случая, когда существующие языки программирования все равно не предлагают необходимых структур данных и их нужно определять с весьма низкого уровня, то делать это нужно либо на C, определяя низкоуровневую библиотеку, а затем над ней надстраивая высокий уровень, либо на Форте. При этом, за несколько часов можно освоить программирование (я даже несколько сомневаюсь, что слово программирование реально подходит) на Форте куда эффективнее, чем на любом другом языке.
                0
                Обратил, но, натукнувшись на blogs.trust.ua/levenchuk/2010/05/11/505/Novii-Forth----yazik-programmirovaniya-Factor/ заинтересовался

                Кстати, по поводу Smalltalk: Алан Кей грязно ругается по поводу нынешних версий этого языка, говоря, что первоначальную идею объектов современные (начиная с 1980г.) версии Smalltalk испортили и исказили в угоду производительности на текущих архитектурах железа. А поначалу в Smalltalk объекты были не пассивными, а вполне активными и автономными, то бишь не столько «объектами», сколько акторами (http://en.wikipedia.org/wiki/Actor_model). Это было в том числе в Smalltalk-71. Следующая версия языка работала в 180 раз быстрее, но вся красота из нее ушла — и дальше неожиданные рассуждения о том, что в те акторские поры родственником Smalltalk был PLANNER и General Problem Solver, т.е. логическая парадигма программирования. В FONC эту идею «объектов-акторов» поднимают на щит в виде двух идей: логического языка и парадигмы «программирования как планирования» (ага, это от PLANNER, оказывается), а также идеи выдать каждому объекту собственный IP-адрес в интернете (ибо этот объект вполне автономен и мало ли где может находиться — тут и до SOA недалеко).

                Опять же, акторские языки продолжают цвести и пахнуть до сих пор — хотя так же нишево, как и стековые (concatenative languages). Вот пример: ABE (Actor-Based Environment) и его язык Humus — dalnefre.net/drupal/node/2. Автор этого проекта Dale Shumacher говорит, что идеи FONC/STEP уже были учтены в этом проекте.

        Only users with full accounts can post comments. Log in, please.