<?xml version="1.0" encoding="windows-1251"?>
<FictionBook xmlns="http://www.gribuser.ru/xml/fictionbook/2.0" xmlns:xlink="http://www.w3.org/1999/xlink">
<description>
  <title-info>
  <genre>sci_tech</genre>
  <genre>comp_osnet</genre>
  <genre>comp_programming</genre>
  <author>
   <first-name>Сергей</first-name>
   <last-name>Посохов</last-name>
   <home-page>http://www.r-5.org</home-page>
  </author>
  <book-title>Veronica Programming Language (05.05.2021)</book-title>
  <coverpage>
   <image xlink:href="#BookCover"/>
  </coverpage>
  <annotation>
   <image xlink:href="#BookCover"/>
   <empty-line/>
   <p>Книга содержит руководство по программированию на языке Veronica в операционной системе OS/2. Изложение ведётся от простого к сложному, позволяя изучить основы программирования начинающим и пополнить свои знания опытным программистам. Даётся подробное описание команд и основных функций языка (включая доступ к базам данных и многопоточную обработку сообщений), а также большое количество примеров.</p>
   <empty-line/>
  </annotation>
  <keywords>Veronica, OS/2, R-5, Selene</keywords>
  <date value="2021-05-05">05.05.2021</date>
  <lang>ru</lang>
  <src-lang>ru</src-lang>
  </title-info>
  <document-info>
  <author>
   <first-name>Сергей</first-name>
   <last-name>Посохов</last-name>
   <home-page>http://www.r-5.org</home-page>
  </author>
  <date value="2021-05-05">05.05.2021</date>
  <src-url>http://www.r-5.org</src-url>
  <id>veronica</id>
  <version>20210505</version>
  </document-info>
</description>
<body>
<!-- -#- -->

<section><!-- 1 -->
<title><p>Краткое описание</p></title>
<empty-line/>
<image xlink:href="#Veronica@About"/>
<empty-line/>
<p>
Veronica - язык программирования, основанный на записи выражений
с помощью скобок и использующий правила для работы с данными из
языка программирования Selene. И в том и в другом языке довольно
часто встречаются безымянные функции, а ветки операторов условия
и циклы в них - точно такие же функции, как и все остальные.
</p>
<empty-line/>
<p>
Запись выражений в скобках и слева направо может показаться
странной тем, кто раньше программировал на других языках.
Чтобы знакомство со скобочной записью не вызывало затруднений,
в этой книге есть примеры, раскрывающие особенности языка и
основные приёмы кодирования на нём. В следующей главе они
даются в двойной записи: на языке Veronica и на языке Selene.
</p>
<section><!-- 2 -->
<title><p>Несколько примеров</p></title>
<p>
Самая короткая программа на Selene и Veronica выглядит так:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   -&gt;                                                         (@ +-
     say 'Hello, World!'                                          (say 'Hello, World!')
   ;                                                          )</code>
</p>
<empty-line/>
<p>
Как видно, она печатает строку и завершается.
</p>
<empty-line/>
<p>
А вот так выглядит описание структуры данных:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   rgb &gt;&gt;                                                     (% rgb +-
     . : integer : 2^8                                            {integer : 2^8} .
     red, green, blue : integer : 2^8                             {integer : 2^8} red green blue
   ;                                                          )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   generic-rgb &gt;&gt;                                             (% generic-rgb +-
     . : #1                                                       {#1} .
     red, green, blue : #1                                        {#1} red green blue
   ;                                                          )
   
   rgb = generic-rgb [integer : 2^8]                          (% rgb : +-
                                                                  generic-rgb [integer : 2^8]
                                                              )</code>
</p>
<empty-line/>
<p>
И действия с ней:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   brightness -r = color -&gt;                                   (@ brightness:r color +-
     color.                                                       (" color.
       round ((red +                                                  (# round ((/ 3) (+ red
               green +                                                                   green
               blue) / 3)                                                                blue)))
     ;                                                            )
   _                                                          +
     color : rgb                                                  {rgb} color
   ;                                                          )</code>
</p>
<empty-line/>
<p>
Как видно, здесь описана структура из 4-х байт и над ней -
функция, позволяющая читать из неё поле "brightness", вот
так:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   -&gt;                                                         (@ +-
     say color.brightness                                         (say color.brightness -
     ' color : rgb = .                                                (% -rgb &gt; .color)
   ;                                                              )
                                                              )</code>
</p>
<empty-line/>
<p>
И на место обращения к этому полю будет подставлен вызов
функции:
</p>
<empty-line/>
<p>
<code>
   -&gt;                                                         (@ +-
     say brightness color                                         (say (# brightness color) -
         &#8593;                                                              &#8593;
   ;                                                              )
                                                              )</code>
</p>
<empty-line/>
<p>
Так что команда "say" напечатает рассчитанное (в данном
случае - нулевое) значение.
</p>
<empty-line/>
<p>
В следующем примере выполняется расчёт взвешенного среднего
по массиву или списку:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   weighted-average = list @fp -&gt;                             (@ weighted-average list @fp +-
     weight, divider, total = 0 ..                                (0 .. .weight .divider .total)
                                                                  (# records-in list (@ item +-
     - records-in list ( item -&gt;                                                         (+= 1 .weight)
                                 weight += 1                                             (+= (* weight (# fp item)) .total)
                                 total += (fp item) * weigh                              (+= weight .divider)
                                 divider += weight                                   )
                       )                                          )
                                                                  ([divider] &gt;
     if divider -&gt; total / divider                                    ((/ divider) total)
     else 0                                                       &gt;
   _                                                                  0
     list : A|L                                                   )
   ;                                                          +
                                                                  {A|L} list
                                                              )</code>
</p>
<empty-line/>
<p>
В списке могут быть записаны какие угодно данные: строки,
числа, структуры и даже другие массивы или списки. Из них
будут получены числа, для чего вызывается вспомогательная
функция, переданная на вход функции, рассчитывающей среднее.
Внутри она видна как параметр "fp()".
</p>
<empty-line/>
<p>
Вызов этой функции (и расчёт взвешенного среднего) в языке
Selene выглядит так:
</p>
<empty-line/>
<p>
<code>
   ++- Selene
   
   avg = weighted-average available-vacancies ( list-item -&gt;
                                                             list-item.salary
                                              )</code>
</p>
<empty-line/>
<p>
А в языке Veronica - вот так:
</p>
<empty-line/>
<p>
<code>
   ** Veronica
   
   (# weighted-average available-vacancies (@ list-item +-
                                               (list-item.salary)
                                           )
   &gt;
       .avg
   )</code>
</p>
<empty-line/>
<p>
Как видно, в точке вызова задаётся безымянная функция,
которая и получает число из каждой структуры данных.
В данном случае это поле с уровнем заработной платы в
объявлениях о работе.
</p>
<empty-line/>
<p>
В следующем примере - цикл с двумя счётчиками, который
выполняет разворот квадратной матрицы относительно её
главной диагонали:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   transpose = a + b -&gt;                                       (@ transpose a .b +-
     do {1..a._} i, {1..a.i._} j -&gt;                               (.* 1..a._ x 1..a.i._ j &gt;
       b.j.i = a.i.j                                                  (a.i.j .b.j.i)
     ;                                                            )
   _                                                          +
     a : array : array : I|F                                      {array : array : I|F} a
     b : array : array : I|F : [a]                                {array : array : I|F : [a]} b
   _                                                          +
     a.1._ = a._                                                  [= a._] a.1._
   ;                                                          )</code>
</p>
<empty-line/>
<p>
А здесь - цикл с условиями:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   while (chars file) -&gt;                                      (.* [# chars] file &gt;
     line = '' ..                                                 ('' .. .line)
                                                                  (# line-out output-file line -
     - line-out output-file line                                      (# transform string-8bit .line -
     ' line = transform string-8bit                                       (# line-in input-file .string-8bit)
     ' string-8bit = line-in input-file                               )
                                                                  )
     if (line = ';.') -&gt; leave                                    ([= ';.'] line &gt;
     else iterate                                                     -&gt;&gt;
   ;                                                              &gt;
                                                                      ;
                                                                  )
                                                              )</code>
</p>
<empty-line/>
<p>
Этот цикл будет выполняться до тех пор, пока функция
"chars()" в его заголовке будет возвращать единицу, а
когда она вернёт нулевое значение - цикл завершится.
</p>
<empty-line/>
<p>
В следующем примере - разворот массива:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   reverse = src + dest -&gt;                                    (@ reverse src .dest +-
     do {1..src._} i -&gt;                                           (.* 1..src._ i &gt;
       dest.i = src.((src._ + 1) - i)                                 (src.((- i) (+ src._ 1) .dest.i)
     ;                                                            )
   _                                                          +
     src : array : &lt;T&gt;                                            {array : &lt;T&gt;} src
     dest : array : T : [src]                                     {array : T : [src]} dest
   ;                                                          )</code>
</p>
<empty-line/>
<p>
И разворот списка с помощью рекурсии:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   reverse -rc = list + reversed -&gt;                           (@ reverse:rc list .reversed +-
     if list -&gt;                                                   ([list] &gt;
       detach list head tail                                          (detach list head tail)
                                                                      ([tail] &gt;
       if tail -&gt;                                                         (attach reversed (# reverse tail) head)
         attach reversed (reverse tail) head                          &gt;
       else                                                               (attach reversed head)
         attach reversed head                                         )
       ;                                                          )
     ;                                                        +
   _                                                              {list : &lt;T&gt;} list
     list : list : &lt;T&gt;                                            {list : T : [list]} reversed
     reversed : list : T : [list]                             )
   ;</code>
</p>
<empty-line/>
<p>
В языках Selene и Veronica параметры функций выглядят
как константы, и вместо изменения массива или списка
создаётся ещё один массив или список - но зато функции
будут работать даже в случае, когда данные были закрыты
от дальнейших изменений.
</p>
<empty-line/>
<p>
Просмотр дерева каталогов:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   files -rc = path mask @fp -&gt;                               (@ files:rc path mask @fp +-
     - files-in target ( name -&gt;                                  (# files-in target (@ name +-
                                 if (name IS_DIRECTORY) -&gt;                               ([IS_DIRECTORY] name &gt;
                                   - files name mask fp                                      (# files name mask fp)
                                 else                                                    &gt;
                                   - fp name                                                 (# fp name)
                       )                                                                 )
     ' target = path '/' mask                                                        )
   ;                                                              -
                                                                      (&#038; path '/' mask .target)
                                                                  )
                                                              )</code>
</p>
<empty-line/>
<p>
Здесь рекурсивная функция обходит дерево и вызывает себя
же (с именем каталога) или внешнюю функцию (с именем файла).
Эта задача примечательна тем, что расход памяти в ней зависит
от глубины дерева каталогов, но не зависит от количества файлов
в них: она требует O(1) памяти вне зависимости от того, сколько
файлов было записано в каталогах внутри дерева.
</p>
<empty-line/>
<p>
Подсчёт частоты встречаемости слов в тексте:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   frequency = text + fq -&gt;                                   (@ frequency text .fq +-
     nlog2n = max + n -&gt;                                          (@ nlog2n max .n +-
       n = max                                                        (max .n)
                                                                      (.* 2..max i &gt;
       do {2..max} i -&gt;                                                   ([&lt; max] (* i (# log2 i)) &gt;
         if (i * (log2 i) &lt; max) -&gt; n = i; iterate                            (i .n)
         else leave                                                       &gt;
       ;                                                                      -&gt;&gt;
     ;                                                                    )
                                                                      )
     fq : [length] = {} ..                                        )
     ' length = nlog2n spaces
     ' spaces - words-in text :: spaces ++                        (^ [length] .. .fq -
                                                                      (# nlog2n spaces .length -
     - words-in text ( word -&gt;                                            (# words-in text :: (++ .spaces))
                               if fq.!word -&gt; fq:word = 1             )
                               else fq:word ++                    )
                     )                                            (# words-in text (@ word +-
   _                                                                                   ([fq.!word] &gt;
     text : string                                                                         (1 .fq:word)
     fq : list : string &lt;- integer                                                     &gt;
   ;                                                                                       (++ .fq:word)
                                                                                       )
                                                                                   )
                                                                  )
                                                              +
                                                                  {string} text
                                                                  {list : string &lt;- integer} fq
                                                              )</code>
</p>
<empty-line/>
<p>
Эта задача требует приблизительно O(n*log2(n)) памяти
в зависимости от количества слов в строке. В ней, как
и в примерах выше, используется приём с заполнением
переменной, содержащей пустое значение, и закрытием её
от дальнейших изменений.
</p>
<empty-line/>
<p>
Быстрая сортировка:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   sort = src @compare + dest -&gt;                              (@ sort src @compare .dest +-
     dest = sort src SORT_ASC compare                             (# sort src SORT_ASC compare .dest)
   _                                                          +
     src : list : &lt;T&gt;                                             {list : &lt;T&gt;} src
     dest : list : &lt;T&gt; : [src]                                    {list : &lt;T&gt; : [src]} dest
   ;                                                          )
                                                              (@ sort:rc list order-by @compare .sorted +-
   sort -rc = list order-by @compare + sorted -&gt;                  (' .. .lesser .greater)
     lesser, greater = {} ..                                      (detach -e list 1st others)
                                                                  (.* others x &gt;
     detach -e list 1st others                                        ([# compare] x 1st order-by &gt;
                                                                          (attach greater x)
     for others x -&gt;                                                  &gt;
       if (compare x 1st order-by) -&gt; attach greater x                    (attach lesser x)
       else attach lesser x                                           )
     ;                                                            )
                                                                  ([list] &gt;
     if list -&gt;                                                       (attach sorted ls-sorted 1st gt-sorted -
       attach sorted ls-sorted 1st gt-sorted                              (# sort lesser compare order-by .ls-sorted
       ' ls-sorted = sort lesser compare order-by,                         # sort greater compare order-by .gt-sorted)
         gt-sorted = sort greater compare order-by                    )
     ;                                                            )
   _                                                          +
     order-by : SORT_* : {ASC DESC}                               {SORT_* : {ASC DESC}} order-by
   ;                                                          )</code>
</p>
<empty-line/>
<p>
Быстрая сортировка требует O(n*log(n)) действий, а расход
памяти в ней стремится к O(n^2), поскольку на каждом шаге
создаются два новых списка. Тем не менее, эта сортировка -
действительно одна из самых быстрых: у многих сортировок
время работы и расход памяти стремятся к O(n^2).
</p>
<empty-line/>
<p>
Следующий пример - команда "expect", позволяющая выполнять
регрессионные тесты:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   #&lt;- avg, regression                                        #&lt;- avg, regression
   
   -&gt;                                                         (@ +-
     expect avg a s (                                             (expect avg a s (@ +-
                      x = avg {}                                                      (# avg _' .x)
                    )                                                             )
     ' a = ERR_PARAMETER | ERR_EMPTY_*,                           -
       s = 'when list is empty'                                       (+ERR_PARAMETER+ERR_EMPTY_* .a)
   ;                                                                  ('when list is empty' .s)
                                                                  )
                                                              )</code>
</p>
<empty-line/>
<p>
В данном случае эта команда ждёт, что функция "average()"
выполнит команду "exit" с параметром "ERR_*" и завершится.
В таком случае тест считается пройденным - и это так и
задумано.
</p>
<empty-line/>
<p>
Доопределение вызываемой функции двумя безымянными
функциями в языке Selene:
</p>
<empty-line/>
<p>
<code>
   ++- Selene
   
   read-webpage = location + reply -&gt;
     - wget location ( received total -&gt;
                                         say "\v" received ' bytes received.'
                     )
                     ( rc -&gt;
                             reply = &lt;
                                       text : rc.data
                                       header : rc.header
                                     &gt;
                     )
     + ec, error &gt;
       handle ec error
     + ec &gt;
       catch ec
     ;
   
     if reply.header.code -&gt;
       &lt;&gt;
     else
       &lt;&lt;- ERR_NO_HTTP_CODE
     ;
   _
     location : string
     reply : wp-reply
   ;</code>
</p>
<empty-line/>
<p>
И в языке Veronica:
</p>
<empty-line/>
<p>
<code>
   ** Veronica
   
   (@ read-webpage location .reply +-
       (# wget location (@ received total +-
                            (say "\v" received ' bytes received.')
                        )
                        (@ rc +-
                            (%
                                rc.data   .text
                                rc.header .header
                            &gt;
                                .reply
                            )
                        )
       + ec error &gt;
           (# handle ec error)
       + ec &gt;
           (# catch ec)
       )
       ([reply.header.code] &gt;
           ;
       &gt;
           &lt;&lt;- (ERR_NO_HTTP_CODE)
       )
   +
       {string} location
       wp-reply {reply}
   )</code>
</p>
<empty-line/>
<p>
В данном случае это получение Web-страницы и проверка
HTTP-кода от Web-сервера. Точно так же выглядит расчёт
взвешенного среднего в одном из примеров выше.
</p>
<empty-line/>
<p>
Отправка запросов к СУБД и обработка исключений:
</p>
<empty-line/>
<p>
<code>
   ++- Selene                                                 ** Veronica
   
   transact = sender receiver amount -&gt;                       (@ transact sender receiver amount +-
     + alarm -&gt;                                                   (\ alarm &gt;
       - sql-series (                                                 (# sql-series (@ +-
                      debit   &lt;                                           (# debit   (%
                                cid : sender,                                            cid sender
                                sum : amount                                             sum amount
                              &gt;,                                                     )
                      credit  &lt;                                           )
                                cid : receiver,                           (# credit  (%
                                sum : amount                                             cid receiver
                              &gt;,                                                         sum amount
                      journal &lt;                                                      )
                                opcode : JR_TRANSFER,                     )
                                cid-1  : sender,                          (# journal (%
                                cid-2  : receiver,                                       opcode JR_TRANSFER
                                amount : _                                               cid-1  sender
                              &gt;                                                          cid-2  receiver
                    )                                                                    amount _
     + ec, error &gt;                                                                   )
       log error; handle ec                                               )
     ;                                                                )
                                                                  + ec error &gt;
     task-name = 'transact ' receiver ''                              (log error)
                 ' &lt; ' sender ' (' amount ')'                         (# handle ec)
                                                                  )
     if alarm -&gt;                                                  (&#038; 'transact ' receiver
       log LOG_SPACES task-name                                      ' &lt; ' sender ' (' amount ')' .task-name)
     else                                                         ([alarm] &gt;
       log LOG_EXCLAMATION task-name                                  (log LOG_SPACES task-name)
     ;                                                            &gt;
   _                                                                  (log LOG_EXCLAMATION task-name)
     sender, receiver : integer                                   )
     amount : float                                           +
   ;                                                              {integer} sender receiver
                                                                  {float} amount
                                                              )</code>
</p>
<empty-line/>
<p>
Если здесь произойдёт ошибка, будет вызвана команда "log"
и функция "handle()", а затем выполнение будет продолжено
со следующей строки после обработчика ошибок. В переменной
"alarm" останется код ошибки, который позже будет проверен
в операторе условия, чтобы выполнить одну из двух команд
"log".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Состояние программы</p></title>
<p>
Довольно важная особенность языка Veronica - запрет на
создание глобальных переменных:
</p>
<empty-line/>
<p>
<code>
   (% program-state +-
       ...
   )
   
   (% program-state &gt;                                                             ** !!!
       .state
   )
   
   (@ +-
       (# msg-in (@ msg +-
                     ...
       )
   )</code>
</p>
<empty-line/>
<p>
И вместо того, чтобы объявлять структуру "state", где будет
записано состояние программы, в её заголовке, надо объявить
и создать её в точке вызова функции "msg-in()", вот так:
</p>
<empty-line/>
<p>
<code>
   (% program-state +-
       ...
   )
   
   (@ +-
       (# msg-in (@ msg +-
                     (# process state msg (@ st +-
                                              (st .state)
                                          )
                     )
       -
           (%
               .. .state
           )
       )
   )
   
   (@ process state st @fp +-
       ...
   +
       {program-state} state
   )</code>
</p>
<empty-line/>
<p>
Теперь структура "state" будет передаваться от одной функции
к другой:
</p>
<empty-line/>
<p>
<code>
                     (# process state msg ...
                                &#8593;</code>
</p>
<empty-line/>
<p>
И будет изменяться внутри безымянной функции, переданной на
вход функции "msg-in()", вот так:
</p>
<empty-line/>
<p>
<code>
                     (# process state msg (@ st +-
                                              (st .state)
                                              &#8593;</code>
</p>
<empty-line/>
<p>
И в дополнение к тому, что во всех функциях она будет видна
с правами доступа только на чтение, все её изменения будут
сосредоточены в одном месте.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Данные в памяти</p></title>
<p>
Структуры данных, расположенные в памяти, должны быть связаны
таким образом, чтобы из них получилось дерево:
</p>
<empty-line/>
<image xlink:href="#Relations-1@About"/>
<empty-line/>
<p>
Оно так и называется: дерево структур данных. Когда на структуру
создаётся несколько ссылок, она временно закрывается от изменений,
а циклические ссылки - запрещены:
</p>
<empty-line/>
<image xlink:href="#Relations-2@About"/>
<empty-line/>
<p>
Отказ от перекрёстных и циклических ссылок ведёт к тому, что
действия с одним и тем же типом данных оказываются расположены
в одном месте (включая доступ на чтение).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Запись выражений</p></title>
<p>
В языке Veronica применяется запись выражений слева направо,
когда первым задаётся действие, а последним - переменная, где
должен оказаться результат:
</p>
<empty-line/>
<p>
<code>
   (+ 2 2 .x)                                                                     ** "x = 2 + 2"</code>
</p>
<empty-line/>
<p>
Как видно, это нисколько не затрудняет чтение программы, так
как скобки довольно наглядно задают начало и конец цепочки:
</p>
<empty-line/>
<p>
<code>
   (* 2 2 .x)                                                                     ** "x = 2 * 2"</code>
</p>
<empty-line/>
<p>
Некоторые действия, в том числе вычитание и деление, требуют
ещё пары скобок:
</p>
<empty-line/>
<p>
<code>
   ((- 1) x .y)                                                                   ** "y = x - 1"</code>
</p>
<empty-line/>
<p>
В таком случае не остаётся вопросов о том, где в выражении
делимое и делитель:
</p>
<empty-line/>
<p>
<code>
   ((/ 2) x .y)                                                                   ** "y = x / 2"</code>
</p>
<empty-line/>
<p>
Вызовы функций и заголовки операторов условия записываются
по тем же правилам. И даже структуры данных создаются так же.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Поток выполнения</p></title>
<p>
В отличие от многих других языков программирования, в языке
Veronica все функции должны быть записаны под одним и тем же
правилам:
</p>
<empty-line/>
<p>
<code>
   (@ +-
    &#9484;&#9472;
    &#9474; f(x)
    &#9474; =, +=, ++ ...
    &#9492;&#9472;
    &#9484;&#9472;
    &#9474; for
    &#9474; =, +=, ++ ...
    &#9492;&#9472;
    &#9484;&#9472;
    &#9474; if
    &#9492;&#9472;
    &#9484;&#9472;
    &#9474; return или if/return
    &#9492;&#9472;
   )</code>
</p>
<empty-line/>
<p>
Все они начинаются с объявления переменных и заполнения внешних
переменных нулями, затем в них появляются вызовы других функций
и вычисления, после них возможны операторы циклов, а затем -
операторы условия и оператор возврата.
</p>
<empty-line/>
<p>
Так, в следующем примере функция, рассчитывающая факториал,
записана правильно:
</p>
<empty-line/>
<p>
<code>
   (@ factorial:rc n +-
       ('/ [= 0] n / [= 1] n &gt;
           1
       &gt;
           (* n (# factorial ((- 1) n)))
       )
   )</code>
</p>
<empty-line/>
<p>
И здесь:
</p>
<empty-line/>
<p>
<code>
   (@ factorial:rc n +-
       ('/ [= 0] n / [= 1] n &gt;
           1, (* n (# factorial ((- 1) n)))
       )
   )</code>
</p>
<empty-line/>
<p>
А в этом примере она записана с ошибкой:
</p>
<empty-line/>
<p>
<code>
   (@ factorial:rc n +-
       ('/ [= 0] n / [= 1] n &gt;
           1
       )                                                                          ** &#8592; ?!!..
       (* n (# factorial ((- 1) n)))
   )</code>
</p>
<empty-line/>
<p>
Из-за того, появление операторов условия до вычислений
или возврат из середины функции (как и записано в этом
примере) считается ошибкой.
</p>
<empty-line/>
<p>
Кроме того, поток выполнения задаётся по правилам,
которые ограничивают возможность изменения переменных:
параметры функции остаются неизменными на всё время её
работы, а все изменяемые переменные должны располагаться
в начале функции (то есть до первого вызова любой другой
функции в ней), и точно такие же правила действуют внутри
операторов условий и в циклах.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Почти как язык Ассемблера</p></title>
<p>
Да, всё верно, Veronica может работать как язык Ассемблера,
в который транслированы выражения на языке Selene.
</p>
<empty-line/>
<p>
Так и задумано.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>&nbsp;</p></title>
<p>&nbsp;</p>
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Дерево типов данных</p></title>
<p>
Типы данных в языке Veronica позволяют задать возможные
действия, выполняемые с каждой переменной, а также следить
за расходом памяти (в последнее время её стало много, но
кеши процессоров увеличиваются в объёме не так быстро, как
память).
</p>
<empty-line/>
<p>
Поддерживаемые типы данных были собраны в дерево (такое
же, как и в языке Selene), и к ним были добавлены правила
приведения типов (что позволило, например, использовать
числа там, где ожидаются строки - но всё же не позволило
записывать расчёты и вычислительные выражения со строковыми
переменными, как в некоторых других языках).
</p>
<section><!-- 2 -->
<title><p>Поддерживаемые типы</p></title>
<p>
Типы данных, поддерживаемые в языке Veronica, связаны между
собой так, как показано на следующем рисунке:
</p>
<empty-line/>
<p>
<code>
   &#183; Любой тип данных
     &#183; "arg" - параметры функций
       &#183; "const" - константы без значений
         &#183; константы вида "C_CONST_NAME"
         &#183; наборы констант: "DO_SOMETHING | WRITE_LOG"
       &#183; "I|F|S" - числа или строки
         &#183; "I|F" - целые числа и числа с точкой
           &#183; "integer" - целое число без знака
           &#183; "float" - число с точкой без знака
           &#183; "integer +-" - целое число со знаком
           &#183; "float +-" - число с точкой со знаком
         &#183; "string" - строки
       &#183; "record" - структуры данных
         &#183; структуры, заданные пользователем
       &#183; "A|L" - массивы и списки
         &#183; "array" - массивы
           &#183; массивы переменных других типов
         &#183; "list" - списки
           &#183; списки: обыкновенные и "ключ-значение"
     &#183; невидимые типы
       &#183; перечислители в циклах "for &lt;A|L&gt;"
       &#183; срезы по строкам, массивам и спискам</code>
</p>
<empty-line/>
<p>
Возможно приведение типов с перемещением вверх и вниз по
дереву, когда тип "list" используется там, где ожидается
тип "A|L". Также действуют правила подстановки переменных,
когда число подставляется на то место, где ожидается строка
(но, например, использование строк в качестве чисел - не
допускается).
</p>
<empty-line/>
<p>
Пустые значения выглядят так:
</p>
<empty-line/>
<p>
<code>
   Тип данных:                   Значение по умолчанию:
     Константы                            .
     Числа                                0
     Строки                               ''
     Структуры                            .
     Массивы и списки                     _'
     Списки "ключ-значение"               _^</code>
</p>
<empty-line/>
<p>
При этом поля в пустой структуре будут заполнены пустыми
значениями (включая другие структуры в ней).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Создание переменных</p></title>
<p>
Создание новой переменной выглядит как пересылка в неё
значения:
</p>
<empty-line/>
<p>
<code>
   (0 .x)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ('' .string)</code>
</p>
<empty-line/>
<p>
А создание списка строк выглядит так:
</p>
<empty-line/>
<p>
<code>
   (' -string &gt; .items)</code>
</p>
<empty-line/>
<p>
Здесь одинарная кавычка означает создание списка, затем
после "-" задаётся тип, и затем, после "&gt;", задаётся имя
переменной, которая в дальнейшем позволит работать со
списком.
</p>
<empty-line/>
<p>
Несколько переменных с одним и тем же значением создаются
так:
</p>
<empty-line/>
<p>
<code>
   ('' .first-name .second-name)</code>
</p>
<empty-line/>
<p>
А здесь создаётся переменная с сохранением её значения
в другой переменной:
</p>
<empty-line/>
<p>
<code>
   (0 .(x .previous))</code>
</p>
<empty-line/>
<p>
Причём так можно и поменять значения двух переменных
местами:
</p>
<empty-line/>
<p>
<code>
   (x .(y .x))</code>
</p>
<empty-line/>
<p>
Несколько переменных разных типов создаются так:
</p>
<empty-line/>
<p>
<code>
   (' .. .numbers
    0 .. .max-number)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (' .. .records
    . .. .max-record)</code>
</p>
<empty-line/>
<p>
Причём в последних двух примерах тип списка не будет
задан: это список, содержащий любые данные (часто это
не имеет значения, так как их типы всё равно задаются
в точках входа в функции).
</p>
<empty-line/>
<p>
Две точки означают, что переменная не будет закрыта от
изменений.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Точки входа в функции</p></title>
<p>
Во многих случаях функция может быть задана и без
типов - как в следующем примере, где типы выводятся
из текста функции:
</p>
<empty-line/>
<p>
<code>
   (@ factorial n .res +-
       (1 .. .res)
       (.* 1..n x &gt;
           (*= x .res)
       )
   )</code>
</p>
<empty-line/>
<p>
Но в таком случае эта функция будет вызываться и
для чисел с точкой (к ним тоже применим оператор
умножения), и лучше было бы задать типы, а вместе
с ними - и условие, при котором функция может быть
вызвана:
</p>
<empty-line/>
<p>
<code>
   (@ factorial n .res +-
       (1 .. .res)
       (.* 1..n x &gt;
           (*= x .res)
       )
   +
       {integer} n
   +
       [&gt; 0] n
   )</code>
</p>
<empty-line/>
<p>
Точно так же, в следующем примере функция будет
работать с любым числом - как с точкой, так и без
точки:
</p>
<empty-line/>
<p>
<code>
   (@ max +-
       (// ([&gt; y] x) x y)
   +
       {I|F : +-} x y
   )</code>
</p>
<empty-line/>
<p>
А здесь - она будет работать с массивом или списком,
содержащим любые данные, но чтобы он не был пустым:
</p>
<empty-line/>
<p>
<code>
   (@ max items .res +-
       (.* items item &gt;
           (![res] &gt;
               (item .res)
           &gt;
               ([&gt; res] item &gt;
                   (item .res)
               &gt;
                   ;
               )
           )
       )
   +
       {A|L} x y
   +
       [items]
   )</code>
</p>
<empty-line/>
<p>
То, что это массив или список, можно было бы и не
задавать (это выводится компилятором из того, что
к переменной "items" будет применён оператор цикла).
Требование, чтобы массив или список не был пустым,
задаётся в предпоследней строке.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Константы</p></title>
<p>
Константы выглядят как величины, к которым не привязаны
никакие значения, и могут быть использованы для того,
чтобы менять поведение программы. Например, константы
"*_ENDIAN" должны быть описаны таким образом:
</p>
<empty-line/>
<p>
<code>
   (% *_ENDIAN +-
      BIG
      LITTLE
   &gt;
      BIG
   )</code>
</p>
<empty-line/>
<p>
И тогда их можно подставить в оператор условия - вот так:
</p>
<empty-line/>
<p>
<code>
   ([= BIG_ENDIAN] format &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Теперь если в переменной "format" окажется константа
"BIG_ENDIAN", будет выполнена первая ветка оператора
условия, а во всех остальных случаях - вторая.
</p>
<empty-line/>
<p>
Константы могут быть и длинными:
</p>
<empty-line/>
<p>
<code>
   (% WS_* +-
      IMAGES/ICONS
      IMAGES/PHOTOS
      ...
      ICON/FAVORITES/PNG
   )</code>
</p>
<empty-line/>
<p>
И могут иметь значения, связанные с ними и задаваемые в
файлах с настройками:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Files.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   [ws]
   images/icons = /www/files/images/icons
   images/photos = /www/files/images/photos
   ...
   icon/favorites/png = favicon.png</code>
</p>
<empty-line/>
<p>
И теперь, если воспользоваться оператором в виде обратной
кавычки, вот так:
</p>
<empty-line/>
<p>
<code>
   (&#038; `WS_IMAGES/ICONS '/' `WS_ICON/FAVORITES/PNG .icon-file-name)</code>
</p>
<empty-line/>
<p>
Он загрузит значение в то место, где стоит этот оператор
(при этом константы и переменные с ними так и останутся
величинами без значений). Так что в последнем примере в
переменной "icon-file-name" окажется полный путь к файлу
"favicon.png", загруженный из файла "files.ini".
</p>
<empty-line/>
<p>
А в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (% STR_* +-
      WP_TITLE
      WP_HEADLINE
      ...
   )</code>
</p>
<empty-line/>
<p>
Константы "STR_*" используются для загрузки строк на
разных языках:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Strings.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   [str_wp]
   title.en = Title text
   title.ru = Текст заголовка
   ...</code>
</p>
<empty-line/>
<p>
А оператор в виде двух обратных кавычек, как в следующем
примере:
</p>
<empty-line/>
<p>
<code>
   (say `STR_TITLE`user-settings.language)</code>
</p>
<empty-line/>
<p>
Позволяет загружать их из файлов настроек (так что строка
будет загружена в зависимости от кода страны и языка в поле
"language" в структуре "user-settings").
</p>
<empty-line/>
<p>
Наборы констант задаются с помощью оператора "+", вот так:
</p>
<empty-line/>
<p>
<code>
   (# char-in file-name position +READ_INTEGER+BIG_ENDIAN .number)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# char-out file-name number position mode -
       (+WRITE_DWORD
        +LITTLE_ENDIAN .mode)
   )</code>
</p>
<empty-line/>
<p>
А операторы сравнения выглядят следующим образом:
</p>
<empty-line/>
<p>
<code>
   ([is const] x &gt;                                  ([= *_ENDIAN] x &gt;
       ...                                              ...
   )                                                )
   
   ([like arg] x &gt;                                  ([= BIG_ENDIAN] x &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Здесь оператор "is" проверяет тип переменной, оператор
"like" проверяет возможность приведения её к более общему
типу (из типа "const" может быть получен тип "arg", так
что этот оператор будет работать), а два оператора "="
выполняют проверку на принадлежность значения ко множеству
констант и на равенство константе.
</p>
<empty-line/>
<p>
константа может быть связана с типом, у которого в таком
случае она будет единственным возможным значением:
</p>
<empty-line/>
<p>
<code>
   (% null : +-
      SQL_NULL
   )</code>
</p>
<empty-line/>
<p>
Теперь можно создать переменную или поле структуры с таким
типом:
</p>
<empty-line/>
<p>
<code>
   (% sales +-
       {string} region
       {integer | null} total-value
   )</code>
</p>
<empty-line/>
<p>
И выполнять присваивание:
</p>
<empty-line/>
<p>
<code>
   (0 .row.total-value)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (SQL_NULL .row.total-value)</code>
</p>
<empty-line/>
<p>
И проверки:
</p>
<empty-line/>
<p>
<code>
   ([is integer] row.total-value &gt;                  ([&gt; 0] row.total-value &gt;
       ...                                              ...
   )                                                )
   
   ([like I|F] row.total-value &gt;                    (![= 0] row.total-value &gt;
       ...                                              ...
   )                                                )
   
   ([is const] row.total-value &gt;                    (![is null] row.total-value &gt;
       ...                                              ...
   )                                                )
   
   ([= SQL_*] row.total-value &gt;                     (![= SQL_NULL] row.total-value &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Теперь если в поле структуры окажется число, тип данных
этого поля будет установлен в "integer", а более общие
типы будут заданы как "I|F" и "arg", а если там окажется
константа "SQL_NULL", его тип будет задан как "null", и
при этом будут работать проверки на равенство типу "const"
и возможность приведения к обобщённому типу "arg". Обычно
проверки выглядят как "is integer" и "is null".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Числа</p></title>
<p>
Для создания переменной, содержащей число, предназначен
оператор пересылки:
</p>
<empty-line/>
<p>
<code>
   (0 .x
    1 .y
    2 .z)</code>
</p>
<empty-line/>
<p>
Как видно, он выглядит так же, как и операторы, создающие
переменные других типов (две точки означают, что переменная
не будет закрыта от изменений). Созданные переменные могут
быть подставлены в вычислительные выражения:
</p>
<empty-line/>
<p>
<code>
   (+ x y z .total)
   
   ((/ 3) (+ x y z) .average)
   
   (# sqrt ((/ 3) (+ (** x) (** y) (** z))) .sigma)</code>
</p>
<empty-line/>
<p>
А также переданы функциям в качестве параметров:
</p>
<empty-line/>
<p>
<code>
   (@ max +-
       ([&gt; y] x &gt;
           x y
       )
   +
       {I|F : +-} x y
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ hypotenuse x y z +-
       (# sqrt (+
                  (** x)
                  (** y)
                  (** z)
               )
       )
   +
       {I|F : +-} x y z
   )</code>
</p>
<empty-line/>
<p>
Переменным с числами могут быть заданы знак и размерность:
</p>
<empty-line/>
<p>
<code>
   (% amount : +-                                   (% velocity : +-
       integer : 2^32 : {L}                             float : +- : .. : 2^32 : {Km/H}
   )                                                )</code>
</p>
<empty-line/>
<p>
Что позволит ловить ошибки вроде пересечения нуля:
</p>
<empty-line/>
<p>
<code>
   (0 -amount &gt; .quantity)
   
   (say ((- 1) quantity))                                                         ** !!!</code>
</p>
<empty-line/>
<p>
Или сложения двух величин с разной размерностью:
</p>
<empty-line/>
<p>
<code>
   (0 -amount &gt; .quantity
    0 -velocity &gt; .speed)
   
   (say (+ quantity speed))                                                       ** !!!</code>
</p>
<empty-line/>
<p>
При том, что умножение величин с разной размерностью - не
вызывает ошибок:
</p>
<empty-line/>
<p>
<code>
   (say (# sqrt (* quantity speed)))</code>
</p>
<empty-line/>
<p>
Компилятор умеет выполнять вывод размерностей - так же, как
и вывод типов. Так что в последнем примере размерность числа,
выданного на печать, окажется равной "{(L*Km)/H}".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Строки</p></title>
<p>
Пустая строковая переменная создаётся следующим образом:
</p>
<empty-line/>
<p>
<code>
   ('' .string)</code>
</p>
<empty-line/>
<p>
А переменная со строкой - вот так:
</p>
<empty-line/>
<p>
<code>
   ('ABCD' .string)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (''ABCD .string)</code>
</p>
<empty-line/>
<p>
Длину строки в байтах (не символах) можно получить так:
</p>
<empty-line/>
<p>
<code>
   (say string._)</code>
</p>
<empty-line/>
<p>
При этом работать со строкой как с массивом - не получится:
</p>
<empty-line/>
<p>
<code>
   (say string.1)                                                                 ** !!!</code>
</p>
<empty-line/>
<p>
И так - тоже нельзя:
</p>
<empty-line/>
<p>
<code>
   ('Z' .string.1)                                                                ** !!!</code>
</p>
<empty-line/>
<p>
Перебрать буквы в строке можно с помощью функции "chars-in()",
вызывая её с безымянной функцией:
</p>
<empty-line/>
<p>
<code>
   (# chars-in string (@ char +-
                          (say char)
                      )
   -
       ('ABCD' .string)
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   A
   B
   C
   D</code>
</p>
<empty-line/>
<p>
Похожая на неё функция "words-in()" позволяет перебрать слова:
</p>
<empty-line/>
<p>
<code>
   (# words-in string (@ char +-
                          (say char)
                      )
   -
       ('AB CD' .string)
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   AB
   CD</code>
</p>
<empty-line/>
<p>
Оператор в виде кавычек и равенства позволяет менять
переменную со строкой, пока она не была закрыта от
изменений:
</p>
<empty-line/>
<p>
<code>
   ('ABCD' .. .string)
   (''=
       "\n"
       '4321'
   &gt;
       .string
   )
   (say string)</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   ABCD
   4321</code>
</p>
<empty-line/>
<p>
Функции "ext-left()" и "ext-right()" позволяют дополнить
строку пробелами:
</p>
<empty-line/>
<p>
<code>
   (# ext-right string ' ' length .line)            (# ext-left string ' ' length .line)</code>
</p>
<empty-line/>
<p>
А также пробелами и символами "\t" - слева или справа:
</p>
<empty-line/>
<p>
<code>
   (# ext-right string ' ' "\t"                     (# ext-left string "\t" ' '
                tab-size length .line)                          tab-size length .line)</code>
</p>
<empty-line/>
<p>
Проверка наличия строки в другой строке выполняется с
помощью обыкновенных операторов условия:
</p>
<empty-line/>
<p>
<code>
   ([string 'D'] &gt;                                  ([file-name '.txt' STR_RIGHT] &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
А оператор "~" позволяет выбрать из строки несколько
байтов:
</p>
<empty-line/>
<p>
<code>
   ([= 'A'] (~ string 1) &gt;                          ([= '.txt'] (~ file-name 3 STR_RIGHT) &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Поиск строки в строке выглядит так:
</p>
<empty-line/>
<p>
<code>
   (string word -&gt; .position)
   (string word position -&gt; .next-position)</code>
</p>
<empty-line/>
<p>
А удаление пробелов слева и справа - вот так:
</p>
<empty-line/>
<p>
<code>
   (string -&gt; .no-spaces)
   (string STR_RIGHT -&gt; .no-spaces-right)</code>
</p>
<empty-line/>
<p>
Выбрать байты из строки можно с помощью всё того же
оператора "~", задав ему номер байта и их количество,
или же просто номер:
</p>
<empty-line/>
<p>
<code>
   (say (~ file-name 1 length STR_RIGHT -
            ('.ext'._ length)
        )
   )</code>
</p>
<empty-line/>
<p>
При этом отрицательные числа не допускаются:
</p>
<empty-line/>
<p>
<code>
   (say (~ 'Text' -1))                                                            ** !!!</code>
</p>
<empty-line/>
<p>
Команда "parse" позволяет разбирать строки по словам:
</p>
<empty-line/>
<p>
<code>
   (parse file-name name '.' ext)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (parse file-name _ '.' .)</code>
</p>
<empty-line/>
<p>
Здесь "_" означает "использовать ту же переменную"
(если она не была закрыта от дальнейших изменений),
а "." означает "не использовать никаких переменных
и отбросить всё, что было в строке после точки".
</p>
<empty-line/>
<p>
Оператор "!" предназначен для того, чтобы разбирать
строки по правилам:
</p>
<empty-line/>
<p>
<code>
   ([! '*/'] str &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ([! '*/'] str &gt;
       (! str '*/articles/^[0-9]/*' .article-id)
   &gt;
       (! str '*/articles/^[0-9]' .article-id)
   )</code>
</p>
<empty-line/>
<p>
В последнем примере он выбирает из строки с адресом
Web-страницы содержащийся в ней номер, а его место в
строке задаётся с помощью символа "^" и прямоугольных
скобок. В тех редких случаях, когда правила задаются
где-то в файлах с настройками, это бывает удобно.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Структуры</p></title>
<p>
Структуры данных в языке Veronica выглядят как несколько
переменных (полей структуры), собранных вместе:
</p>
<empty-line/>
<p>
<code>
   (% rgb +-
       {integer : 2^8} .
       {integer : 2^8} red green blue
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (% generic-rgb +-
       {#1} .
       {#1} red green blue
   )
   
   (% rgb : +-
       generic-rgb [integer : 2^8]
   )</code>
</p>
<empty-line/>
<p>
Доступ к полям структуры выполняется через точку:
</p>
<empty-line/>
<p>
<code>
   (% -rgb &gt;
       2^3 .red
       2^4 .green
       2^5 .blue
   &gt;
       .color
   )
   (say color.red)</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   8</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (say white-color.green -
       (% -rgb &gt;
           ((- 1) 2^8) .red .green .blue)
       &gt;
           .white-color
       )
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   255</code>
</p>
<empty-line/>
<p>
Поиск внутри структуры выполняется так:
</p>
<empty-line/>
<p>
<code>
   (255 .color.[field-name])</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   ([color..[field-name]] &gt;
       (255 .color.[field-name])
   )</code>
</p>
<empty-line/>
<p>
При этом отсутствие поля в структуре проверяется так:
</p>
<empty-line/>
<p>
<code>
   (![color..[field-name]] &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Но не так:
</p>
<empty-line/>
<p>
<code>
   ([color.![field-name]] &gt;                                                       ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
С помощью точки можно добраться и до полей структуры,
которую возвращает вызываемая функция - и вместо того,
чтобы создавать ещё одну переменную:
</p>
<empty-line/>
<p>
<code>
   (p.title .product-title -
       (# parse-xml description .p)
   )</code>
</p>
<empty-line/>
<p>
Обратиться к полям структуры в одну строку:
</p>
<empty-line/>
<p>
<code>
   ((# parse-xml description).title .product-title)</code>
</p>
<empty-line/>
<p>
При этом структуры, в отличие от констант, чисел и строк,
принадлежат каким-то пространствам имён, и все действия с
ними должны быть сосредоточены в тех же пространствах имён.
Обратиться к полям структуры из другого пространства имён
на запись можно во время создания структуры - а затем они
будут доступны только на чтение:
</p>
<empty-line/>
<p>
<code>
   (255 .color.red)                                                               ** &#8592; namespace:read-only !!!</code>
</p>
<empty-line/>
<p>
Но если написать над полями структуры функции-переходники:
</p>
<empty-line/>
<p>
<code>
   (@ red:w *c value +-                   (@ green:w *c value +-                  (@ blue:w *c value +-
       (value .c.red)                         (value .c.green)                        (value .c.blue)
   +                                      +                                       +
       {rgb} c                                {rgb} c                                 {rgb} c
       {integer : 2^8} value                  {integer : 2^8} value                   {integer : 2^8} value
   +                                      +                                       +
       [0 &lt;= %% &lt;= 255] value                 [0 &lt;= %% &lt;= 255] value                  [0 &lt;= %% &lt;= 255] value
   )                                      )                                       )</code>
</p>
<empty-line/>
<p>
Поля структуры будут доступны и на запись:
</p>
<empty-line/>
<p>
<code>
   (255 .color.red)</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (2^4 .color.red
    2^5 .color.green
    2^6 .color.blue)</code>
</p>
<empty-line/>
<p>
Как правило, переменная со структурой после её создания
закрывается от изменений:
</p>
<empty-line/>
<p>
<code>
   (255 .color.red)                                                               ** &#8592; variable:read-only !!!</code>
</p>
<empty-line/>
<p>
И в таком случае вместо неё надо создать новую структуру:
</p>
<empty-line/>
<p>
<code>
   (% -rgb &gt;
       color.% .%
       255     .red
   &gt;
       .next-color
   )</code>
</p>
<empty-line/>
<p>
Ясно, что это правило может вызвать снижение скорости, с
которой будет выполняться программа - и тогда структуры
надо создавать с флажком, разрешающим их менять:
</p>
<empty-line/>
<p>
<code>
   (% -rgb:w &gt;
       2^3 .red
       2^4 .green
       2^5 .blue
   &gt;
       .color
   )</code>
</p>
<empty-line/>
<p>
И теперь доступ к полям структуры на изменение:
</p>
<empty-line/>
<p>
<code>
   (+= 1 .color.red)</code>
</p>
<empty-line/>
<p>
Не вызовет ошибок. Но как только эта структура будет возвращена
из той функции, где она была создана - и она будет закрыта от
дальнейших изменений (то есть, флажок, разрешающий изменения,
не действует дальше функции, в которой он был задан).
</p>
<empty-line/>
<p>
Структура или массив структур могут быть созданы и как ссылка
(или "срез") над массивом байтов:
</p>
<empty-line/>
<p>
<code>
   (byte-array.[position] .record)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (byte-array.[position] length .record-array)
   
   (byte-array.[position-from .. position-to] .record-array)</code>
</p>
<empty-line/>
<p>
При этом данные будут закрыты от изменений (поскольку данные
закрываются от изменений, когда на них появляется несколько
ссылок - а это как раз и есть ссылка на данные).
</p>
<empty-line/>
<p>
Структуры могут создаваться на основе других структур,
и не только как в примере выше, где тип данных "rgb"
создаётся на основе типа "generic-rgb" с добавлением
параметра "integer : 2^8", но и с добавлением одной
или нескольких структур в другую. Так, в следующем
примере:
</p>
<empty-line/>
<p>
<code>
   (% steps +-                                      (% !colored +-
       (% line +-                                       {rgb} color background
           {integer} x y width height               )
       )
                                                    (% !figure +-
       {array : line} lines                             {steps} outline
   )                                                )</code>
</p>
<empty-line/>
<p>
Создаётся два типа данных, "figure" и "colored", которые
могут быть использованы для создания других типов данных -
пусть это будут "square" и "circle":
</p>
<empty-line/>
<p>
<code>
   (% square : figure colored +-                    (% circle : figure colored +-
       {figure colored} _                               {figure colored} _
       {integer} side                                   {integer} radius
   )                                                )</code>
</p>
<empty-line/>
<p>
Для них могут быть написаны функции, выполняющие расчёт
площади геометрической фигуры - вот так:
</p>
<empty-line/>
<p>
<code>
   (@ area:r square +-                              (@ area:r circle +-
       (** square.side)                                 (* :pi (** circle.radius))
   +                                                +
       {square} _                                       {circle} _
   )                                                )</code>
</p>
<empty-line/>
<p>
И пусть теперь для структуры данных "steps", которая
располагается внутри структуры "figure", будет написана
функция, выполняющая её рисование - вот так:
</p>
<empty-line/>
<p>
<code>
   (@ draw ps steps +-
       (.* steps.lines line &gt;
           (# draw ps line)
       )
   
   +
       {steps} _
   )</code>
</p>
<empty-line/>
<p>
И теперь, поскольку структуры "square" и "circle" содержат
её внутри - все функции для работы с ней могут быть вызваны
и для внешней структуры. Так что функция, которая рисовала
бы её, как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ draw ps figure +-
       (.* figure.outline.lines line &gt;                                            ** ?!!..
           (# draw ps line)
       )
   +
       {square | circle} figure
   +
       [&gt; 0] (# area figure)
   )</code>
</p>
<empty-line/>
<p>
Или как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ draw ps figure +-
       (# draw figure.outline)                                                    ** ?!!..
   +
       {square | circle} figure
   +
       [&gt; 0] (# area figure)
   )</code>
</p>
<empty-line/>
<p>
Здесь не требуется. И вызывать функцию рисования надо не так:
</p>
<empty-line/>
<p>
<code>
   (# draw figure.outline)                                                        ** ?!!..</code>
</p>
<empty-line/>
<p>
А вот так:
</p>
<empty-line/>
<p>
<code>
   (# draw figure)</code>
</p>
<empty-line/>
<p>
То есть, функция "draw()" будет доступна сразу для структур
"square" и "circle" просто из-за того, что они содержат поле
"outline" со структурой типа "steps" - а для него эта функция
уже написана.
</p>
<empty-line/>
<p>
В том же случае, когда внутри структуры содержится несколько
структур с одинаковыми функциями - для одной них надо написать
переходник, чтобы компилятор знал, какую функцию вызвать. Но в
любом случае, она будет вызываться для внешней структуры, а не
для одного из её полей.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Деревья</p></title>
<p>
Структуры могут быть объединены в деревья, как в следующем
примере, где из каждой структуры доступны две такие же
структуры - которые в таком случае как раз и составляют
дерево:
</p>
<empty-line/>
<p>
<code>
   (% tree-node:rc +-                                                                       &#183;&#183;&#183;         &#183;&#183;&#183;
     {tree-node} left                                                                     /           /
     {tree-node} right                                                            &gt;&gt; &#9472; **         &#183;&#183;&#183;
     {record | I|F|S|} data                                                               \     /
   )                                                                                        &#183;&#183;&#183;         &#183;&#183;&#183;
                                                                                                \     /
                                                                                                  &#183;&#183;&#183;
                                                                                                      \
                                                                                                        &#183;&#183;&#183;</code>
</p>
<empty-line/>
<p>
Количество веток внутри дерева может быть и больше двух,
но двоичные деревья получили наибольшее распространение,
поскольку они позволяют добраться до любого узла дерева
с помощью ключа из нулей и единиц:
</p>
<empty-line/>
<p>
<code>
   (@ search tree-root key .value +-
       (@ select-next tn bit +-
           ('// &gt;
               [= 0] bit &#038;&#038; [tn.left] / tn.left
           &gt;
               [= 1] bit &#038;&#038; [tn.right] / tn.right
           &gt;
               (exit ERR_KEY_NOT_FOUND)
           )
       )
   
       (node.data .value -
           (# bits-in key (@ bit +-
                              (# select-next node bit .node)
                          )
           -
               (tree-root .. .node)
           )
       )
   +
       {tree-node} tree-root
       {integer : 2^64} key
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "bits-in()" выбирает биты из числа, а
функция "select-next()" переходит к следующей ветке
дерева. В конце эта функция возвращает значение из
поля "data" последнего выбранного узла дерева.
</p>
<empty-line/>
<p>
Пройти дерево можно так:
</p>
<empty-line/>
<p>
<code>
   (@ walk-tree:rc node @fp +-
       (" node.
           (# fp data -
               (# walk-tree left fp : [left]
                # walk-tree right fp : [right])
           )
       )
   +
       {tree-node} node
   )</code>
</p>
<empty-line/>
<p>
А развернуть - вот так:
</p>
<empty-line/>
<p>
<code>
   (@ reverse:rc node .reversed +-
       (%
           right     .left
           left      .right
           node.data .data
       -
           ((// [node.left] (# reverse node.left) .) .left) |
           ((// [node.right] (# reverse node.right) .) .right)
       &gt;
           .reversed
       )
   +
       {tree-node} node reversed
   )</code>
</p>
<empty-line/>
<p>
И в том, и в другом примере для обхода дерева была написана
рекурсивная функция, которая вызывает себя же - и работает
до тех пор, пока не встретит узел дерева, у которого нет
ни одной ветки. В таком случае она начинает возвращаться
из рекурсии - и рано или поздно выходит из той ветки дерева,
с которой начала выполнение.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Массивы и списки</p></title>
<p>
Массивы и списки в языке Veronica работают как цепочки
со значениями, которыми могут быть константы, числа, а
также структуры, строки, и другие массивы и списки.
Массив позволяет добраться до любого значения по его
номеру, а список удобен тем, что позволяет добавлять и
удалять значения - и при этом они позволяют добавлять
значения справа (в конце массива или списка) и перебирать
их от первого до последнего. Номера в массивах начинаются
с единицы (именно так: номера от единицы, а не от нуля,
как в других языках), доступ по нулевому номеру вызывает
ошибку.
</p>
<empty-line/>
<p>
Создать массив можно с помощью оператора пересылки
значения:
</p>
<empty-line/>
<p>
<code>
   (' .array)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (' -{array : integer : +-} &gt; .array)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (' 1 2 3
      4 5 6 7 8 9 .numbers)</code>
</p>
<empty-line/>
<p>
Перебрать значения в массиве можно с помощью оператора
цикла:
</p>
<empty-line/>
<p>
<code>
   (.* numbers n &gt;
       (say n)
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   1
   2
   3
   4
   5
   6
   7
   8
   9</code>
</p>
<empty-line/>
<p>
А если создать массив из других массивов:
</p>
<empty-line/>
<p>
<code>
   ('
       (' 1 2 3)
       (' 4 5 6 7 8 9)
   &gt;
       .numbers
   )</code>
</p>
<empty-line/>
<p>
Можно будет перебрать его значения с помощью цикла с
двумя счётчиками:
</p>
<empty-line/>
<p>
<code>
   (.* 1..numbers._ i 1..numbers.i._ j &gt;
       (say '[ ' i ' : ' j ' ] -&gt; ' numbers.i.j)
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   [ 1 : 1 ] -&gt; 1
   [ 1 : 2 ] -&gt; 2
   [ 1 : 3 ] -&gt; 3
   [ 2 : 1 ] -&gt; 4
   [ 2 : 2 ] -&gt; 5
   [ 2 : 3 ] -&gt; 6
   [ 2 : 4 ] -&gt; 7
   [ 2 : 5 ] -&gt; 8
   [ 2 : 6 ] -&gt; 9</code>
</p>
<empty-line/>
<p>
Над типом данных массива или списка может быть создан
переходник - вот так:
</p>
<empty-line/>
<p>
<code>
   (% matrix : +-
       array : float : float : +- : ..
   )</code>
</p>
<empty-line/>
<p>
И теперь можно создавать массивы с заданным типом:
</p>
<empty-line/>
<p>
<code>
   (' -matrix &gt; .sparse-matrix)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (' -matrix &gt; ._)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (' -matrix [rows cols] &gt; .solid-matrix)</code>
</p>
<empty-line/>
<p>
А здесь - новый тип создаётся как переходник над массивом
структур данных:
</p>
<empty-line/>
<p>
<code>
   (% bitmap : +-
       (% pt : +-
           {rgb} pixel
       )
   
       array : array : pt
   )</code>
</p>
<empty-line/>
<p>
И в более кратком виде:
</p>
<empty-line/>
<p>
<code>
   (% bitmap : +-
       array : array : (% +-
                           {rgb} pixel
                       )
   )</code>
</p>
<empty-line/>
<p>
И теперь можно создавать массивы с заданным типом:
</p>
<empty-line/>
<p>
<code>
   (' -bitmap &gt; .sparse-bitmap)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (' -bitmap [height width] &gt; .solid-bitmap)</code>
</p>
<empty-line/>
<p>
Добраться до элемента массива по его номеру можно так:
</p>
<empty-line/>
<p>
<code>
   (say solid-bitmap.1.1)</code>
</p>
<empty-line/>
<p>
А узнать длину массива - вот так:
</p>
<empty-line/>
<p>
<code>
   (say solid-bitmap._ solid-bitmap.1._)</code>
</p>
<empty-line/>
<p>
Как видно, массивы позволяют получить произвольный доступ
к одному из значений по его номеру. Списки, в отличие от
массивов, удобнее для того, чтобы сохранять в них значения,
добавляя их в список и удаляя их из списка.
</p>
<empty-line/>
<p>
Создать список можно с помощью всё той же команды пересылки
значения, задав ей тип "list":
</p>
<empty-line/>
<p>
<code>
   (' -list &gt; 1 2 3
              4 5 6 7 8 9 .numbers)</code>
</p>
<empty-line/>
<p>
А команды "attach" и "detach" позволяют добавлять значения
в список и удалять их:
</p>
<empty-line/>
<p>
<code>
   (attach numbers 0)
   (detach numbers x others)</code>
</p>
<empty-line/>
<p>
И теперь, если пройти список в цикле:
</p>
<empty-line/>
<p>
<code>
   (.* others n &gt;
       (say n)
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   2
   3
   4
   5
   6
   7
   8
   9
   0</code>
</p>
<empty-line/>
<p>
Команда "attach" с флажком "-l" добавляет значения в
начало списка:
</p>
<empty-line/>
<p>
<code>
   (attach -l list element)</code>
</p>
<empty-line/>
<p>
А команда "detach" с флажком "-r" удаляет их с конца
списка:
</p>
<empty-line/>
<p>
<code>
   (detach -r list element others)</code>
</p>
<empty-line/>
<p>
Она же с флажком "-e" пропускает пустой список и не
выбрасывает ошибку во время выполнения:
</p>
<empty-line/>
<p>
<code>
   (detach -e list element others)</code>
</p>
<empty-line/>
<p>
А можно и так:
</p>
<empty-line/>
<p>
<code>
   (detach -e -r list element others)</code>
</p>
<empty-line/>
<p>
Узнать длину списка можно так:
</p>
<empty-line/>
<p>
<code>
   (say list._)</code>
</p>
<empty-line/>
<p>
А вот возможности доступа к произвольному значению у
списков нет:
</p>
<empty-line/>
<p>
<code>
   (say list.1)                                                                   ** !!!</code>
</p>
<empty-line/>
<p>
При этом, команда "attach" работает с массивами:
</p>
<empty-line/>
<p>
<code>
   (attach array x)</code>
</p>
<empty-line/>
<p>
Но с флажком "-l" она с ними не работает:
</p>
<empty-line/>
<p>
<code>
   (attach -l array x)                                                            ** !!!</code>
</p>
<empty-line/>
<p>
Команда "detach" с массивами не работает:
</p>
<empty-line/>
<p>
<code>
   (detach array x)                                                               ** !!!</code>
</p>
<empty-line/>
<p>
И с флажком "-r" - тоже:
</p>
<empty-line/>
<p>
<code>
   (detach -r array x)                                                            ** !!!</code>
</p>
<empty-line/>
<p>
Но может создавать "срезы" над ними:
</p>
<empty-line/>
<p>
<code>
   (detach array x others)</code>
</p>
<empty-line/>
<p>
И с флажком "-r":
</p>
<empty-line/>
<p>
<code>
   (detach -r array x others)</code>
</p>
<empty-line/>
<p>
И как ответ на вопрос, в каком случае удобнее использовать
массивы, а в каком списки: если количество значений известно
заранее - удобнее массивы, а если их может быть сколько угодно
или их надо добавлять и удалять - удобнее списки.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Списки "ключ-значение"</p></title>
<p>
Списки ключей и значений и они же хеш-таблицы (и они же
"именованные массивы", хотя это название не вполне точно)
позволяют сопоставить значения друг другу вне зависимости
от их типа данных. Часто в качестве ключей и значений в
таких списках появляются строки - но это могут быть и
любые другие типы данных: это не важно.
</p>
<empty-line/>
<p>
Пустой список "ключ-значение" создаётся так:
</p>
<empty-line/>
<p>
<code>
   (^ .ru-en)</code>
</p>
<empty-line/>
<p>
А список с ключами и значениями в виде строк - вот так:
</p>
<empty-line/>
<p>
<code>
   (^ -dictionary &gt; .ru-en -
       (% dictionary : +-
           list : string &lt;- string
       )
   )</code>
</p>
<empty-line/>
<p>
Добавить в список ключи и значения можно с помощью команды
"attach":
</p>
<empty-line/>
<p>
<code>
   (attach ru-en 'Colt' + '"Кольт"')
   (attach ru-en 'Bowie Knife' + 'Нож "Боуи"')
   (attach ru-en 'Police Baton' + 'Полицейская дубинка')
   (attach ru-en 'Puukko' + 'Финский нож с грибком на черене')</code>
</p>
<empty-line/>
<p>
А выбрать их из списка - с помощью команды "detach":
</p>
<empty-line/>
<p>
<code>
   (detach ru-en word-en + word-ru others)</code>
</p>
<empty-line/>
<p>
И если перебирать значения в цикле:
</p>
<empty-line/>
<p>
<code>
   (.* ru-en key value &gt;
       (say key ' - ' value)
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   Colt - "Кольт"
   Bowie Knife - Нож "Боуи"
   Police Baton - Полицейская дубинка
   Puukko - Финский нож с грибком на черене</code>
</p>
<empty-line/>
<p>
Проверить наличие ключа в списке можно таким образом:
</p>
<empty-line/>
<p>
<code>
   ([table.:key] &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А отсутствие - вот так:
</p>
<empty-line/>
<p>
<code>
   ([table.!key] &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Но не так:
</p>
<empty-line/>
<p>
<code>
   (![table.:key] &gt;                                                               ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
А проверить, есть ли в таблице хотя бы одно значение -
вот так:
</p>
<empty-line/>
<p>
<code>
   ([table] &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (![table] &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Доступ к значению по ключу выполняется так:
</p>
<empty-line/>
<p>
<code>
   (say table:key)</code>
</p>
<empty-line/>
<p>
А узнать количество ключей и значений можно так:
</p>
<empty-line/>
<p>
<code>
   (say table._)</code>
</p>
<empty-line/>
<p>
Обращаться к массивам и к обыкновенным спискам с оператором
выбора значения нельзя:
</p>
<empty-line/>
<p>
<code>
   (say array:i)                                                                  ** !!!
   
   (say list:number)                                                              ** !!!</code>
</p>
<empty-line/>
<p>
И точно так же, у массивов нет оператора проверки существования
ключа (номера) и значения: номер с единицы будет доступен, если
по нему было записано значение, или если массив был создан сразу
с указанием длины, которая была больше, чем этот номер.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Описание и вызов функций</p></title>
<p>
Функцией в языке Veronica считается любая часть программы,
способная получать и возвращать какие-либо значения: это
и явно объявленные функции, и безымянные функции, и ветки
операторов условия и циклы - всё это функции.
</p>
<empty-line/>
<p>
Столь необычные на первый взгляд правила построения языка
происходят из-за того, как Veronica работает с данными:
они не остаются на одном и том же месте (как было когда-то
в языках с глобальными переменными), а передаются от одной
функции к другой - "протаскиваются" через них, как через
цепочки выполнения. В конце концов они отправляются на
печать, а занимаемая ими память освобождается.
</p>
<empty-line/>
<p>
И похоже, что эти правила не будут признаны большинством
разработчиков ни в ближайшее время, ни в далёком будущем:
они заставляют более тщательно составлять код программ -
но делать это никто не хочет (и не будет). Они вряд ли
когда-нибудь появятся в других языках. Лучше было бы не
рассказывать об этом, а ограничиться краткими справочными
руководствами - вроде этой книги.
</p>
<section><!-- 2 -->
<title><p>Передаваемые значения</p></title>
<p>
Значения, которые получит функция, задаются в её заголовке:
</p>
<empty-line/>
<p>
<code>
   (@ max items .res +-
       ...
   +
       {A|L} items
   +
       [items]
   )</code>
</p>
<empty-line/>
<p>
Здесь переменная "items" будет доступна только на чтение
(так, что она будет уже не "переменной", а константой), а
переменная "res" будет доступна на заполнение (и она должна
с самого начала содержать пустое значение).
</p>
<empty-line/>
<p>
После возврата из функции переменная "res" будет закрыта
от дальнейших изменений.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Возвращаемые значения</p></title>
<p>
Значения, которые функция возвращает, должны быть заданы
в её заголовке (в таком случае перед ними ставится точка),
либо получены в вычислительном выражении в последней строке
функции:
</p>
<empty-line/>
<p>
<code>
   (@ max items .res +-
       (.* items item &gt;
           (![res] &gt;
               (item .res)
           &gt;
               ([&gt; res] item &gt;
                   (item .res)
               &gt;
                   ;
               )
           )
       )
   +
       {A|L} items
   +
       [items]
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ max +-
       (// ([&gt; y] x) x y)
   +
       {I|F : +-} x y
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ average list @fp +-
       (0 .. .weight .divider .total)
       (.* list item &gt;
           (+= 1 .weight)
           (+= (* weight (# fp item)) .total)
           (+= weight .divider)
       )
       ([divider] &gt;
           ((/ divider) total)
       &gt;
           0
       )
   +
       {A|L} list
   )</code>
</p>
<empty-line/>
<p>
При этом функция может состоять из одной строки с именем
переменной или поля структуры данных, либо из единственного
оператора условия:
</p>
<empty-line/>
<p>
<code>
   (# weighted-average price-list (@ item +-
                                      item.ws-price
                                  )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# weighted-average price-list (@ item +-
                                      (" item.
                                          ([ws-price] &gt;
                                              ws-price
                                          &gt;
                                              price
                                          )
                                      )
                                  )
   )</code>
</p>
<empty-line/>
<p>
И в таком случае возвращаемое значение может быть задано
в кратком виде, а переменная может иметь любой тип данных.
В примерах выше это числа, но те же функции будут работать
и с любыми другими типами.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Точка начала выполнения</p></title>
<p>
Она же "основная функция":
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (say 'Hello, World!')
   )</code>
</p>
<empty-line/>
<p>
В данном случае она не разбирает параметры командной
строки, а делает то, что от неё требуется (печатает
строку) и завершается.
</p>
<empty-line/>
<p>
Параметры командной строки задаются так:
</p>
<empty-line/>
<p>
<code>
   Veronica.exe program.= --verbose-mode=on -d World</code>
</p>
<empty-line/>
<p>
И отображаются на массив, содержащий массивы строк -
вот так:
</p>
<empty-line/>
<p>
<code>
   ('
     (' 'program.=' ''       )
     (' '--verbose-mode' 'on')
     (' '-d' ''              )
     (' 'World' ''           )
   )</code>
</p>
<empty-line/>
<p>
Разобрать его можно таким образом:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (@ parse-command-line command-line +-
           ([= 1] command-line._ &gt;
               (say 'Usage: Veronica.exe ' script-name ' &lt;name&gt;' -
                   (command-line.first-element.1 .script-name -
                       (1 .first-element)
                   )
               )
               (# exit -1)
           )
       )
   
       (# parse-command-line command-line)
       (say 'Hello, ' command-line.latest-element.1 '!' -
           (command-line._ .latest-element)
       )
   +
       {array : string : string} command-line
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "parse-command-line()" проверяет количество
элементов в массиве "command-line", и если там только один
элемент (имя файла программы), она печатает сообщение о том,
как её запускать, после чего вызывает функцию "exit()", и
она завершает выполнение программы, возвращая код ошибки.
В том же случае, когда в массиве "command-line" несколько
элементов - программа печатает последний из них (в данном
примере это слово "World").
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Внутренние функции</p></title>
<p>
Довольно часто для того, чтобы программа стала более
наглядной, функции располагаются внутри других функций -
как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ sigma values @fp +-
       (@ sum values @fp .res +-
           (.* values x &gt;
               (+= (** (# fp x)) .res)
           )
       )
   
       (# sqrt ((/ values._) (# sum values fp)))
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "sum()" будет видна и доступна только
внутри функции "sigma()", которая теперь сократилась
до одной строки.
</p>
<empty-line/>
<p>
Другой похожий приём - объявить функцию, которая будет
видна только внутри другой функции:
</p>
<empty-line/>
<p>
<code>
   (@ sigma values @fp +-
       (# sqrt ((/ values._) (# sum values fp)))
   )
   
   (@ sum 'sigma values @fp .res +-
       (.* values x &gt;
           (+= (** (# fp x)) .res)
       )
   )</code>
</p>
<empty-line/>
<p>
Это удобно в тех случаях, когда функции становятся
слишком длинными, а также когда функция должна быть
видна внутри нескольких функций:
</p>
<empty-line/>
<p>
<code>
   (@ quick-sort:rc list @compare .sorted +-
       ...
   )
   
   (@ mix-sort *array @compare +-
       ...
   )</code>
</p>
<empty-line/>
<p>
И тогда они могут быть названы таким образом:
</p>
<empty-line/>
<p>
<code>
   (@ compare '%sort a b +-
       ([&gt; b] a)
   )</code>
</p>
<empty-line/>
<p>
И функции "mix-sort()" и "quick-sort()" смогут вызывать
функцию "compare()", которая нужна им для того, чтобы
сравнить два элемента массива или списка (в данном случае -
любые два элемента, для которых написан оператор сравнения).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Проверяющие функции</p></title>
<p>
Для того, чтобы функция могла быть поставлена в заголовок
оператора условия, ей надо добавить флажок ":a", как в
следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ empty:a string +-
       ('/ ![string] / [= '-'] string /
                     / [= '.'] string / ![# trim] string &gt;
           1 0
       )
   )</code>
</p>
<empty-line/>
<p>
И тогда можно написать так:
</p>
<empty-line/>
<p>
<code>
   ([# empty] str &gt;                                 (![# empty] str &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
А если добавить ей константу в последнем параметре:
</p>
<empty-line/>
<p>
<code>
   (@ empty:a string IS_EMPTY +-
       ...
   )</code>
</p>
<empty-line/>
<p>
То можно будет написать так:
</p>
<empty-line/>
<p>
<code>
   ([IS_EMPTY] str &gt;                                (![IS_EMPTY] str &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
В случае с массивами и списками в заголовке проверяющей
функции надо указать несколько параметров:
</p>
<empty-line/>
<p>
<code>
   (@ contains:a array element .res +-
       (.* array x &gt;
           ([= element] x &gt;
               (1 .res) -&gt;&gt;
           &gt;
               ;
           )
       )
   )</code>
</p>
<empty-line/>
<p>
И тогда можно написать так:
</p>
<empty-line/>
<p>
<code>
   ([# contains] a item &gt;                           (![# contains] a item &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Причём среди параметров могут быть заданы точность сравнения
(как в виде константы "V_*", так и в одной из переменных), а
также вспомогательные переменные (вроде значений, позволяющих
рассчитать точность в относительных величинах).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Поля в структурах</p></title>
<p>
В том случае, когда к структуре данных требуется добавить
поля, которые могут быть рассчитаны, для этого удобнее
написать функции с флажками "w" и "r", вот так:
</p>
<empty-line/>
<p>
<code>
   (@ brightness:r color +-
       (" color.
           (# round ((/ 3) (+ red
                              green
                              blue)))
       )
   +
       {rgb} color
   )</code>
</p>
<empty-line/>
<p>
Теперь со структурой данных, которая выглядит следующим
образом:
</p>
<empty-line/>
<p>
<code>
   (% rgb +-
       {integer : 2^8} .
       {integer : 2^8} red green blue
   )</code>
</p>
<empty-line/>
<p>
Можно будет работать так, как будто в ней есть это поле:
</p>
<empty-line/>
<p>
<code>
   (say color.brightness)</code>
</p>
<empty-line/>
<p>
А если добавить к ней функции с точкой в именах:
</p>
<empty-line/>
<p>
<code>
   (@ brightness.average:r color +-                 (@ brightness.hypotenuse:r color +-
       (" color.                                        (" color.
           (# round ((/ 3) (+ red                           (# round (# sqrt ((/ 3) (+ (** red)
                              green                                                    (** green)
                              blue)))                                                  (** blue)))))
       )                                                )
   +                                                +
       {rgb} color                                      {rgb} color
   )                                                )</code>
</p>
<empty-line/>
<p>
То можно будет написать так:
</p>
<empty-line/>
<p>
<code>
   (" color.brightness.
       (say average)
       (say hypotenuse)
   )</code>
</p>
<empty-line/>
<p>
При этом, раз функция записи в структуру не была к ней
добавлена, это поле будет доступно на чтение - но не на
запись или изменение:
</p>
<empty-line/>
<p>
<code>
   (" color.brightness.
       (2^5 .average)                                                             ** !!!
   )</code>
</p>
<empty-line/>
<p>
И в таком случае лучше было бы написать функцию, которая
создавала бы новую структуру.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Сторожевые функции</p></title>
<p>
Сторожевые функции предназначены для того, чтобы проверять
значения параметров, передаваемых в другие функции. Например,
для тригонометрических функций "sin()" и "cos()"
</p>
<empty-line/>
<p>
<code>
   (@ sin x +-                                      (@ cos x +-
       ...                                              ...
   +                                                +
       {integer : +-} x                                 {integer : +-} x
   )                                                )</code>
</p>
<empty-line/>
<p>
Могут быть написаны две функции, проверяющие их параметры:
</p>
<empty-line/>
<p>
<code>
   (@ * 'sin x +-                                   (@ * 'cos x +-
       ('// &gt;
           [&gt; 180] x / ERR_PARAMETER
       &gt;                                                &#183;
           [&lt; -180] x / ERR_PARAMETER                   &#183;
       &gt;                                                &#183;
           ;
       )
   +                                                +
       {I|F : +-} x                                     {I|F : +-} x
   )                                                )</code>
</p>
<empty-line/>
<p>
И ещё две функции, проверяющие их возвращаемые значения:
</p>
<empty-line/>
<p>
<code>
   (@ + 'sin x +-                                   (@ + 'cos x +-
       ('// &gt;
           ('&#038; [&gt; 0] x &#038;&#038;
               [&lt; 0] y) / ERR_RETURN_VALUE
       &gt;
           ('&#038; [&gt; 0] x &#038;&#038;
               [&gt; 1] y) / ERR_RETURN_VALUE
       &gt;
           ('&#038; [&lt; 0] x &#038;&#038;                               &#183;
               [&gt; 0] y) / ERR_RETURN_VALUE              &#183;
       &gt;                                                &#183;
           ('&#038; [&lt; 0] x &#038;&#038;
               [&lt; -1] y) / ERR_RETURN_VALUE
       &gt;
           ('&#038; [= 0] x &#038;&#038;
              ![= 0] y) / ERR_RETURN_VALUE
       &gt;
           ;
       )
   +                                                +
       {integer : +-} x                                 {integer : +-} x
       {float : +-} y                                   {float : +-} y
   )                                                )</code>
</p>
<empty-line/>
<p>
И теперь эти проверки будут выполняться постоянно, даже
в обыкновенных вычислительных выражениях:
</p>
<empty-line/>
<p>
<code>
   (+ (# sin x)
      (# cos x) .y)</code>
</p>
<empty-line/>
<p>
Они не занимают время процессора, поскольку не
обращаются к памяти на запись, выполняя сравнение
с переходом (при этом дальний переход в большинстве
случаев не выполняется, так как все параметры заданы
правильно).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Действия со структурами</p></title>
<p>
В тех случаях, когда одна структура располагается внутри
другой структуры, все функции для работы с ней становятся
доступны и для другой структуры. Это позволяет обойтись
без постоянного упоминания её полей в тексте программы.
</p>
<empty-line/>
<p>
И, например, если в программе были созданы две структуры:
</p>
<empty-line/>
<p>
<code>
   (% ask-vs-bid +-                                 (% salary-vs-total +-
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
И для доступа к ним были написаны функции:
</p>
<empty-line/>
<p>
<code>
   (@ attractiviy ab SLR_ASK_BID +-                 (@ attractiviy st SLR_SALARY_TOTAL +-
       ...                                              ...
   +                                                +
       {ask-vs-bid} ab                                  {salary-vs-total} st
   )                                                )</code>
</p>
<empty-line/>
<p>
То после того, как они были собраны в одну структуру:
</p>
<empty-line/>
<p>
<code>
   (% vacancy-item +-
       {ask-vs-bid} ask-x-bid
       {salary-vs-total} salary-x-total
   )</code>
</p>
<empty-line/>
<p>
Те же функции могут быть вызваны и с ней:
</p>
<empty-line/>
<p>
<code>
   (say (# attractiviy vacancy SLR_ASK_BID))
   (say (# attractiviy vacancy SLR_SALARY_TOTAL))</code>
</p>
<empty-line/>
<p>
А если написать над ней "r"-функции:
</p>
<empty-line/>
<p>
<code>
   (@ attractiviy.ask-bid:r v +-                    (@ attractiviy.salary-total:r v +-
       (# attractiviy v SLR_ASK_BID)                    (# attractiviy v SLR_SALARY_TOTAL)
   +                                                +
       {vacancy-item} v                                 {vacancy-item} v
   )                                                )</code>
</p>
<empty-line/>
<p>
Запись будет ещё более краткой:
</p>
<empty-line/>
<p>
<code>
   (say vacancy.attractiviy.ask-bid))
   (say vacancy.attractiviy.salary-total))</code>
</p>
<empty-line/>
<p>
И доступ к полям структур в таком случае не занимает время
процессора: компилятор заменит вызовы "wr"-функций на чтение
и изменение полей в структурах, и "wr"-функции, как ещё одна
прослойка, вызываться не будут.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Надстройки и переходники</p></title>
<p>
В том случае, когда функция должна иметь параметры, которые
могут быть не заданы (так называемые параметры по умолчанию),
над ней может быть написана функция-переходник - вот так:
</p>
<empty-line/>
<p>
<code>
   (@ hypotenuse x y z +-                           (@ hypotenuse x y : +-
       ...                                              hypotenuse x y 0
   +                                                )
       {I|F : +-} x y z
   )</code>
</p>
<empty-line/>
<p>
И теперь можно написать:
</p>
<empty-line/>
<p>
<code>
   (say (# hypotenuse 1 1))</code>
</p>
<empty-line/>
<p>
И вызвать функцию с тремя параметрами, задав только первые
два.
</p>
<empty-line/>
<p>
Точно так же, когда функция располагается в другом пространстве
имён и ей надо дать более краткое и понятное название, над ней
можно создать переходник:
</p>
<empty-line/>
<p>
<code>
   (@ charset char-array from .string : +-
       convert ++ i18n
   +
       {string} char-array from string
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ transform byte-array from .bitmap : +-
       convert ++ image-tools
   +
       {string} from
       {array : integer : 2^8} byte-array bitmap
   )</code>
</p>
<empty-line/>
<p>
И теперь эти две функции можно будет вызывать по их новым
именам - в том пространстве имён, где были созданы переходники
над ними. Теперь если подключить несколько пространств имён, где
совпадают названия функций, это совпадение не будет заметно.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Преобразователи данных</p></title>
<p>
Типы данных в языке Veronica выводятся компилятором, и для
всех переменных действует правило: тип данных не меняется
после заполнения переменной. Так что в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (0 .x)
   ('0' .x)                                                                       ** !!!
   ('ABC' .x)                                                                     ** !!!</code>
</p>
<empty-line/>
<p>
В переменную "x" может быть записано только первое значение,
и после этого она будет считаться содержащей число. Для того,
чтобы записать в неё строки, надо воспользоваться операторами
в виде стрелок:
</p>
<empty-line/>
<p>
<code>
   (0 .x)
   ('0' -&gt; .x)
   ('ABC' -&gt;&gt; .x)</code>
</p>
<empty-line/>
<p>
Обыкновенная стрелка выполнит преобразование и не допустит
ошибок, вторая - отправит в случае ошибки в переменную пустое
значение (в данном случае - она так и останется равной нулю).
</p>
<empty-line/>
<p>
Стрелки также используются для того, чтобы печатать числовые
значения. Например, вместо того, чтобы записать так:
</p>
<empty-line/>
<p>
<code>
   (say (# vacancy.attractiviy.ask-bid))
   (say (# vacancy.attractiviy.salary-total))</code>
</p>
<empty-line/>
<p>
Иногда удобнее записать так:
</p>
<empty-line/>
<p>
<code>
   (say &lt;- (# vacancy.attractiviy.ask-bid) '0.00')
   (say &lt;- (# vacancy.attractiviy.salary-total) '0.00')</code>
</p>
<empty-line/>
<p>
И оператор-стрелка выполнит округление печатаемой величины,
не меняя при этом содержимое переменной.
</p>
<empty-line/>
<p>
Стрелки будут полезны и для преобразования структур в другие
структуры и строки. Обыкновенная стрелка в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (color -string -&gt; .str)</code>
</p>
<empty-line/>
<p>
Будет доступна в том случае, если структура "color" получит
преобразующую функцию с флажком "x":
</p>
<empty-line/>
<p>
<code>
   (@ string-from:x color .string +-
       (# make-string color .string)
   +
       {rgb} color
       {string} _
   )</code>
</p>
<empty-line/>
<p>
А двойная стрелка:
</p>
<empty-line/>
<p>
<code>
   (string -rgb -&gt;&gt; .color)</code>
</p>
<empty-line/>
<p>
Появится в случае, если та же структура получит функцию с
флажком "xx":
</p>
<empty-line/>
<p>
<code>
   (@ color-from:xx string .color +-
       (# compose-rgb string .color : ('&#038; [string] &#038;&#038;
                                          [IS_CORRECT] string))
   +
       {string} _
       {rgb} color
   )</code>
</p>
<empty-line/>
<p>
И если в последнем примере строка окажется пустой или в ней
не будет записан цвет в строковом представлении - в переменной
"color" так и останется пустое значение.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Виртуальные функции</p></title>
<p>
Виртуальные функции - приём, позволяющий написать функцию
для общего типа данных, а затем вызывать её, чтобы в каждом
случае вместо неё работала функция, написанная для одного из
типов данных, созданных на его основе. Проще всего показать
это на примере, когда одна структура располагается внутри
другой, и они связаны соотношением "общий тип - производный
тип".
</p>
<empty-line/>
<p>
Пусть в программе были заданы следующие типы:
</p>
<empty-line/>
<p>
<code>
   (% steps +-                                      (% !colored +-
       (% line +-                                       {rgb} color background
           {integer} x y width height               )
       )
                                                    (% !figure +-
       {array : line} lines                             {steps} outline
   )                                                )</code>
</p>
<empty-line/>
<p>
И для общего типа "figure" была объявлена (но не написана)
функция, отмеченная флажком "v":
</p>
<empty-line/>
<p>
<code>
   (@ area:r:v figure : +-
       area:r figure
   +
       {figure} _
   )</code>
</p>
<empty-line/>
<p>
Теперь, если воспользоваться типом "figure" для создания
других типов:
</p>
<empty-line/>
<p>
<code>
   (% square : figure colored +-                    (% circle : figure colored +-
       {figure colored} _                               {figure colored} _
       {integer} side                                   {integer} radius
   )                                                )</code>
</p>
<empty-line/>
<p>
И написать для каждого из них функцию с таким же названием:
</p>
<empty-line/>
<p>
<code>
   (@ area:r square +-                              (@ area:r circle +-
       (** square.side)                                 (* :pi (** circle.radius))
   +                                                +
       {square} _                                       {circle} _
   )                                                )</code>
</p>
<empty-line/>
<p>
То с переменными этих типов можно будет работать как
с переменными типа "figure", и в том числе, создавая
массивы структур данных:
</p>
<empty-line/>
<p>
<code>
   (' -figure &gt; .vector-image)</code>
</p>
<empty-line/>
<p>
Вызывать эти функции, обращаясь к виртуальной функции:
</p>
<empty-line/>
<p>
<code>
   (.* vector-image figure &gt;
       ([&gt; 0] figure.area &gt;
           ...
       )
   )</code>
</p>
<empty-line/>
<p>
Которая в таком случае становится переходником, позволяющим
выбрать одну из функций. Выбор зависит от настоящего типа
переменной, и выглядит как цепочка операторов условия, где
проверяется тип переменной (это, кстати, работает не слишком
быстро - но зато без дальних прыжков по оперативной памяти,
которые могли бы замедлить выполнение ещё сильнее).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Несколько примеров</p></title>
<p>
В следующем примере функция "files()" обходит дерево каталогов
и вызывает для каждого файла в них функцию в параметре "fp":
</p>
<empty-line/>
<p>
<code>
   (@ files:rc path mask @fp +-
       (# files-in target (@ name +-
                              ([IS_DIRECTORY] name &gt;
                                  (# files name mask fp)
                              &gt;
                                  (# fp name)
                              )
                          )
       -
           (&#038; path '/' mask .target)
       )
   )</code>
</p>
<empty-line/>
<p>
А здесь функция "frequency()" рассчитывает частоту, с которой
слова встречаются в тексте:
</p>
<empty-line/>
<p>
<code>
   (@ frequency text .fq +-
       (@ nlog2n max .n +-
           (max .n)
           (.* 2..max i &gt;
               ([&lt; max] (* i (# log2 i)) &gt;
                   (i .n)
               &gt;
                   -&gt;&gt;
               )
           )
       )
   
       (^ [length] .. .fq -
           (# nlog2n spaces .length -
               (# words-in text :: (++ .spaces))
           )
       )
       (# words-in text (@ word +-
                            ([fq.!word] &gt;
                                (1 .fq:word)
                            &gt;
                                (++ .fq:word)
                            )
                        )
       )
   +
       {string} text
       {list : string &lt;- integer} fq
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "sort()" выполняет быструю сортировку, в чём
ей помогает функция сравнения, передаваемая ей в параметре
"compare":
</p>
<empty-line/>
<p>
<code>
   (@ sort list @compare .sorted +-
       (# sort list SORT_ASC compare .sorted)
   +
       {list : &lt;T&gt;} list
       {list : &lt;T&gt; : [list]} sorted
   )
   
   (@ sort:rc list order-by @compare .sorted +-
       (' .. .lesser .greater)
       (detach -e list 1st others)
       (.* others x &gt;
           ([# compare] x 1st order-by &gt;
               (attach greater x)
           &gt;
               (attach lesser x)
           )
       )
       ([list] &gt;
           (attach sorted ls-sorted 1st gt-sorted -
               (# sort lesser compare order-by .ls-sorted
                # sort greater compare order-by .gt-sorted)
           )
       )
   +
       {SORT_* : {ASC DESC}} order-by
   )</code>
</p>
<empty-line/>
<p>
А здесь функция "reverse()" разворачивает список:
</p>
<empty-line/>
<p>
<code>
   (@ reverse:rc list .reversed +-
       ([list] &gt;
           (detach list head tail)
           ([tail] &gt;
               (attach reversed (# reverse tail) head)
           &gt;
               (attach reversed head)
           )
       )
   +
       {list : &lt;T&gt;} list
       {list : T : [list]} reversed
   )</code>
</p>
<empty-line/>
<p>
На этих примерах и задачах может быть оценён расход памяти
и времени выполнения. Так, например, в последнем случае
разворот списка с помощью рекурсии потребует O(n) памяти
и O(n) времени, где "n" - длина списка.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Операторы условия</p></title>
<p>
Операторы условия в языке Veronica выглядят как составные
функции, выполняющие одну или несколько проверок, и затем -
либо ещё одну функцию, либо одну из двух функций, которые
называются ветками оператора условия. Правила, согласно
которым они должны быть написаны - те же, что и для всех
остальных функций.
</p>
<empty-line/>
<p>
Кое-где ещё остались учебники (в основном, кстати,
на русском языке), где условия рассматриваются как
надстройки над операторами перехода. Здесь это не
так, и следующие главы раскрывают этот вопрос более
подробно.
</p>
<empty-line/>
<p>
Кроме того, Veronica накладывает ограничения на цепочки
проверок, и предлагает несколько способов более краткой
записи выражений в заголовках операторов условия. Всё
это замедляет написание программ - но зато позволяет
сделать их более доступными для чтения и исправления.
</p>
<section><!-- 2 -->
<title><p>Операторы условия</p></title>
<p>
Оператор условия состоит из заголовка, задаваемого с помощью
прямоугольных скобок, и действий, которые будут выполнены в
том случае, если будет выполнено условие в заголовке:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И отдельно задаются действия, которые будут выполнены, если
условие в заголовке выполнено не будет:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Возможна и краткая запись в одну строку:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt; (-- .x))</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   ([&lt;= 0] x &gt; (0 .x)
             &gt; (-- .x))</code>
</p>
<empty-line/>
<p>
Условия и действия могут быть записаны "лесенкой", как в следующем
примере:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt;
       ([&lt; 0] x &gt;
           ...
                &gt;
           ...
       )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt;
       ([&lt; 0] x &gt; ...
                &gt; ...
       )
   )</code>
</p>
<empty-line/>
<p>
Но не так:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt; [&lt; 0] x &gt;                                                                    ** !!!
       ...
   &gt; [= 0] x &gt;                                                                    ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
И не так:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt;
       ([&lt; 0] x &gt; ...)                                                            ** !!!
   )</code>
</p>
<empty-line/>
<p>
Поскольку в таких случаях может быть потеряно одно из условий
(в последнем примере так и есть: потеряна проверка на равенство
нулю - то есть, это условие пропущено, и обнаружить эту ошибку
довольно трудно).
</p>
<empty-line/>
<p>
Условия в заголовке могут быть объединены в цепочку "ИЛИ":
</p>
<empty-line/>
<p>
<code>
   ('/ [&gt; 0] x / [&gt; 0] y &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ('/ [&gt; 0] x / [&gt; 0] y &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А также в цепочку "И":
</p>
<empty-line/>
<p>
<code>
   ('&#038; [&gt; 0] x &#038;&#038; [&gt; 0] y &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Которая в таком случае не должна иметь продолжения "что делать
в противоположном случае" в том же операторе условия:
</p>
<empty-line/>
<p>
<code>
   ('&#038; [&gt; 0] x &#038;&#038; [&gt; 0] y &gt;
       ...
   &gt;                                                                              ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
И такие цепочки надо разворачивать зеркально, чтобы из них
получились цепочки "И", либо выносить их во внутренние функции,
и затем вызывать их в заголовках оператора условия.
</p>
<empty-line/>
<p>
Проверка остатка от деления выглядит так:
</p>
<empty-line/>
<p>
<code>
   ([%_ 2] number &gt;                                 (![%_ 2] number &gt;
       ...                                              ...
   &gt;                                                &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Она же в менее краткой записи:
</p>
<empty-line/>
<p>
<code>
   ([= 0] ((%% 2) number) &gt;                         (![= 0] ((%% 2) number) &gt;
       ...                                              ...
   &gt;                                                &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
А проверка на равенство и неравенство нулю - вот так:
</p>
<empty-line/>
<p>
<code>
   ([length] &gt;                                      (![length] &gt;
       ...                                              ...
   &gt;                                                &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
То же в случае со строками:
</p>
<empty-line/>
<p>
<code>
   ([string] &gt;                                      (![string] &gt;
       ...                                              ...
   &gt;                                                &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Структурами:
</p>
<empty-line/>
<p>
<code>
   ([record] &gt;                                      (![record] &gt;
       ...                                              ...
   &gt;                                                &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
А также массивами и списками:
</p>
<empty-line/>
<p>
<code>
   ([array] &gt;                                       (![array] &gt;
       ...                                              ...
   &gt;                                                &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Проверки с отрицанием могут быть включены в цепочки:
</p>
<empty-line/>
<p>
<code>
   ('/ ![length] / [error-code] &gt;
       ...
   )
   
   ('/ ![length] / ![= NO_ERROR] error-code &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ('&#038; [length] &#038;&#038; ![error-code] &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
При этом двойное отрицание запрещено:
</p>
<empty-line/>
<p>
<code>
   (![[%_ 2] number] &gt;                                                            ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
И в последнем примере надо было бы написать так:
</p>
<empty-line/>
<p>
<code>
   (![%_ 2] number &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Что гораздо проще читать - а также менять, когда дело
доходит до изменений. Операторы условия встречаются в
программах довольно часто, и от них к тому же зависит
поведение программы. Так что без правил их записи не
обойтись.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Проверки с точностью</p></title>
<p>
В большинстве случаев числа можно сравнивать без
указания точности - например, две константы, как
в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (1 .x)
   (1 .y)
   
   ([= y] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Окажутся равны в любом случае, как два целых числа.
То же относится и к коротким дробям вида "A/B", как
здесь:
</p>
<empty-line/>
<p>
<code>
   (0.25 .x)
   (0.25 .y)
   
   ([= y] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И здесь:
</p>
<empty-line/>
<p>
<code>
   ((/ 2) 1 .x)
   ((/ 2) 1 .y)
   
   ([= y] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А также константам "pi" и "e", которые Veronica умеет
хранить в памяти без потери точности:
</p>
<empty-line/>
<p>
<code>
   (:pi .x)
   (:pi .y)
   
   ([= y] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Но как только появляется число, которое не может быть
записано в память в точном представлении - как здесь:
</p>
<empty-line/>
<p>
<code>
   ((/ :pi) 1 .x)
   ((/ :pi) 1 .y)
   
   ([= y] x &gt;                                                                     ** &#8592; precision required !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
Операторам сравнения требуется задание точности:
</p>
<empty-line/>
<p>
<code>
   ([= y] x precision &gt;                             ([= y] x area precision &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ([= y] x V_PRECISION &gt;                           ([= y] x V_AREA V_PRECISION &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
При этом за каждой константой "V_*" должна стоять
запись в файле с настройками:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Settings.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   [v]
   precision = ( 1 / 100 ) / 4
   ...</code>
</p>
<empty-line/>
<p>
И тогда сравнение будет выполнено с заданной точностью
(в последнем примере это 1/4 процента).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Краткая форма записи</p></title>
<p>
Во многих случаях операторы условия могут быть записаны
в более краткой форме. Например, в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ max +-
       ([&gt; y] x &gt;
           x
       &gt;
           y
       )
   )</code>
</p>
<empty-line/>
<p>
Оператор условия может быть сокращён на две строки:
</p>
<empty-line/>
<p>
<code>
   (@ max +-
       ([&gt; y] x &gt;
           x y
       )
   )</code>
</p>
<empty-line/>
<p>
А вместо двух функций:
</p>
<empty-line/>
<p>
<code>
   (@ max +-                                        (@ max +-
       x                                                y
   +                                                )
       [&gt; y] x
   )</code>
</p>
<empty-line/>
<p>
Может быть использован оператор из одной строки:
</p>
<empty-line/>
<p>
<code>
   (@ max +-
       (// ([&gt; y] x) x y)
   )</code>
</p>
<empty-line/>
<p>
А три строки в следующем примере:
</p>
<empty-line/>
<p>
<code>
   ([&lt; 0] x &gt;
       (0 .x)
   )</code>
</p>
<empty-line/>
<p>
Могут быть сжаты до одной:
</p>
<empty-line/>
<p>
<code>
   (0 .x : [&lt; 0] x)</code>
</p>
<empty-line/>
<p>
Цепочка "ИЛИ", состоящая из проверок с одной переменной:
</p>
<empty-line/>
<p>
<code>
   ('/ [= DO_LOGIN] action / [= DO_LOGOUT] action &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Может быть задана в кратком виде:
</p>
<empty-line/>
<p>
<code>
   ([= DO_LOGIN, DO_LOGOUT] action &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И точно так же, цепочка "И", состоящая из одинаковых проверок
с разными переменными:
</p>
<empty-line/>
<p>
<code>
   ('&#038; [&gt; 0] a &#038;&#038; [&gt; 0] b &#038;&#038; [&gt; 0] c &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Может быть записана таким образом:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] a, b, c &gt;
      ...
   )</code>
</p>
<empty-line/>
<p>
То же со строками:
</p>
<empty-line/>
<p>
<code>
   ([string 'D' / 'd'] &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   ([file-name '.txt' / '.me' +STR_RIGHT+STR_ANY_CASE] &gt;
       ...
   ;</code>
</p>
<empty-line/>
<p>
Эта запись работает с константами (они должны относиться
к одному множеству), числами и строками, кроме случаев,
когда требуется задавать точность сравнения с помощью
констант "V_*".
</p>
<empty-line/>
<p>
Проверки "от-до" удобнее задавать не в виде цепочки "И",
как здесь:
</p>
<empty-line/>
<p>
<code>
   ('&#038; [&gt;= 0] x &#038;&#038; [&lt; width] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А сразу в виде двух значений "от-до":
</p>
<empty-line/>
<p>
<code>
   ([0 &lt;= %% &lt; width] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Всё это заметно увеличивает читаемость программы. Гораздо
проще задать проверку "от-до" или перечислить одинаковые
значения через запятую, чем повторять несколько раз одно и
то же (особенно в последнем примере).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Загружающий оператор</p></title>
<p>
Эта форма записи встречается редко - но когда она появляется
в программе, её становится особенно трудно читать. А именно -
последовательный выбор одного из значений, которые могут быть
пустыми:
</p>
<empty-line/>
<p>
<code>
   ('' .. .title)
   
   (item.title:settings.language-code .title)
   (item.title:LC_EN .title : ![title])
   ('-' .title : ![title])</code>
</p>
<empty-line/>
<p>
Удобнее записать в виде одного оператора:
</p>
<empty-line/>
<p>
<code>
   (//..
       item.title:settings.language-code
       item.title:LC_EN
       '-'
   &gt;
       .title
   )</code>
</p>
<empty-line/>
<p>
Он проверит первую переменную, затем вторую, и затем третью -
и остановится на той из них, которая будет содержать какое-то
значение. Пустые переменные этот оператор пропускает.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Последовательные проверки</p></title>
<p>
Последовательные проверки обычно используются для того,
чтобы выбрать одно из значений в зависимости от разных
условий. Так, в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ r50c a +-
       (1 .. .x)
       (((% 1) a) .i ((%% 1) a) .f)
       ([&lt; 0.75] f &gt;
           (0.5 .x)
       ),
       ([&lt; 0.25] f &gt;
           (0 .x)
       )
       (+ i x)
   +
       {float} a
   )</code>
</p>
<empty-line/>
<p>
И здесь:
</p>
<empty-line/>
<p>
<code>
   (@ r50c a +-
       (0 .. .x)
       (((% 1) a) .i ((%% 1) a) .f 0 .x)
       ([&gt;= 0.25] f &gt;
           (0.5 .x)
       ),
       ([&gt;= 0.75] f &gt;
           (1 .x)
       )
       (+ i x)
   +
       {float} a
   )</code>
</p>
<empty-line/>
<p>
Оператор условия изменяет переменную "f" и передаёт её
следующему оператору условия, и затем функция возвращает
полученное значение.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Лесенки из операторов условия</p></title>
<p>
Запись условий в виде "лесенки", где каждая ветка
имеет продолжение в виде двух веток, выглядит так:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt;
       ([&lt; 0] x &gt;
           ...
       &gt;
           ...
       )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt;
       ([&lt; 0] x &gt; ...
                &gt; ...
       )
   )</code>
</p>
<empty-line/>
<p>
Условия внутри лесенки не теряются, и в том случае,
если в одной из веток ничего делать не надо, внутри
неё ставится точка с запятой - вот так:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x &gt;
       ...
   &gt;
       ([&lt; 0] x &gt;
           ...
       &gt;
           ;
       )
   )</code>
</p>
<empty-line/>
<p>
А в следующем примере, где рассчитывается наибольший
общий делитель двух чисел, в последней ветке внутри
лесенки выполняется выход из цикла:
</p>
<empty-line/>
<p>
<code>
   (@ gcd x y .res +-
       (x .. .a y .. .b)
       (.* &gt;
           ([&gt; b] a &gt;
               (-= b .a)
           &gt;
               ([&gt; a] b &gt;
                   (-= a .b)
               &gt;
                   -&gt;&gt;
               )
           )
       )
       (a .res)
   +
       {integer} x y
   +
       [&gt; 0] x y
   )</code>
</p>
<empty-line/>
<p>
Как видно, цикл внутри этой функции будет работать
до тех пор, пока два числа не станут равными, а как
только это произойдёт, одно из них будет записано в
переменную, где должно быть возвращаемое значение -
и функция завершится. До тех пор она будет уменьшать
числа и искать наибольший общий делитель.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Переключатели</p></title>
<p>
Переключатели - ещё одна форма операторов условия,
позволяющая выполнить одну из веток и не выполнять
остальные.
</p>
<empty-line/>
<p>
Переключатели в языке Veronica выглядят так:
</p>
<empty-line/>
<p>
<code>
   (@ days month +-
       ('// month &gt;
           [= 1, 3, 5, 7, 8, 10, 12] / 31
       &gt;
           [= 4, 6, 9, 11] / 30
       &gt;
           [= 2] / 28
       &gt;
           (exit ERR_PARAMETER)
       )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ file-format bytes +-
       ('// bytes &gt;
           [IS_PNG] / FILE_PNG
       &gt;
           [IS_JPEG] / FILE_JPG
       &gt;
           [IS_GIF87] / FILE_GIF
       &gt;
           [IS_GIF89A] / FILE_GIF
       &gt;
           [IS_PM_ICON] / FILE_ICO
       &gt;
           [IS_PM_BITMAP] / FILE_BMP
       &gt;
           (exit ERR_FILE_FORMAT)
       )
   +
       {array : integer : 2^8} bytes
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ in-cube:a pt corner +-
       ('// &gt;
           [0 &lt;= %% &lt; corner.x] pt.x &#038;&#038;
           [0 &lt;= %% &lt; corner.y] pt.y &#038;&#038;
           [0 &lt;= %% &lt; corner.z] pt.z /
           1
       &gt;
           0
       )
   +
       {point} pt corner
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ in-cube:a pt corner +-
       ('// &gt;
           ('/ [&lt; 0] / [&gt;= corner.x] pt.x) / 0
       &gt;
           ('/ [&lt; 0] / [&gt;= corner.y] pt.y) / 0
       &gt;
           ('/ [&lt; 0] / [&gt;= corner.z] pt.z) / 0
       &gt;
           1
       )
   +
       {point} pt corner
   )</code>
</p>
<empty-line/>
<p>
В последнем примере используется неявное свойство
переключателей: они работают до тех пор, пока не
будет выполнено хотя бы одно из условий. И этот
переключатель был написан таким образом, что во
всех условиях он выбрасывает ноль, а если ни одно
из них не сработает - единицу.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Обработка исключений</p></title>
<p>
Исключения в языке Veronica работают как дальние возвраты из
нескольких функций сразу, и обычно применяются для обработки
ошибок (логика программы на исключениях построена быть не может,
так как никто не знает заранее, куда вернётся поток выполнения,
и речь может идти разве что о дальних возвратах).
</p>
<empty-line/>
<p>
Исключения предназначены для того, чтобы сообщать об ошибках во
время выполнения, не вызывающих потерю данных. В случае деления
на ноль или переполнения программа немедленно завершается.
</p>
<section><!-- 2 -->
<title><p>Точки продолжения</p></title>
<p>
Задать в программе точку возврата из нескольких функций сразу
можно с помощью оператора "\", как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ transact socket request .lines +-
       (\ alarm &gt;
           (# translate text .lines -
               (# send socket request .text
                # check text)
           )
       )
   )</code>
</p>
<empty-line/>
<p>
Как видно, здесь вызывается функция "translate()", которая
получает на вход переменную "text", полученную от функций
"send()" и "check()". Внутри функции "check()" выполняются
проверки, которые выглядят так:
</p>
<empty-line/>
<p>
<code>
   (@ check 'transact text +-
       (. .. .alarm)
       (parse text . '&lt;title&gt;' title '&lt;/title&gt;' .)
       (![title] &gt;
           (ERR_NO_TITLE .alarm)
       &gt;
           (parse text . '&lt;charset&gt;' charset '&lt;/charset&gt;' .)
           (![= `TS_DOC_CHARSET] charset &gt;
               (ERR_CHARSET .alarm)
           &gt;
               ...
           )
       )
       ([alarm] &gt;
           &lt;&lt;- (alarm)
       &gt;
           ;
       )
   )</code>
</p>
<empty-line/>
<p>
И если в переменной "alarm" внутри этой функции окажется
код ошибки - он возвращается на уровень выше:
</p>
<empty-line/>
<p>
<code>
           &lt;&lt;- (alarm)</code>
</p>
<empty-line/>
<p>
И оказывается в переменной "alarm" внутри функции "transact()",
после чего выполнение продолжается с закрывающей круглой скобки
внутри оператора "\".
</p>
<empty-line/>
<p>
К оператору "\" может быть добавлен и обработчик ошибок,
которые выбрасывает команда "exit":
</p>
<empty-line/>
<p>
<code>
   (@ transact socket request .lines +-
       (\ alarm &gt;
           (# translate text .lines -
               (# send socket request .text
                # check text)
           )
       + ec &gt;
           (# handle ec)
       )
   )</code>
</p>
<empty-line/>
<p>
И теперь, если она встретится где-нибудь в программе, код
ошибки попадёт в переменные "alarm" и "ec", после чего начнёт
выполняться функция "handle()", содержащая обработчик ошибок.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Исключения</p></title>
<p>
Исключения, которые выбрасывает команда "exit" с параметром
в виде константы "ERR_*", приводят к тому, что выполнение
программы откатывается назад на несколько уровней выше (до
первого обработчика исключений), а в выходной поток STDERR
печатается цепочка вызовов с сообщением об ошибке:
</p>
<empty-line/>
<p>
<code>
   (exit ERR_PARAMETER)</code>
</p>
<empty-line/>
<p>
При этом ошибки вроде деления на ноль или выхода за границы
массива не могут быть обработаны:
</p>
<empty-line/>
<p>
<code>
   (say ((/ 0) 1))                                                                ** !!!
   
   (say array.1 -                                                                 ** !!!
       (' .array)
   )</code>
</p>
<empty-line/>
<p>
И для того, чтобы узнавать о них, наличие STDERR обязательно
(то есть, обойтись обыкновенным обработчиком исключений во всех
случаях не получится - как не получится и прятать сообщения об
ошибках, когда это неисправимые ошибки во время выполнения).
</p>
<empty-line/>
<p>
Добавить обработчик исключений можно так:
</p>
<empty-line/>
<p>
<code>
   (@ read-webpage location .reply +-
       (# wget location (@ received total +-
                            (say "\v" received ' bytes received.')
                        )
                        (@ rc +-
                            (rc .reply)
                        )
       + ec error &gt;
           (# handle ec error)
       + ec &gt;
           (# catch ec)
       )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ read-webpage location .reply +-
       (# wget location (@ received total +-
                            (say "\v" received ' bytes received.')
                        )
                        (@ rc +-
                            (rc .reply)
                        )
       + ec error &gt;
           (# handle ec error)
       + ec &gt;
           (# catch ec)
       )
       ([reply.header.code] &gt;
           ;
       &gt;
           &lt;&lt;- (ERR_NO_HTTP_CODE)
       )
   )</code>
</p>
<empty-line/>
<p>
Теперь если в программе будет выполнена команда "exit", код
ошибки и текстовое сообщение о ней попадут в переменные "ec"
и "error", после чего будет вызвана функция "handle()", а при
ошибке в ней - функция "catch()".
</p>
<empty-line/>
<p>
Как правило, функции "handle()" и "catch()" выглядят следующим
образом:
</p>
<empty-line/>
<p>
<code>
   (@ handle 'read ERR_PORT_* string +-
       ...
   )
   
   (@ handle 'read ERR_DNS_* string +-
       ...
   )
   
   (@ handle 'read error-code string +-
       ...
   )
   
   (@ catch 'read error-code string +-
       ...
   )</code>
</p>
<empty-line/>
<p>
B за ними скрывается несколько функций с одинаковыми названиями,
которые обрабатывают константы "ERR_*".
</p>
<empty-line/>
<p>
Обработчик исключений может выглядеть и так:
</p>
<empty-line/>
<p>
<code>
   (@ read-rss location .records +-
       (\ &gt;
           (# transform reply .records -
               (# read-webpage location .reply)
           )
       + &gt;
           ;
       )
   )</code>
</p>
<empty-line/>
<p>
И даже так:
</p>
<empty-line/>
<p>
<code>
   (@ read-alt-text file-name @fp +-
       (# fp (# char-in file-name)
       + &gt;
           ;
       )
   )</code>
</p>
<empty-line/>
<p>
В таком случае функции "handle()" и "catch()" не вызываются -
но это не отменяет записи сообщения об ошибке в STDERR.
</p>
<empty-line/>
<p>
И наконец, во многих функциях поддерживаются константы с
именами вроде "READ_ANYWAY", "PROCESS_ANYWAY" и так далее:
</p>
<empty-line/>
<p>
<code>
   (@ read-alt-text file-name .text +-
       (# char-in file-name READ_ANYWAY .text)
   )</code>
</p>
<empty-line/>
<p>
И тогда функция не будет выбрасывать исключения - но всё
равно будет сбрасывать в STDERR сообщения об ошибках.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Функции высшего порядка</p></title>
<p>
Функции высшего порядка выглядят и работают как части
программы, которым передаются на вход другие части
программы, чтобы они могли их вызывать. Это позволяет
написать, например, функцию сортировки, добавив к ней
функцию сравнения, и таким образом выполнять сортировку
любых данных. Похожим образом могут быть решены задачи
обхода дерева каталогов, чтения данных из XML, подсчёта
среднего и другие.
</p>
<empty-line/>
<p>
Ещё недавно эти приёмы были редкостью: по-видимому,
скорость компьютеров на рубеже 1990-х и 2000-х не
позволяла написать хоть сколько-нибудь быстрый
компилятор, способный выбирать функции из текста
программы (в том числе и безымянные) и составлять
из них синтаксическое дерево. Сейчас они есть во
всех современных языках.
</p>
<section><!-- 2 -->
<title><p>Примеры и задачи</p></title>
<p>
В следующем примере функция "average()" рассчитывает
взвешенное среднее по массиву или списку, используя
для этого оператор цикла:
</p>
<empty-line/>
<p>
<code>
   (@ average list @fp +-
       (0 .. .weight .divider .total)
       (.* list item &gt;
           (+= 1 .weight)
           (+= (* weight (# fp item)) .total)
           (+= weight .divider)
       )
       ([divider] &gt;
           ((/ divider) total)
       &gt;
           0
       )
   +
       {A|L} list
   )</code>
</p>
<empty-line/>
<p>
А здесь точно такая же функция вызывает "records-in()",
чтобы она прошла массив или список (она пройдёт его от
первого элемента до последнего) и вызывала для каждого
элемента в нём безымянную функцию:
</p>
<empty-line/>
<p>
<code>
   (@ average list @fp +-
       (0 .. .weight .divider .total)
       (# records-in list (@ item +-
                              (+= 1 .weight)
                              (+= (* weight (# fp item)) .total)
                              (+= weight .divider)
                          )
       )
       ([divider] &gt;
           ((/ divider) total)
       &gt;
           0
       )
   +
       {A|L} list
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "records-in()" вызывается для того, чтобы
читать цены из XML-файла и заполнять переменную "prices":
</p>
<empty-line/>
<p>
<code>
   (@ xml-prices-in file-name .prices +-
       (# records-in file-name '/' PARSE_FILE (@ record +-
                                                  (attach prices (# parse-record record))
                                              )
       )
   +
       {string} file-name
       {A|L : price} prices
   )</code>
</p>
<empty-line/>
<p>
Вызвать функцию "xml-prices-in()" можно так:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       &#183;
       &#183;
       &#183;
   
       (# xml-prices-in file-name .prices)
   
       &#183;
       &#183;
       &#183;
   )</code>
</p>
<empty-line/>
<p>
А если добавить ей в качестве параметра безымянную
функцию:
</p>
<empty-line/>
<p>
<code>
   (@ xml-prices-in file-name @fp +-
       (# records-in file-name '/' PARSE_FILE (@ record +-
                                                  (# fp record)
                                              )
       )
   +
       {string} file-name
   )</code>
</p>
<empty-line/>
<p>
То её вызов будет выглядеть так:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       &#183;
       &#183;
       &#183;
   
       (# xml-prices-in file-name (@ record +-
                                      ...
                                  )
       )
   
       &#183;
       &#183;
       &#183;
   )</code>
</p>
<empty-line/>
<p>
Точно так же выглядит функция сортировки, которая
получает на вход функцию сравнения:
</p>
<empty-line/>
<p>
<code>
   (@ sort list @compare .sorted +-
       (# sort list SORT_ASC compare .sorted)
   +
       {list : &lt;T&gt;} list
       {list : &lt;T&gt; : [list]} sorted
   )
   
   (@ sort:rc list order-by @compare .sorted +-
       (' .. .lesser .greater)
       (detach -e list 1st others)
       (.* others x &gt;
           ([# compare] x 1st order-by &gt;
               (attach greater x)
           &gt;
               (attach lesser x)
           )
       )
       ([list] &gt;
           (attach sorted ls-sorted 1st gt-sorted -
               (# sort lesser compare order-by .ls-sorted
                # sort greater compare order-by .gt-sorted)
           )
       )
   +
       {SORT_* : {ASC DESC}} order-by
   )</code>
</p>
<empty-line/>
<p>
Функция сравнения выглядит так:
</p>
<empty-line/>
<p>
<code>
   (@ cmp a b +-                                    (@ cmp a b order-by:SORT_DESC +-
       ([&gt; b] a)                                        (![&gt; b] a)
   )                                                )
   
   (@ cmp a b +-                                    (@ cmp a b order-by:SORT_DESC +-
       ([= CR_GT] rc -                                  (![= CR_GT] rc -
           (# collate a b .rc)                              (# collate a b .rc)
       )                                                )
   )                                                )</code>
</p>
<empty-line/>
<p>
Как видно, она состоит из нескольких функций, и одна из
них будет вызвана в зависимости от типа данных и значения
параметра "order-by", где должна быть константа "SORT_*".
А вызов функции сортировки вместе с функцией сравнения
выглядит так:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (say abc -
           (# sort sequence (@ a b +- (# cmp a b)) .abc -
               (' 'B' 'A' 'C' 'D' 'F' 'E' .sequence)
           )
       )
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "sort()" получает на вход переменную
"sequence" и безымянную функцию, которая вызывает
функцию сравнения "cmp()" для пар элементов массива
"sequence".
</p>
<empty-line/>
<p>
Функции, принимающие на вход другие функции, могут быть
собраны в цепочки:
</p>
<empty-line/>
<p>
<code>
   (@ select-from users .report +-
       (# select s (@ x .res +-
                       (attach res &lt;&lt;- x)
                   ) .report
       -
           (# skip t 10 .s -
               (# take a 100 .t -
                   (# from users (@ x +-
                                     [&gt; 20] x.age
                                 ) .a
                   )
               )
           )
       )
   +
       {A|L : user} users
       {A|L : string} report
   )</code>
</p>
<empty-line/>
<p>
А на каком-нибудь C-подобном языке такая же цепочка была
бы записана вот так:
</p>
<empty-line/>
<p>
<code>
   list &lt;string&gt; select_from (list &lt;user&gt; users)
   {
     return users.where (x =&gt; x.age &gt; 20)
                 .take (100).skip (10)
                 .select (x =&gt; x.toString ());
   }</code>
</p>
<empty-line/>
<p>
Этот приём позволяет создать "язык запросов" для любой
предметной области - но запросы будут выглядеть как вызовы
функций и будут записаны снизу вверх.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Дерево каталогов</p></title>
<p>
Пройти дерево каталогов можно следующим образом:
</p>
<empty-line/>
<p>
<code>
   (@ files:rc path mask @fp +-
       (# files-in target (@ name +-
                              ([IS_DIRECTORY] name &gt;
                                  (# files name mask fp)
                              &gt;
                                  (# fp name)
                              )
                          )
       -
           (&#038; path '/' mask .target)
       )
   )</code>
</p>
<empty-line/>
<p>
Как видно, функция "files()" заходит в каждый каталог,
перемещаясь вглубь по дереву, и для каждого найденного
файла вызывает функцию, переданную ей в качестве параметра.
</p>
<empty-line/>
<p>
Вызов функции "files()" выглядит так:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (# files './' '*.txt' (@ name +-
                                 (say name)
                             )
       )
   )</code>
</p>
<empty-line/>
<p>
В таком случае она начнёт с текущего каталога, и будет
перебирать внутри дерева файлы с расширением "*.txt" -
а безымянная функция будет выполнять команду "say" и
печатать их имена.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Статистика по зарплатам</p></title>
<p>
В этом примере функция "records-in()" вызывается для
того, чтобы преобразовать список объявлений о работе в
отчёт с индексом конкуренции (он же "competition index"),
который рассчитывается как отношение количества вакансий
к количеству резюме:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (say "\t\t\t\t" 'Total'
            "\t\t"     'Last Month' "\n")
       (.* report row &gt;
           (say title "\t\t\t" total
                      "\t\t" last-month -
                (row.report.competition-index.title .title) |
                (row.report.competition-index.total '0.00' -&gt; .total) |
                (row.report.competition-index.last-month '0.00' -&gt; .last-month)
           )
       -
           (# read .salaries
            # process salaries .report)
       )
   )
   
   (@ process salaries .report +-
       (@ report-item-from title item +-
           (![= 0] item.vacancies.total &gt;
               (% -salary-report &gt;
                   title                                                   .report.competition-index.title
                   ((/ item.vacancies.total) item.resumes-total)           .report.competition-index.total
                   ((/ item.vacancies.last-month) item.resumes-last-month) .report.competition-index.last-month
               )
           &gt;
               (% -salary-report &gt;
                   title                                                   .report.competition-index.title
                   ((/ item.vacancies.last-month) item.resumes-last-month) .report.competition-index.total
                   ((/ item.vacancies.last-month) item.resumes-last-month) .report.competition-index.last-month
               )
           )
       )
   
       (# records-in salaries (@ job-title salary-item +-
                                  (attach report (# report-item-from job-title salary-item))
                              )
       )
   +
       {list : string &lt;- salary} salaries
       {list : string &lt;- salary-report} report
   )
   
   (@ read .salaries +-
       (^
           ''                        (% + vacancies.total vacancies.last-month resumes.total resumes.last-month )
           'Программист'             (%             37688                 5841        416208             363771 )
           'Программист 1С'          (%              4293                  846        140367             113422 )
           'Программист Web'         (%              2174                  348         97281              84908 )
           'Программист PHP'         (%              1779                  489         42034              35338 )
           'Программист C++'         (%               681                  108         35313              21743 )
           'Программист Delphi'      (%               127                   48         39332              22949 )
           'Программист Java'        (%              1245                  474         34142              25179 )
           'Программист C#'          (%               697                  341         25906              20744 )
           'Программист SQL'         (%               335                  148         73193              68230 )
           'Программист JavaScript'  (%               719                  282         37453              32372 )
           'Программист Python'      (%               556                  211         15780              13260 )
           'Программист Ruby'        (%               164                   45          2376               2270 )
           'Системный администратор' (%              3784                  634        273298             126631 )
           'Агент'                   (%              5328                  405        306179             159430 )
           'Юрист'                   (%              8419                 1568        968479             399608 )
           'Бухгалтер'               (%             54017                 3433       2371355             958053 )
           'Аналитик'                (%              5261                 2969        394656             371047 )
           'Маркетолог'              (%              6786                 1164        191428             162237 )
           'Администратор'           (%             62175                 3238       1880289             809838 )
           'Инженер'                 (%             85284                 5993       2730572            1191587 )
           'Менеджер'                (%            522871                22389       5156307            2578817 )
           '2018'                    (%                                 646949                         34572790 )
       &gt;
           .salaries
       )
   +
       {list : string &lt;- salary} salaries
   )
   
   (% salary +-
       (%
           {integer} total
           {integer} last-month
       ) vacancies
       (%
           {integer} total
           {integer} last-month
       ) resumes
   )
   
   (% salary-report +-
       {string} title
       (%
           {float} total
           {float} last-month
       ) competition-index
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
                                   Total           Last Month
   
   Программист                     11.04           62.27
   Программист 1С                  32.70           134.07
   Программист Web                 34.17           44.74
   Программист PHP                 23.63           72.27
   Программист C++                 51.85           201.32
   Программист Delphi              309.7           478.10
   Программист Java                27.42           53.12
   Программист C#                  37.17           60.83
   Программист SQL                 218.49          461.01
   Программист JavaScript          52.09           114.79
   Программист Python              28.38           62.84
   Программист Ruby                14.49           50.44
   Системный администратор         72.22           199.73
   Агент                           57.47           393.65
   Юрист                           115.03          254.85
   Бухгалтер                       43.90           279.07
   Аналитик                        75.01           124.97
   Маркетолог                      28.21           139.38
   Администратор                   30.24           250.10
   Инженер                         32.02           198.83
   Менеджер                        9.86            115.18
   2018                            53.44           53.44</code>
</p>
<empty-line/>
<p>
Как видно, хуже всего идут дела у пишущих на Delphi и
знатоков SQL. Дальше идёт обыкновенный офисный планктон,
затем системные администраторы и программисты на C++, и
затем - все остальные.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Рекурсия и циклы</p></title>
<p>
Как и операторы условия, в языке Veronica рекурсия и
циклы выглядят и работают как составные функции, которые
выполняются до тех пор, пока не будет достигнуто условие,
задаваемое в точке входа в рекурсию или в заголовке цикла.
Правила, согласно которым они должны быть записаны, не
отличаются от правил записи для всех остальных функций,
и основаны на том, что в языке нет глобальных переменных -
так что данные не располагаются где-то в одном месте, а
передаются от одной функции к другой.
</p>
<section><!-- 2 -->
<title><p>Циклы со счётчиком</p></title>
<p>
В следующем примере цикл со счётчиком перебирает числа
от единицы до "n" и печатает их:
</p>
<empty-line/>
<p>
<code>
   (.* 1..n i &gt;
       (say i)
   )</code>
</p>
<empty-line/>
<p>
А здесь цикл со счётчиком перебирает числа от "n" до
единицы:
</p>
<empty-line/>
<p>
<code>
   (.* n..1 -1 i &gt;
       (say i)
   )</code>
</p>
<empty-line/>
<p>
Цикл с двумя счётчиками:
</p>
<empty-line/>
<p>
<code>
   (.* 1..n i 1..k j &gt;
       (say i "\t" j)
   )</code>
</p>
<empty-line/>
<p>
И ещё один:
</p>
<empty-line/>
<p>
<code>
   (.* n..1 -1 i k..1 -1 j &gt;
       (say i "\t" j)
   )</code>
</p>
<empty-line/>
<p>
Для выхода из цикла существует оператор в виде стрелки:
</p>
<empty-line/>
<p>
<code>
   (.* 1..lines._ i &gt;
       (![= ';.'] lines.i &gt;
           (say lines.i)
       &gt;
           -&gt;&gt;
       )
   )</code>
</p>
<empty-line/>
<p>
А операторы "+" и "-" позволяют увеличивать и уменьшать
счётчик цикла:
</p>
<empty-line/>
<p>
<code>
   (.* 1..length i &gt;                                (.* length..1 -1 i &gt;
       ...                                              ...
       ([step-over] &gt;                                   ([step-over] &gt;
           + (step-over)                                    - (step-over)
       &gt;                                                &gt;
           ;                                                ;
       )                                                )
   )                                                )</code>
</p>
<empty-line/>
<p>
Вызывать функции в заголовках циклов запрещено:
</p>
<empty-line/>
<p>
<code>
   (.* 1..(# count array) i &gt;                                                     ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
Но можно вызвать их один раз перед выполнением цикла,
спрятав переменную внутри цикла:
</p>
<empty-line/>
<p>
<code>
   (.* 1..length i &gt;
       ...
   -
       (# count array .length)
   )</code>
</p>
<empty-line/>
<p>
Счётчик цикла не виден после его завершения:
</p>
<empty-line/>
<p>
<code>
   (.* from..to i &gt;
       (say i)
   )
   &#183;
   &#183;
   &#183;
   (say i)                                                                        ** !!!</code>
</p>
<empty-line/>
<p>
Но есть возможность изменения внешней переменной, где
и окажется последнее значение счётчика цикла:
</p>
<empty-line/>
<p>
<code>
   (0 .. .n)
   (.* from..to i .n &gt;
       (say i)
   )
   &#183;
   &#183;
   &#183;
   (say n)</code>
</p>
<empty-line/>
<p>
В редких случаях, как в следующем примере, цикл может
быть записан и без счётчика:
</p>
<empty-line/>
<p>
<code>
   (@ fibonacci max @fp +-
       (0 .. .previous
        1 .. .next)
       (.* 1..max &gt;
           ((+ next prev) .(next .previous))
           (# fp next)
       )
   )</code>
</p>
<empty-line/>
<p>
В действительности счётчик там есть, но он нигде не
используется. А в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ nlog2n max .n +-
       (max .n)
       (.* 2..max i &gt;
           ([&lt; max] (* i (# log2 i)) &gt;
               (i .n)
           &gt;
               -&gt;&gt;
           )
       )
   )</code>
</p>
<empty-line/>
<p>
Счётчик цикла "i" позволяет найти число "n", чтобы
значение функции "n*log2(n)" оказалось как можно большим,
но меньше заданной величины. В те далёкие времена, когда
были изобретены циклы со счётчиком, они примерно так и
использовались - и в таком виде они нужны и до сих пор.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Выбирающие циклы</p></title>
<p>
В следующем примере оператор цикла используется для
того, чтобы перебирать структуры в массиве или списке.
Каждая структура содержит поле "title", которое выдаётся
на печать:
</p>
<empty-line/>
<p>
<code>
   (.* items x &gt;
       (say x.title)
   )</code>
</p>
<empty-line/>
<p>
А здесь - цикл просматривает структуры данных, и если
в поле "title" есть точка с запятой, они добавляются в
массив или список в переменной "res":
</p>
<empty-line/>
<p>
<code>
   (.* items x .res &gt;
       ([! '*; *'] x.title &gt;
           (attach res x)
       &gt;
           ;
       )
   )</code>
</p>
<empty-line/>
<p>
Выбирающие циклы могут иметь счётчики:
</p>
<empty-line/>
<p>
<code>
   (.* items i x &gt;
       (say i "\t" x.title)
   )</code>
</p>
<empty-line/>
<p>
И заполнять переменные:
</p>
<empty-line/>
<p>
<code>
   (.* items i x .res &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А также выбирать из списков ключи и значения:
</p>
<empty-line/>
<p>
<code>
   (.* items key value &gt;                            (.* items i key value &gt;
       (say key "\t" value)                             (say i "\t" key "\t" value)
   )                                                )</code>
</p>
<empty-line/>
<p>
И при этом заполнять переменные:
</p>
<empty-line/>
<p>
<code>
   (.* items key value .res &gt;                       (.* items i key value .res &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Для выхода из циклов существует оператор в виде стрелки:
</p>
<empty-line/>
<p>
<code>
   (.* items x &gt;
       ([x.title] &gt;
           (say x.title)
       &gt;
           -&gt;&gt;
       )
   -
       (# read-from file-name .items)
   )</code>
</p>
<empty-line/>
<p>
Вызывать функции в заголовках циклов нельзя:
</p>
<empty-line/>
<p>
<code>
   (.* (# read-from file-name) x &gt;                                                ** !!!
       (say x.title)
   )</code>
</p>
<empty-line/>
<p>
Но есть возможность вызвать функцию перед выполнением
цикла, чтобы она заполнила переменную, которая будет
доступна внутри цикла:
</p>
<empty-line/>
<p>
<code>
   (.* items x &gt;
       (say x.title)
   -
       (# read-from file-name .items)
   )</code>
</p>
<empty-line/>
<p>
На первый взгляд кажется, что это всего лишь другая
форма записи, но всё дело в том, что во многих языках
цикл будет вызывать функцию в своём заголовке каждый
раз на каждом следующем шаге - и от этой формы записи
лучше заранее отказаться (как и было сделано).
</p>
<empty-line/>
<p>
Нередко в заголовке цикла задаётся только массив или
список - как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (.* vacancies &gt;
       (attach salary-report vacancies.keyword + professional-attractiviy -
           (((/ vacancies.offers-total) vacancies.offers-with-salary) .professional-attractiviy)
       )
   )</code>
</p>
<empty-line/>
<p>
Либо переменная со списком и заполняемая переменная:
</p>
<empty-line/>
<p>
<code>
   (.* vacancies .salary-report &gt;
       (^ vacancies.keyword professional-attractiviy -
           (((/ vacancies.offers-total) vacancies.offers-with-salary) .professional-attractiviy)
       )
   )</code>
</p>
<empty-line/>
<p>
И значение, которое возвращает или создаёт последний
оператор внутри цикла, добавляется во вторую переменную:
</p>
<empty-line/>
<p>
<code>
   (.* vacancies .salary-report &gt;
       (^ vacancies.keyword professional-attractiviy -
          &#8593;                 &#8593;
         key              value</code>
</p>
<empty-line/>
<p>
В данном случае это пара "ключ-значение", где в качестве
ключа используется строка, а в качестве значения - число с
точкой: название вакансии и отношение количества вакансий с
указанной заработной платой к общему количеству вакансий.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Циклы с условием</p></title>
<p>
Такой цикл будет повторять заданное действие до тех пор,
пока будет выполняться условие в его заголовке:
</p>
<empty-line/>
<p>
<code>
   (.* [&gt; 0] x &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (.* [&gt; 0] x &#038;&#038; [&gt; 0] y &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И в более краткой записи:
</p>
<empty-line/>
<p>
<code>
   (.* [&gt; 0] x, y &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
При этом, если условия в заголовке были связаны в цепочку
"ИЛИ", как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (.* [= 0] departure / [= 0] digression &gt;                                       ** ?!!..
       ...
   )</code>
</p>
<empty-line/>
<p>
Вполне возможно, что такой цикл никогда не завершится. И
в таком случае его лучше было бы переписать так:
</p>
<empty-line/>
<p>
<code>
   (.* [= 0] departure &#038;&#038; [= 0] digression &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Чтобы цикл работал правильно. А вот цепочки с отрицанием
в заголовках циклов - запрещены:
</p>
<empty-line/>
<p>
<code>
   (.* ![terminate-flag-1] &#038;&#038; ![terminate-flag-2] &gt;                               ** !!!
       ...
   )
   
   (.* ![no-error-1] / ![no-error-2] &gt;                                            ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
И в таком случае лучше было бы добавить в тело цикла ещё
один оператор условия:
</p>
<empty-line/>
<p>
<code>
   (.* &gt;
       ...
       ('/ [terminate-flag-1] / [terminate-flag-1] &gt;
           -&gt;&gt;
       &gt;
           ;
       )
   )</code>
</p>
<empty-line/>
<p>
Или переписать заголовок цикла:
</p>
<empty-line/>
<p>
<code>
   (.* [= 0] terminate-flag-1, terminate-flag-2 &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
При этом одиночные отрицания в заголовках - возможны:
</p>
<empty-line/>
<p>
<code>
   (.* ![terminate-flag] &gt;                          (.* ![# all-values] array &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
Хотя и здесь лучше было бы записать так:
</p>
<empty-line/>
<p>
<code>
   (.* [flag] &gt;                                     (.* [# no-values] array &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
В некоторых случаях в заголовки циклов с условием могут
быть добавлены функции, которые в таком случае должны
быть отмечены отдельным флажком:
</p>
<empty-line/>
<p>
<code>
   (.* [# chars] file &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
В том числе их можно и соединять в цепочки:
</p>
<empty-line/>
<p>
<code>
   (.* [bytes-exists] / [# process-exists] process-id &gt;
       (# peek socket .bytes-exists)
       ...
   -
       (0 .. .bytes-exists)
   )</code>
</p>
<empty-line/>
<p>
И в таком виде циклы с условием часто используются для
чтения строк из текстовых файлов:
</p>
<empty-line/>
<p>
<code>
   (@ levels 'salary file-name .records +-
       (.* [# chars] file &gt;
           ('' .. .line)
           (# line-in file-name .line)
           ([line] &gt;
               (parse line salary "\t" quantity "\t" job-title "\t" .)
               (attach records (%
                                  salary    -&gt; ._
                                  quantity  -&gt; ._
                                  job-title -&gt; ._
                               )
               )
           )
       )
   +
       {A|L : salary} records
   )</code>
</p>
<empty-line/>
<p>
А также для обработки сообщений, поступающих по сети:
</p>
<empty-line/>
<p>
<code>
   (@ thread 'listener pipe +-                      (@ listener +-
       (.* [# chars] pipe &gt;                             (# net x (%
           (# char-in pipe .request                                  ''127.0.0.1 .ip
            # process request .response                                     8080 .port
            # char-out pipe response)                                       2000 .timeout
       )                                                         )
   )                                                             (@ pipe +-
                                                                     (start thread pipe)
                                                                 )
                                                        -
                                                            (+N_TCP+N_LISTENER .x)
                                                        )
                                                    )</code>
</p>
<empty-line/>
<p>
Циклы в последних двух примерах будут выполняться до
тех пор, пока функция "chars()" будет возвращать "1",
а как только она вернёт "0" - цикл завершается.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Вложенные циклы</p></title>
<p>
Циклы внутри других циклов - запрещены:
</p>
<empty-line/>
<p>
<code>
   (.* 1.n i &gt;
       (.* 1..k j &gt;                                                               ** !!!
           ...
       )
   )</code>
</p>
<empty-line/>
<p>
И вместо них надо использовать циклы с двумя счётчиками:
</p>
<empty-line/>
<p>
<code>
   (.* 1.n i 1..k j &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Циклы внутри условий - запрещены:
</p>
<empty-line/>
<p>
<code>
   ([# chars] file &gt;
       (.* &gt;                                                                      ** !!!
           ...
       )
   )</code>
</p>
<empty-line/>
<p>
И вместо них надо более правильно задавать условие в
заголовке цикла:
</p>
<empty-line/>
<p>
<code>
   (.* [# chars] file &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
В данном случае цикл не будет выполняться, если функция
"chars()" вернёт нулевое значение - так что добавлять
оператор условия поверх цикла не нужно.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Рекурсивные вызовы</p></title>
<p>
Рекурсия - приём, когда функция во время работы повторно
входит в ту же функцию, или, как ещё говорят, "вызывает
себя же". В этом нет ничего особенного, если учесть, что
в языке Veronica нет глобальных переменных. А раз их нет -
ничто не мешает вызвать ту же самую функцию ещё раз.
</p>
<empty-line/>
<p>
Рекурсия используется при обходе дерева каталогов:
</p>
<empty-line/>
<p>
<code>
   (@ files:rc path mask @fp +-
       (# files-in target (@ name +-
                              ([IS_DIRECTORY] name &gt;
                                  (# files name mask fp)
                              &gt;
                                  (# fp name)
                              )
                          )
       -
           (&#038; path '/' mask .target)
       )
   )</code>
</p>
<empty-line/>
<p>
А также дерева структур данных:
</p>
<empty-line/>
<p>
<code>
   (@ walk-tree:rc node @fp +-
       (" node.
           (# fp data -
               (# walk-tree left fp : [left]
                # walk-tree right fp : [right])
           )
       )
   +
       {tree-node} node
   )</code>
</p>
<empty-line/>
<p>
И требует меньше памяти, чем создание массивов на каждом
шаге (этот приём, хоть и кажется полнейшей дикостью, всё
ещё используется в некоторых языках и библиотеках).
</p>
<empty-line/>
<p>
В языке Veronica рекурсивные функции должны быть отмечены
флажком "rc", а хвостовая рекурсия отмечается флажком "c".
Создание "петель" в программном коде - считается ошибкой.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Хвостовая рекурсия</p></title>
<p>
Хвостовая рекурсия выглядит как обыкновенная рекурсия,
но в таком случае функция крутится "на месте" - как в
следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ read-messages:c msg-queue @fp +-
       (# fp msg -
           (# read msg-queue .msg)
       )
       ([# chars] msg-queue &gt;
           (# read-messages *)
       &gt;
           ;
       )
   )</code>
</p>
<empty-line/>
<p>
Всё то же самое может быть записано и с помощью цикла:
</p>
<empty-line/>
<p>
<code>
   (@ read-messages:c msg-queue @fp +-
       (.* [# chars] msg-queue &gt;
           (# fp (# read msg-queue))
       )
   )</code>
</p>
<empty-line/>
<p>
И в большинстве случаев циклы оказываются удобнее, чем
повторный вход в ту же функцию - и к тому же, их легче
читать.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Задача о расстояниях</p></title>
<p>
Задача о расстояниях, она же задача случайного блуждания
и ещё с десяток названий - расчётная задача, позволяющая
узнать, сколько пройдёт случайная точка за N шагов. Как
известно, правдоподобное расстояние в таком случае может
быть оценено по формуле "средняя длина шага * квадратный
корень из количества шагов", но настоящее расстояние по
итогам большого числа экспериментов получается меньше:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (# random-walk 5000 250 6.3225 12)
   )
   
   (@ random-walk experiments steps avg-step-size repeats +-
       (say 'Average' "\t\t" 'Probable' "\n")
       (.* 1..repeats &gt;
           (say average "\t\t" probable -
                ((# calculate-average-distance experiments steps avg-step-size) '0.00' -&gt; .average) |
                ((# calculate-probable-distance steps avg-step-size) '0.00' -&gt; .probable)
           )
       )
   )
   
   (@ calculate-average-distance experiments steps avg-step-size +-
       (0 .. .divider .total)
       (.* 1..report._ i &gt;
           (+= (# abs report.i) .total)
           (++ .divider)
       -
           (# repeat-random-walks experiments steps avg-step-size .report)
       )
       ([&gt; 0] divider &gt;
           ((/ divider) total)
       &gt;
           (error ERR_EMPTY_VARIABLE)
       )
   )
   
   (@ repeat-random-walks experiments steps avg-step-size .report +-
       (.* 1..experiments .report &gt;
           (# do-random-walk steps avg-step-size)
       )
   +
       {integer} experiments
       {array : integer} report
   )
   
   (@ do-random-walk steps avg-step-size .distance +-
       (.* 1..steps &gt;
           ([= 1] (# rand 0 1) &gt;
               (+= (* 2 (# rand 1 avg-step-size)) .distance)
           &gt;
               (-= (* 2 (# rand 1 avg-step-size)) .distance)
           )
       )
   +
       {integer} steps avg-step-size
       {integer : +-} distance
   )
   
   (@ calculate-probable-distance steps avg-step-size +-
       (* (# sqrt steps) avg-step-size)
   +
       {integer} steps avg-step-size
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   Average         Probable
   
   78.88           99.97
   78.50           99.97
   80.61           99.97
   78.61           99.97
   79.12           99.97
   79.13           99.97
   79.29           99.97
   80.37           99.97
   80.06           99.97
   80.94           99.97
   79.24           99.97
   79.05           99.97</code>
</p>
<empty-line/>
<p>
Как видно, правдоподобное расстояние здесь приближается
к 100, а среднее расстояние по итогам 5000 экспериментов
по 250 шагов в каждом оказывается в диапазоне 78.5..81 -
то есть, формула для оценки наиболее правдоподобного
расстояния оценивает его не точно.
</p>
<empty-line/>
<p>
Более точной формулы пока не придумано.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>&nbsp;</p></title>
<p>&nbsp;</p>
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Команды и операторы</p></title>
<p>
Команды и операторы, показанные в следующих главах,
были изобретены для того, чтобы программы на языке
Veronica могли быть записаны в более кратком виде.
Компилятор во время построения синтаксического дерева
разворачивает их в цепочки вызовов функций, что даёт
возможность записать в виде нескольких строк любые
действия - даже те, которые не могут быть записаны с
помощью вызовов функций.
</p>
<section><!-- 2 -->
<title><p>Команды языка</p></title>
<p>
В следующем примере команда "say" печатает число,
полученное умножением двух на два:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (say (* 2 2))
   )</code>
</p>
<empty-line/>
<p>
И за этой краткой записью скрывается вызов функции
"std-out()" с двумя параметрами, а именно - строкой
и символом перехода на следующую строку:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (# std-out _str "\n" -
           ((* 2 2) -string &gt; ._str)
       )
   )</code>
</p>
<empty-line/>
<p>
Точно такая же команда "log" сбрасывает текстовые
сообщения в поток STDLOG, а не STDOUT:
</p>
<empty-line/>
<p>
<code>
   (\ alarm &gt;
       ...
   + ec error &gt;
       (log error)
       (# handle ec)
   )</code>
</p>
<empty-line/>
<p>
И её часто используют вместе с параметрами, задающими
отступ вправо и влево:
</p>
<empty-line/>
<p>
<code>
   (log +1 'Entering ' task-name ' -- msg_id: ' message.id)
   
   &#183;
   &#183;
   &#183;
   
   (log -1 'Leaving ' task-name ' -- msg_id: ' message.id)</code>
</p>
<empty-line/>
<p>
Ведение логов с помощью этой команды не должно заменять
сбрасывание сообщений об ошибках в STDERR: они в любом
случае будут сбрасываться в STDERR, как только где-то в
программе появится команда "exit" или возникнет ошибка
во время выполнения.
</p>
<empty-line/>
<p>
Создать переменную с заданными типом позволяет команда
"allocate":
</p>
<empty-line/>
<p>
<code>
   (allocate type-name variable)</code>
</p>
<empty-line/>
<p>
При этом тип задаётся строкой:
</p>
<empty-line/>
<p>
<code>
   ('array : array : rgb-24' .type-name)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (`BMP_MATRIX_TYPE .type-name)</code>
</p>
<empty-line/>
<p>
И не теряется во время последующих действий с переменной:
</p>
<empty-line/>
<p>
<code>
   ([is {array : array : rgb-24}] variable &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Команда "explain" позволяет получить какие-то сведения о
структуре данных или одном из её полей:
</p>
<empty-line/>
<p>
<code>
   (explain record.field-name field-info)</code>
</p>
<empty-line/>
<p>
И как правило, используется для печати отчётов:
</p>
<empty-line/>
<p>
<code>
   ('
       (% + title      field-info             )
       (%   `STR_TITLE (explain product.title))
       (%   `STR_PRICE (explain product.price))
       (%   `STR_COLOR (explain product.color))
   &gt;
       .report.columns
   )</code>
</p>
<empty-line/>
<p>
Здесь в переменной "report.columns" окажутся все сведения
о том, как добраться до полей в структурах "product" - так,
что теперь они могут быть напечатаны. Названия столбиков в
отчётах задаются отдельно.
</p>
<empty-line/>
<p>
Команда "Relay" предназначена для того, чтобы вызвать сразу
несколько функций:
</p>
<empty-line/>
<p>
<code>
   (relay syntax-checker text ST_* report)</code>
</p>
<empty-line/>
<p>
Она развернётся в цепочку вызовов:
</p>
<empty-line/>
<p>
<code>
   (# syntax-checker text ST_TEXT_INI_FILE .report:ST_TEXT_INI_FILE
    # syntax-checker text ST_XML_INI_FILE .report:ST_XML_INI_FILE
    # syntax-checker text ST_SE_INI_FILE .report:ST_SE_INI_FILE)</code>
</p>
<empty-line/>
<p>
Каждая функция сможет заполнить список "ключ-значение"
с константой, записав туда структуру данных (причём это
могут быть структуры разных типов), а набирать функции
вручную необязательно.
</p>
<empty-line/>
<p>
Команда "parse" позволяет разбирать строки по словам:
</p>
<empty-line/>
<p>
<code>
   (parse file-name _ '.' ext)                      (parse -r file-name path . '/' _ '.' .)</code>
</p>
<empty-line/>
<p>
А команда "!" позволяет разбирать их по правилам в виде
звёздочек и прямоугольных скобок:
</p>
<empty-line/>
<p>
<code>
   ([! '*/'] str &gt;
       (! str '*/articles/^[0-9]/*' .article-id)
   &gt;
       (! str '*/articles/^[0-9]' .article-id)
   )</code>
</p>
<empty-line/>
<p>
В том случае, когда содержимое строки не соответствует
правилам её разбора, обе команды выбрасывают неисправимую
ошибку во время выполнения, причём эти ошибки - не то же,
что исключения: перехватить и обработать такую ошибку не
получится, она приводит к завершению программы с печатью
цепочки вызовов в STDERR.
</p>
<empty-line/>
<p>
С помощью команды "attach" можно добавлять элементы в
списки - слева или справа:
</p>
<empty-line/>
<p>
<code>
   (attach list element)                            (attach -l list element)
   
   (attach list key + value)                        (attach -l list key + value)</code>
</p>
<empty-line/>
<p>
А в массивы - слева:
</p>
<empty-line/>
<p>
<code>
   (attach array element)</code>
</p>
<empty-line/>
<p>
Команда "detach" позволяет забирать элементы из списков:
</p>
<empty-line/>
<p>
<code>
   (detach list element tail)                       (detach -r list element tail)
   (detach list element _)                          (detach -r list element _)
   (detach list .)                                  (detach -r list .)
   
   (detach list key + value tail)                   (detach -r list key + value tail)
   (detach list key + value _)                      (detach -r list key + value _)
   (detach list .)                                  (detach -r list .)</code>
</p>
<empty-line/>
<p>
И может работать и с массивами, создавая "срезы" над ними:
</p>
<empty-line/>
<p>
<code>
   (detach array element tail)                      (detach -r array element tail)
   
   (detach array key + value tail)                  (detach -r array key + value tail)</code>
</p>
<empty-line/>
<p>
Та же команда с флажком "-e" позволяет работать и с пустыми
списками:
</p>
<empty-line/>
<p>
<code>
   (detach -e list 1st _)                           (detach -e -r list 1st _)
   
   (detach -e list key + value _)                   (detach -e -r list key + value _)</code>
</p>
<empty-line/>
<p>
И будет работать с массивами, создавая "срезы" над ними:
</p>
<empty-line/>
<p>
<code>
   (detach -e array 1st others)                     (detach -e -r array 1st others)
   
   (detach -e array key + value others)             (detach -e -r array key + value others)</code>
</p>
<empty-line/>
<p>
Команды "attach" и "detach", как и команды "parse" и "!",
как только что-то идёт не так, выбрасывают неисправимые
ошибки во время выполнения, не допуская потери данных или
их записи не по назначению. Перехватить эти ошибки нельзя.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Операторы сравнения</p></title>
<p>
В том случае, когда к какому-либо типу данных надо
добавить операторы сравнения, это делается следующим
образом:
</p>
<empty-line/>
<p>
<code>
   (@ [&gt;=] a b V_AREA precision &gt;
       (// ([&gt;= (# area b)] (# area a) precision) 1 0)
   +
       {figure} a b
   )</code>
</p>
<empty-line/>
<p>
И теперь в программе можно написать:
</p>
<empty-line/>
<p>
<code>
   ([&gt;= b] a V_AREA precision &gt;                     ([&gt;= b] a V_AREA V_PRECISION &gt;
       ...                                              ...
   &gt;                                                &gt;
       ...                                              ...
   )                                                )</code>
</p>
<empty-line/>
<p>
А здесь:
</p>
<empty-line/>
<p>
<code>
   (@ [=] a b PR_TITLE STR_MIXED_CASE &gt;
       (// ([= b.title] a.title STR_MIXED_CASE) 1 0)
   +
       {product-item} a b
   )</code>
</p>
<empty-line/>
<p>
Оператор сравнения позволит написать так:
</p>
<empty-line/>
<p>
<code>
   ([= selected-item] price-list-item PR_TITLE STR_MIXED_CASE &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И точно так же, оператор для сравнения даты и времени:
</p>
<empty-line/>
<p>
<code>
   (@ [=] a b DT_DAYS &gt;
       (# compare a b CR_EQ DT_DAYS)
   +
       {datetime} a b
   )</code>
</p>
<empty-line/>
<p>
Позволит написать так:
</p>
<empty-line/>
<p>
<code>
   ([= today] item.date DT_DAYS &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А оператор для сравнения строк:
</p>
<empty-line/>
<p>
<code>
   (@ [=] a b STR_MIXED_CASE &gt;
       (# compare a b CR_EQ STR_MIXED_CASE)
   +
       {string} a b
   )</code>
</p>
<empty-line/>
<p>
Позволит написать так:
</p>
<empty-line/>
<p>
<code>
   ([= name] word STR_MIXED_CASE &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Так появляется возможность выполнять сортировку структур
данных, вызывая функцию сортировки с наименьшим возможным
числом параметров - то есть, вместо длинной записи:
</p>
<empty-line/>
<p>
<code>
   (# sort array (@ a b +- (# compare a b)) .sorted-array)</code>
</p>
<empty-line/>
<p>
Обойтись более краткой:
</p>
<empty-line/>
<p>
<code>
   (# sort array .sorted-array)</code>
</p>
<empty-line/>
<p>
И функция "sort()" будет самостоятельно вызывать операторы
сравнения, чтобы выполнить сортировку.
</p>
<empty-line/>
<p>
То же самое относится и к другим операторам.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Несколько действий</p></title>
<p>
Операторы и команды в языке Veronica позволяют выполнять
одно и то же действие с несколькими переменными, чтобы не
повторять их по нескольку раз. Так, в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (0 .x .y .z)</code>
</p>
<empty-line/>
<p>
Команда записи значения (она работает как оператор
присваивания) запишет нулевое значение сразу в три
переменные. А здесь:
</p>
<empty-line/>
<p>
<code>
   (++ .x .y .z)</code>
</p>
<empty-line/>
<p>
Три переменные будут увеличены на единицу. А в следующем
примере:
</p>
<empty-line/>
<p>
<code>
   (0 .(z .(y .x))</code>
</p>
<empty-line/>
<p>
Значения переменных будут переданы по цепочке, слева
направо. Причём с помощью этой формы записи можно и
поменять значения двух переменных:
</p>
<empty-line/>
<p>
<code>
   (a .(b .a))</code>
</p>
<empty-line/>
<p>
И точно так же, оператор сравнения с цепочкой "ИЛИ":
</p>
<empty-line/>
<p>
<code>
   ('/ [= 1] x / [= 2] x / [= 3] x &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Может быть уменьшен до более краткой формы записи:
</p>
<empty-line/>
<p>
<code>
   ([= 1, 2, 3] x &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А оператор сравнения с цепочкой "И":
</p>
<empty-line/>
<p>
<code>
   ('&#038; [&gt; 0] x &#038;&#038; [&gt; 0] y &#038;&#038; [&gt; 0] z &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Может быть уменьшен до такой формы записи:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x, y, z &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
При этом, поскольку в цепочках "И" не допускается
продолжение "иначе", как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   ('&#038; [&gt; 0] x &#038;&#038; [&gt; 0] y &#038;&#038; [&gt; 0] z &gt;
       ...
   &gt;                                                                              ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
Оно будет считаться ошибкой и здесь:
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] x, y, z &gt;
       ...
   &gt;                                                                              ** !!!
       ...
   )</code>
</p>
<empty-line/>
<p>
И в таком случае надо будет создать внутри функции
ещё одну внутреннюю функцию и перенести в неё часть
проверок, чтобы оператор сравнения оказался более
кратким.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Действия с числами</p></title>
<p>
Операторы для выполнения действий с числами (сложение,
вычитание, умножение, деление) выглядят следующим образом:
</p>
<empty-line/>
<p>
<code>
   (+ 1 x) (++ .y)                                  Сложение
   ((- y) x) (-- .x)                                Вычитание
   (* x y .z) (*= 2 .y)                             Умножение
   ((/ y) x .z) (/= 5 .y)                           Деление
   
   ((% 2) x .y)                                     Деление нацело
   ((%% 2) x .z)                                    Остаток от деления</code>
</p>
<empty-line/>
<p>
Смена знака и получение обратной величины выполняется
так:
</p>
<empty-line/>
<p>
<code>
   (*= (-1) .x)                                     Смена знака числа
   ((/ x) 1 .y)                                     Обратная величина</code>
</p>
<empty-line/>
<p>
Расчёты с процентами:
</p>
<empty-line/>
<p>
<code>
   (+= @x .y)                                       Изменение числа на несколько процентов
   ((\ y) x .z)                                     Получение процентов из двух чисел</code>
</p>
<empty-line/>
<p>
И правила их выполнения:
</p>
<empty-line/>
<p>
<code>
   (+ 20 100)       &#8594;     120
   ((- 100) 120)    &#8594;      20
   (+ @0.2 100)     &#8594;     120
   ((\ 100) 120)    &#8594;     0.2
   (* 100 0.2)      &#8594;      20
   ((/ 100) 20)     &#8594;     0.2</code>
</p>
<empty-line/>
<p>
Алгебра и тригонометрия:
</p>
<empty-line/>
<p>
<code>
   (** 2 x .y)                                      Возведение в степень
   (# sqrt y .x)                                    Квадратный корень
   
   (# sin x .y)                                     Тригонометрические функции
   (# cos x .y)
   ...</code>
</p>
<empty-line/>
<p>
Квадратные корни и логарифмы:
</p>
<empty-line/>
<p>
<code>
   (# ln x .y)                                      Логарифм (показатель степени) по основанию "e"
   (# log x 2 .y)                                   Логарифм (показатель степени) по основанию 2
   (# log x 10 .y)                                  Логарифм (показатель степени) по основанию 10
   
   (# exp x .e)                                     Функция "e^x" (экспонента от x)
   :exp                                             Число "e"
   :pi                                              Число "пи"
   
   (# pi 2)                                         Число "пи/2" (точная величина)
   (# sqrt 2)                                       Корень из двух (точная величина)</code>
</p>
<empty-line/>
<p>
Как видно, эти операторы способны возвращать значения в
то место в программе, где стоит открывающая скобка (так,
что из них могут быть составлены цепочки) а также позволяют
менять переменные "на месте" и создавать новые переменные.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Действия со строками</p></title>
<p>
Команда в виде двух одинарных кавычек предназначена
для того, чтобы менять строку "на месте" - до того,
как переменная будет закрыта от дальнейших изменений:
</p>
<empty-line/>
<p>
<code>
   (''= word .string)                               Присоединение строки к строке
   (''= template record .string)                    Присоединение к строке данных из структуры/массива/списка</code>
</p>
<empty-line/>
<p>
А функции "ext-left()" и "ext-right()" добавляют к
строке пробелы и символы "\t" слева или справа:
</p>
<empty-line/>
<p>
<code>
   (# ext-left string ' ' length .line)             Добавление пробелов к строке слева
   
   
   (# ext-right string ' ' length .line)            Добавление пробелов к строке справа
   
   
   (# ext-left string "\t" ' '                      Добавление "\t" и пробела к строке слева
               tab-size length .line)
   
   (# ext-right string ' ' "\t"                     Добавление пробела и "\t" к строке справа
                tab-size length .line)</code>
</p>
<empty-line/>
<p>
Операторы в виде стрелок с указанием типов данных
позволяют создавать из строк структуры и заполнять
их:
</p>
<empty-line/>
<p>
<code>
   (string -integer -&gt; .number)                     Получение числа из строки
   (string -integer -&gt;&gt; .number)                    Получение числа или нулевого значения
   
   (string format -datetime -&gt; .time)               Получение даты и времени из строки
   (string format -datetime -&gt;&gt; .time)              Получение даты и времени или пустого значения</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (% -rgb &gt;                                        Получение чисел из строк "#RRGGBB"
       (cs 2 2 STR_0xFF) -&gt; .red                                               &#8593;
       (cs 4 2 STR_0xFF) -&gt; .green                                               &#8593;
       (cs 6 2 STR_0xFF) -&gt; .blue                                                  &#8593;
   &gt;
       .color
   )</code>
</p>
<empty-line/>
<p>
Печать числа в выбранной системе счисления:
</p>
<empty-line/>
<p>
<code>
   (number '0.00' -&gt; .string)                       Печать числа в десятичном виде
   (number 'x4' STR_0xFF -&gt; .string)                Печать числа в шестнадцатеричном виде</code>
</p>
<empty-line/>
<p>
Удаление пробелов и символов перехода на новую строку:
</p>
<empty-line/>
<p>
<code>
   (string -&gt; .str)                                 Удаление пробелов и символов перевода строки
   
   (string STR_RIGHT -&gt; .str)                       Удаление пробелов и символов перевода строки в конце строки
   
   (string symbols -&gt; .str -                        Удаление заданных символов в начале строки
       ((' "\n" "\r") .symbols)
   )
   
   (string symbols STR_RIGHT -&gt; .str -              Удаление заданных символов в конце строки
       ((' "\n" "\r") .symbols)
   )</code>
</p>
<empty-line/>
<p>
Перебор букв/слов/строк в строке:
</p>
<empty-line/>
<p>
<code>
   (# chars-in string (@ char +-                    Печать букв или байтов в строке (зависит от кодировки)
                          ...
                      )
   )
   
   (# words-in string (@ word +-                    Печать слов в строке (разделители по умолчанию)
                          ...
                      )
   )
   
   (# words-in string ' ' (@ word +-                Печать слов в строке (разделитель - пробел)
                              ...
                          )
   )
   
   (# lines-in string (@ line +-                    Печать строк в тексте (разделитель - "\n")
                          ...
                      )
   )</code>
</p>
<empty-line/>
<p>
Перебор букв/слов/строк в файле:
</p>
<empty-line/>
<p>
<code>
   (# chars-in file-name READ_FILE (@ char +-       Выбор букв или байтов из файла без чтения его в память
                                       ...
                                   )
   )
   
   (# words-in file-name READ_FILE (@ char +-       Выбор слов из файла без чтения его в память (разделители по умолчанию)
                                       ...
                                   )
   )
   
   (# words-in file-name ' ' READ_FILE (@ char +-   Выбор слов из файла без чтения его в память (разделитель - пробел)
                                           ...
                                       )
   )
   
   (# lines-in file-name READ_FILE (@ line +-       Выбор строк из файла без чтения его в память (разделитель - "\n")
                                       ...
                                   )
   )</code>
</p>
<empty-line/>
<p>
Как видно, константа "READ_FILE" приводит к тому,
что функции "chars-in()", "words-in()" и "lines-in()"
работают со строкой как с именем файла, и если файл
не будет найден - выбрасывают исключение с константой
"ERR_*".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Структуры данных</p></title>
<p>
Точно так же, как и операторы сравнения, к структурам
данных могут быть добавлены операторы сложения, вычитания,
умножения и деления - как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ [+] ab cd .acbd &gt;
       (%
           (+ ab.real cd.real)           .real
           (+ ab.imaginary cd.imaginary) .imaginary
       &gt;
           .acbd
       )
   +
       {complex} ab cd acbd
   )</code>
</p>
<empty-line/>
<p>
И теперь переменные типа "complex" можно складывать:
</p>
<empty-line/>
<p>
<code>
   (+ xi yi .zi)</code>
</p>
<empty-line/>
<p>
А функции в виде заглавных букв и оператор умножения в
следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ [T] matrix &gt;                                  (@ [*] a b &gt;
       (# transpose matrix)                             (# multiply a b)
   +                                                +
       {matrix} _                                       {matrix} a b
   )                                                )
   
   (@ [I] matrix &gt;
       (# inverse matrix MT_INVERSE_AJDT)
   +
       {matrix} _
   )
   
   (@ [J] matrix &gt;
       (# transpose matrix MT_CONJUGATE_TRANSPOSE)
   +
       {matrix} _
   )</code>
</p>
<empty-line/>
<p>
Позволяют выполнять расчёты с матрицами:
</p>
<empty-line/>
<p>
<code>
   (@ calc-x g h y &gt;
       (* I&lt;* hb h&gt; hb y -
           (* T&lt;h&gt; I&lt;b&gt; .hb -
               (* g T&lt;g&gt; .b)
           )
       )
   +
       {matrix} g h y
   )</code>
</p>
<empty-line/>
<p>
Ещё несколько операторов предназначены для работы с
векторами на плоскости:
</p>
<empty-line/>
<p>
<code>
   (^= angle .vector)
   (*= @factor .vector)</code>
</p>
<empty-line/>
<p>
А также соединения структур данных:
</p>
<empty-line/>
<p>
<code>
   (&lt;+&gt; prices primary-demand .secondary-demand)</code>
</p>
<empty-line/>
<p>
Следующий пример - функция, которая вызывается во время
создания структуры:
</p>
<empty-line/>
<p>
<code>
   (@ &lt;&gt; *file &gt;
       (# fs FS_TMP .file.tmp-name)
   +
       {file-in-queue} file
   )</code>
</p>
<empty-line/>
<p>
А здесь - функция, вызываемая во время её удаления:
</p>
<empty-line/>
<p>
<code>
   (@ [] file &gt;
       ('&#038; [file.tmp-name] &#038;&#038; [# file-exists] file.tmp-name &gt;
           (# fs FS_DELETE file.tmp-name)
       )
   +
       {file-in-queue} file
   )</code>
</p>
<empty-line/>
<p>
Как видно, первая функция создаёт временный файл,
а вторая - удаляет его. И сама структура данных с
типом "file-in-queue" в последних двух примерах
создаётся для того, чтобы файл, созданный где-то
внутри дерева каталогов, был виден в программе
как структура данных, а вместо полей у неё были
бы "wr"-функции.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Массивы и списки</p></title>
<p>
Команды "attach" и "detach" для работы со списками
и массивами уже были показаны в предыдущих главах -
а здесь они перечислены вместе со всеми сочетаниями
их параметров:
</p>
<empty-line/>
<p>
<code>
   (attach list value)                              Добавление значения
   (attach list key + value)                        Добавление ключа и значения
   
   (attach -l list value)                           Добавление значения в начало списка
   (attach -l list key + value)                     Добавление ключа и значения в начало списка
   
   (detach list head tail)                          Отсоединение первого значения
   (detach list head _)                             Отсоединение первого значения
   (detach list .)                                  Отсоединение первого значения
   
   (detach list key + value tail)                   Отсоединение первого ключа и значения
   (detach list key + value _)                      Отсоединение первого ключа и значения
   (detach list .)                                  Отсоединение первого ключа и значения
   
   (detach -r list head tail)                       Отсоединение последнего значения
   (detach -r list head _)                          Отсоединение последнего значения
   (detach -r list .)                               Отсоединение последнего значения
   
   (detach -r list key + value tail)                Отсоединение последнего ключа и значения
   (detach -r list key + value _)                   Отсоединение последнего ключа и значения
   (detach -r list .)                               Отсоединение последнего ключа и значения
   
   (detach -e list head tail)                       Отсоединение первого значения с возможностью пустого списка
   (detach -e list head _)                          Отсоединение первого значения с возможностью пустого списка
   (detach -e list .)                               Отсоединение первого значения с возможностью пустого списка
   
   (detach -e list key + value tail)                Отсоединение первого ключа и значения с возможностью пустого списка
   (detach -e list key + value _)                   Отсоединение первого ключа и значения с возможностью пустого списка
   (detach -e list .)                               Отсоединение первого ключа и значения с возможностью пустого списка
   
   (detach -e -r list head tail)                    Отсоединение последнего значения с возможностью пустого списка
   (detach -e -r list head _)                       Отсоединение последнего значения с возможностью пустого списка
   (detach -e -r .)                                 Отсоединение последнего значения с возможностью пустого списка
   
   (detach -e -r list key + value tail)             Отсоединение последнего ключа и значения с возможностью пустого списка
   (detach -e -r list key + value _)                Отсоединение последнего ключа и значения с возможностью пустого списка
   (detach -e -r list .)                            Отсоединение последнего ключа и значения с возможностью пустого списка</code>
</p>
<empty-line/>
<p>
В свою очередь, команды и операторы для работы со
списками "ключ-значение" выглядят так:
</p>
<empty-line/>
<p>
<code>
   ('ABCD' .list:key)                               Добавление или изменение значения
   (list:key .value)                                Чтение значения по значению ключа
   
   ([list.:key] &gt; ...)                              Проверка существования ключа
   ([list.!key] &gt; ...)                              Проверка, что ключа не существует
   
   (\= key .list)                                   Удаление ключа и значения
   (\= * .list)                                     Удаление всех ключей и значений</code>
</p>
<empty-line/>
<p>
Команды "insert" и "remove" позволяют добавить
элемент в любое место в списке или удалить его:
</p>
<empty-line/>
<p>
<code>
   (insert -s list item new-item)
   (remove -s list item)</code>
</p>
<empty-line/>
<p>
И с указанием номеров элементов - от единицы до длины
списка:
</p>
<empty-line/>
<p>
<code>
   (insert -a list position new-item)
   (remove -a list position)</code>
</p>
<empty-line/>
<p>
Создать новый список "ключ-значение" на основе
существующего и добавить в него новые ключи и
значения можно так:
</p>
<empty-line/>
<p>
<code>
   (^
       list:% .%
       key    .value
   &gt;
       .list
   )</code>
</p>
<empty-line/>
<p>
А создать новый список, не добавляя ключи и значения -
вот так:
</p>
<empty-line/>
<p>
<code>
   (^                                               (^
       list:% .% \= key                                 list:% .% \= * lifetime-{ms}
   &gt;                                                &gt;
       .list                                            .list
   )                                                )</code>
</p>
<empty-line/>
<p>
Команды с одинарной кавычкой предназначены для того,
чтобы создавать пересечения списков:
</p>
<empty-line/>
<p>
<code>
   ('|| a b .all-records)
   ('|! a b .a-plus-b)
   ('!&#038; a b .a-minus-b)
   ('&#038;&#038; a b .intersection)
   
   ('!&#038; b a .b-minus-a)
   ('|| ('!&#038; a b) ('!&#038; b a) .difference)</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   ('|! a b SRC_KEYS ARG_KEYS .all-keys-and-values)</code>
</p>
<empty-line/>
<p>
При этом следующие команды:
</p>
<empty-line/>
<p>
<code>
   ('|| list (' x) .list)                           ('|| (' x) list .list)</code>
</p>
<empty-line/>
<p>
Будут преобразованы в команды "attach" и "detach":
</p>
<empty-line/>
<p>
<code>
   (attach list x)                                  (attach -l list x)</code>
</p>
<empty-line/>
<p>
Возможно и изменение списка "на месте" с добавлением
элементов из другого списка:
</p>
<empty-line/>
<p>
<code>
   ('||= src .dest)</code>
</p>
<empty-line/>
<p>
Эта команда работает как оператор цикла:
</p>
<empty-line/>
<p>
<code>
   (.* src x &gt;
       (attach dest x)
   )</code>
</p>
<empty-line/>
<p>
А команда с оператором в виде стрелки:
</p>
<empty-line/>
<p>
<code>
   (attach list &lt;- x)</code>
</p>
<empty-line/>
<p>
Выполняет преобразование данных и добавляет их в список:
</p>
<empty-line/>
<p>
<code>
   (attach list _x -
       (x -report-item -&gt; ._x)
   )</code>
</p>
<empty-line/>
<p>
В последнем примере тип данных в переменной "x" может
быть каким угодно: главное, чтобы для него был написан
оператор-стрелка.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Разряды в числах</p></title>
<p>
Для того, чтобы переключать разряды в числах, создаётся
текстовый файл примерно такого содержания:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; rgb-color.bin &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   32 .. 25  .alpha
   24 .. 17  .red
   16 ..  9  .green
    8 ..  1  .blue</code>
</p>
<empty-line/>
<p>
Компилятор читает этот файл и создаёт из него функции,
позволяющие читать разряды в числах и менять их:
</p>
<empty-line/>
<p>
<code>
   ((# rgb-color brush.color).alpha .transparency)</code>
</p>
<empty-line/>
<p>
Предполагается, что разряды в числах начинаются справа,
то есть:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1&#9474; = 1
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
    3 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
    2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1</code>
</p>
<empty-line/>
<p>
И таким образом, короткие числа будут расположены
внутри более длинного числа начиная справа:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1&#9474; = 1
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9524;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
         alpha            red            green           blue</code>
</p>
<empty-line/>
<p>
Ещё один способ создать такой же преобразователь -
обойтись без текстового файла и написать функцию с
прямоугольными скобками:
</p>
<empty-line/>
<p>
<code>
   (@ rgb-color color .result +-
       _bit[
               32 .. 25  .
               24 .. 17  .red
               16 ..  9  .green
                8 ..  1  .blue
           ]
           (%
               color .%
           )
           (@ x +-
               (x .result)
           )
   +
       {integer : 2^32} color
       {rgb} result
   )</code>
</p>
<empty-line/>
<p>
И можно будет обращаться к ней:
</p>
<empty-line/>
<p>
<code>
   ((// ('/ [&gt; threshold.red]   (# rgb-color brush.color).red)   /
            [&gt; threshold.green] (# rgb-color brush.color).green) /
            [&gt; threshold.blue]  (# rgb-color brush.color).blue)) 1 0) .visible)</code>
</p>
<empty-line/>
<p>
А если отметить её флажком "x", вот так:
</p>
<empty-line/>
<p>
<code>
   (@ rgb-color:x color .result +-
   
       &#183;
       &#183;
       &#183;
   
   +
       {integer : 2^32} color
       {rgb} result
   )</code>
</p>
<empty-line/>
<p>
Компилятор создаст из неё стрелку:
</p>
<empty-line/>
<p>
<code>
   (0x00FF0000 -rgb -&gt; .color)</code>
</p>
<empty-line/>
<p>
Именно этот пример создаст 32-разрядное число,
где включит разряды, соответствующие полю "red",
и выключит все остальные разряды.
</p>
<empty-line/>
<p>
Ещё один вопрос - переключение разрядов по их
кратким именам, которые выглядят как константы.
Для начала надо объявить эти константы:
</p>
<empty-line/>
<p>
<code>
   (% ONBOARD_ADC_* :: 2^32 +-
       ZX_VOLTAGE     :: 0x00000001
       ZX_AMPERAGE    :: 0x00000002
       ZX_TEMPERATURE :: 0x00000004
       ...
   )</code>
</p>
<empty-line/>
<p>
И можно включать и выключать разряды:
</p>
<empty-line/>
<p>
<code>
   (# bit-out bits ONBOARD_ADC/ZX_VOLTAGE 1 .bits)</code>
</p>
<empty-line/>
<p>
А также их читать:
</p>
<empty-line/>
<p>
<code>
   ([# bit-in] bits ONBOARD_ADC/ZX_VOLTAGE &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
И даже выполнять над ними логические действия:
</p>
<empty-line/>
<p>
<code>
   (# bitmask bits ONBOARD_ADC/ZX_VOLTAGE B_AND .bits)</code>
</p>
<empty-line/>
<p>
В этом примере разряд "ZX_VOLTAGE" будет включён,
поскольку действие "AND" оставляет единицу в том
разряде, где в одном из параметров была единица -
и в константе она была, так что разряд включается.
</p>
<empty-line/>
<p>
Функция "shift()" выполняет сдвиг разрядов влево или
вправо:
</p>
<empty-line/>
<p>
<code>
   (# shift x 24 SHIFT_LEFT .x)
   (# shift x 24 SHIFT_RIGHT .x)</code>
</p>
<empty-line/>
<p>
И её можно вызывать, например, так:
</p>
<empty-line/>
<p>
<code>
   (# shift (# shift x 24 SHIFT_LEFT) 24 SHIFT_RIGHT .x)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# shift x 32 +SHIFT_RIGHT+SHIFT_SIGNED .wide-integer)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# shift x 1 +SHIFT_LEFT+SHIFT_CR .shuffle-bits)</code>
</p>
<empty-line/>
<p>
Функция "bits-in()" позволяет перебрать разряды в
числе:
</p>
<empty-line/>
<p>
<code>
   (@ non-zeros byte .i +-
       (# bits-in byte (@ bit +-
                           ([bit] &gt;
                               (++ .i)
                           &gt;
                               -&gt;&gt;
                           )
                       )
       )
   +
       {integer : 2^8} byte
   )</code>
</p>
<empty-line/>
<p>
А функция "bitcount()" подсчитывает их:
</p>
<empty-line/>
<p>
<code>
   (@ non-zeros byte +-
       (# bitcount byte 1)
   +
       {integer : 2^8} byte
   )</code>
</p>
<empty-line/>
<p>
Эти функции перебирают разряды в числах слева направо,
то есть от старшего к младшему. В тех задачах, где они
применяются, имеет смысл перебирать разряды именно так:
слева направо.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Регулярные выражения</p></title>
<p>
В том случае, когда команд "parse" и "!" оказывается
недостаточно для того, чтобы разбирать строки, вместо
них могут быть созданы текстовые файлы с правилами
разбора строк - вот так:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; web-addr.str &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   http://
       { domain : *.* }
   /
       { path : * }
   /page.cgi
       { parameters : * }</code>
</p>
<empty-line/>
<p>
И компилятор создаст над ними функции, выполняющие
преобразование строк в структуры данных и обратно -
и можно будет вызывать их в любом месте в программе:
</p>
<empty-line/>
<p>
<code>
   ((# web-addr string).parameters .args)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# web-addr (%
                   domain-name .domain
                   wp-path     .path
                   arg-pairs   .parameters
               )
   &gt;
       .location
   )</code>
</p>
<empty-line/>
<p>
Но не так:
</p>
<empty-line/>
<p>
<code>
   (# web-addr 'http://www.example.net' .location)                                ** !!!</code>
</p>
<empty-line/>
<p>
Со строками эти функции работать не будут: им нужны
структуры с полями, которые были заданы в правилах
разбора в фигурных скобках.
</p>
<empty-line/>
<p>
Правила эти выглядят следующим образом:
</p>
<empty-line/>
<p>
<code>
   *                                                Строка
   *.*                                              Две строки, разделитель - точка
   
   ^[?]*                                            Вопросительный знак и затем строка
   ^[a-z][0-9][-/]                                  Символы "a-z" и "0-9" и знаки препинания
   ^[a-z][0-9].*^[a-z]                              Символы "a-z" и "0-9", затем точка, строка, и символы "a-z"
   ^[?]^[a-z][0-9][-.:]*                            Вопросительный знак, символы "a-z" и "0-9" или знаки препинания, затем строка</code>
</p>
<empty-line/>
<p>
То есть, в текстовом файле можно было бы записать:
</p>
<empty-line/>
<p>
<code>
   http://
       { domain : *.* }
   /
       { path : * }
   /page.cgi
       { parameters : ^[?]^[a-z][0-9][-.:]* }</code>
</p>
<empty-line/>
<p>
И тогда запись в поле "parameters" будет сопровождаться
проверками, так что если строка не соответствует правилам -
функция "web-addr()" выбросит неисправимую ошибку во время
выполнения.
</p>
<empty-line/>
<p>
Правила могут быть заданы и в обыкновенной функции, в том
числе и с флажками "xx" или "x":
</p>
<empty-line/>
<p>
<code>
   (@ web-addr:xx query-string .request +-
       _str[
               http://
                   { domain : *.* }
               /
                   { path : * }
               /page.cgi
                   { parameters : * }
           ]
           (%
               query-string .%
           )
           (@ x +-
                (x .request)
           )
   +
       {string} query-string
       {web-rq} request
   )</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (@ web-addr arg-pairs WS_URL .query-string +-
       _str[
               http://
                   { domain : *.* }
               /
                   { path : * }
               /page.cgi
                   { parameters : ^[?]^[a-z][0-9][-.:]* }
           ]
           (%
               `WS_DOMAIN_NAME .domain
               `WS_SCRIPT_PATH .path
               arg-pairs       .parameters
           )
           (@ x +-
                (x .query-string)
           )
   +
       {string} arg-pairs
       {string} query-string
   )</code>
</p>
<empty-line/>
<p>
И можно вызывать их:
</p>
<empty-line/>
<p>
<code>
   (ws.parameters .args -
       (query-string -web-rq -&gt;&gt; .ws)
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (arg-pairs WS_URL -&gt; .query-string)</code>
</p>
<empty-line/>
<p>
Именно эти приёмы относятся к тем временам, когда данные
располагались в текстовых файлах, а программы (в основном
на скриптовых языках) должны были их читать и вносить в
них изменения. Сегодня они уже редко встречаются: всё то
же самое делается с помощью SQL-серверов, которые работают
быстрее, чем скрипты для доступа к данным в текстовом виде.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Операторы свёртки</p></title>
<p>
Операторы свёртки предназначены для того, чтобы записывать
операторы циклов в более кратком виде. То есть, вместо того,
чтобы записать цикл с заголовком, как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ replace items n item .replaced +-
       (.* items i current &gt;
           ([= n) i &gt;
               (attach replaced item)
           &gt;
               (attach replaced current)
           )
       )
   +
       {A|L : &lt;T&gt;} items
       {A|L : T : [items]} replaced
   )</code>
</p>
<empty-line/>
<p>
Можно написать оператор в круглых скобках, который будет
вызывать безымянную функцию и работать как оператор цикла:
</p>
<empty-line/>
<p>
<code>
   (@ replace items n item .replaced +-
       ((#) items (@ i current +-
                      (// ([= n] i) item current)
                  )
       &gt;
           .replaced
       )
   +
       {A|L : &lt;T&gt;} items
       {A|L : T : [items]} replaced
   )</code>
</p>
<empty-line/>
<p>
Ещё один пример:
</p>
<empty-line/>
<p>
<code>
   ((';') words .csv-string)</code>
</p>
<empty-line/>
<p>
И ещё один:
</p>
<empty-line/>
<p>
<code>
   (("\n") lines .text)</code>
</p>
<empty-line/>
<p>
В последних двух примерах из слов или строк создаётся строка,
и для этого выполняется оператор цикла - но вместо безымянной
функции задаётся параметр.
</p>
<empty-line/>
<p>
Операторы в фигурных скобках будут крутиться "на месте", как
в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ {[S]} item +-
       (say &lt;- item)
   )</code>
</p>
<empty-line/>
<p>
Что позволяет написать:
</p>
<empty-line/>
<p>
<code>
   ({S} ('
            'A' 'B' 'C' 'D' 'E' 'F'
        )
   )</code>
</p>
<empty-line/>
<p>
А следующий оператор:
</p>
<empty-line/>
<p>
<code>
   (@ {[*]} n item +-
       (* n item)
   )</code>
</p>
<empty-line/>
<p>
Позволяет написать:
</p>
<empty-line/>
<p>
<code>
   (@ factorial n +-                                (@ factorial n +-
       ({*} 1..n)                                       1
   )                                                +
                                                        [= 0] n
                                                    )</code>
</p>
<empty-line/>
<p>
В примере выше функция "factorial()" вернёт единицу,
если ей будет передано нулевое значение (факториал нуля
равен единице), и выполнит оператор в фигурных скобках
для значений больше нуля. Этот оператор перемножит числа
от единицы до заданного значения.
</p>
<empty-line/>
<p>
И наконец, двойное двоеточие позволяет выполнять короткую
функцию в цикле, передавая её на вход другой функции:
</p>
<empty-line/>
<p>
<code>
   (# words-in text :: (++ .spaces))</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# lines-in file READ_FILE :: (++ .lines))</code>
</p>
<empty-line/>
<p>
Этот оператор используется в задаче подсчёта частоты, с которой
слова встречаются в тексте:
</p>
<empty-line/>
<p>
<code>
   (@ frequency text .fq +-
       (@ nlog2n max .n +-
           (max .n)
           (.* 2..max i &gt;
               ([&lt; max] (* i (# log2 i)) &gt;
                   (i .n)
               &gt;
                   -&gt;&gt;
               )
           )
       )
   
       (^ [length] .. .fq -
           (# nlog2n spaces .length -
               (# words-in text :: (++ .spaces))
           )
       )
       (# words-in text (@ word +-
                            ([fq.!word] &gt;
                                (1 .fq:word)
                            &gt;
                                (++ .fq:word)
                            )
                        )
       )
   +
       {string} text
       {list : string &lt;- integer} fq
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "frequency()" вначале пробегает по строке
и подсчитывает количество слов в ней, а затем - ещё раз
пробегает по ней и заполняет список ключей и значений.
Для подсчёта количества слов как раз и используется
двойное двоеточие.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Задания препроцессору</p></title>
<p>
Препроцессор в языке Veronica - часть компилятора, которая
работает с исходным текстом программы до того, как из него
будет построено синтаксическое дерево. Именно таким образом
в программах создаются пространства имён, ведутся логи и
пишутся тесты: препроцессор выступает как надстройка поверх
языка программирования.
</p>
<section><!-- 2 -->
<title><p>Пространства имён</p></title>
<p>
Пространства имён в языке Veronica используются для того,
чтобы собрать в одном месте функции и описания типов данных,
и затем подключать их к программе, как только они потребуются.
Весь код в таком случае оказывается собран в дерево каталогов,
что в больших программах довольно удобно.
</p>
<empty-line/>
<p>
Точки входа в пространства имён задаются таким образом:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; math.desc &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   ...
   
   (@ fa :: +-
       {I|F} / {I|F}
   )
   
   (@ fab :: +-
       {I|F} {I|F} / {I|F}
   )
   
   (@ fx :: +-
       {I|F : +-} / {I|F : +-}
   )
   
   (@ fxy :: +-
       {I|F : +-} {I|F : +-} / {I|F : +-}
   )
   
   (# square-root +-
       {fa} sqrt
   )
   
   (# algebra +-
       {fa} ln
       {fx} exp
       {fx} pow
       ...
   )
   
   (# trigonometry +-
       {fx} sin
       {fx} cos
       {fx} tn
       {fx} ctn
       {fx} atn
       {fx} actn
       {fx} atan2
       ...
   )
   
   ...
   
   (% vector : +-
       array : I|F
   )
   
   (% matrix : +-
       array : array : I|F
   )
   
   ...
   
   ## math fast :: square-root : {*}
   ## math i754 :: algebra, square-root, trigonometry : {*}</code>
</p>
<empty-line/>
<p>
Как видно, здесь описаны типы функций (то есть "функция
с одним или двумя параметрами"), они соединены в наборы
функций ("алгебра", "тригонометрия" и так далее), и для
них написаны типы данных ("вектор", "матрица" и другие).
В последних строках наборы функций объединяются ещё раз -
это и есть пространства имён ("math fast" и "math i754",
которые будут видны всем: это задаётся звёздочкой).
</p>
<empty-line/>
<p>
Теперь достаточно добавить в заголовок файла с исходными
текстами одну строку:
</p>
<empty-line/>
<p>
<code>
   # math fast                                      # math i754
   
   (@ sqrt x +-                                     (@ sqrt x +-
       ...                                              ...
   +                                                +
       {I|F} x                                          {I|F} x
   )                                                )
   
   &#183;                                                (@ ln x +-
   &#183;                                                    ...
   &#183;                                                +
                                                        {I|F : +-} x
                                                    )
   
                                                    (@ exp x +-
                                                        ...
                                                    +
                                                        {I|F : +-} x
                                                    )
   
                                                    &#183;
                                                    &#183;
                                                    &#183;</code>
</p>
<empty-line/>
<p>
И все функции и типы данных в этом файле будут считаться
принадлежащими к заданному пространству имён (и в таком
случае файл с описаниями точек входа должен располагаться
на одном уровне выше в дереве каталогов).
</p>
<empty-line/>
<p>
Теперь надо подключить пространство имён, создав для
этого ещё один текстовый файл:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; colors.inc &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   #&lt;- math.desc fpoint hw - stdlib-551a</code>
</p>
<empty-line/>
<p>
И между файлами "*.inc" и "*.desc" будет установлена
связь, вот так:
</p>
<empty-line/>
<p>
<code>
      # math                 math:sqrt()
        &#9650;                        &#9474;&#9650;
        &#9474;&#9474;                       &#9474;&#9474;
        &#9474;&#9474;                       &#9474;&#9474;
        &#9474;&#9660;                       &#9660;
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;           &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; math.desc &#9474; &#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9654; &#9474; colors.inc &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;           &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;</code>
</p>
<empty-line/>
<p>
И в заголовке любого файла в программе можно подключить
функции из одного из пространств имён, добавив ещё одну
строку в заголовок файла:
</p>
<empty-line/>
<p>
<code>
   #&lt;- math fast
   
   (@ brightness color +-
       (" color.
           (# sqrt (+
                      (** red)
                      (** green)
                      (** blue)
                   )
           )
       )
   )</code>
</p>
<empty-line/>
<p>
Теперь функция "brightness()", рассчитывающая яркость
цвета из трёх его составляющих, сможет вызывать функцию
"sqrt()" из пространства имён "math fast". Точно такая
же функция из пространства имён "math i754" ей мешать не
будет.
</p>
<empty-line/>
<p>
В том случае, когда надо подключить функции из нескольких
наборов - над ними можно создать переходники:
</p>
<empty-line/>
<p>
<code>
   #&lt;- math i754 *, math fast
   
   (@ fast-sqrt : +-
       sqrt ++ math fast
   )
   
   ...</code>
</p>
<empty-line/>
<p>
Или - создать ещё один текстовый файл, где задать настройки
препроцессора:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; math.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   [math]
   fp = (
          math i754 *,
          math fast
        )</code>
</p>
<empty-line/>
<p>
И подключить пространства имён с его помощью:
</p>
<empty-line/>
<p>
<code>
   #&lt;- ~{math.fp}
   
   (@ brightness color +-
       (" color.
           (# sqrt (+
                      (** red)
                      (** green)
                      (** blue)
                   )
           )
       )
   )</code>
</p>
<empty-line/>
<p>
В данном случае препроцессор подставит на место строки
"~{math.fp}" в заголовке файла строки "math i754 *" и
"math fast", и функция "brightness()" будет вызывать
функцию "sqrt()" из того пространства имён, которое
было подключено со звёздочкой, то есть из "math i754".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Подключение библиотек</p></title>
<p>
Описания точек входа в библиотеки выглядят так же, как
и описания точек входа в пространства имён - то есть:
</p>
<empty-line/>
<p>
<code>
   (# pm-api +-
       (% ulong : +-
           integer : 2^32
       )
   
       (% mparam : +-
           integer : 2^64 | record
       )
   
       (% handle : +-
           integer : 2^32 : {#1}
       )
   
       (% msg-queue : +- handle [MsgQueue])
       (% desktop : +- handle [Desktop])
       (% window : +- handle [Window])
       (% frame : +- handle [Frame])
       (% port : +- handle [Port])
   
       (% HWND_* +-
           DESKTOP
           PARENT
           OWNER
           TOP
       )
   
       ...
   
       (@ locate :: +- {HWND_*} / {desktop})
       (@ alert :: +- {desktop} {string} {string} / .)
   ;
   
   ## pm :: pm-api : {*}</code>
</p>
<empty-line/>
<p>
А в файлах с исходными текстами вместо функций
создаются переходники:
</p>
<empty-line/>
<p>
<code>
   # pm
   
   (% HWND_* :: 2^32 +-
       DESKTOP :: 0x001
       PARENT  :: 0x101
       OWNER   :: 0x110
       TOP     :: 0x011
       ...
   )
   
   (@ locate :: +- WinStartSession ++ 'PMCalls.dll')
   (@ alert  :: +- WinMsgBox       ++ 'PMCalls.dll')
   
   ...</code>
</p>
<empty-line/>
<p>
Внешне они выглядят как обыкновенные функции - и над
ними тоже могут быть созданы переходники:
</p>
<empty-line/>
<p>
<code>
   # pm
   
   (@ alert text : +-
       alert . text ''
   )</code>
</p>
<empty-line/>
<p>
И теперь, подключив это пространство имён:
</p>
<empty-line/>
<p>
<code>
   #&lt;- pm
   
   (% +-
       (# alert 'Hello, World!')
   )</code>
</p>
<empty-line/>
<p>
Можно использовать его описания типов данных и вызывать
функции. В примере выше вызывается функция "alert()" с
один параметром - переходник над функцией из библиотеки.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Команды препроцессора</p></title>
<p>
Препроцессор - часть компилятора, которая читает исходные
тексты и вносит в них изменения до того, как с ними начнёт
работать компилятор. Никакая логика в задания препроцессору
не добавляется.
</p>
<empty-line/>
<p>
Для примера, пусть в программе создано два файла с настройками:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; main.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   [netwk]
   file-name = network
   
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; network.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   [tcp-ip]
   ip-version = 4
   port-range = 2^16</code>
</p>
<empty-line/>
<p>
Теперь можно написать задание препроцессору, чтобы он
считал эти настройки:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; program.crc &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   #! main.ini config -
    | main.ini settings config -
   
   #! ~{netwk.file-name}.ini config -
    | ~{netwk.file-name}.ini settings config -</code>
</p>
<empty-line/>
<p>
В это место в программе будет подставлено:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; program.crc &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   #! main.ini config -
    | main.ini settings config -
   
   #! network.ini config -
    | network.ini settings config -</code>
</p>
<empty-line/>
<p>
И настройки из файлов будут загружены. Теперь эти значения
могут быть подставлены в текст программы:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; network.inc &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   #&lt;- api.desc ipv~{tcp-ip.ip-version}</code>
</p>
<empty-line/>
<p>
И препроцессор подставит в эту стоку значение из файла с
настройками:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; network.inc &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   #&lt;- api.desc ipv4</code>
</p>
<empty-line/>
<p>
Так появляется возможность подключать к программе любые
наборы функций, задавая настройки в текстовых файлах - и
в том числе работать с разными версиями библиотек.
</p>
<empty-line/>
<p>
Кроме того, препроцессор позволяет задавать флажки в
заголовках функций:
</p>
<empty-line/>
<p>
<code>
   #|- while, -c
   (@ chars file-name &gt;
       ...
   +
       {string} file-name
   )</code>
</p>
<empty-line/>
<p>
И теперь функция "chars()" может быть добавлена в
заголовки циклов и в функции с хвостовой рекурсией.
А здесь:
</p>
<empty-line/>
<p>
<code>
   #|&lt;//&gt;
   (@ msg-in @fp &gt;
       ...
   +
       {message-handler} fp
   )
   
   (% message-handler :: +-
       {message} / .
   )</code>
</p>
<empty-line/>
<p>
Функция "msg-in()" получит возможность создавать структуры
данных с состоянием программы:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (# msg-in (@ msg +-
                     ...
       -
           (%
               .. .state
           )
       )          &#8593;
   )</code>
</p>
<empty-line/>
<p>
Этот флажок - подсказка компилятору, что вместо функции
с длинным названием была написана функция с более кратким
названием:
</p>
<empty-line/>
<p>
<code>
   #|+ short-name
   (@ too-long-name x y z &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Точка прерывания:
</p>
<empty-line/>
<p>
<code>
   #.</code>
</p>
<empty-line/>
<p>
Она же с печатью в STDOUT значений переменных:
</p>
<empty-line/>
<p>
<code>
   #. i "\t" array.i</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   #.. items</code>
</p>
<empty-line/>
<p>
В таком виде точка прерывания может быть поставлена
в любом текстовом редакторе, после чего достаточно
запустить компилятор. Отладчик для добавления точки
прерывания не требуется.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Ведение логов</p></title>
<p>
Для того, чтобы вести логи во время переходов между
пространствами имён и функциями в них, надо добавить
к тем функциям, где будут сбрасываться логи, ещё по
две функции.
</p>
<empty-line/>
<p>
То есть, к функции "send-msg()" в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ send-msg message .res &gt;
       ...
   +
       {msg-packet-b64} message
       {msg-result} res
   )</code>
</p>
<empty-line/>
<p>
Должна быть написана ещё одна функция:
</p>
<empty-line/>
<p>
<code>
   (@ -&gt;&gt; 'send-msg f-name caller-name message &gt;
       (log +1 'Entering ' f-name '.[' caller-name '], id: ' message.id)
   +
       {string} f-name, caller-name
       {msg-packet-b64} message
   )</code>
</p>
<empty-line/>
<p>
И ещё одна:
</p>
<empty-line/>
<p>
<code>
   (@ &lt;&lt;- 'send-msg f-name caller-name message res &gt;
       (log -1 'Leaving ' f-name '.[' caller-name '], r.id: ' res.id)
   +
       {string} f-name, caller-name
       {msg-packet-b64} message
       {msg-result} res
   )</code>
</p>
<empty-line/>
<p>
Они и будут вести логи. А если добавить ещё и две
проверяющие функции:
</p>
<empty-line/>
<p>
<code>
   (@ * 'send-msg message &gt;
       ('// &gt;
           ![message.id] / ERR_MSG_ID
       &gt;
           ![message.text] / ERR_MSG_TEXT
       &gt;
           .
       )
   +
       {msg-packet-b64} message
   )</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (@ + 'send-msg message res &gt;
       ('// &gt;
           ![res.id] / ERR_RES_ID
       &gt;
           .
       )
   +
       {msg-packet-b64} message
       {msg-result} res
   )</code>
</p>
<empty-line/>
<p>
То её параметры будут проверены, а сообщения
о том, что она начала и завершила свою работу,
будут записаны в STDLOG.
</p>
<empty-line/>
<p>
Последовательность выполнения всех этих функций
выглядит так:
</p>
<empty-line/>
<p>
<code>
   &#9474;
   &#9492;&#9472;&#9654; * send-msg
   &#9474;
   &#9492;&#9472;&#9654; -&gt;&gt; send-msg
   &#9474;
   &#9492;&#9472;&#9654; send-msg
   &#9474;
   &#9492;&#9472;&#9654; &lt;&lt;- send-msg
   &#9474;
   &#9492;&#9472;&#9654; + send-msg
   &#9474;
   &#9660;</code>
</p>
<empty-line/>
<p>
Как видно, вначале проверяются параметры, а затем
выполняется запись текстовой строки в STDLOG, но
перед возвратом из функции она пишет сообщение в
STDLOG о том, что она завершила работу. Если она
вернёт неправильные параметры - сообщение об ошибке
попадёт в STDERR, но текстовое сообщение всё равно
будет сброшено в STDLOG.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Покрытие тестами</p></title>
<p>
Для выполнения тестов в языке Veronica существует
команда "expect", вызывающая одну или несколько
функций и проверяющая возвращаемые ими значения:
</p>
<empty-line/>
<p>
<code>
   #&lt;- avg, regression                              #&lt;- avg, regression
   
   (@ +-                                            (@ +-
       (expect avg s (@ +-                              (expect avg s (@ +-
                         (// ([= 2] x) 1 0 -                              (// ([= -0.7] x) 1 0 -
                             (# avg (' 1 2 3) .x)                             (# avg (' -1 2.2 -3.3) .x)
                         )                                                )
                     )                                                )
       -                                                -
           ('works with integer numbers' .s)                ('works with float+- numbers' .s)
       )                                                )
   )                                                )</code>
</p>
<empty-line/>
<p>
Она также умеет ловить исключения и проверять
константы "ERR_*":
</p>
<empty-line/>
<p>
<code>
   #&lt;- avg, regression
   
   (@ +-
       (expect avg a s (@ +-
                           (# avg _' .x)
                       )
       -
           (+ERR_PARAMETER+ERR_EMPTY_* .a)
           ('when list is empty' .s)
       )
   )</code>
</p>
<empty-line/>
<p>
Как видно, функция "avg()" в этом примере была покрыта
двумя наборами тестов: одни проверяют, как она работает
с обыкновенными данными, а другие - как она поведёт себя
с неправильными данными. Здесь, получив на вход пустой
список, она должна будет выбросить исключение с константой
"ERR_*".
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>&nbsp;</p></title>
<p>&nbsp;</p>
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Дата и время</p></title>
<p>
Язык Veronica позволяет работать с датой и временем с
точностью до миллисекунд (в соответствии со стандартом
NTP), а также создавать в программах структуры данных,
содержащие значения даты и времени с точностью до одной
секунды.
</p>
<empty-line/>
<p>
К этим структурам данных были добавлены "wr"-функции,
а также вычислительные операторы, позволяющие узнать
расстояние между двумя событиями во времени, а также
сравнить их между собой.
</p>
<section><!-- 2 -->
<title><p>Представление данных</p></title>
<p>
Структура данных, предназначенная для того, чтобы
хранить значения даты и времени, выглядит следующим
образом:
</p>
<empty-line/>
<p>
<code>
   (% +d-time +-
       (% internal +-
           {integer : 2^16} year
           {integer : 2^8} month day
           {integer : 2^8} hours mins seconds
   
           {integer : 2^8} flags
       )
   
       (% timestamp +-
           {#1} #2
       )
   
       {internal} internal
       {timestamp} timestamp
   )
   
   (% datetime : +-
       d-time [milliseconds, integer : 2^64]
   )</code>
</p>
<empty-line/>
<p>
Как видно, в ней записаны дата и время с точностью
до секунды, а также точное время по стандарту NTP,
записанное с точностью до 1/1000 секунды. Получить
текущее время можно с помощью функции "clock()",
вот так:
</p>
<empty-line/>
<p>
<code>
   (# clock .time)</code>
</p>
<empty-line/>
<p>
А время с заданной точностью - вот так:
</p>
<empty-line/>
<p>
<code>
   (# clock DT_SECONDS .time)</code>
</p>
<empty-line/>
<p>
В свою очередь, функция "today()" возвращает время
с точностью до суток - то есть, оно будет установлено
на "0:00" текущего дня:
</p>
<empty-line/>
<p>
<code>
   (# today .date)</code>
</p>
<empty-line/>
<p>
Как видно, структура "d-time" объявлена с символом
"+", и к её полям нельзя получить доступ напрямую -
но их можно читать и изменять с помощью "wr"-функций,
которые были написаны для типа данных "datetime".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Задание даты и времени</p></title>
<p>
Прямого доступа к полям структуры "datetime" нет,
но для того, чтобы работать с ними, были написаны
"wr"-функции, которые позволяют их менять - как в
следующем примере:
</p>
<empty-line/>
<p>
<code>
   (% -datetime &gt;
          1 .day
          1 .month
       2001 .year
   &gt;
       .millenium
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (%
       millenium.%         .%
       (+ 1 millenium.day) .day
   &gt;
       .next-day
   )</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (++ .millenium.day)</code>
</p>
<empty-line/>
<p>
При этом они выполняют проверки значений, которые
должны быть записаны в поля структуры:
</p>
<empty-line/>
<p>
<code>
   (32 .millenium.day)                                                            ** !!!</code>
</p>
<empty-line/>
<p>
И в том случае, когда значение не может быть задано -
выбрасывают неисправимую ошибку во время выполнения.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Вычислительные действия</p></title>
<p>
Значения даты и времени можно сдвигать вперёд и назад,
воспользовавшись для этого константой "DT_*", вот так:
</p>
<empty-line/>
<p>
<code>
   (+ 1 today DT_DAYS .tomorrow)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ((- 1) today DT_DAYS .yesterday)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (++ DT_SECONDS .time)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (-- DT_SECONDS .time)</code>
</p>
<empty-line/>
<p>
Ещё один оператор возвращает расстояние между двумя
событиями:
</p>
<empty-line/>
<p>
<code>
   ((- before) after DT_MILLISECONDS .duration)</code>
</p>
<empty-line/>
<p>
И он проверяет, чтобы второе событие действительно
происходило после первого:
</p>
<empty-line/>
<p>
<code>
   ((- after) before DT_MILLISECONDS .duration)                                   ** !!!</code>
</p>
<empty-line/>
<p>
Оператор сравнения также требует константы "DT_*",
задающей точность:
</p>
<empty-line/>
<p>
<code>
   ([= today] item.date DT_DAYS &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Причём он может быть записан и с вычислениями с
одной и той же константой:
</p>
<empty-line/>
<p>
<code>
   ([&gt;= today] (+ 7 item.date) DT_DAYS &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Или с двумя, когда вторая константа задаёт меньший
отрезок времени, чем первая:
</p>
<empty-line/>
<p>
<code>
   ([&gt;= today DT_DAYS] (+ delta item.date DT_SECONDS) &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Но когда вторая константа задаёт больший отрезок -
оператор сравнения не будет работать:
</p>
<empty-line/>
<p>
<code>
   ([&gt;= today DT_SECONDS] (+ delta item.date DT_DAYS) &gt;                           ** ?!!..
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Причём сами по себе вычисления и сравнение в последнем
примере записаны верно - но это распространённая ошибка,
и компилятор обнаруживает её заранее.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Округление до ближайшего</p></title>
<p>
Округлить структуру данных до ближайшего точного
значения можно так:
</p>
<empty-line/>
<p>
<code>
   ((- ((%% 15) time)) time DT_SECONDS .nearest-{-15s})</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (+ (- ((%% 15) time) time) 15 DT_SECONDS .nearest-{+15s})</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   ((- ((%% 15) time)) time DT_MINS .nearest-{-15m})</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (+ (- ((%% 15) time) time) 15 DT_MINS .nearest-{+15m})</code>
</p>
<empty-line/>
<p>
Как видно, во всех этих примерах считается расстояние
до ближайшей точки во времени ("duration"), и затем эта
величина добавляется к структуре или вычитается из неё -
так, что дата и время сдвигается вверх или вниз к этой
точке.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Действия со строками</p></title>
<p>
Создать структуру "datetime" из строки можно с
помощью стрелки:
</p>
<empty-line/>
<p>
<code>
   (say time -
       (% -datetime &gt;
           string format -&gt; .%
       -
           ('01.01.2001 21:07:44' .string) |
           ('DD.MM.YYYY HH:MM:SS' .format)
       &gt;
           .time
       )
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   2001-01-01 21:07:44</code>
</p>
<empty-line/>
<p>
Точно так же, оператор-стрелка позволяет получить
строку из структуры "datetime":
</p>
<empty-line/>
<p>
<code>
   (say &lt;- time 'W, DD.MM.YYYY HH:mm:ss' -
       (% -datetime &gt;
             2001 .year   1 .month   1 .day
               21 .hours 07 .minits 44 .seconds
           DT_GMT .timezone
       &gt;
           .time
       )
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   Mon, 01.01.2001 21:07:44</code>
</p>
<empty-line/>
<p>
А если добавить к нему код страны и языка:
</p>
<empty-line/>
<p>
<code>
   (say &lt;- time 'W, DD.MM.YYYY HH:mm:ss' 'ru-RU')</code>
</p>
<empty-line/>
<p>
Тот же пример напечатает:
</p>
<empty-line/>
<p>
<code>
   Понедельник, 01.01.2001 21:07:44</code>
</p>
<empty-line/>
<p>
И наконец, команда "say" со структурой "datetime" без
параметров:
</p>
<empty-line/>
<p>
<code>
   (say time)</code>
</p>
<empty-line/>
<p>
Напечатает строку в формате по умолчанию:
</p>
<empty-line/>
<p>
<code>
   2001-01-01 21:07:44</code>
</p>
<empty-line/>
<p>
И в таком виде строки удобно подставлять в запросы
к базам данных или сбрасывать в логи (как обычно и
делается).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Названия дней недели</p></title>
<p>
Константа "WD_*" позволяет проверить день недели:
</p>
<empty-line/>
<p>
<code>
   ([= WD_SATURDAY) date.weekday &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А константа "YM_*" - название месяца:
</p>
<empty-line/>
<p>
<code>
   ([= YM_MARCH] date.season &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Здесь за оператором сравнения скрывается "r"-функция,
которая и возвращает константу.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Файлы и каталоги</p></title>
<p>
С точки зрения языка Veronica, файлы выглядят как
записи на устройствах, имеющие название и длину в
байтах. Они располагаются в каталогах, которые
собраны в дерево от корня устройства.
</p>
<empty-line/>
<p>
Функции для работы с файлами позволяют читать и
изменять их, а также переименовывать/удалять их
и переносить с одного места на другое. Ещё несколько
функций позволяют отправлять сообщения к файловой
системе, задавая больше параметров, чем при вызове
обыкновенных функций.
</p>
<section><!-- 2 -->
<title><p>Чтение и запись данных</p></title>
<p>
Прочитать данные из файла можно с помощью функции
"char-in()", которая имеет следующие параметры:
</p>
<empty-line/>
<p>
<code>
   (# char-in file-name .string)                                                  ** Весь файл за один раз
   (# char-in file-name position .string)                                         ** Один байт с заданной точки
   (# char-in file-name position length .string)                                  ** "Length" байтов
   (# char-in file-name position length READ_WORD .string)                        ** "Length * 2" байтов
   
   (# char-in file-name .chars)                                                   ** Весь файл за один раз
   (# char-in file-name position +READ_INTEGER+LITTLE_ENDIAN .chars)              ** Число с заданной точки</code>
</p>
<empty-line/>
<p>
А записать данные в файл - с помощью функции
"char-out()":
</p>
<empty-line/>
<p>
<code>
   (# char-out file-name string)                                                  ** Весь файл за один раз
   (# char-out file-name string position)                                         ** Один байт с заданной точки
   
   (# char-out file-name string position length)                                  ** "Length" байтов
   (# char-out file-name string position length WRITE_WORD)                       ** "Length * 2" байтов
   
   (# char-out file-name chars)                                                   ** Весь файл за один раз
   (# char-out file-name number position +WRITE_INTEGER+LITTLE_ENDIAN)            ** Число с заданной точки</code>
</p>
<empty-line/>
<p>
Прочитать одну строку, не загружая файл в память
полностью, можно с помощью функции "line-in()":
</p>
<empty-line/>
<p>
<code>
   (# line-in file-name .string)                                                  ** Следующая строка
   (# line-in file-name READ_SPACES .string)                                      ** Пробелы тоже читать</code>
</p>
<empty-line/>
<p>
А добавить строку в файл - с помощью функции
"line-out()":
</p>
<empty-line/>
<p>
<code>
   (# line-out file-name string)                                                  ** Добавить строку в конец файла</code>
</p>
<empty-line/>
<p>
Функция "charset()" позволяет менять кодировку
строк:
</p>
<empty-line/>
<p>
<code>
   (# charset CHR_IN `CSV_CHARSET s .string -
       (# line-in csv-file .s)
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# line-out csv-file s -
       (# charset CHR_OUT `CSV_CHARSET string .s)
   )</code>
</p>
<empty-line/>
<p>
А функция "bc()" позволяет читать и записывать данные
в сжатом виде:
</p>
<empty-line/>
<p>
<code>
   (# bc x +B_IN+B_DECOMPRESS .record -             (# bc x +B_IN+B_64 .record -
       (# char-in file-name .x)                         (# char-in file-name .x)
   )                                                )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# char-out file-name x -                        (# char-out file-name x -
       (# bc record +B_OUT+B_COMPRESS .x)               (# bc record +B_OUT+B_64 .x)
   )                                                )</code>
</p>
<empty-line/>
<p>
Функция "chars()" позволяет читать строки из файла
до тех пор, пока они не закончатся:
</p>
<empty-line/>
<p>
<code>
   (.* [# chars] file &gt;
       (say line -
           (# line-in file .line)
       )
   )</code>
</p>
<empty-line/>
<p>
И то же самое умеет делать функция "lines-in()":
</p>
<empty-line/>
<p>
<code>
   (# lines-in file READ_FILE (@ line +-
                                  (say line)
   )</code>
</p>
<empty-line/>
<p>
Для того, чтобы проверить существование файла,
предназначена функция "file-exists()":
</p>
<empty-line/>
<p>
<code>
   ([# file-exists] file-name &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А узнать длину файла позволяет функция "file-size()":
</p>
<empty-line/>
<p>
<code>
   ([&gt; 0] (# file-size file-name) &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Проверить существование каталога позволяет функция
"directory-exists()":
</p>
<empty-line/>
<p>
<code>
   ([# directory-exists] path &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
А узнать, файл это или каталог, можно с помощью
функций "is-file()" и "is-directory()":
</p>
<empty-line/>
<p>
<code>
   ([# is-file] path &gt;
       ...
   &gt;
       ([# is-directory] path &gt;
           ...
       &gt;
           (exit ERR_FILE_NAME)
       )
   )</code>
</p>
<empty-line/>
<p>
При этом функции "file-exists()" и "directory-exists()"
выбрасывают ошибку в том случае, если им будет передан
файл вместо каталога или каталог вместо файла. Изменить
или отключить это поведение нельзя.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Просмотр дерева каталогов</p></title>
<p>
Пройти дерево каталогов можно с помощью функции
"files-in()":
</p>
<empty-line/>
<p>
<code>
   (# files-in target (@ name +-
                          ...
                      )
   )</code>
</p>
<empty-line/>
<p>
Она же позволяет выбрать первые несколько файлов
в каталоге:
</p>
<empty-line/>
<p>
<code>
   (# files-in target length (@ name +-
                                 ...
                             )
                             (@ total +-
                                 ...
                             )
   )</code>
</p>
<empty-line/>
<p>
А затем выбрать следующие несколько файлов:
</p>
<empty-line/>
<p>
<code>
   (# files-in target from-name length (@ name +-
                                           ...
                                       )
                                       (@ total +-
                                           ...
                                       )
   )</code>
</p>
<empty-line/>
<p>
И в конце выбрать остальные файлы:
</p>
<empty-line/>
<p>
<code>
   (# files-in target from-name (@ name +-
                                    ...
                                )
                                (@ total +-
                                    ...
                                )
   )</code>
</p>
<empty-line/>
<p>
В следующем примере функция "files()" вызывает функцию
"files-in()", чтобы пройти дерево:
</p>
<empty-line/>
<p>
<code>
   (@ files:rc path mask @fp +-
       (# files-in target (@ name +-
                              ([IS_DIRECTORY] name &gt;
                                  (# files name mask fp)
                              &gt;
                                  (# fp name)
                              )
                          )
       -
           (&#038; path '/' mask .target)
       )
   )</code>
</p>
<empty-line/>
<p>
Она вызывается так:
</p>
<empty-line/>
<p>
<code>
   (@ +-
       (# files './' '*.txt' (@ name +-
                                 (say name)
                             )
       )
   )</code>
</p>
<empty-line/>
<p>
Как видно, каждый раз, встречая каталог, она входит
в рекурсию и просматривает файлы в нём, а встречая
файл - вызывает безымянную функцию. В примере выше
безымянная функция печатает имена файлов.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Запросы к файловой системе</p></title>
<p>
Создать каталог можно с помощью функции "fs()", передав
ей константу "FS_MKDIR":
</p>
<empty-line/>
<p>
<code>
   (# fs FS_MKDIR dir-name)</code>
</p>
<empty-line/>
<p>
Константа "FS_LINK" позволяет создать ссылку на каталог:
</p>
<empty-line/>
<p>
<code>
   (# fs FS_LINK link-name dir-name)</code>
</p>
<empty-line/>
<p>
А константа "FS_TMP" - временный файл:
</p>
<empty-line/>
<p>
<code>
   (# fs FS_TMP .file-name)</code>
</p>
<empty-line/>
<p>
Или временный каталог:
</p>
<empty-line/>
<p>
<code>
   (# fs +FS_TMP+FS_DIR .dir-name)</code>
</p>
<empty-line/>
<p>
С помощью константы "FS_INFO" можно узнать свойства
каталога или файла:
</p>
<empty-line/>
<p>
<code>
   (# fs FS_INFO file-name .attr)</code>
</p>
<empty-line/>
<p>
А также задавать и менять их:
</p>
<empty-line/>
<p>
<code>
   (# fs ft file-name (%
                          midnight-today .mod-time
                      )
   -
       (+FS_INFO+FS_TIME .ft)
   )</code>
</p>
<empty-line/>
<p>
Константа "FS_CD" позволяет узнать каталог, с
которым работает поток:
</p>
<empty-line/>
<p>
<code>
   (# fs FS_CD .current-path)</code>
</p>
<empty-line/>
<p>
И она же позволяет задать его:
</p>
<empty-line/>
<p>
<code>
   (# fs FS_CD path)</code>
</p>
<empty-line/>
<p>
С помощью константы "FS_COPY" можно копировать
файлы:
</p>
<empty-line/>
<p>
<code>
   (# fs FS_COPY (%
                     file-name   .from
                     target-path .to-dir
                 )
   )</code>
</p>
<empty-line/>
<p>
И она же позволяет копировать каталоги:
</p>
<empty-line/>
<p>
<code>
   (# fs ft (%
                file-name   .from
                target-path .to-dir
            )
   -
       (+FS_COPY+FS_DIR .ft)
   )</code>
</p>
<empty-line/>
<p>
Константа "FS_MOVE" позволяет переносить каталоги и
файлы и переименовывать их:
</p>
<empty-line/>
<p>
<code>
   (# fs FS_MOVE (%
                     file-name   .from
                     target-path .to-dir
                 )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# fs ft (%
                file-name   .from
                target-path .to-dir
            )
   -
       (+FS_MOVE+FS_DIR .ft)
   )</code>
</p>
<empty-line/>
<p>
А удалить файл или каталог можно с помощью константы
"FS_DELETE":
</p>
<empty-line/>
<p>
<code>
   (# fs FS_DELETE file-name)</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (# fs ft file-name -                             (# fs ft file-name -
       (+FS_DELETE+FS_DIR .ft)                          (+FS_DELETE+FS_LINK .ft)
   )                                                )</code>
</p>
<empty-line/>
<p>
Константа "FS_REQUEST" предназначена для того, чтобы
выполнять несколько действий за один раз - например,
создать файл и открыть его на запись при условии, что
такого файла не существует:
</p>
<empty-line/>
<p>
<code>
   (# fs fs file-name .file -
       (+FS_REQUEST
        +FS_RETURN_IF_EXISTS
        +FS_CREATE_IF_NEW+FS_WRITE .fs)
   )</code>
</p>
<empty-line/>
<p>
Или открыть файл на чтение, если он существует, и
ещё один файл на запись, заменив его, если он уже
существует:
</p>
<empty-line/>
<p>
<code>
   (# fs fs source-file-name
         ft target-file-name .source .target -
       (+FS_REQUEST
        +FS_OPEN_IF_EXISTS
        +FS_RETURN_ON_ERROR
        +FS_READ .fs) |
       (+FS_REQUEST
        +FS_REPLACE_IF_EXISTS
        +FS_RETURN_ON_ERROR
        +FS_WRITE .ft)
   )
   ('&#038; [source] &#038;&#038; [target] &gt;
       (# char-in source .text
        # char-out target text)
       ...
   )</code>
</p>
<empty-line/>
<p>
Подобные действия, даже в последнем примере, выполняются
за одно обращение к файловой системе - которая следит за
тем, чтобы они были неразрывными, "атомарными", то есть,
выполнялись за один раз. Получив флажок "FS_RETURN_ON_ERROR",
функция "fs()" не будет выбрасывать исключение, а без него -
выбросит ошибку с константой "ERR_*", как только что-то
пойдёт не так.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Работа с изображениями</p></title>
<p>
Открыть картинку в файле PNG или JPEG можно с помощью
функции "image-in()", а записать её в файл - с помощью
"image-out()":
</p>
<empty-line/>
<p>
<code>
   (# image-in file-name .picture
    # transform picture .bitmap
    # image-out file-name bitmap)</code>
</p>
<empty-line/>
<p>
Узнать размер изображения позволяет функция "image-size()":
</p>
<empty-line/>
<p>
<code>
   ([# image-size] name &gt;
       ...
   )
   
   ([# image-size] name IMG_WIDTH &gt;
       ...
   )
   
   ([# image-size] name IMG_HEIGHT &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Изображение в памяти выглядит как структура данных, и его
можно создать, задав ширину и высоту:
</p>
<empty-line/>
<p>
<code>
   (% -bitmap &gt;
          1024 .width
           768 .height
       CLR_RGB .color
   &gt;
       .picture
   )</code>
</p>
<empty-line/>
<p>
Точка начала координат располагается слева внизу, так что
они соответствуют ширине и высоте, считая от единицы:
</p>
<empty-line/>
<p>
<code>
         &#9472; &#9472;&#9488;
   &#9650;        &#8595;
   &#9474;
   &#9474;
   &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9654;</code>
</p>
<empty-line/>
<p>
В структуре будут доступны поля, которые выполнены в виде
"wr"-функций:
</p>
<empty-line/>
<p>
<code>
   picture.width
   picture.height
   picture.average-color
   picture.right-rotation-angle
   picture.pages.current-page.points.x.y
   picture.pages.current-page.display-milliseconds</code>
</p>
<empty-line/>
<p>
Их можно будет читать:
</p>
<empty-line/>
<p>
<code>
   (picture.pages.1.points.1.768    .top-left-corner)
   (picture.pages.1.points.1024.768 .top-right-corner)
   (picture.pages.1.points.1024.1   .bottom-right-corner)
   (picture.pages.1.points.1.1      .bottom-left-corner)</code>
</p>
<empty-line/>
<p>
А некоторые из них - даже и менять:
</p>
<empty-line/>
<p>
<code>
   (++ .picture.pages.current-page)</code>
</p>
<empty-line/>
<p>
Получить доступ к точкам изображения и начать рисовать на
нём позволяет функция "draw()":
</p>
<empty-line/>
<p>
<code>
   (# draw bitmap other-bitmap x y)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# draw bitmap other-bitmap (%
                                   x y ._
                                   ...
                               )
   )</code>
</p>
<empty-line/>
<p>
И в более кратком виде:
</p>
<empty-line/>
<p>
<code>
   (attach picture.art (%
                           ART_LINE .a
                           thin-line-brush .b
                           corner-x .x corner-y .y
                           rectangle.width .w rectangle.height .h
                       )
   (attach picture.art (%
                           ART_TEXT .a
                           CLR_DARK_BLUE .c
                           headline-string .s
                           ...
                       )
   ...</code>
</p>
<empty-line/>
<p>
Здесь команда "attach" приведёт к выполнению "wr"-функции,
изменяющей в структуре "picture" поле "art". Эта функция и
выполнит рисование.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Настройки программы</p></title>
<p>
Для того, чтобы читать файлы настроек, в языке
Veronica существует отдельный набор функций. Они
позволяют работать с INI-файлами и XML-файлами, а
также со скриптами, записанными в виде S-выражений.
</p>
<section><!-- 2 -->
<title><p>Представление данных</p></title>
<p>
В большинстве программ настройки хранятся в файлах с
расширением "*.ini", в виде текста с именами разделов,
ключами и значениями:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Settings.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   ;
   ; Country settings
   ;
   [country]
   language = ru-RU
   timezone.country = Russia
   timezone.city = Moscow
   
   ;
   ; Server address and port name
   ;
   [network]
   server_name = 127.0.0.1
   ;server_port = 8000</code>
</p>
<empty-line/>
<p>
В языке Veronica есть возможность прочитать их все за
один раз, и тогда ключи и значения будут расположены в
памяти в виде списка:
</p>
<empty-line/>
<p>
<code>
   settings:'country':'language'                                                  ** "ru-RU"
   settings:'country':'timezone':'country'                                        ** "Russia"
   settings:'country':'timezone':'city'                                           ** "Moscow"
   
   settings:'network':'server_name'                                               ** "127.0.0.1"</code>
</p>
<empty-line/>
<p>
Настройки могут быть записаны и с фигурными скобками с
указанием типов данных - как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Settings.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   ;
   ; Country settings
   ;
   country = {
       language = ru-RU
       timezone = {
           country = Russia
           city = Moscow
       }
   }, {
       language : char[5]
   }
   
   ;
   ; Server address and port name
   ;
   network = {
       server_name = 127.0.0.1
       ;server_port = 8000
   }, {
       server_name : char[16]
       server_port : char[4]
   }</code>
</p>
<empty-line/>
<p>
И после чтения их в память они будут выглядеть так:
</p>
<empty-line/>
<p>
<code>
   settings:'country':'language'                                                  ** "ru-RU"
   settings:'country':'timezone':'country'                                        ** "Russia"
   settings:'country':'timezone':'city'                                           ** "Moscow"
   
   settings:'network':'server_name'                                               ** "127.0.0.1"
   
   settings:'country':'language.#'                                                ** "char[5]"
   
   settings:'network':'server_name.#'                                             ** "char[16]"
   settings:'network':'server_port.#'                                             ** "char[4]"</code>
</p>
<empty-line/>
<p>
Или они могут состоять из описаний типов данных, без
ключей и значений - как здесь:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Database.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   tree = {
       id : integer
       parent_id : integer
       name : string : not null
   }, {
       id : primary key
       parent_id : key
   }</code>
</p>
<empty-line/>
<p>
И тогда после чтения в память они будут выглядеть так:
</p>
<empty-line/>
<p>
<code>
   tree:'id.#'                                                                    ** "integer"
   tree:'parent_id.#'                                                             ** "integer"
   tree:'name.#'                                                                  ** "string : not null"
   
   tree:'+1':'id.#'                                                               ** "primary key"
   tree:'+1':'parent_id.#'                                                        ** "key"</code>
</p>
<empty-line/>
<p>
В данном случае список "tree" задаёт поля в таблице
в базе данных, и может быть использован для доступа к
ней. Прочитав такие настройки, можно будет отправлять
запросы на чтение, изменение, добавление и удаление
строк в таблице (как обычно и делается).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Внутренние настройки</p></title>
<p>
Внутренние настройки располагаются в INI-файлах в
виде списков из 2-х уровней:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Export.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   [rss]
   charset = ISO-8859-1
   charset.ru-RU = UTF-8
   title = Offers &#038; Salaries
   link = http://www.example.com</code>
</p>
<empty-line/>
<p>
Из них будет собран один файл, где вместо "&lt;NM&gt;" в
каждом случае будет подставлено название пространства
имён:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; &lt;ExeName&gt;.ini &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
    &#183;
    &#183;
    &#183;
   [&lt;NM&gt;.Export:rss]
   charset = ISO-8859-1
   charset.ru-RU = UTF-8
   title = Offers &#038; Salaries
   link = http://www.example.com
    &#183;
    &#183;
    &#183;</code>
</p>
<empty-line/>
<p>
А также созданы константы, позволяющие получить эти
настройки во время выполнения:
</p>
<empty-line/>
<p>
<code>
   charset = `RSS_CHARSET</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   charset = `RSS_CHARSET`settings.language_code</code>
</p>
<empty-line/>
<p>
При этом файл с настройками может быть изменён даже
во время выполнения программы - и они будут считаны
заново - но не раньше, чем в очереди в основном потоке
появится сообщение "reset" или закончатся все сообщения.
Настройки читает функция "msg-in()", которая проверяет
INI-файлы, когда сообщений не остаётся, а также когда в
очереди появляется первое сообщение.
</p>
<empty-line/>
<p>
Каждый раз во время доступа по константе настройки не
читаются - это было бы слишком медленно.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Внешние настройки</p></title>
<p>
Обратиться к файлу с настройками и прочитать их все в
память за один раз позволяет функция "prf-in()":
</p>
<empty-line/>
<p>
<code>
   (# prf-in file-name .profile)</code>
</p>
<empty-line/>
<p>
Её можно вызывать с константой "READ_ANYWAY", чтобы
она не проверяла существование файла и не выбрасывала
ошибку, когда он не будет найден:
</p>
<empty-line/>
<p>
<code>
   (# prf-in file-name READ_ANYWAY .profile)</code>
</p>
<empty-line/>
<p>
Раздел из файла настроек может быть считан так:
</p>
<empty-line/>
<p>
<code>
   (# prf-in file-name group-name .settings)</code>
</p>
<empty-line/>
<p>
И с константой "READ_ANYWAY":
</p>
<empty-line/>
<p>
<code>
   (# prf-in file-name group-name READ_ANYWAY .settings)</code>
</p>
<empty-line/>
<p>
Значение по названию раздела и ключа может быть
получено следующим образом:
</p>
<empty-line/>
<p>
<code>
   (# prf-in file-name group-name key-name .value)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# prf-in file-name group-name key-name +READ_FLOAT+READ_ANYWAY .value)</code>
</p>
<empty-line/>
<p>
А изменить значение позволяет функция "prf-out()":
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name group-name key-name value)</code>
</p>
<empty-line/>
<p>
Её можно вызвать и с ещё одним параметром, который
позволяет добавить комментарий:
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name group-name key-name value sample-value)</code>
</p>
<empty-line/>
<p>
Записать в файл сразу целый раздел можно так:
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name group-name settings)</code>
</p>
<empty-line/>
<p>
А записать его с заголовком - вот так:
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name group-name settings title-text)</code>
</p>
<empty-line/>
<p>
Весь файл за один раз может быть записан таким образом:
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name profile)</code>
</p>
<empty-line/>
<p>
И с заголовком:
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name profile title-text)</code>
</p>
<empty-line/>
<p>
Добавлять и удалять разделы в файлах настроек позволяет
функция "prf-edit()":
</p>
<empty-line/>
<p>
<code>
   (# prf-edit PRF_ADD_GROUP file-name group-name)
   (# prf-edit PRF_ADD_GROUP file-name group-name type-text)
   (# prf-edit PRF_ADD_GROUP file-name group-name type-text title-text)
   
   (# prf-edit PRF_HIDE_GROUP file-name group-name)
   (# prf-edit PRF_RESTORE_GROUP file-name group-name)
   
   (# prf-edit PRF_DELETE_GROUP file-name group-name)</code>
</p>
<empty-line/>
<p>
Она же позволяет добавлять и удалять ключи в разделах:
</p>
<empty-line/>
<p>
<code>
   (# prf-edit PRF_ADD_KEY file-name group-name key-name type-text)
   (# prf-edit PRF_ADD_KEY file-name group-name key-name type-text value)
   (# prf-edit PRF_ADD_KEY file-name group-name key-name type-text value sample-value)
   
   (# prf-edit PRF_HIDE_KEY file-name group-name key-name)
   (# prf-edit PRF_RESTORE_KEY file-name group-name key-name)
   
   (# prf-edit PRF_DELETE_KEY file-name group-name key-name)</code>
</p>
<empty-line/>
<p>
Создать пустой файл можно так:
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# prf-out file-name FF_BRACES)</code>
</p>
<empty-line/>
<p>
При этом, если файл с настройками не удаётся создать
или он уже существует, функция "prf-out()" выбрасывает
исключение с константой "ERR_*".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Настройки в XML</p></title>
<p>
XML представляет собой наиболее полный формат для любых
структур данных, позволяющий в том числе передавать их по
сети:
</p>
<empty-line/>
<p>
<code>
   &lt;?xml version="1.0" encoding="UTF-8"?&gt;
   &lt;rss version="0.92"&gt;&lt;channel&gt;
   &lt;title&gt;Average Prices&lt;/title&gt;&lt;link&gt;http://www.example.net&lt;/link&gt;
   &lt;description&gt;Average Prices and Statistics&lt;/description&gt;
   
   &lt;item date="..." keywords="..." salary="..."&gt;
    &lt;title&gt;...&lt;/title&gt;&lt;link&gt;...&lt;/link&gt;
    &lt;description&gt;...&lt;/description&gt;
   &lt;item&gt;
   ...
   
   &lt;/channel&gt;
   &lt;/rss&gt;</code>
</p>
<empty-line/>
<p>
Если считать данные в XML в память, они будут выглядеть
следующим образом:
</p>
<empty-line/>
<p>
<code>
   (% xml-record:rc &gt;                                         &#183;&#183;&#183;         &#183;&#183;&#183;
       {string} id                                          /           /
       {string} name data                           &gt;&gt; &#9472; ** - &#183;&#183;&#183;   &#183;&#183;&#183;
       {list : string &lt;- I|F|S} attr                        \     /
       {list : xml-record} records                            &#183;&#183;&#183; - &#183;&#183;&#183;   &#183;&#183;&#183;
   )                                                              \     /
                                                                    &#183;&#183;&#183; - &#183;&#183;&#183;
                                                                        \
                                                                          &#183;&#183;&#183;</code>
</p>
<empty-line/>
<p>
Прочитать их из текстовой строки можно с помощью
функции "records-in()":
</p>
<empty-line/>
<p>
<code>
   (' -prices &gt; .. ._)
   
   (# records-in text '/' (@ r +-
                              (attach prices (# parse-record r))
                          )
   )</code>
</p>
<empty-line/>
<p>
С константой "PARSE_ALL" она будет читать все
структуры внутри дерева, а не только структуры
первого уровня:
</p>
<empty-line/>
<p>
<code>
   (# records-in text '/' flags (@ r +-
                                    (attach prices (# parse-record r))
                                )
   -
       (PARSE_ALL .flags)
   )</code>
</p>
<empty-line/>
<p>
А с константой "PARSE_FILE" она прочитает файл:
</p>
<empty-line/>
<p>
<code>
   (# records-in file-name '/' PARSE_FILE (@ r +-
                                              (attach prices (# parse-record r))
                                          )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# records-in file-name '/' flags (@ r +-
                                         (attach prices (# parse-record r))
                                     )
   -
       (+PARSE_FILE+PARSE_ALL+READ_ANYWAY .flags)
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# records-in file-name start-from PARSE_FILE (@ r +-
                                                     ...
                                                 )
   -
       ('rss/channel/item.1' .start-from)
   )</code>
</p>
<empty-line/>
<p>
А если вызвать функцию "records-in()" для любой
структуры - она будет перебирать структуры внутри
неё:
</p>
<empty-line/>
<p>
<code>
   (# records-in record (@ r +-
                            ...
                        )
   )</code>
</p>
<empty-line/>
<p>
И как правило, при разборе данных в XML так и делается:
вначале эта функция выполняется для того, чтобы найти
структуру с нужным названием - и затем она же позволяет
просмотреть её содержимое и получить данные.
</p>
<empty-line/>
<p>
Для того, чтобы напечатать XML, создаётся текстовый
файл с расширением "*.tpl", вот так:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Avg_prices.tpl &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   &lt;?xml version="1.0" encoding="(/ {string} charset /)"?&gt;
   &lt;rss version="0.92"&gt;&lt;channel&gt;
   &lt;title&gt;(/ {string} title /)&lt;/title&gt;&lt;link&gt;http://www.example.net&lt;/link&gt;
   &lt;description&gt;(/ {string} description /)&lt;/description&gt;
   
   (/ (.* records x &gt; /)
   &lt;item date="(/ {string} x.date /)" keywords="(/ {string} x.keywords /)" price="(/ {float} x.price /)"&gt;
    &lt;title&gt;(/ {string} x.title /)&lt;/title&gt;&lt;link&gt;(/ {string} x.link /)&lt;/link&gt;
    &lt;description&gt;(/ {string} x.description /)&lt;/description&gt;
   &lt;item&gt;
   (/ ) /)
   
   &lt;/channel&gt;
   &lt;/rss&gt;</code>
</p>
<empty-line/>
<p>
Компилятор отображает его на функцию с таким же именем:
</p>
<empty-line/>
<p>
<code>
   (# avg-prices (%
                     RSS_CHARSET      .charset
                     RSS_TITLE        .title
                     RSS_URL          .link
                     RSS_CHANNEL_DESC .description
                     price-list       .records
                 )
                 (@ const +-
                     ([= RSS_TITLE] const &gt;
                         (&#038; `const ' (' date ')')
                     &gt;
                         (`const)
                     )
                 )
                 (@ text +-
                     (say text)
                 )
   -
       ((# clock) `RSS_DATE_FORMAT -&gt; .date)
   )</code>
</p>
<empty-line/>
<p>
Вызывая её с параметрами в виде структуры данных и
двумя безымянными функциями, можно получить XML в
текстовом виде: в данном случае это будут цены из
переменной "price-list".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>S-выражения</p></title>
<p>
Настройки в виде S-выражений, или так называемых
структурных выражений, удобны тем, что их можно
(и даже нужно) выполнять как обыкновенные скрипты,
где все действия заданы в скобочной записи, и каждое
действие начинается с глагола:
</p>
<empty-line/>
<p>
<code>
   (redirect
       (relay-ip '10.0.1.5')
       (dest-ip
           (select source-ip
               (when '192.168.200.17' '10.0.20.7')
               (when '192.168.200.34' '10.0.20.7')
               (when '192.168.200.41' '10.0.20.8')
               (else '10.0.20.9')
           )
       )
   )</code>
</p>
<empty-line/>
<p>
Ещё пример:
</p>
<empty-line/>
<p>
<code>
   (broadcast
       (source-ip '10.0.1.5')
       (dest-ip
           ('
               '10.0.20.10'
               '10.0.20.11'
               '10.0.12.12'
           )
       )
       (source-port 8000)
       (dest-port 8000)
   )</code>
</p>
<empty-line/>
<p>
И ещё один:
</p>
<empty-line/>
<p>
<code>
   (broadcast
       (source-ip '10.0.1.5')
       (dest-ip .res
           (.* (listeners source-ip source-port) addr .res &gt;
                (&#038; '10.0.' addr)
           )
           (.* (loggers source-ip source-port) addr .res &gt;
                (&#038; '10.0.' addr)
           )
       )
       (source-port 8000)
       (dest-port 8000)
   )</code>
</p>
<empty-line/>
<p>
И ещё:
</p>
<empty-line/>
<p>
<code>
   (broadcast
       (source-ip '10.0.1.5')
       (dest-ip ('
                    (# dest-for listeners source-ip source-port)
                    (# dest-for loggers source-ip source-port)
                )
       )
       (source-port 8000)
       (dest-port 8000)
   )
   (@ dest-for dest src-ip src-port .res +-
       (.* (dest src-ip src-port) addr .res &gt;
            (&#038; '10.0.' addr)
       )
   )</code>
</p>
<empty-line/>
<p>
И ещё один пример:
</p>
<empty-line/>
<p>
<code>
   (rewrite
       (short-url '/articles/*')
       (reply 301 (&#038; '/news/' (# id-from doc-name) '/' query-string))
   )
   (@ id-from str .article-id +-
       ([! '*/'] str &gt;
           (! str '*/articles/^[0-9]/*' .article-id)
       &gt;
           (! str '*/articles/^[0-9]' .article-id)
       )
   )</code>
</p>
<empty-line/>
<p>
Как видно, S-выражения представляют собой полноценный
язык программирования, который ничем не отличается от
большинства современных скриптовых языков. S-выражения
не имеют скрытого состояния и работают без глобальных
переменных: они получают какие-то данные на вход - и
печатают данные в ответ, полностью забывая, что было
сделано на предыдущем шаге.
</p>
<empty-line/>
<p>
В памяти S-выражения выглядят как структуры данных,
связанные в виде дерева:
</p>
<empty-line/>
<p>
<code>
   (% сommand:rc +-                                           &#183;&#183;&#183;         &#183;&#183;&#183;
       {string} id name                                     /           /
       {list : I|F|S | command} tokens              &gt;&gt; &#9472; ** - &#183;&#183;&#183;   &#183;&#183;&#183;
   )                                                        \     /
                                                              &#183;&#183;&#183; - &#183;&#183;&#183;   &#183;&#183;&#183;
                                                                  \     /
                                                                    &#183;&#183;&#183; - &#183;&#183;&#183;
                                                                        \
                                                                          &#183;&#183;&#183;</code>
</p>
<empty-line/>
<p>
Получить их из текстовой строки можно с помощью функции
"commands-in()":
</p>
<empty-line/>
<p>
<code>
   (' -rewrite-rule &gt; .. .rules)
   
   (# commands-in text '/' (@ cmd +-
                               (attach rules (# parse-command cmd)
                           )
   )</code>
</p>
<empty-line/>
<p>
Она же с флажком "PARSE_ALL" просмотрит всё дерево, а
не только структуры верхнего уровня:
</p>
<empty-line/>
<p>
<code>
   (# commands-in text '/' flags (@ cmd +-
                                     (attach rules (# parse-command cmd)
                                 )
   -
       (PARSE_ALL .flags)
   )</code>
</p>
<empty-line/>
<p>
А с флажком "PARSE_FILE" - прочитает их из файла:
</p>
<empty-line/>
<p>
<code>
   (# commands-in file-name '/' PARSE_FILE (@ cmd +-
                                               (attach rules (# parse-command cmd)
                                           )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# commands-in file-name '/' flags (@ cmd +-
                                          (attach rules (# parse-command cmd)
                                      )
   -
       (+PARSE_ALL+PARSE_FILE+READ_ANYWAY .flags)
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# commands-in file-name start-from PARSE_FILE (@ cmd +-
                                                      ...
                                                  )
   -
       ('redirect/dest-ipselect' .start-from)
   )</code>
</p>
<empty-line/>
<p>
При этом, если взять любую структуру и вызвать для
неё ту же функцию - она начнёт перебирать структуры
внутри неё:
</p>
<empty-line/>
<p>
<code>
   (# commands-in command (@ cmd +-
                              ...
                          )
   )</code>
</p>
<empty-line/>
<p>
То есть, для того, чтобы выполнить скрипт, записанный
в виде S-выражений, достаточно прочитать структуры на
первом уровне, а затем - пойти в глубину дерева, вызывая
функцию "commands-in()" для каждой из них.
</p>
<empty-line/>
<p>
Для того, чтобы печатать S-выражения, создаётся файл
с расширением "*.tpl", который выглядит примерно так:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; Rewrite.tpl &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   (/ (.* pairs x &gt; /)
   (rewrite
       ((/ {string} x.src.tag /) (/ {string} x.src.from /))
       ((/ {string} x.dest.tag /) (/ {string} x.dest.code /) (/ {string} x.dest.to /))
   )
   (/ ) /)</code>
</p>
<empty-line/>
<p>
Компилятор отображает его на функцию с таким же именем:
</p>
<empty-line/>
<p>
<code>
   (# rewrite (%
                  rewrite-rules .pairs
              ) ()
              (@ text +-
                  ...
              )
   )</code>
</p>
<empty-line/>
<p>
И теперь, если вызвать её и передать ей на вход
безымянную функцию - она вызовет её с текстовым
деревом, полученным из списка ключей и значений
в переменной "rewrite-rules".
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>SQL-серверы</p></title>
<p>
Системы управления базами данных, они же SQL-серверы и
они же СУБД, работают поверх файловой системы и хранят
данные в виде таблиц, где они связаны с помощью первичных
и вторичных ключей, позволяющих СУБД выполнять запросы,
соединяя данные из таблиц.
</p>
<empty-line/>
<p>
Современные СУБД работают с большой скоростью (заметно
быстрее, чем файловые системы), а также дают возможность
построчного сохранения данных по мере их изменения. Всё
это разобрано в других книгах, а здесь - краткие примеры
таблиц и запросов к ним.
</p>
<section><!-- 2 -->
<title><p>Структура таблиц</p></title>
<p>
Таблицы в следующем примере позволяют сохранять сведения
о предложениях о работе от различных компаний, добавляя к
ним ключевые слова:
</p>
<empty-line/>
<p>
<code>
   DROP TABLE IF EXISTS company;
   
   CREATE TABLE company (
       id int(10) NOT NULL auto_increment,
   
       title varchar(255) NOT NULL,
   
       PRIMARY KEY (id),
       UNIQUE KEY (title)
   );
   
   DROP TABLE IF EXISTS vacancy;
   
   CREATE TABLE vacancy (
       id int(10) NOT NULL auto_increment,
       company_id int(10) NOT NULL,
   
       start_date datetime,
       up_date datetime,
       salary int(10),
       salary_to int(10),
       title varchar(255) NOT NULL,
       text varchar(1024) NOT NULL,
   
       PRIMARY KEY (id),
       KEY (company_id)
   );
   
   DROP TABLE IF EXISTS keyword;
   
   CREATE TABLE keyword (
       id int(10) NOT NULL auto_increment,
   
       keyword char(20) NOT NULL,
   
       PRIMARY KEY (id),
       UNIQUE KEY (keyword)
   );
   
   DROP TABLE IF EXISTS vacancy_keyword;
   
   CREATE TABLE vacancy_keyword (
       vacancy_id int(10) NOT NULL,
       keyword_id int(10) NOT NULL,
   
       UNIQUE KEY (vacancy_id, keyword_id),
       UNIQUE KEY (keyword_id, vacancy_id)
   );</code>
</p>
<empty-line/>
<p>
Описание этих таблиц в виде файла с настройками выглядело
бы так:
</p>
<empty-line/>
<p>
<code>
   company = {
       id : integer
       title : string : not null
   }, {
       id : primary key
       title : unique key
   }
   
   vacancy = {
       id : integer
       company_id : integer : not null
       start_date : datetime
       up_date : datetime
       salary : integer
       salary_to : integer
       title : string : not null
       text : string : [1024] : not null
   }, {
       id : primary key
       company_id : key
   }
   
   keyword = {
       id : integer
       keyword : string : not null
   }, {
       id : primary key
       keyword : unique key
   }
   
   vacancy_keyword = {
       vacancy_id : integer : not null
       keyword_id : integer : not null
   }, {
       vacancy_id, keyword_id : unique key
       keyword_id, vacancy_id : unique key
   }</code>
</p>
<empty-line/>
<p>
Как видно, в таблицах создаются ключи первого уровня
(поля "id") и ключи второго уровня (поля "*_id"), что
позволяет объединять таблицы - вот так:
</p>
<empty-line/>
<p>
<code>
   SELECT
       k.keyword AS keyword,
       AVG(v.salary) AS average_salary
   FROM
       vacancy v,
       keyword k,
       vacancy_keyword vk
   WHERE
       vk.vacancy_id = v.id
   AND
       vk.keyword_id = k.id
   AND
       v.start_date &gt;= DATE_SUB(NOW(), INTERVAL 1 YEAR)
   GROUP BY
       k.id
   ORDER BY
       k.keyword ASC</code>
</p>
<empty-line/>
<p>
А получить средние цены по всем вакансиям можно так:
</p>
<empty-line/>
<p>
<code>
   SELECT
       AVG(salary) AS average_salary
   FROM
       vacancy
   WHERE
       start_date &gt;= DATE_SUB(NOW(), INTERVAL 1 MONTH)</code>
</p>
<empty-line/>
<p>
Как видно, в этих примерах среднее считается за последний
год или месяц - но число лет или месяцев тоже может быть
передано как параметр. В следующих главах об этом рассказано
более подробно.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Подключение к СУБД</p></title>
<p>
Для того, чтобы подключиться к SQL-серверу, вызывается
функция "database()", вот так:
</p>
<empty-line/>
<p>
<code>
   (0 .. .result)
   ((' DB_SRV_1 DB_SRV_2 DB_SRV_3) .servers)
   (.* servers srv-name &gt;
       (# database (%
                       `DB_LOGIN    .login
                       `DB_PASSWORD .password
                       `server      .database
                   )
       &gt;
           .result
       )
       ([result] &gt;
           -&gt;&gt;
       &gt;
           ;
       )
   )
   ([result] &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Здесь она подключается к основному и двум вспомогательным
серверам - на случай, если первый по каким-либо причинам
окажется недоступен. Как правило, таблицы на всех серверах
содержат одни и те же данные: это достигается с помощью
триггеров.
</p>
<empty-line/>
<p>
Два и более запросов на изменение, добавление или удаление
данных надо в обязательном порядке заключать в транзакции -
вне зависимости от того, поддерживает ли их SQL-серверами,
или нет. Открыть и завершить транзакцию можно с помощью
функции "sql-series()":
</p>
<empty-line/>
<p>
<code>
   (\ alarm &gt;
       (# sql-series (@ +-
                         ...
                     )
       )
   + ec error &gt;
       (log error)
       (# handle ec)
   )
   ([alarm] &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Переключение между разными SQL-серверами выполняется так:
</p>
<empty-line/>
<p>
<code>
   (# database DB_SECONDARY .prev-db)</code>
</p>
<empty-line/>
<p>
И вот так:
</p>
<empty-line/>
<p>
<code>
   (# database prev-db)</code>
</p>
<empty-line/>
<p>
При этом, соединения с каждым SQL-сервером должны быть
открыты заранее.
</p>
<empty-line/>
<p>
Функции для закрытия соединений нет: они закрываются во
время завершения программы.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Отправка запросов</p></title>
<p>
Для того, чтобы отправлять запросы к базам данных, надо
создать файлы с расширением "*.sql", вот так:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;                              &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; With_salary.tpl &#9474;                              &#9474; Total.tpl &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;                              &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   SELECT                                           SELECT
       COUNT(*) AS with_salary                          COUNT(*) AS total
   FROM                                             FROM
       vacancy                                          vacancy
   WHERE                                            WHERE
       start_date &gt;= DATE_SUB(NOW(),                    start_date &gt;= DATE_SUB(NOW(),
                              INTERVAL                                         INTERVAL
                              (/ {integer} x /)                                (/ {integer} x /)
                              WEEK)                                            WEEK)
   AND
       salary IS NOT NULL
   AND
       salary &gt; 0</code>
</p>
<empty-line/>
<p>
Компилятор отобразит их на функции с такими же именами -
и можно будет вызывать их, как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (0 .. .with-salary .total)
   (# with-salary (%
                      weeks .x
                  )
                  (@ row +-
                      (row.with-salary .with-salary)
                  )
   )
   (# total (%
                weeks .x
            )
            (@ row +-
                (row.total .total)
            )
   )
   (say &lt;- ((/ total) with-salary) '0.00')</code>
</p>
<empty-line/>
<p>
Добавление данных выполняется так:
</p>
<empty-line/>
<p>
<code>
   INSERT INTO vacancy
   (
       company_id,
       start_date, up_date,
       salary, salary_to,
       title, text
   )
   VALUES
   (
       (/ {integer} company-id /),
       (/ {datetime} start-date /), (/ {datetime} up-date /),
       (/ {string} salary /), (/ {string} salary-to /),
       (/ {string} title /), (/ {string} text /)
   )</code>
</p>
<empty-line/>
<p>
И функция выглядит следующим образом:
</p>
<empty-line/>
<p>
<code>
   (# add-vacancy (%
                      company-id ._
                      start-date ._ up-date   ._
                      salary     ._ salary-to ._
                      title      ._ text      ._
                  ) ()
   )</code>
</p>
<empty-line/>
<p>
Изменение - так:
</p>
<empty-line/>
<p>
<code>
   UPDATE
       vacancy
   SET
       up_date = NOW()
   WHERE
       id = (/ {integer} id /)</code>
</p>
<empty-line/>
<p>
И функция выглядит вот так:
</p>
<empty-line/>
<p>
<code>
   (# update-vacancy-time (%
                              vacancy-id .id
                          ) ()
   )</code>
</p>
<empty-line/>
<p>
А удаление выполняется таким образом:
</p>
<empty-line/>
<p>
<code>
   DELETE FROM
       vacancy
   WHERE
       id = (/ {integer} id /)
   LIMIT 1</code>
</p>
<empty-line/>
<p>
И функция выглядит так:
</p>
<empty-line/>
<p>
<code>
   (# delete-vacancy (%
                         vacancy-id .id
                     ) ()
   )</code>
</p>
<empty-line/>
<p>
Как видно, все функции будут проверять передаваемые им
параметры - и эти проверки не позволят подставить вместо
целого числа строку "0 OR 1&gt;0".
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Запросы с текстом</p></title>
<p>
В том случае, когда в запрос надо подставить строку,
не выполняя никаких проверок, параметры запроса надо
задать как "text" или "text : spaces", то есть:
</p>
<empty-line/>
<p>
<code>
   SELECT
       *
   FROM
       keyword
   WHERE
       id IN (/ {string : text : spaces} condition /)
   ORDER BY
       keyword (/ {string : text} order-by /)</code>
</p>
<empty-line/>
<p>
И функция, создаваемая компилятором, выглядит так:
</p>
<empty-line/>
<p>
<code>
   (# keywords (%
                   condition ._
                   order-by  ._
               )
               (@ row +-
                   (attach result row.keyword)
               )
   )</code>
</p>
<empty-line/>
<p>
Как видно, параметр "order-by" в этом примере может
быть любой строкой, а параметр "condition" - строкой
с пробелами. Предполагается, что они будут проверены
вручную перед составлением запроса.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Запросы "на месте"</p></title>
<p>
Ещё один способ отправлять запросы к базам данных -
обойтись без текстового файла и написать функцию с
прямоугольными скобками:
</p>
<empty-line/>
<p>
<code>
   (@ avg-from-to weeks .result +-
       _sql[
               SELECT
                   AVG(
                          (salary_to - salary) /
                          ((salary_to + salary) / 2)
                      )
                   AS avg_from_to
               FROM
                   vacancy
               WHERE
                   start_date &gt;= DATE_SUB(NOW(), INTERVAL (/ {integer} weeks /) WEEK)
           ]
           (%
               weeks ._
           )
           (@ x +-
               (x.avg-from-to .result)
           )
   +
       {integer} weeks
       {float} result
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ avg-from-to weeks .result +-
       _sql[
               CALL avg_from_to((/ {integer} weeks /))
           ]
           (%
               weeks ._
           )
           (@ x +-
               (x.avg-from-to .result)
           )
   +
       {integer} weeks
       {float} result
   )</code>
</p>
<empty-line/>
<p>
В редких случаях это бывает удобно - например, в
последнем примере функция "avg-from-to()" вызывает
внутри СУБД хранимую процедуру "avg_from_to()", и
создавать для этого ещё один текстовый файл вовсе
необязательно. В других случаях этот же приём будет
мешать и затруднит чтение и исправление программы.
</p>
<empty-line/>
<p>
На скорость отправки запросов он не влияет: они
в любом случае будут выполняться как заранее
подготовленные запросы ("prepared statements").
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>&nbsp;</p></title>
<p>&nbsp;</p>
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Многопоточность</p></title>
<p>
Для того, чтобы ускорить выполнение программы,
её можно разделить на несколько потоков, каждый
из которых будет выполняться независимо от всех
остальных. Это вполне возможно, при условии, что
в программе нет глобальных переменных, которые
сдерживали бы её выполнение - и в языке Veronica
так и было сделано: глобальных переменных здесь
нет.
</p>
<section><!-- 2 -->
<title><p>Создание потоков</p></title>
<p>
Потоки создаются и сразу же ставятся на выполнение
с помощью команды "start", как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ read-socket socket-name .reply +-
       (start wget wp (@ rc +-
                          (rc .reply.page)
                      )
       -
           (&#038; socket-name '/wp' .wp)
       )
       (start wget css (@ rc +-
                           (rc .reply.style)
                       )
       -
           (&#038; socket-name '/style' .css)
       )
   +
       {string} socket-name
       {webpage} reply
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "read-socket()" обращается сразу к
двум сетевым соединениям, получая из них данные
одновременно - в двух потоках.
</p>
<empty-line/>
<p>
К этой команде не добавляют обработчики исключений,
поскольку к тому времени, когда возникнет исключение,
точка выполнения может уйти далеко от этой команды:
</p>
<empty-line/>
<p>
<code>
   (@ read-socket socket-name .reply +-
       (start wget wp (@ rc +-
                          (rc .reply.page)
                      )
       -
           (&#038; socket-name '/wp' .wp)
       + ec error &gt;
           (# handle ec error)                                                    ** ?!!..
       )
       (start wget css (@ rc +-
                           (rc .reply.style)
                       )
       -
           (&#038; socket-name '/style' .css)
       + ec error &gt;
           (# handle ec error)                                                    ** ?!!..
       )
   +
       {string} socket-name
       {webpage} reply
   )                                                                              ** &#8592; we are here</code>
</p>
<empty-line/>
<p>
Так что ставить обработчики исключений надо на один
или несколько уровней выше.
</p>
<empty-line/>
<p>
Флажок "-d" позволяет выполнять функцию в настоящем
(не легковесном) потоке, созданном операционной
системой:
</p>
<empty-line/>
<p>
<code>
   (@ read-socket socket-name .reply +-
       (start -d wget wp (@ rc +-
                             (rc .reply.page)
                         )
       -
           (&#038; socket-name '/wp' .wp)
       )
       (start -d wget css (@ rc +-
                              (rc .reply.style)
                          )
       -
           (&#038; socket-name '/style' .css)
       )
   +
       {string} socket-name
       {webpage} reply
   )</code>
</p>
<empty-line/>
<p>
При ошибке, когда такой поток выбрасывает исключение,
функция "read-socket()" узнаёт об этом и тоже бросает
исключение - то есть, флажок задаёт режим выполнения,
но не способ обработки исключений.
</p>
<empty-line/>
<p>
Потоку может быть передано название порта, куда он мог
бы присылать сообщения:
</p>
<empty-line/>
<p>
<code>
   (@ read-socket socket-name port-name +-
       (start -d wget wp port-name -
           (&#038; socket-name '/wp' .wp)
       )
       (start -d wget css port-name -
           (&#038; socket-name '/style' .css)
       )
   +
       {string} socket-name port-name
   )</code>
</p>
<empty-line/>
<p>
Отправить сообщение можно так:
</p>
<empty-line/>
<p>
<code>
   (write port-name reply)</code>
</p>
<empty-line/>
<p>
А читать сообщения - вот так:
</p>
<empty-line/>
<p>
<code>
   (read port-name result)</code>
</p>
<empty-line/>
<p>
В случае ошибки в очередь потока, создавшего новый поток,
будет отправлено сообщение с заголовком "произошла ошибка"
и константой "ERR_*", так что то же самое исключение выбросит
команда "read", читающая сообщения из очереди.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Защита внешних данных</p></title>
<p>
В том случае, когда несколько потоков должны обращаться
к внешним данным с доступом на чтение и запись, эти данные
надо закрыть семафором, ограничивающим доступ. Для защиты
внутренних данных семафоры не используются, поскольку все
переменные в программе доступны потокам только на чтение.
</p>
<empty-line/>
<p>
Семафор создаётся с помощью функции "env()":
</p>
<empty-line/>
<p>
<code>
   (# env MTX_CREATE ('
                         WS_SERVER/IMAGES
                         WS_WEB_CACHE/IMAGES
                     )
   )</code>
</p>
<empty-line/>
<p>
И так же удаляется:
</p>
<empty-line/>
<p>
<code>
   (# env MTX_DELETE ('
                         WS_SERVER/IMAGES
                         WS_WEB_CACHE/IMAGES
                     )
   )</code>
</p>
<empty-line/>
<p>
Ожидание семафора и захват его с доступом на чтение
выполняет команда "wait" с флажком "-r":
</p>
<empty-line/>
<p>
<code>
   (wait -r ('
                WS_SERVER/IMAGES
            )
   )
   
   &#183;
   &#183;
   &#183;
   
   (wait -r ('
                WS_WEB_CACHE/IMAGES
            )
   )
   
   &#183;
   &#183;
   &#183;</code>
</p>
<empty-line/>
<p>
А ожидание и захват с доступом на запись - команда "wait"
с флажком "-w":
</p>
<empty-line/>
<p>
<code>
   (wait -r ('
                WS_SERVER/IMAGES
            )
            MTX_PRIMARY
   )
   
   &#183;
   &#183;
   &#183;
   
   (wait -w ('
                WS_SERVER/IMAGES
            )
            MTX_SECONDARY
   )
   
   &#183;
   &#183;
   &#183;
   
   (wait -r ('
                WS_WEB_CACHE/IMAGES
            )
            MTX_PRIMARY
   )
   
   &#183;
   &#183;
   &#183;
   
   (wait -w ('
                WS_WEB_CACHE/IMAGES
            )
            MTX_SECONDARY
   )
   
   &#183;
   &#183;
   &#183;</code>
</p>
<empty-line/>
<p>
В таком случае она иногда получает ещё и константы
"primary/secondary", которые соответствуют действиям
первого и второго уровня ("primary" и "secondary"),
как и показано в примере выше.
</p>
<empty-line/>
<p>
Ограничивающие семафоры могут быть созданы и поверх
дерева каталогов:
</p>
<empty-line/>
<p>
<code>
   (# env MTX_CREATE ('
                         source-path
                         target-path
                     )
   )</code>
</p>
<empty-line/>
<p>
И тогда их ожидание и захват выполняются так:
</p>
<empty-line/>
<p>
<code>
   (wait -w ('
                source-path
                target-path
            )
   )</code>
</p>
<empty-line/>
<p>
И теперь вместо хранения всех файлов в одном
каталоге:
</p>
<empty-line/>
<p>
<code>
   R:/Www/files/images/photos/nature.jpg &#8592;
   R:/Www/files/images/photos/nature.jpg   &#8592;                                      ** !!!</code>
</p>
<empty-line/>
<p>
Их можно разделить по нескольким каталогам - даже
при отсутствии SQL-сервера, который тоже позволяет
это сделать:
</p>
<empty-line/>
<p>
<code>
   R:/Www/files/images/photos/1287531            &#8730;
   R:/Www/files/images/photos/1287532            &#8730;
   R:/Www/files/images/photos/1287531/nature.jpg &#8592;
   R:/Www/files/images/photos/1287532/nature.jpg &#8592;</code>
</p>
<empty-line/>
<p>
Здесь ограничивающий семафор будет использован для
того, чтобы создать внутри каталога "photos" каталог
с порядковым номером - а дальше потоки будут работать
одновременно.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Сетевые соединения</p></title>
<p>
Для того, чтобы отправлять и получать запросы по
сети, в языке Veronica есть ещё несколько функций,
работающих с сетевыми протоколами TCP и UDP.
</p>
<section><!-- 2 -->
<title><p>Работа с одним соединением</p></title>
<p>
Открыть и слушать единственное TCP-соединение можно
с помощью функции "net()", как в следующем примере:
</p>
<empty-line/>
<p>
<code>
   (@ listener +-
       (# net x (%
                    ''127.0.0.1 .ip
                           8080 .port
                           2000 .timeout
                )
                (@ pipe +-
                    (start thread pipe)
                )
       -
           (+N_TCP+N_LISTENER .x)
       )
   )</code>
</p>
<empty-line/>
<p>
Как видно, эта функция получает на вход безымянную
функцию, вызывающую функцию "thread()" в отдельном
легковесном потоке:
</p>
<empty-line/>
<p>
<code>
   (@ thread 'listener pipe +-
       (.* [# chars] pipe &gt;
           (# char-in pipe .request
            # process request .response
            # char-out pipe response)
       )
   )</code>
</p>
<empty-line/>
<p>
Та же функция на стороне клиента позволяет установить
соединение с сервером, отправлять ему запросы и получать
данные в ответ:
</p>
<empty-line/>
<p>
<code>
   (@ client request .reply +-
       (# net x (%
                    ''127.0.0.1 .ip
                           8080 .port
                          10000 .timeout
                )
                (@ pipe +-
                    (# char-out pipe request
                     # char-in pipe .reply)
                )
       -
           (+N_TCP+N_PEER .x)
       )
   )</code>
</p>
<empty-line/>
<p>
При этом, если задать "-1" в параметре "timeout", вот
так:
</p>
<empty-line/>
<p>
<code>
                             -1 .timeout</code>
</p>
<empty-line/>
<p>
То клиент будет выполнять ожидание до тех пор, пока
сервер ему не ответит - либо пока соединение не будет
закрыто (и тогда функция "net()" выбросит исключение с
константой "ERR_*").
</p>
<empty-line/>
<p>
Открыть и слушать UDP-соединение можно так:
</p>
<empty-line/>
<p>
<code>
   (@ listener +-
       (# net x (%
                    ''127.0.0.1 .ip
                           4040 .port
                           2000 .timeout
                )
                (@ pipe +-
                    (start thread pipe)
                )
       -
           (+N_UDP+N_LISTENER .x)
       )
   )</code>
</p>
<empty-line/>
<p>
А клиент в этом случае выглядит так:
</p>
<empty-line/>
<p>
<code>
   (@ client request .reply +-
       (# net x (%
                    ''127.0.0.1 .ip
                           4040 .port
                           2025 .ep-port
                          10000 .timeout
                )
                (@ pipe +-
                    (# char-out pipe request
                     # char-in pipe .reply)
                )
       -
           (+N_UDP+N_PEER .x)
       )
   )</code>
</p>
<empty-line/>
<p>
Здесь на стороне клиента создаётся ещё один порт, куда
сервер присылает ответ. Параметр "timeout" в таком случае
должен быть задан как на стороне сервера, так и на стороне
клиента (и как правило, он задаётся как достаточно большое
число в десятичной системе счисления).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Работа с несколькими соединениями</p></title>
<p>
Открыть несколько TCP-соединений на стороне сервера можно
таким образом:
</p>
<empty-line/>
<p>
<code>
   (@ listener +-
       (# net x (%
                    ('
                        (% + ip          port)
                        (%   '127.0.0.1' 4000)
                        (%   '127.0.0.1' 4040)
                        (%   '127.0.0.1' 8000)
                        (%   '127.0.0.1' 8080)
                    &gt;
                        .open
                    )
                    2000 .timeout
                )
                (@ pipe ip port +-
                    (start thread pipe port)
                )
       -
           (+N_TCP+N_LISTENER .x)
       )
   )</code>
</p>
<empty-line/>
<p>
А несколько UDP-соединений открываются так:
</p>
<empty-line/>
<p>
<code>
   (@ listener +-
       (# net x (%
                    ('
                        (% + ip          port)
                        (%   '127.0.0.1' 4000)
                        (%   '127.0.0.1' 4040)
                        (%   '127.0.0.1' 8000)
                        (%   '127.0.0.1' 8080)
                    &gt;
                        .open
                    )
                    10000 .timeout
                )
                (@ pipe ip port +-
                    (start thread pipe port)
                )
       -
           (+N_UDP+N_LISTENER .x)
       )
   )</code>
</p>
<empty-line/>
<p>
Подключение со стороны клиента выглядит так же, как и
в остальных случаях.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Асинхронный двунаправленный ввод-вывод</p></title>
<p>
Не поддерживается.
</p>
<empty-line/>
<p>
Вместо него надо использовать легковесные потоки: с ними
программу легче отлаживать и вносить в неё изменения. Всё,
что должно работать одновременно - будет работать как раз
так, но все одновременно выполняемые действия должны быть
скрыты за командами "start", "read" и "write".
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Порты ввода-вывода</p></title>
<p>
Следующие несколько глав рассказывают о доступе
к последовательным и параллельным портам, а также
функциях, позволяющих получить отчёт о доступном
объёме памяти и состоянии операционной системы.
</p>
<section><!-- 2 -->
<title><p>Запросы к ядру и драйверам</p></title>
<p>
Отправить запрос к одному из драйверов позволяет
функция "dev()", а открыть доступ к нему - функция
"kern()", вот так:
</p>
<empty-line/>
<p>
<code>
   (# dev ds h (@ string +-
                   (say string)
               )
   -
       (+D_DASD+D_MESSAGE .ds) |
       (# kern K_DDC driver-name .h -
           ('IBMS506$' .driver-name)
       )
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   R1.8.15 /!BIOS
   Daniela's Bus Master IDE Driver for OS/2 Version 1.8.15
   ...</code>
</p>
<empty-line/>
<p>
Та же функция "kern()" позволяет узнать состояние
операционной системы:
</p>
<empty-line/>
<p>
<code>
   (say (&#038; mb ' megabytes free.')
   -
       ((/ 2^20) snapshot.memory.free-bytes .mb -
           (# kern K_STATE .snapshot)
       )
   )</code>
</p>
<empty-line/>
<p>
Этот пример напечатает:
</p>
<empty-line/>
<p>
<code>
   2994 megabytes free.</code>
</p>
<empty-line/>
<p>
Уменьшить приоритет потока можно так:
</p>
<empty-line/>
<p>
<code>
   (# kern PRTY_IDLE)</code>
</p>
<empty-line/>
<p>
А всего приложения - вот так:
</p>
<empty-line/>
<p>
<code>
   (# kern +PRTY_IDLE+PRTY_PROCESS)</code>
</p>
<empty-line/>
<p>
Остановить поток на короткое время (в миллисекундах)
можно таким образом:
</p>
<empty-line/>
<p>
<code>
   (# kern K_SLEEP_MS delay -
       (1000 .delay)
   )</code>
</p>
<empty-line/>
<p>
А понизить приоритет и остановить поток на короткое
время - вот так:
</p>
<empty-line/>
<p>
<code>
   (# kern K_SLEEP_MS_IDLE delay -
       (7500 .delay)
   )</code>
</p>
<empty-line/>
<p>
В том же случае, когда поток останавливать не надо,
функция "kern()" позволяет уступить время другим
приложениям и потокам:
</p>
<empty-line/>
<p>
<code>
   (# kern K_TIMESLICE)                             (# kern K_TIMESLICE_IDLE)</code>
</p>
<empty-line/>
<p>
Она же позволяет получить список работающих приложений:
</p>
<empty-line/>
<p>
<code>
   (snapshot.aplications .apps -
       (# kern K_APPSTATE .snapshot)
   )
   (.* apps a &gt;
       (say a.exe-name)
   )</code>
</p>
<empty-line/>
<p>
А добраться до списка окон можно с помощью функции
"env()":
</p>
<empty-line/>
<p>
<code>
   (# env TASK_TITLE title)</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (# env TASK_TITLE (%
                         title-string  .title
                         document-name .doc-name
                     )
   )</code>
</p>
<empty-line/>
<p>
Та же функция позволяет ждать завершения приложения:
</p>
<empty-line/>
<p>
<code>
   (# env WAIT_ID zip.process_id)</code>
</p>
<empty-line/>
<p>
Или запуска нового приложения:
</p>
<empty-line/>
<p>
<code>
   (# env WAIT_NEW `SQL_EXENAME)</code>
</p>
<empty-line/>
<p>
И в таком случае поток останавливается до тех пор,
пока в списке работающих приложений не появится
строка, заданная в одном из файлов настроек (точно
так же можно ждать появления сетевого соединения
или PIPE-соединения или подключения устройства).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Чтение и запись данных</p></title>
<p>
Кроме получения текстовых сообщений, функция "dev()"
может обратиться к драйверу, передать ему запрос и
получить от него данные:
</p>
<empty-line/>
<p>
<code>
   (# dev ds h (@ r +-
                   (say (&#038; 'First screen supports ' r.colors ' colors.'))
               )
   -
       (+D_SCREEN+D_INFO .ds) |
       (# kern K_DDC driver-name .h -
           ('SCREEN01$' .driver-name)
       )
   )</code>
</p>
<empty-line/>
<p>
А чтение и запись в последовательные порты выполняется
с помощью функций "char-in()", "char-out()", "line-in()"
и "line-out()":
</p>
<empty-line/>
<p>
<code>
   (# char-out port command
    # char-out port "^Z"
    # line-in port -
       (`PORT_NAME .port)
   )</code>
</p>
<empty-line/>
<p>
Управляющий символ "^Z" в данном случае означает
окончание передачи: когда-то его передавали в конце
сообщения, и кое-где устройства всё ещё так работают.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Запуск приложений</p></title>
<p>
Запуск программ с командной строки и получение
от них текстового вывода - давний приём, который
был полезен во времена, когда памяти на компьютерах
было мало. Используется он кое-где и сейчас.
</p>
<section><!-- 2 -->
<title><p>Сжатие данных</p></title>
<p>
Для запуска внешних приложений в языке Veronica
предназначена функция "ext()", которая позволяет
передать им параметры и получить текстовый вывод
от них. Как правило, её используют для сжатия и
преобразования данных.
</p>
<empty-line/>
<p>
И например, для выполнения следующей команды:
</p>
<empty-line/>
<p>
<code>
   resample.exe --format=jpeg --size=128x96 R:/Temp/random-name.png R:/Temp/random-name-tn.jpg</code>
</p>
<empty-line/>
<p>
Она вызывается таким образом:
</p>
<empty-line/>
<p>
<code>
   (@ thumbnail file-name .name-tn +-
       (&#038; fn '-tn.jpg' .name-tn -
           (parse -r file-name fn '.' .)
       )
       (# ext (%
                  'resample.exe'        .name
                  (' '--format=jpeg'
                     '--size=128x96'
                     file-name name-tn) .parameters
                  path                  .workdir
              )
       -
              (parse -r file-name path '/' .)
       )
   )</code>
</p>
<empty-line/>
<p>
Здесь команда "parse" разбирает имя файла, из него
создаётся новое имя (с новым расширением), а затем
функция "ext()" вызывает "resample.exe".
</p>
<empty-line/>
<p>
Такой же приём позволяет добиться того, чтобы
внешнее приложение получало ввод из своего потока
ввода STDIN и печатало текстовый вывод в поток
вывода "STDOUT":
</p>
<empty-line/>
<p>
<code>
   resample.exe --format=jpeg --size=128x96 --length=22984 - -</code>
</p>
<empty-line/>
<p>
Программный код, позволяющий запустить "resample.exe"
таким образом, выглядит так:
</p>
<empty-line/>
<p>
<code>
   (@ thumbnail bytes .result +-
       (# ext (%
                  'resample.exe'     .name
                  STD_PIPE_IO        .mode
                  (' '--format=jpeg'
                     '--size=128x96'
                     length '-' '-') .parameters
                  path               .workdir
              )
       -
           (&#038; '--length=' bytes._ .length) |
           (parse -r file-names.1 path '/' .)
       &gt;
           .resample
       )
       (# char-in resample.stdout .result -
           (# char-out resample.stdin bytes)
       )
   +
       {array : integer : 2^8} bytes result
   )</code>
</p>
<empty-line/>
<p>
Здесь функция "ext()" создаёт переменную "resample",
позволяющую отправить данные в STDIN другого приложения
и получить ответ из его STDOUT. Затем с помощью функций
"char-out()" и "char-in()" ему передаются байты (то есть
те данные, которые надо преобразовать), а ответ от него
записывается в переменную "result".
</p>
<empty-line/>
<p>
А для того, чтобы просто получить текстовый вывод
от запускаемого приложения:
</p>
<empty-line/>
<p>
<code>
   ls -al
   &#9484;
    drwxrwxrwx 1 0 0 -          -     .
    drwxrwxrwx 1 0 0 -          -     ..
    -rw-rw-rw- 1 0 0 01.07.2015 21:54 file-1.txt              &#8594; say 'file-1.txt'
    -rw-rw-rw- 1 0 0 01.07.2015 21:54 file-2.txt              &#8594; say 'file-2.txt'
   &#9492;</code>
</p>
<empty-line/>
<p>
Достаточно передать на вход функции "ext()" безымянную
функцию:
</p>
<empty-line/>
<p>
<code>
   (@ ls-al path .lines +-
       (# ext (%
                  'ls.exe'  .name
                  (' '-al') .parameters
                  path      .workdir
              )
              (@ line +-
                  (attach lines line : [line '.txt']
                                     : [line ':'])
              )
       )
   )</code>
</p>
<empty-line/>
<p>
И она будет вызвана для каждой строки, полученной из
STDOUT внешнего приложения.
</p>
<empty-line/>
<p>
А в том случае, когда текстовый вывод от запускаемого
приложения надо перенаправить во временный файл - вот
так:
</p>
<empty-line/>
<p>
<code>
   ls -al &gt; R:/Temp/server-987-65-43210.tmp</code>
</p>
<empty-line/>
<p>
Его запуск будет выглядеть так:
</p>
<empty-line/>
<p>
<code>
   (@ ls-al path .file-name +-
       (# fs FS_TMP .temp-file)
       (# ext (%
                  'ls.exe'  .name
                  (' '-al') .parameters
                  path      .workdir
                  temp-file .redirect
              )
       )
       ([# file-exists] temp-file &gt;
           (temp-file .file-name)
       &gt;
           &lt;&lt;- (ERR_NO_FILE)
       )
   )</code>
</p>
<empty-line/>
<p>
И наконец, когда текстовый вывод от одного приложения
надо передать на вход другому:
</p>
<empty-line/>
<p>
<code>
   ls.exe -U1 *.txt | zip.exe -q -@ -9rmX 2001-01-01.zip</code>
</p>
<empty-line/>
<p>
Их запуск будет выглядеть так:
</p>
<empty-line/>
<p>
<code>
   (@ zip-ls-al path .archive-name +-
       (. .. .zip)
       (# fs +FS_TMP+FS_DIR .temp-path)
       (&#038; temp-path '/' date '.zip' .archive-name -
           ((# clock) 'YYYY-MM-DD' -&gt; .date)
       )
       (# ext (%
                  'zip.exe'        .name
                  STD_PIPE_IO      .mode
                  (' '-q' '-@'
                     '-9rmX'
                     archive-name) .parameters
                  temp-path        .workdir
              ) .zip
       )
       (# ext (%
                  'ls.exe'    .name
                  (' '-U1'
                     '*.txt') .parameters
                  path        .workdir
              )
              (@ line +-
                  (# line-out zip.stdin line : [line '.txt'])
              )
        # char-out zip.stdin "^Z"
       )
       (# env WAIT_ID zip.process_id)
   )</code>
</p>
<empty-line/>
<p>
Здесь архиватор "zip.exe" получает ключ "-@", чтобы
он читал имена файлов из своего STDIN, а также ключ
"-q", чтобы он ничего не печатал в STDOUT. Затем
запускается "ls.exe", и строки, получаемые из его
STDOUT (полные имена файлов), передаются в STDIN
архиватора. В конце функция "env()" выполняет
ожидание, пока архиватор завершит сжатие (тогда
он закроет ZIP-файл и его можно будет переместить
в другую ветку дерева каталогов - как обычно и
делается).
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Переменные среды</p></title>
<p>
Получить значение переменной среды можно с помощью
функции "env()":
</p>
<empty-line/>
<p>
<code>
   (# env 'BOOKSHELF' .books)</code>
</p>
<empty-line/>
<p>
Она же позволяет перебрать несколько названий и
остановиться на том из них, которое будет задано:
</p>
<empty-line/>
<p>
<code>
   (# env (' 'TEMP' 'TMP' 'TMPDIR') .temp-path)</code>
</p>
<empty-line/>
<p>
Получить с её помощью несколько строк можно так:
</p>
<empty-line/>
<p>
<code>
   (# env 'LIBPATH' ';' .libpath-array)</code>
</p>
<empty-line/>
<p>
А список ключей и значений - вот так:
</p>
<empty-line/>
<p>
<code>
   (# env '%%' (' ' ' '=') .command-line-array)</code>
</p>
<empty-line/>
<p>
В последнем примере "%%" означает "получить ключи
и значения, заданные в командной строке" - она не
относится к переменным среды, но функция "env()"
способна разобрать и командную строку, если это
потребуется.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Печать Web-страниц</p></title>
<p>
Отдельного и более подробного рассмотрения заслуживают
функции, позволяющие принимать запросы по протоколу
HTTP и печатать Web-страницы. За ними стоят обращения
к базам данных, а также задачи вроде сохранения паролей
на стороне сервера - и многие другие.
</p>
<section><!-- 2 -->
<title><p>Обработка запросов</p></title>
<p>
В большинстве случаев (именно так и показано в следующем
примере) Web-сервер читает запросы из очереди сообщений и
выполняется без состояния - то есть:
</p>
<empty-line/>
<p>
<code>
   #&lt;- http server
   
   (@ +-
       (# msg-in (@ msg +-
                     (reply-to msg msg.layer-id)
       )
   )
   
   (@ reply-to msg HTTP_GET +-
       (# check-user request
        # process request -
            (# http-request msg .request)
       )
   )
   
   (@ reply-to msg HTTP_POST +-
       ...
   )
   
   (@ reply-to msg .)</code>
</p>
<empty-line/>
<p>
Здесь функция "msg-in()" выбирает сообщения из очереди,
а функция "reply-to()" печатает что-либо в ответ. Здесь
Web-сервер не использует глобальные переменные, и у него
нет скрытого состояния - так что он не помнит, что было
сделано на предыдущем шаге.
</p>
<empty-line/>
<p>
Добавить к Web-серверу переменные, сохраняющие состояние
между запросами, можно таким образом:
</p>
<empty-line/>
<p>
<code>
   #&lt;- http server
   
   (% program-state +-
       ...
   )
   
   (@ +-
       (# msg-in (@ msg +-
                     (# reply-to msg
                                 msg.layer-id
                                 state (@ st +-
                                           (st .state)
                                       )
                     )
       -
           (%
               .. .state
           )
       )
   )
   
   (@ reply-to msg HTTP_GET state @fp +-
       (# check-user request
        # process request
        # fp state -
            (# http-request msg .request)
       )
   +
       {program-state} state
   )
   
   (@ reply-to msg HTTP_POST state @fp +-
       ...
   +
       {program-state} state
   )
   
   (@ reply-to msg . state @fp +-
       (# fp state)
   +
       {program-state} state
   )</code>
</p>
<empty-line/>
<p>
Здесь перед вызовом функции "msg-in()" создаётся структура
данных "state", и в дальнейшем функция "reply-to()" вызывает
безымянную функцию, которая меняет эту переменную - то есть,
обновляет состояние. При этом переменная "state" закрывается
от дальнейших изменений, и внутри функции "reply-to()" она
доступна только на чтение.
</p>
<empty-line/>
<p>
И в том, и в другом примере вызывается ещё одна функция -
"http-request()", которая заполняет структуру "request" с
данными от пользователя. Чтобы создать её, надо подготовить
файл с расширением "*.http", вот так:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; request.http &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   (% http-request +-
       {integer} id
       {string} action
       {string : html} headline text
       {file : WS_FSIZE} attachment
   
       {string : header} session
       {string : header} language
   )</code>
</p>
<empty-line/>
<p>
И компилятор отобразит его на функцию с таким же названием,
которая и вызывается во время получения запроса.
</p>
<empty-line/>
<p>
Что же касается функции, выполняющей действия в ответ на
запросы - она выглядит как переключатель:
</p>
<empty-line/>
<p>
<code>
   (@ process state request @store +-
       ('// &gt;
           [= 'add'] request.action /
               ...
       &gt;
           [= 'edit'] request.action /
               ...
       &gt;
           [= 'delete'] request.action /
               ...
       &gt;
           [= 'execute-add'] request.action /
               ...
       &gt;
           [= 'execute-edit'] request.action /
               ...
       &gt;
           [= 'execute-delete'] request.action /
               ...
       &gt;
           (exit ERR_PARAMETER)
       )
   +
     {state} _
     {http-request} request
   )</code>
</p>
<empty-line/>
<p>
Часто переключатель не вызывает, а возвращает функцию,
которая должна быть вызвана - в том числе и с частично
заданными параметрами. Но в данном случае это не важно.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Сеансы пользователей</p></title>
<p>
Для того, чтобы на стороне сервера и на стороне
пользователя появился "id" сеанса (то есть для
того, чтобы сервер мог различать пользователей),
в заголовке ответа от сервера должна появиться
строка "Set-Cookie", вот так:
</p>
<empty-line/>
<p>
<code>
   Set-Cookie: session="JYtQx7Kd0Q2vebf9Ai89OFmmI5NeRLHDF0kv5BZMQRitTaTYUrQGyvbwwFa84eAF"; domain="www.example.net"; path=/</code>
</p>
<empty-line/>
<p>
И чтобы её напечатать, надо создать файл с
расширением "*.tpl", вот так:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; user-session.tpl &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   Set-Cookie: session="(/ {string} session /)"; domain="(/ {string} domain-name /)"; path=/</code>
</p>
<empty-line/>
<p>
И вызвать функцию, которую компилятор создаст
из этого файла:
</p>
<empty-line/>
<p>
<code>
   (# user-session (%
                       session-string .session
                       WS_DOMAIN_NAME .domain-name
                   ) ()
                   (@ text +-
                       (say text)
                   )
   )</code>
</p>
<empty-line/>
<p>
Она и напечатает заголовок. Теперь если в файл
с расширением "*.http" добавить поле, где должна
будет появиться строка, содержащая "id" сеанса:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; request.http &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   (% http-request +-
       ...
   
       {string : header} session                                                  ** "JYtQx7Kd0Q2v..."
       {string : header} language
   )</code>
</p>
<empty-line/>
<p>
То именно эта строка и будет выбрана из заголовка
запроса, который присылает пользователь. Так что в
ответе на следующий запрос её можно будет напечатать
повторно.
</p>
<empty-line/>
<p>
Для того, чтобы хранить "id" сеансов на стороне
сервера, в базе данных создаётся таблица с полями
"user_id" и "hash_string", вот так:
</p>
<empty-line/>
<p>
<code>
   DROP TABLE online;
   
   CREATE TABLE online (
       user_id int(10) NOT NULL,
       online int(1) NOT NULL,
       hash_string varchar(255) NOT NULL,
       latest_login datetime NOT NULL
   
       KEY (user_id),
       KEY (hash_string),
       KEY (latest_login)
   );</code>
</p>
<empty-line/>
<p>
И теперь по строке "hash_string" из неё можно будет
получить "user_id":
</p>
<empty-line/>
<p>
<code>
   SELECT
       user_id
   FROM
       online
   WHERE
       hash_string = (/ {string} session /)
   AND
       latest_login &gt; DATE_SUB(NOW(), INTERVAL 1 MONTH)
   LIMIT 1</code>
</p>
<empty-line/>
<p>
Запоминать время последнего посещения можно так:
</p>
<empty-line/>
<p>
<code>
   UPDATE
       online
   SET
       latest_login = NOW()
   WHERE
       hash_string = (/ {string} session /)
   LIMIT 1</code>
</p>
<empty-line/>
<p>
Добавить новую строку - вот так:
</p>
<empty-line/>
<p>
<code>
   INSERT INTO online
   (
       user_id,
       online,
       hash_string,
       latest_login
   )
   VALUES
   (
       (/ {integer} user-id /),
       1,
       (/ {string} session /),
       NOW()
   )</code>
</p>
<empty-line/>
<p>
Удалить пользователя - вот так:
</p>
<empty-line/>
<p>
<code>
   DELETE FROM
       online
   WHERE
       hash_string = (/ {string} session /)
   LIMIT 1</code>
</p>
<empty-line/>
<p>
И почистить таблицу - вот так:
</p>
<empty-line/>
<p>
<code>
   DELETE FROM
       online
   WHERE
       latest_login &lt;= DATE_SUB(NOW(), INTERVAL 1 MONTH)</code>
</p>
<empty-line/>
<p>
Когда-то, много лет назад, когда SQL-серверы ещё были
мало распространены, вместо них "id" сеансов хранили в
файловой системе. Последнее действие, то есть чистка её
от старых сеансов, занимало довольно много времени - но
SQL-сервер, поддерживая индексы в таблицах, выполняет
его гораздо быстрее.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Шифрование паролей</p></title>
<p>
О том, как на стороне сервера выполнять шифрование
паролей написано много - но почему-то до сих пор
они нередко хранятся в таблицах в открытом виде.
Делать это ни в коем случае нельзя, и для того,
чтобы запоминать пароли пользователей и проверять
их, надо воспользоваться функцией шифрования - и в
языке Veronica она тоже есть:
</p>
<empty-line/>
<p>
<code>
   (# crypt string salt i .hash-string)</code>
</p>
<empty-line/>
<p>
Здесь функция "crypt()" добавляет к строке в переменной
"string" случайную строку длиной не менее 16 байтов (как
правило, это данные от датчика случайных чисел), а затем
рассчитает по ним контрольную сумму, повторив эти действия
несколько раз - вот так:
</p>
<empty-line/>
<p>
<code>
   к_сумма = вычислить_хеш( строка '' соль )
   
   do i = 1 to 2^16
   {
     к_сумма = вычислить_хеш( к_сумма '' строка '' соль '' i )
   }</code>
</p>
<empty-line/>
<p>
То, что получится в итоге, выглядит так:
</p>
<empty-line/>
<p>
<code>
   62A50E334BFE71C0-65536-1F093D805A...
   
   &#9492;&#9472;&#9472;&#9472;&#9472; salt &#9472;&#9472;&#9472;&#9472;&#9496; &#9492; i &#9496; &#9492;&#9472;&#9472;&#9472; hash &#9472;&#9472;&#9472;&#9496;</code>
</p>
<empty-line/>
<p>
А получится строка со случайной составляющей ("salt"),
контрольной суммой ("hash") и количеством повторений
для её получения. Она и запоминается в базу данных,
чтобы в дальнейшем сравнивать её с такой же строкой,
полученной от пользователя:
</p>
<empty-line/>
<p>
<code>
   ([= hash] h -
       (# crypt string salt i .h -
           (parse existing-hash salt '-' i '-' hash)
       )
   &gt;
       ...
   &gt;
       ...
   )</code>
</p>
<empty-line/>
<p>
Здесь пароль, набранный пользователем, передаётся
функции "crypt()", чтобы она рассчитала по этой
строке контрольную сумму. Если это значение ("h")
и ранее рассчитанное значение ("hash") из таблицы
с именами и логинами пользователей совпадают - в
таком случае считается, что пароль был набран
правильно. Сам же пароль остаётся у пользователя,
и на сервере он нигде не хранится.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Печать страниц</p></title>
<p>
Для того, чтобы печатать Web-страницы, создаются
файлы с расширением "*.tpl":
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; web-page.tpl &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   &lt;html&gt;
   &lt;head&gt;
   &lt;title&gt;(/ {string} title /)&lt;/title&gt;
   &lt;/head&gt;
   &lt;body&gt;
   
   ...
   
   &lt;/body&gt;
   &lt;/html&gt;</code>
</p>
<empty-line/>
<p>
И компилятор отображает их на функции с такими же
именами:
</p>
<empty-line/>
<p>
<code>
   (# web-page (%
                   WS_TITLE .title
                   records  .report
               )
               (@ const +-
                   (`const`settings.language-code)
               )
               (@ text +-
                   (say text)
               )
   )</code>
</p>
<empty-line/>
<p>
Внутри файлов могут быть операторы условия и циклы:
</p>
<empty-line/>
<p>
<code>
   (/ (.* 1..report._ i &gt;                                    /)
   (/     ([report.i.title] &gt;                                /)
   (/         (# table-row (% report.i.title .text i .cntr)) /)
   (/     &gt;                                                  /)
   (/         (# table-row (% '&#038;nbsp;'       .text i .cntr)) /)
   (/     )                                                  /)
   (/ )                                                      /)</code>
</p>
<empty-line/>
<p>
А также команды для подключения других файлов:
</p>
<empty-line/>
<p>
<code>
   (/         (# table-row (% '&#038;nbsp;'       .text i .cntr)) /)</code>
</p>
<empty-line/>
<p>
Теперь внешний вид в файлах "*.tpl" будет отделён
от логики программы, что позволяет передать часть
работы верстальщику, чтобы он работал над внешним
видом страниц - точно так же, как и с запросами к
базам данных (при этом надо не забывать о том, что
в текстовых файлах действуют правила о том, где и
как располагать операторы условий и циклов, а также
вызовы других функций - чтобы их было легко читать
и исправлять). Делать всё это в одиночку - не надо.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Отправка файлов</p></title>
<p>
Для того, чтобы отправить файл пользователю, надо
указать в HTTP-заголовке имя и длину файла - вот
так:
</p>
<empty-line/>
<p>
<code>
   &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;
   &#9474; file-name.tpl &#9474;
   &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;
   
   Content-Length: (/ {integer} file-length /)
   Content-Disposition: (/ {string} file-name /)</code>
</p>
<empty-line/>
<p>
И вызвать функцию, которая будет создана компилятором
на основе описания в файле "*.tpl":
</p>
<empty-line/>
<p>
<code>
   (# file-name (%
                    name   .file-name
                    length .file-length
                ) ()
                (@ text +-
                    (say text)
                )
   )</code>
</p>
<empty-line/>
<p>
Здесь команда "say" печатает ещё один символ перехода
на новую строку, чтобы показать, что HTTP-заголовок
закончился, и дальше идёт содержимое файла - которое
тоже может быть напечатано с помощью той же команды.
</p>
</section><!-- 2 -->
<section><!-- 2 -->
<title><p>Теги "на месте"</p></title>
<p>
Иногда XML/HTML-теги удобно печатать прямо в тексте
программы, создав для этого функцию с прямоугольными
скобками:
</p>
<empty-line/>
<p>
<code>
   (@ user-session session-string .header +-
       _tpl[
               Set-Cookie: session="(/ {string} session /)"; domain="(/ {string} domain-name /)"; path=/
           ]
           (%
               `WS_DOMAIN_NAME .domain-name
               session-string  .session
           )
           (@ x +-
               (x .header)
           )
   )</code>
</p>
<empty-line/>
<p>
Или так:
</p>
<empty-line/>
<p>
<code>
   (@ rss-line price-list .rss +-
       _tpl[
               &lt;?xml version="1.0" encoding="windows-1251" ?&gt;
               &lt;rss version="2.0"&gt;
   
               &lt;channel&gt;
               ...
               &lt;/channel&gt;
   
               &lt;/rss&gt;
           ]
           (%
               `RSS_TITLE       .title
               `RSS_DESCRIPTION .description
               price-list       .items
           )
           (@ x +-
               (x .rss)
           )
   )</code>
</p>
<empty-line/>
<p>
На скорость печати текста это никак не влияет.
</p>
</section><!-- 2 -->
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>&nbsp;</p></title>
<p>&nbsp;</p>
</section><!-- 1 -->
<section><!-- 1 -->
<title><p>Последняя страница</p></title>
<empty-line/>
<image xlink:href="#WebDevHeroes@LastPage"/>
<empty-line/>
</section><!-- 1 -->

<!-- -#- -->
</body>
<binary content-type="image/png" id="BookCover">[encoded data, 121304 characters]</binary>
<binary content-type="image/png" id="Veronica@About">[encoded data, 121304 characters]</binary>
<binary content-type="image/png" id="Relations-1@About">iVBORw0KGgoAAAANSUhEUgAAAX4AAABoCAAAAADTScQSAAAACXBIWXMAAAABAAAAAQBPJcTWAAACiklEQVR42u2d4U3DMBCFY5Q9yjRswAbpFmzCJGzTTHJUgJAaUGr3zj6f870/CAm9ul+dZzvoNWmSCbnpCQTgBz8CP/gR+A+j+edn2v+zot2ppVd92y7wb0afpOSd75PQedW3JXzIfgT+I+JfDe3WOqNcB8b/fLZ7d5Ze9W39lSa57hleXs/72wrJtrPzqm/bA/5RNp4h8T+JTMtF7o1dMmXpVWgb9dR7OdnZWXrVt+0gfCTnUi67slOdlEgjhg97b/CDHzktvf/uB1U3EC296tv2sPQGWsHSQFtQwgf84EfgBz8CP/gR+MGPqmoe5fgeGD8NF8IH/Aj84EfgBz8CP/gR+MGPwA9+ZKU5+htQt31d68Lh8eu7HJ51YcKH7Cf7h0nT+LN/06va/JqTplqDPK3uBocOH3Xb178u/LXQ36726g5V5RLWr4G67etfF46Iv+nGUxqGT4jqvl2J2L8ufDv70/J2Uk3e9TS1a4CqX6zpaHfwm12N6s/v8ZWg06gsefGctaizNI2Nf7PxXC4fiypNr5/fO6epByePOgwJH9XoteNpv5jFvkVijN9vPvGVGgj8sTTfT8Pyf/gM+OUL7fCLNkq5oU/4gB+BH/wI/HE2np0rgd9VfJkYMpz9HEt9w4dzKuEDfgR+8CPwc+qNfwaWdiaHxG9TzfLqdxE+ZD/ZbxmEdLtU2R+6KUv4tNLajckh8Zs043zrdXS7Mkyk0ewP0ZQtKhPcfxykbyPhpvj83TXbb0VnlNUUBuUP0lS+WNMR/9GmWheqKWtSJvCt15VX6zpLUhNyyTX7R2rKRj52qZ+LO+yDdWuGj/GVGKvb5XqbJPwNZ+nG5GinXu75IPvwodvlufSidvoEpV6r8gNedAwAAAAASUVORK5CYII=</binary>
<binary content-type="image/png" id="Relations-2@About">iVBORw0KGgoAAAANSUhEUgAAAX4AAABrBAMAAACCmPRTAAAAFVBMVEX///8AAAD0AADg4OBkZGRGRkabm5s5VxVRAAAACXBIWXMAAAABAAAAAQBPJcTWAAACCElEQVR42u3aQW7DIBAFUHAugNVkjzgBd8gduq96/ztUpWmb2AwEYzBf+VbVzQTrCQ8YBisFfpmmd9f000+/nltdnfzt7k8//fTTT/8L+5fvx/tfiKFFwBzpf1xM6MgKZh0y0R/QT38jvzzIMmNQDge/l5Fi6FTq98sGWtgqmJJw8J+92FYM6Wuh/+yb+efLh9RWDOn5vcw/Xz6b+B+SzURyTQyV+CMNdvKrHvmjsvnj036f8rcfv3l/og9TYZ19dpXzZ2SFIe3pEzkshxv771ZL6fGbmEOeCHfwt8mffv7M+2vj+M2/u+vWz7FS2Z7zZ2Wlr6wR/fTXNGqwfu7q71EpgPOLDwTEb7o/NPrpp5/+f3/F8eUQfqNwL/rpp59++umnn376If1iMVYPt2Je+98SxfBc3W6I/csV3J84DEXwpw6jIfxi/ngIv3yYmzk3G8S/5snnrlj9nzl3HT7/MfJHnn/GH7+5j2FG9yuV/hgGwX9S2H6F74ddP6+fAfRW5tX8k0X1T87Z4J+chfRb5W7/XTei+/3by+/CE7BH+gu+ABvSv3Hamew3G9jvftIfuv+h/X/Jg+t34Z7Hzp/b/ZN1YSAc+/4ydf3R9dqx/w+50P1i/gDvXwz4/pF++uv8cv3HQ/iR67fBL9Zvx6+f36Z+2POLgILOHwU+fhX4/Ek//ftMooD1/y8D71O5t4MOCAAAAABJRU5ErkJggg==</binary>
<binary content-type="image/jpeg" id="WebDevHeroes@LastPage">[encoded data, 87064 characters]</binary>
</FictionBook>