Uirh : другие произведения.

Еще одно такое-же типа дополнение к 4й главе

Самиздат: [Регистрация] [Найти] [Рейтинги] [Обсуждения] [Новинки] [Обзоры] [Помощь|Техвопросы]
Ссылки:
Школа кожевенного мастерства: сумки, ремни своими руками
 Ваша оценка:
  • Аннотация:
    а на само деле результат очередной ревизии кода (самый конец до- и пере-писывается, а остальное - уже вряд ли)

    Краткое, но почти полное описание Фокала (можно сказать - "ликбез").
        Сначала "базового", потом - текущая версия "вторая базовая".
             (ред 1.07 от 1.4.24) (ревизия.3 от 9.4.24 )

   А то книжка моя "Фокал снаружи и изнутри" это конечно хорошо, но уж слишком
много текста. Да и писана чтобы понять что и как нужно БУДЕТ сделать, и почему.
А надо - что уже сделано, типа руководство и справочник. Да, есть и такое:
результат ревизии. Но что-то мне не нравится. Как-то там всё не так...
   Поэтому пишем заново.


         ================  *  *  *  ===============

     Итак: Фокал - интерпретируемый операторный язык еще первого поколения,
заточенный под режим диалога и ориентированный на экономию всех видов ресурсов
(как ЭВМ, так и человека), поэтому, в частности, максимально лаконичный.
(И, кстати, "строчно-ориентированный".)

   (Все лишние рассуждения, например про поколения языков программирования и
"операторность" - удалил к коврюжьей матери! Но таки надо указать на
"калькуляторную" сущность данного языка, само название которого: КАЛькулятор
ФОрмул в пику ФорТРАНу, который - транслятор. И что Фокал обычно сравнивают с
Бейсиком: да, действительно во многом похожие языки, но Фокал компактнее,
лаконичнее, гораздо лучше продуман и вообще предназначен совсем для другого.)

   "ДИАЛОГ" заключается в том, что человек сидит за терминалом (каковым может
быть даже телетайп середины прошлого века) и вводит командные строки: Фокал
выдаёт "приглашение", в качестве которого обычно символ * (звёздочка); человек
набирает командную строку и нажимает "ввод" (ВК, Enter); Фокал берёт эту
строку и либо сразу выполняет, либо, если в начале строки есть номер, помещает
её под этим номером в память, чтобы выполнить потом. В процессе выполнения
делает то, что предписывают составляющие эту командную строку "операторы".
После чего выдаёт следующее "приглашение"...
   "ОПЕРАТОР" предписывает одно элементарное (для данного языка) законченное
действие. Чаще всего: вычислить выражение и как-то применить его результат.
Оператор Фокала обязательно начинается с ключевого слова, указывающего что
надо сделать, и может содержать что-то еще: второе ключевое слово, выражение,
или даже несколько, тогда они разделяются запятыми, или больше ничего. Сами
операторы, если в строке их несколько, разделяются символами ";". Ключевые
слова подобраны на разные буквы алфавита и могут сокращаться до одной первой
буквы. (Остальные всё равно игнорируются.)
    Примеры операторов Фокала и для сравнения аналогичных операторов Бейсика:
 Type  (2+2)*2       - напечатать результат                 PRINT (2+2)*2
 Set X=(2+2)*2       - сохранить в переменной X             LET X=(2+2)*2
 Ask X               - ввести в X число с терминала         INPUT X
 Coment любой текст  - комментарий до конца строки          REM комментарий

   Фокал, как и современный ему Бейсик, работал с одним единственным типом
данных - числами с плавающей запятой. Операций с ними - пять: + - * / ^.
(Последняя - возведение в степень.) Все скобки ([{<>}]) эквивалентны. Все
кавычки " ' ` - тоже. В кавычках - текстовые константы. Они используются в
операторах ввода и вывода Ask и Type для выдачи пояснений ко вводимым и
выводимым числам. Если там нужна кавычка - заключаем текст в кавычки другого
типа.
   Обратим внимание: в операторах ввода/вывода Ask и Type допустим, не один, а
произвольное количество элементов списка ввода/вывода. Кроме текстовых констант
в виде символов в кавычках, а так же ! (восклицательного знака) предписывающего
переход на следующую строку, он включает для Ask - переменные, куда помещать
вводимые числа, а для Type - выражения, значения которых вывести. А так же
таинственную конструкцию "формат", указывающую как их выводить (см. далее).

   ПЕРЕМЕННЫЕ - только глобальные. И тоже, как и в Бейсике, не требуют
предварительного объявления, а создаются по мере надобности - в момент
первого присваивания. То же самое относится и к элементам массивов, которые
на самом деле - переменные с индексами: каждая из них существует индивидуально.
(Тогда как в Бейсике массив таки надо предварительно объявить оператором DIM.)
Это несколько расточительно, зато удобно. И сама собой исчезает проблема
начального индекса: с единицы, как в Бейсике, нумеруются элементы массива, или
с нуля как в Си? Но в качестве побочного эффекта - возможность обратиться к
одной и той же переменной с разным количеством индексов, да еще и зависящая от
реализации. Впрочем, обычно гарантируется, что нулевое значение индекса
эквивалентно его отсутствию: и Х(0,0) и Х(0) это то же самое, что просто Х.
   Надеюсь, уже не надо объяснять, что "переменная" это такая штучка, в которой
можно хранить одно число?...

   ИМЕНА ПЕРЕМЕННЫХ распознаются по двум первым буквам. Для калькулятора
этого более чем достаточно. Остальные если есть - игнорируются. Как и в других
языках, имена могут состоять из букв и цифр и начинаться должны обязательно с
буквы. Но буквой в Фокале считается всё, что не цифра и не разделитель. Т.е.
это не только русские буквы, но и всякие значки типа @ # $ & \ | _.
   В отличии от переменных, имена всех встроенных функций начинаются на букву
Ф (а переменные на букву Ф называть нельзя, увы) и распознаются по первым
уникальным буквам. При чём, чтобы не гадать как правильно: FRAnd или FRNd,
реализованы все альтернативные варианты. Но именованные функции - только
встроенные. Возможности назначить имена подпрограммам не предусматривается.
   Однако, предусматривается возможность подгрузки недостающих библиотечных
функций: оператором Load (до сих пор не реализованная).
   Набор встроенных функций, как и у Бейсика, включает математические (корень,
логарифм, экспонента, прямые и обратные тригонометрические), генератор
случайных чисел, абсолютное значение числа а так же средства получения его
целой и дробной части. Но вместо SLEEP тупо приостанавливающей выполнение
программы - FCLk сообщающая длительность временного интервала от некоторого
момента, ею же в прошлый раз и установленного. В дополнение к работающим с
числами Ask и Type, функция FCHr для побайтного ввода/вывода. И функция FX для
управления оборудованием, в том числе нестандартным.
   Особняком стоит функция FSUbr предназначенная для обращения к подпрограмме
как к функции -  из середины выражения. С передачей параметра и возвратом
результата. (Можно даже сказать, что это функциональный аналог оператора Do.)
Параметр попадает в спецпеременную с именем &, а в качестве результата
возвращается значение последнего вычисленного в подпрограмме выражения. (Не
важно в каком операторе.)
   В Бейсике, кстати, было сделано аналогично. Там это называлось "функция
определяемая пользователем". Но там она была одна единственная и требовала
предварительного объявления. (Что, кстати, вместе с операторами DATA/READ,
сохранившимися и поныне, указывает на то, что это сляпанное на скорую руку
подмножество всё того же Фортрана было таки изначально ориентировано на
компиляцию, причем с перфокарт.)

   ПРОГРАММНЫЕ СТРОКИ в Фокале, как и в Бейсике - нумеруются. Номера строк
служат как метками для передачи управления, так и средством их упорядочивания:
хранятся и выполняются они в порядке нумерации, независимо от того, в каком
порядке введены. Новая строка с тем же самым номером замещает уже существующую.
Строка без номера называется "прямой" (или "нулевой") и выполняется сразу.
(А нумерованные - соответственно "косвенными".)
   Но в отличии от Бейсика, номера строк не целые числа, а дробные.
В результате программа распадается на структурные единицы - "группы" строк.
Так, что целое число обозначает всю такую группу, а дробное - конкретную
строку в ней. А когда нужна вся программа (например в операторе Write, Modify,
или Eraze (см. далее)) - пишется ключевое слово Ales, что значит "всё".
   Каждая такая группа это "естественная подпрограмма", где сам бог велел
разместить одно законченное сложное действие. При передаче управления такой
структурной единице оператором обращения к подпрограмме Do или функцией FSBr,
возврат автоматически происходит по достижении её конца. При чем даже в том
случае, если там нет оператора возврата Ret.
   Каждая отдельная строка - это тоже естественная подпрограмма! Что удобно
например для отладки: можно запускать строки по одной и смотреть что
получается... Но запустить подпрограмму с "дополнительной точки входа" (т.е.
не с начала группы) - невозможно.
   Как правило может быть до 99 групп и до 99 строк в каждой группе. Но на
самом деле - на сколько памяти хватит. Однако, обратим внимание: номер группы
и строки в ней это не два целых числа, а одно дробное. Так что строка с
номером 1.1 это то же самое, что 1.10, а вовсе не 1.01!

   Операторы УПРАВЛЕНИЯ порядком действий:
    Go  N_строки            - безусловный переход к строке с указанным номером
    Do  N_строки            - переход к подпрограмме
    Ret                     - возврат из подпрограммы
    If (Усл) N1,N2,N3       - условный переход
    For i=Нач,Кон,Шаг; ...  - цикл со счетчиком
    Quit                    - стоп  (и в "прямой" строке - выход из Фокала)
  Условный оператор сравнивает значение "Усл" с нулём - поэтому в нём три
адреса: для случаев когда оно меньше нуля, равно и больше. Последние из них,
впрочем, могут быть опущены и тогда в соответствующих случаях выполняется
остаток строки. Обратим внимание: условие обязательно в скобках.
   В операторе For циклически выполняется остаток строки после оператора. И он
тоже - "естественная подпрограмма": куда бы мы оттуда ни передали управление,
оно всё равно вернется заголовку цикла. Но досрочно выйти из цикла (как в
других языках - просто передачей управления за его пределы) - невозможно. Для
этого придётся установить значение параметра цикла больше конечного.
(Разумеется, в качестве параметра цикла можно использовать любую переменную,
включая элементы массива.) Выражения в заголовке цикла вычисляются только один
раз - перед первой итерацией. И последние из них, так же как и в операторе If,
могут быть опущены. Тогда шаг будет +1. А без конечного значения оператор
эквивалентен присваиванию.
   Обратим внимание: оператор цикла (в отличии от аналогичного оператора
Бейсика) можно использовать и в ненумерованной, "прямой" строке.

   Необходимо отметить, что в Фокале выражение произвольного вида может быть
везде, где по смыслу требуется число. В том числе и в качестве адреса в
операторах перехода - будет "вычисляемый переход", аналог оператора switch
языка Си. И даже когда ввода ожидает оператор Ask. (Который фактически левая
часть оператора присваивания. А правую вводит сидящий за терминалом...)
   Константа - только в конструкции "ФОРМАТ" оператора Type: два целых числа
через точку после символа % (процент) - указывающих сколько всего позиций под
число и какова точность. (Одиночный % восстанавливает формат "по-умолчанию".)
   Например: известно, что facos(-1) это число Пи. С разными форматами:
                номера позиций: 123456789_123456789_123456
       T %5.5   facos(-1),-2 -> 3.1416    -2
       T %10.5  facos(-1),-2 ->     3.1416         -2
       T %5.10  facos(-1),-2 -> 3.141592654    -2
       T %1.10  facos(-1),-2 -> 3.141592654 -2
       T %1.40  facos(-1),-2 -> 3.14159265358979312 -2
       T %      facos(-1),-2 -> 3.14159 -2
   Таким образом, рассматриваемая версия (использующая плавающее число
"двойной точности") даёт максимум 18 значащих цифр и действительно выделяет
под число поле указанной ширины. Но если число занимает больше позиций - без
зазрения совести вылезает за пределы этого поля. Да еще и добавляет после числа
пробел (чтобы идущие подряд числа не сливались), от которого решительно
невозможно (было) избавиться! Но сейчас - можно: он подавляется при наличии
текстовой константы. В том числе и пустой:
       T facos(-1),-2        -> 3.14159 -2
       T facos(-1),"",-2     -> 3.14159-2
   И наконец по желанию па-ачтеннейшей публики... (Ну да, забыл я про это.)
       T %010.5 facos(-1),-1 -> 00003.1416 -000000001

   "ОБСЛУЖИВАНИЕ" ПРОГРАММЫ, в отличии от Бейсика, производится тоже
операторами языка:
      Write  n     - вывести на терминал строку или группу строк номер n
      Eraze  n     - стереть   --//--
      Modify n     - редактировать строку n (а не вводить целиком заново)
      Write        - вывести всю программу
      Write  Ales  - --//-- плюс заголовок-комментарий
      Eraze  Ales  - стереть всё - привести интерпретатор в исходное состояние
      Eraze        - стереть только переменные (программу - нет)
   Запустить выполнение находящихся в памяти строк - любым оператором передачи
управления: Go, Do и даже If. Но если с самого начала и всю программу - то Go
или Do с ключевым словом Ales, или вообще без аргументов.
   Распечатать на бумаге или сохранить программу на внешнем носителе - тоже
оператором Write. Но для этого сначала надо переключить (оператором Operate)
канал вывода на одно из подключенных к машине устройств (например на принтер:
O L), а потом обратно на терминал:   O L; W A; O T.
   Загрузить ранее сохраненную программу еще проще: достаточно переключить на
неё канал ввода, и интерпретатор прочитает её оттуда сам. Но когда она там
кончится, произойдёт ошибка "конец носителя". А по любой ошибке каналы ввода
и вывода автоматически устанавливаются обратно на терминал и его клавиатуру.
(Но вполне можно было добавить в конце какую ни будь прямую команду: хоть
переключающую канал ввода обратно на клавиатуру принудительно, хоть сразу же
запускающую только что загруженную программу:  O P;W A;T "O K"!;O T)
   В оные времена внешних устройств у машины было немного и они указывались в
операторе Operate вторым ключевым словом: Lpt - принтер, Prf - перфоратор,
Rd - перфосчитыватель, Tty - терминал, Kbd - его клавиатура. А сейчас второе
ключевое слово превратилось в "псевдоним" открытого файла. Впрочем L всё так
же обозначает превентивно открытый в ДОС`е файл стандартного принтера, а T и K
терминал и клавиатуру.

    ЗА КАДРОМ ОСТАЛСЯ оператор Xecut (надо бы execut - "выполнить", да буква Е
уже занята), просто вычисляющий выражение и игнорирующий его результат. Он
нужен для вызовов функций с "побочным эффектом" (например FCHr(x) выдающую
байт с кодом х в канал вывода) когда нужен этот эффект, а возвращаемое
функцией значение - нет. Оператор Kill сбрасывающий все внешние устройства в
исходное состояние. (Сейчас после этого пришлось бы перезагружать компьютер.)
И оператор "?" (вопросительный знак), который будучи поставлен перед любым
другим оператором, включает (или выключает) режим "трассировки" - когда текст
выполняющегося оператора сразу же выдаётся на терминал. Это для того чтобы
отследить по какому пути пошло выполнение программы.
   Еще, не знаю как в других реализациях (не проверял), но у моего шефа был
отладочный режим (включаемый '??') когда перед выполнением каждого очередного
оператора, интерпретатор ждал нажатия клавиши - команды: выполнить, продолжить
без отладки или остановить. Был так же оператор Break для организации реакции
на одно внешнее событие. И несколько противоречащий духу Фокала оператор П,
выделяющий в памяти место под массивы, в т.ч. большого размера (так память
используется куда эффективней: не надо хранить имена и индексы для каждого
элемента).

    И ЕЩЕ ПАРА НЮАНСОВ:
 - что можно было как-то распечатать не только все хранящиеся в памяти строки,
но и значения сразу всех переменных. (Вроде бы оператором Type с ключевым
словом $.) Сейчас это поручено оператору Write: ему же полагается выдавать
содержимое памяти интерпретатора в форме пригодной для обратного ввода - вот
пусть и сообщает. С ключевым словом Set результаты деятельности этого
оператора по созданию переменных, с ключевым словом Ope - результат
деятельности оператора Operate... и.т.п.
 - что если оператор Ask ждёт ввода, а ему вместо числа ввести символ @ то
содержимое переменной, куда идёт ввод - не изменяется. Сейчас для этого еще
можно нажать ESC. (Но это только на клавиатуре, а @ - откуда угодно.)
 - что чтобы принудительно остановить работающую программу нужно организовать
какую ни будь ошибку.
   Например, если программа к клавиатуре не обращается - два раза нажать на
ней любую кнопку. В результате переполнится буфер клавиатуры. (Который всего
на один символ.) Если-же ввода ожидает оператор Ask - ввести ему некорректное
выражение, злонамеренно допустив в нём любую ошибку: хоть дисбаланс скобок,
хоть несуществующую переменную. А вот если программа сама забирает содержимое
буфера клавиатуры (с помощью FCHR(-1)) то тут ничего не сделаешь: она всяко
делает это быстрее - средства останова должны быть предусмотрены в самой
программе. Впрочем сейчас в этой ситуации можно нажать комбинацию кнопок
Ctrl/C (Ц-латинская), которую отслеживает сам драйвер клавиатуры. (Однако,
сейчас программа может предусмотреть перехват и такой ситуации...)

         ================  *  *  *  ===============

    Это было то, что я называю "базовым" Фокалом.

    Реализованная мною версия Фокал-1А (точнее её сокращенный вариант 1Б.6,
о котором собственно и идёт речь) работающая на ПЭВМ IBM-PC под ДОС`ом, это
не то что бы расширение базового, а скорее результат археологических раскопок:
"базовый" это реализация фокаловских принципов по-минимуму, а у меня - как бы
оно должно быть, если по-хорошему. Попытка сделать "то что можно так как нужно"
ничего нового туда не добавляя. Все добавления и расширения (включая
полиморфизм, псевдонимы и другие прелести) отложены до "третьего" Фокала.
(А то "второй" уже был, и по-мне, так получился весьма не очень.) Так что пока
морально готовимся к этому "третьему" и шлифуем "первый"...
    Фокал-1А, в точности также как и базовый (который просто Фокал-1,
существовавший, впрочем, в виде множества по-разному называвшихся версий),
работает с одним единственным типом данных - числами с плавающей запятой, и
сохраняет все вышеперечисленные ограничения. Но (кроме дополнительных удобств,
типа редактора командной строки) отличается тем, что в ней с одной стороны не
реализованы операторы Kill и Load, а с другой - дополнительно реализованы:
 - средства доступа к файловой системе
 - набор "красивостей" (операторы ! Help; ф-ии FКурсор FЦВет FМышь FBip FTMp)
 - настоящие локальные переменные (для передачи функции нескольких параметров)
 - несколько "улучшенные" операторы For If Go Do Ret Eraze Write Ask Modify
 - реакция на (внешние) события и (внутренние) ошибки - оператор Break
 - и средства вызывать их искусственно - операторы Kill n; Quit n;
 - механизм табулостопов
 - и наконец кое что для работы со строками (в т.ч. Write %)

    В именах переменных различаются все буквы - и заглавные, и строчные, и
русские, и латинские. А в ключевых словах и именах встроенных функций - нет:
там они эквивалентны. В номерах групп и строк можно использовать не по две,
а до четырех цифр. Текстовая константа выдаётся оператором Type куда угодно,
а оператором Ask - только на терминал. И только в том случае, если ввод - с
клавиатуры, иначе игнорируется. Наличие текстовой константы (в т.ч. и пустой)
подавляет вывод оператором Ask приглашения ":", которое выдаётся тоже только
на терминал. А в операторе Type текстовая константа подавляет вывод пробела,
разделяющего числа, чтобы не сливались.
   Ну и комментарий теперь - не только с ключевого слова Coment (фактически с
буквы Ц), но и с не задействованных в языке спецсимволов # $ & : @ \ | ~
которые, впрочем, все тоже считаются буквами. (Буква вообще всё, что не цифра и
не разделитель.) В UNIX`е первая строчка разрешенного на выполнение текстового
файла должна начинаться с #! после которых - командная строка для sh чтобы
запустить программу-интерпретатор, которая и будет этот файл выполнять. (Если
конечно это не сам sh.) А для самого интерпретатора это должно выглядеть как
комментарий. Вот и. Ну и остальные - до кучи...

                   *  *  *

    Для ДОСТУПА К ФАЙЛОВОЙ СИСТЕМЕ модернизирован оператор Operate - чтобы он
мог открывать и закрывать файлы, а также передвигать указатель чтения/записи.
А чтобы получить его текущее положение - добавлена ф-я FTEll. Второе ключевое
слово в операторе О теперь "псевдоним" ранее открытого файла.
      О "имя_файла"  Х   - открыть файл под псевдонимом Х
      О              Х   - переключить на него канал ввода или вывода
      О (смещение)   Х   - позиционировать его указатель
      О (        )   Х   - фиктивная операция (ничего не делает)
      О "другое_имя" Х   - закрыть файл и открыть другой под тем же псевдонимом
      О      ""      Х   - только закрыть
    Как именно открыть файл и/или какой канал переключать - указывается
дополнительными буковками второго ключевого слова (которые, в отличии от
других ключевых слов, здесь не все игнорируются): R - на чтение, W - на запись,
А - на добавление (тоже на запись, но в конец файла); T - открыть файл как
текстовый (по умолчанию), B - как бинарный. Буква N при переключении канала
ввода запрещает чистить входной буфер оператора Ask. (Он же строчный
аккумулятор, он же "А2" (для краткости): см. далее.)
    Разумеется, файл можно открыть и на чтение и на запись одновременно.
И использовать и для того и для другого попеременно без особых
предосторожностей. Хотя конечно надо иметь в виду, что указатель текущего
места и для того и для другого - общий и автоматически передвигается при
каждой операции. Оператор Ask имеет тенденцию читать за раз целую строку.
А функция FCHR хотя и по одному символу, но прямо из канала ввода только если
входной буфер оператора Ask пуст (иначе оттуда)... А вот открыть можно
по-разному. Если написать: О "файл" Xwr; (т.е. в первую очередь для записи),
то файл становится пустой; а вот если О "файл" Хrw; то его содержимое
сохраняется.

    Функция FTEll сообщает информацию о том файле, который участвовал в
последнейпо времени операции ввода/вывода, пусть даже и фиктивной, как О () Х.
Прежде всего конешно положение указателя ввода/вывода.
    Но на эту функцию свалили сообщать так же размер файла, сведения о нажатости
"интересных" кнопок (для терминала) и даже искать файлы в текущем каталоге!
(В общем всё, что не знали кому поручить.)

    Псевдонимы открытых файлов могут быть на все буквы алфавита, но некоторые
уже задействованы:
 - K,  T - по прежнему клавиатура и терминал
 - I,O,E - стандартный ввод, вывод и вывод ошибок (как в UNIX`e);
 - C,  L - стандартные компорт и принтер (особенность ДОС`a)
 - A,  B - псевдонимы двух первых файлов, указанных в командной строке при
запуске Фокала: если они есть, то открываются автоматически (на чтение и
дозапись). И канал ввода устанавливается на А дабы загрузить находящуюся в
этом файле программу. Если их нет, то ищется файл "sav.f" и если найден, тоже
открывается под псевдонимом А. А при выходе из интерпретатора (если сидящий
за терминалом нажал кнопку ESC) - предлагается сохранить программу в этом
файле.
                   *  *  *

          Всякие КРАСИВОСТИ

    Оператор Help выдаёт справки по операторам и встроенным функциям из файла
"foc.hlp" (или "foc-1b.hlp"), который на настоящий момент является основной
документацией к текущей версии. Поэтому выдаваемые им тексты страдают излишней
громоздкостью и многословностью, увы. Но поэтому-же всё что можно подчерпнуть
оттуда - здесь максимально кратко.
    Фокал это два файла: собственно интерпретатор foc.exe и вот - файл справки.
Кроме того в файле _fpks.txt, если он есть в текущем каталоге, при завершении
работы интерпретатора сохраняется содержимое пула (мешка) ранее введенных
строк редактора командной строки. А при следующем запуске - загружается
обратно. (А чтобы не в текущем, или другое имя файла - нужна переменная среды
FPKS.) А из файла sav.f, если он есть в текущем каталоге, при запуске
интерпретатора грузится сохранённая в нём прошлый раз программа. Если конечно
имя файла с программой не указано в командной строке. (Первым аргументом.)
Вернее: то или другое, если есть, автоматически открывается под псевдонимом А
и на него переключается канал ввода.

    Экранный редактор командной строки (который в принципе тоже "красивость и
удобство"), кроме упомянутого пула ранее введенных строк, имеет "карман" под
фрагмент строки, и дополнительную возможность захватить туда фрагмент с
любого места экрана. Режим работы - только "вставка", режим "замены" не
реализован (как-то вот не понадобился).
    Все команды этого редактора (кроме "ввода", "забоя" и "эскейпа") это
кнопки между основной и дополнительной частями клавиатуры: стрелочки и
находящиеся над ними Ins,  Home,  PageUp,
                     Del,  End,   PageDn.  А так же их комбинации с кнопками
Sh, Alt и Cntrl.
    Редактор позволяет так же включить/выключить "отладочное окно". Это
средство по-возможности не испортить изображение на экране отладочной
информацией - вывод в некоторую ограниченную область экрана (выделенную
рамочкой), содержимое которой на это время сохраняется. Если рамка одинарная,
то перед выполнением каждого очередного оператора отладочное окно убирается с
экрана, а после выполнения - восстанавливается. С тем, чтобы сам оператор и,
возможно, трассировочная информация, были в этом окне, а результат его работы
- на основном экране. (Если рамка двойная - то не убирается.)
   Средств переместить отладочное окно или изменить его размер пока нет.
Нормально работает только в текстовом режиме, в графических - крайне медленно.
В общем полезность и целесообразность этого средства - под вопросом.

    Оператор ! выполняет команды операционной системы.

    Функция FTMp сообщает текущую дату/время в виде количества суток с начала
тысячелетия, а так же преобразует это в удобочитаемый вид и обратно.
    Функция FBip издаёт звуки с помощью встроенного в машину динамика, включая
ноты и азбуку морзе.
    Функции FЦВет, FViz и FКурсор управляют отображением информации на дисплее.
FЦВет устанавливает цвет выводимых символов (вплоть до редактирования цветовой
палитры); FViz сообщает параметры текущего режима работы, типа количества
строк на экране и символов в строке; FКурсор (она же FCS) не только
перемещает по экрану курсор, но и считывает символ в текущей позиции, а так
же позволяет немножко редактировать содержимое экранного ОЗУ. Включая
изменение цвета знакомест, прокрутку экрана и рисование рамочек символами
псевдографики.
   Функция FМышь обслуживает манипулятор мышь - включает/выключает, сообщает
перемещение и нажатость кнопочек. События по манипуляциям с мышью в некой
области экрана заказываются там же где и эта область (окно) - с помощью
функции FViz, а реакция на них устанавливается оператором Break.

   Действия, выполняемые перечисленными спецфункциями, определяются видом и
количеством их аргументов (что, впрочем, практиковалось и в базовом Фокале):
аргумент может быть не только числом (вычисляющим его выражением), но и
текстовой константой, а так же может быть пропущен - при условии сохранения
разделяющих аргументы запятых. Кроме того любым функциям причитается
произвольное количество аргументов (в т.ч. и функции FSUbr - см. далее),
лишние просто игнорируются. А если аргумент ровно один, то скобки вокруг него
могут быть опущены.

    В отличии от базового Фокала, генератор случайных чисел FRND выдаёт
значения в диапазоне от 0 до 1 (а не от -1 до +1). Функциям FLog FSQrt
(логарифм и корень) второй аргумент указывает степень корня и основание
логарифма. Функция FATan(X,Y) то же самое что FATan(X/Y).
    К стандартным математическим функциям добавлены FMIn и FMAx выдающие самый
маленький и самый большой из своих аргументов. Они призваны играть роль
логических операций И и ИЛИ - как для обычной бинарной логики, так и
трёхзначной с нейтральным значением (например нулевым), или вообще нечеткой.
    Функция FX, предназначенная для управления внешними устройствами,
обращается только к портам ввода/вывода. (Средств доступа к ОЗУ нет, увы.)
А вот без первого аргумента, указывающего операцию, выполняет побитовые
операции над двоичными числами. Чтобы, например, можно было выделить в наборе
признаков нажатых спец-кнопок клавиатуры те, которые соответствуют кнопке
левый Alt:   O () K; S a=FX(,FTEll(),2^9);
    Аналогично, функция FSbr без первого аргумента не вызывает никаких
подпрограмм, а сразу возвращает содержимое числового аккумулятора (он же
"А1") - внутренней переменной Фокала, куда попадает результат выражения,
вычисленного в очередном операторе. Но так же и другие полезные вещи...

                   *  *  *

         АККУМУЛЯТОРНОСТЬ.

    Археологические раскопки обнаружили в недрах Фокала два аккумулятора: А1 и
А2, которые были там всегда, но которых почему-то никто не замечал. А третий
(А3) сконденсировался буквально на глазах!
    Первый: (А1) "числовой" аккумулятор - вышеупомянутая внутренняя переменная,
куда попадает значение почти любого выражения - чтобы его могла вернуть
"функция определяемая пользователем" FSUBr. Волевым методом решено, что адреса
переходов в операторах Go, Do, If а так же параметры, передаваемые в
подпрограммы, туда не попадают. Но попадают дополнительные "возвращаемые
значения" операторов. (Например счетчик строк, реально скопированных
оператором W %;.)
    Второй: (А2) "строчный" аккумулятор - входной буфер оператора Ask. (Который
вишь вводит за раз целую строку, а использует её - постепенно.) Вокруг А2
постепенно сам собой нарос механизм работы с текстовыми строками. (См. далее.)
Существенно связанный с подсистемой ввода/вывода. Потому что считается что
тексты (как совокупность строк) слишком громоздки, и могут храниться только
снаружи, например в виде файлов.
    Третий аккумулятор (условно "А3") - это та внутренняя переменная, куда
попадает указатель на файл, поучаствовавшей в последней по времени операции
ввода/вывода (в т.ч. и фиктивной) чтобы функция FTEll могла сообщить про него
какую-то информацию. И он нам тоже еще пригодится...

                   *  *  *

           ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ

    В базовом Фокале, при обращении к подпрограмме с помощью спецфункции
FSBR можно передать только один параметр. Попадающий в переменную с именем &,
которая, по всей видимости, была глобальной. То есть при рекурсивном вызове
такой функции, предыдущее её значение безвозвратно терялось.
    В данной реализации разрешается передавать подпрограмме произвольное
количество параметров. В том числе и при обращении к ней оператором Do.
А также при т.н. "структурном переходе" - порождении ситуации оператором
Quit (см. далее).
    Эти (позиционные) параметры помещаются в настоящие локальные переменные,
доступные по именам & (или &0 или $ или $0), &1, &2, &3... (или $1, $2, $3...)
или как элементы одномерного массива &(...) включая и такие как &(-1), куда
обычно помещается количество переданных параметров.
    Локальные переменные создаются не только при передаче параметров, но и так
же как и глобальные - при первом присваивании. Но в отличии от них, сперва
ищутся у подпрограмм с меньшей степенью вложенности. И обращение к
несуществующей локальной переменной ошибки не вызывает.

                   *  *  *

        УСОВЕРШЕНСТВОВАНИЕ ОПЕРАТОРОВ

     В операторе For:
 - если шаг не указан, то он принимает значение +1 или -1 в зависимости от
того, больше конечное значение начального или меньше
 - цикл завершается когда параметр цикла выйдет за конечное значение более чем
на пол шага. (Чтобы правильно работал и в случае когда шаг - не целый.)
 - после окончания цикла его параметр, как и полагается, сохраняет то значение,
которые было на момент последней итерации.
 - допускается форма из одного только ключевого слова: For;. Тогда остаток
строки выполняется ровно один раз, но как подпрограмма. (Это - в основном для
ограничения области действия структурного перехода - см.далее.)
     В операторе If:
 - допускается отсутствие не только последних, но и любого из адресов перехода
(при условии сохранения разделяющих их запятых) - в соответствующем случае
выполняется остаток строки
 - допускается нулевой адрес перехода - в соответствующем случае выполнение
текущей строки просто завершается
 - допускается отрицательный адрес перехода - он эквивалентен пропуску: в
соответствующем случае выполняется остаток строки.
    Всё это - чтобы использовать оператор вообще без адресов. Например:
      If (x)  ,0;   t "икс не равно нулю"!
      If (x) 0, ,0; t "икс равно нулю "!
 - из трёх адресов перехода вычисляется только один. Остальные - пропускаются.
Потому, что это может быть выражение общего вида, в том числе дающее побочные
эффекты.
     Оператор Go
 - с нулевым адресом передаёт управление на начало текущей строки - для
организации цикла без счетчика. Например:
        ...тело цикла...;  If(Усл)0; G 0;     Циклится пока  Усл >= 0
     Оператор Do
 - кроме адреса перехода может содержать произвольное количество выражений.
Они все вычисляются и дают начальные значения локальных переменных &, &1, &2...
для вызываемой подпрограммы.
 - некоторые из этих выражений могут быть пропущены - при наличии разделяющих
их запятых
 - адрес перехода тоже может отсутствовать - при наличии отделяющей его от
следующих выражений запятой. Тогда никакая подпрограмма не вызывается, значения
попадают в локальные переменные текущей подпрограммы.
 - НО: оператор Do (или Go) состоящий из одного только ключевого слова - для
запуска всей программы - исключение. Может быть в строке только единственным
(или последним, и даже ; после него не допускается), иначе вызывает ошибку.
     Оператор Ret
 - допускает одно выражение, вычисляющее возвращаемое значение подпрограммы.
Оно попадает в числовой аккумулятор (А1) и, возможно, возвращается с помощью
функции FSUbr. Ret Х;  эквивалентно   Хecut Х; Ret;
     Функция FSUbr так же как и оператор Do
 - допускает произвольное количество аргументов, а так же пропуск любых из них.
 - первый аргумент, как и раньше - адрес для передачи управления подпрограмме,
остальные становятся начальными значениями её локальных переменных &, &1, &2...
 - при пропуске первого аргумента (как и Do) никакой подпрограммы не вызывает;
сразу возвращает текущее содержимое аккумулятора А1. Остальные аргументы, если
есть, попадают в локальные переменные текущей подпрограммы.
     Оператор Do и ф-я FSUbr передают управление точно, а Go и If - примерно.
Отсутствие строки с указанным номером в первом случае вызывает ошибку, а во
втором - передачу управления на следующую. (В пределах статуса выполнения!!!)
     Операторы Set и Хecut
 - допускают несколько выражений в операторе X и несколько присваиваний в
операторе Set - через запятую. Выпендрёж чистой воды.
     Оператор Quit
 - допускает один или несколько аргументов: первый - номер ошибки (ситуации),
остальные - параметры, которые будут переданы подпрограмме реакции на эту
ситуацию (см. ниже).
 - допускается чтобы один из этих аргументов был текстовой константой. Это
сообщение о данной ошибке сидящему за терминалом человеку. Для оператора Quit
выполняющегося в "прямой" (нулевой) строке наличие такого аргумента блокирует
завершение работы интерпретатора. Примечание: в командном файле все строки
"прямые"...

                   *  *  *

    Для операторов Write и Eraze в базовом Фокале понимавших только одно
ключевое слово Ales - "всё", добавлены дополнительные ключевые слова,
совпадающие с названиями операторов, результаты работы которых надо показать
или удалить. (Чтобы не спутать с ними выражение, начинающееся с имени
переменной, его всегда можно заключить в лишние скобки.)
  - Open  - список всех открытых файлов
  - Set   - список всех переменных
  - Do    - состояние операционного стека, включая локальные объекты
  - Break - список ловушек на события
     а так же
  - Reg   - содержимое регистров форматного механизма (см. далее)
  - Tab   - таблица табулостопов (в виде спецкомментария - см. далее.)
  - Pul или Мешок - Write ничего не сообщает, а только помещает содержимое
строчного аккумулятора в пул (мешок) ранее введенных строк редактора командной
строки; Eraze делает его пустым.
    Но "Set", "Do" и "Open"  - только для Write, потому что переменные оператор
Eraze и раньше стирал без всяких ключевых слов, а содержимое стека возвратов
стереть нельзя впринципе. Файлов Eraze тоже закрывать не уполномочен...
(Только когда Eraze Ales; - сразу все, но кроме тех двух, на которые
направлены каналы ввода и вывода.)
    Регистры форматного механизма E R; очищает, а вот аккумулятор А2 - нет.
(Как впрочем и А1 и А3 тоже не очищаются ничем.) Но он очищается автоматически
(если не запретить) при переключении канала ввода, и "вручную": Ask %
(см. далее).

    Операторам Write и Eraze дополнительно поручено выдавать и соответственно
стирать не все переменные какие есть, а только некоторые. (Например элементы
одного массива.) Указанные текстовой константой в качестве аргумента:
     Write "имя";  Eraze "имя";

                   *  *  *

            СИТУАЦИИ И СОБЫТИЯ

    При возникновении любой ошибки, базовый Фокал, будучи фактически
программируемым калькулятором, останавливал выполнение программы и передавал
управление человеку, сидящему за терминалом. В настоящей версии на этот
случай реализован "механизм ситуаций".
    Ситуация, возникшая в результате ошибки или искусственно созданная
оператором Quit, начинает "распространяться". И если никакой реакции на неё
небыло предусмотрено, то, как и раньше, останавливает работу программы с
выдачей сообщения об ошибке. И переключением каналов ввода и вывода на
терминал и клавиатуру. Передавая тем самым управление человеку за терминалом.
    Но любая из подпрограмм может поставить на данную ситуацию "ловушку". Если
ситуация так и не возникла, то это не окажет никакого влияния на ход выполнения
программы: когда подпрограмма завершится, ловушка будет просто удалена так же,
как и принадлежащие этой подпрограмме локальные переменные. А вот если и когда
возникшая где-то во вложенных подпрограммах ситуация дораспространяется до
этого места - ловушка сработает, будет выполнена предусмотренная в ней реакция
(в том числе "нулевая") и столь предусмотрительная подпрограмма завершится
нормально, как будто никаких ошибок небыло. Это и есть "структурный переход".
    Позволяющий, в частности, досрочно выйти из цикла. Или даже нескольких
вложенных. Например нам нужно найти в матрице a() элемент a(i,j) значение
которого больше или равно k. Полезный результат - вот как раз индексы i и j,
которые здесь параметры циклов.
      5.4 B 17; F i=1,N1; F j=1,N2; I(a(i,j)-k) 0; Q 17
    Да, параметры циклов можно было сохранить во вспомогательных переменных и
установить равными конечным значениям N1 и N2. Но вот так - изящнее.
    Номер ситуации 17 здесь выбран "от балды", главное: ошибок с таким номером
заведомо нет.
    Обратим внимание: строкой 5.4 выполнение подпрограммы в группе 5 и
закончится. Потому что срабатывание ловушки ведёт к выходу из ближайшей
подпрограммы. Если нам надо, чтобы после 5.4 выполнялись так же строки 5.5,
5.6, 5.7... можно ограничить распространение ситуации одной только текущей
строкой - следующим образом:
      5.4 F; B 17; F i=1,N1; F j=1,N2; I(a(i,j)-k) 0; Q 17; C см. оператор For

    Ошибки (ситуации), так же как и номера программных строк, объединяются в
группы, чтобы можно было поставить ловушку как на всю группу ошибок сразу, так
и на конкретную. Группы ошибок:
  0 - действия человека за терминалом (например 0.3 - нажата Ctrl/C)
  1 - синтаксические ошибки (например 1.4 - дисбаланс скобок)
  2 - арифметические ошибки (например 2.1 - деление на ноль)
  3 - ошибки ввода/вывода   (например 3.2 - конец файла)
  4 - прочие ошибки         (например 4.3 - нет переменной)
  5 - недостаток ресурсов   (например 5.1 - не хватает оперативной памяти)

                   *  *  *

    Реакцию на внешние по отношению к программе события тоже устанавливает
оператор Break. События отличаются от ситуаций тем, что их номера -
отрицательные, ловушки на них - глобальные, и реакция не может быть "нулевой":
если указана нулевая реакция - отслеживание данного события отменяется.
    Фокал - интерпретатор медленный, поэтому внешние события именно что
"отслеживаются": с каждым из них связан счетчик, при возникновении события он
наращивается. А когда запускается подпрограмма реакции - передаётся ей в
качестве одного из аргументов и обнуляется.
    Реакция на событие (аналог прерываний) запускается "вне очереди" между
выполнением операторов основной программы. Тоже как подпрограмма. И на время
её работы реакция на другие события запрещается. (А вот если она сама вызовет
подпрограмму - там уже можно.)
    Следует иметь в виду, что некоторые операторы могут выполняться очень
долго. Например включающие ожидание ввода с терминала. В том числе когда
"интерфейсная" часть интерпретатора ожидает ввода очередной командной строки.
Реакция в это время, разумеется, запущена быть не может, но события -
отслеживаются.

    События искусственно порождаются оператором Kill. Первые 256 из них это
реальные машинные прерывания. (Поэтому: обращаться осторожно!) События с
номерами от 1000 - внутренние. Например 1003 - нажатие комбинации кнопок
Ctrl/C, а 1004 - окончание звукового фрагмента. Имеется так же возможность
заказать события с помощью функций FViz и FCLk - на действия с мышкой в
пределах указанной области и на истечение временного интервала (или с
указанным периодом).
    Так как некоторые из прерываний - обращения к ДОС`у и БИОС`у,
предполагающие передачу параметров (через регистры процессора), то в операторе
Kill допускается произвольное количество дополнительных параметров. Первые из
которых раскладываются по регистрам, а потом помещаются в локальные переменные.
(В А1 - признак "успешности".)

    При отсутствии первого аргумента (при условии сохранения разделяющих
аргументы запятых) оператор Kill ничего не делает, а только раскладывает
остальные аргументы по локальным переменным текущей подпрограммы.

                   *  *  *

    ТАБУЛОСТОПЫ - существующее с незапамятных времён средство для облегчения
печати таблиц, или каких либо данных в виде аккуратных колонок. У пишущих
машинок это были выдвигающиеся железочки, помечавшие начала полей, а кнопка
ТАБ перемещала каретку до следующей такой позиции.
    Фокал имитирует этот механизм, поддерживая таблицу табулостопов. Установить
(и считать) которые можно как ф-ей FViz, так и с помощью спецкомментария:
  :    :        :      :      :     :        :    ;
    Здесь каждое очередное двоеточие отмечает начало поля, а завершаться
такая строка должна либо символом ! указывающим позицию автопереноса, либо
символом ; указывающим на продолжение "стандартным" способом - через каждые
восемь (или указанное всё той же FViz) число позиций.
    Табулостопы автоматически используются оператором Write и могут быть
использованы так же операторами Ask и Type:  двоеточие в их списке ввода/вывода
указывает на переход к позиции очередного табулостопа.

    Развитие идеи табулостопов предполагает сопоставить каждому такому полю
сначала порядковый номер (чтобы заполнять или читать поля в произвольном
порядке), а потом и имя плюс тип, что даст возможность например обращаться к
полям записи из реляционной базы данных (типа .dbf). Но потребует двойной
буферизации: такая запись должна быть считана из файла и/или записана туда
только сразу и целиком. (Что известно как "транзакция".)

                   *  *  *

    Минимальные средства работы с символами, а значит и с составленными из них
текстовыми строками были в Фокале всегда: ф-я FCHR преобразует символы в числа
(и обратно), с которыми дальше можно делать что угодно, в т.ч. хранить в
обычных переменных и элементах массивов... Да, кое что с отдельными строками и
даже целыми текстами таким макаром сделать можно. (Буковки это фактически и
есть короткие целые числа, а строки - массивы таких чисел.) Но уж больно это
громоздко, сложно и расточительно: средство зело низкоуровневое...
    Более продвинутые средства: строчный аккумулятор (который А2) и возможность
не только хранить там одну текстовую строку, а так же вводить её и выводить на
внешние устройства (в файлы), или формировать (с помощью Ask %%), но и
применять - подставляя вместо текстовой константы везде, где она уместна,
написав ! вместо "...". Везде, кроме операторов ввода/вывода Ask и Type, где !
испокон веку применяется для других целей. И которые и без того работают с А2.
(Ну Ask - так точно: А2 это же его входной буфер!)  Но такая строка всего одна.
И с возможностью её обработки - анализа и преобразования дело обстоит ничуть
не лучше.
    И наконец возможность хранения любого числа текстовых строк - под видом
строк программы. (Грамматику которых оказывается никто никогда не проверяет.
Только во время выполнения.) И обработка с помощью форматного механизма
оператора Write %. Плюс регистры этого самого форматного механизма...

   И в результате в языке, работающем только и исключительно с числами, вдруг
образовалось СРЕДСТВО РАБОТЫ СО СТРОКАМИ. Но совсем не такое, как
планировалось. (И теперь нам для полного счастья пожалуй что не хватает только
средств работы со структурами данных.)
   Как такое получилось? Как набор побочных эффектов при доработке операторов
и функций, в основном связанных с вводом/выводом, на предмет удовлетворения
разных всяких нужд... В частности:

  Ask  Х, Y...    - (штатное использование оператора Ask - ввод чисел)
  Ask %           - делает А2 пустым
  Ask             - вводит строку в А2 из канала ввода (но не использует)
  Write N_стр     - (штатное использование оператора Write - вывод программы)
  Write %    1    - выдаёт строку из А2 в канал вывода
  Write %YYY 0    - преобразует строку в А2 в соответствии с выражением YYY
  !               - подставляет строку из А2 туда где допустима "..."
  Mod N_стр       - (штатное использование оператора Modify - редактирование)
  Mod N_стр = N2  - (в форме "с присваиванием" - перемещение строк программы)
  Mod N_стр = !   - копирует А2 в память программных строк
  Mod ! = N_стр   - копирует программную строку в А2
  Mod ! = "...."  -  запись константы в А2
  Mod N = "...."  -  запись константы в указанную программную строку
  Mod  "ком_стр"  - выполнить командную_строку (или поместить в память)
  Mod  !          -   --//-- но взятую из А2
 FCHr(-1)         - (штатное использование функции FCHr - ввод )
 FCHr(c,d,...)    - (       --//--                      и вывод )
 FCHr()           - вернуть назад 1 байт и сообщить сколько осталось
 FCHr(,N)         -   --//--      N байт  (передвинуть маркер в любую сторону)
 FCHr(,)          - сообщить сколько байт уже использовано (т.е. до маркера)
 FCHr("А")        - выдаёт код буквы "А"
 FCHr("АБ",c,d, ) - --//--, остальное - в аккумулятор

   Указатель текущего символа в А2 (он же "маркер") фактически делит его на ту
часть, где символы якобы уже использованы оператором Ask и ту, где еще нет, а
FCHr(-1) читает байты непосредственно из канала ввода только если А2 пуст.
Иначе - из него, сдвигая маркер. Но может подвинуть и в другую сторону.
И вообще переставить куда угодно и/или определить местоположение. И может не
только читать символы из А2 но и записывать...

   Читает строку в А2 оператор Аск (т.к. это его входной буфер), но сам же и
использует. А с абсолютно пустым списком ввода - только читает. Но только если
А2 - пуст. (Однако это - не проблема: A %) Выдаёт наружу оператор Write
"с форматом", а сохраняет в памяти программных строк и загружает оттуда
обратно в А2 - оператор Modify "с присваиванием"...
   Которые все исходно были предназначены для чего-то совершенно другого...
Например A %; чистит А2 чтобы ввод следующего числа был точно со следующей
строки...

                   *  *  *

    Миграция формата из Type в другие операторы ввода/вывода. А так же его
мутация в "спецформат" %% и %%% - ради специфических нужд, в основном
связанных терминалом. Вернее с редактором командной строки и просмотрщиком.

     Ask %NN - (где NN - цифры) - ограничение ширины поля ввода - чтобы
совместно с ф-ей FКурсор, перемещающей курсор по экрану, вписывать что либо в
поля некоторой нарисованной на экране таблицы так, чтобы вводимый текст не
вылезал за пределы этого поля. (Примечание: аналогично ограничить поле вывода
для Type - с помощью "окна вывода", задаваемого функцией FViz.)
     Ask %   - сделать А2 пустым, чтобы следующее вводимое число было заведомо
с новой строки
     Ask %%  - --//-- плюс режим формирования в А2 "начального значения"
     Ask %%% - то же, но добавление к содержимому А2
  "Начальное значение" - чтобы сидящий за терминалом человек мог не набирать
это целиком, а либо просто согласиться, либо малость отредактировать. (Нет,
стереть, нажав ESC, и набрать с нуля он конечно тоже может...)
   Для этого оператор Ask переходит в особый режим, когда он работает в
точности как оператор Type (до конца оператора или до !), но сформированный
текст попадает не в канал вывода, а в А2. Таким образом, что маркер (указатель,
разделяющий уже использованную часть и еще нет) всё время в конце. И А2
выглядит пустым. А то Ask обращается за новой строкой только когда этот его
буфер. А упомянутый указатель всё время остаётся в его конце. А то Ask
обращается за новой строкой только когда, при попытке ввести следующее число,
ему больше не удаётся найти в А2 ничего, похожего на выражение. В том числе
если он пуст. А если нет - чистит. А тут - "не догадывается": он же уже
ВЫГЛЯДИТ пустым... Спецформат %% как и % очищает А2, а %%% - нет (только
обрезает еще не использованную часть строки) позволяя добавить к тому, что
помещено в А2 ранее.
   Планируется распространить действие %%% за пределы оператора (и отменять его
только принудительно), а то исторически так сложилось, что некоторые функции
(например FTELL) свой текстовый "побочный эффект" (в данном случае - имя
найденного файла) помещают, как и полагается, в А2, а некоторые (FTMP) - сразу
в канал вывода... И ломать это - нецелесообразно...

   Спецформат %% в операторе Type предписывает подключить (при выводе на
терминал) встроенный просмотрщик (см. далее). А спецформат %%% еще и
"автотабуляцию" в нём - вывод каждого следующего элемента с позиции очередного
табулостопа. Но сейчас устарел и отменен: нынче это делается по-другому.
(См. табулостопы.) И теперь планируется задействовать безработный спецформат
(по аналогии с таким же в Ask) для включения буферизации вывода. (Еще не
реализованного.) Это когда выводимые символы сначала накапливаются в отдельном
буфере, а выводятся все разом по Type !. Что даёт, в частности, возможность
заполнения полей (указанных табулостопами) в произвольном порядке. А в
дальнейшем присвоения этим полям не только номера но имени и типа - как у полей
записи реляционной базы данных...

    Write % копирует строки из канала ввода в канал вывода (см. далее). В том
числе, если с терминала, то Write %NN как и для Ask, тоже ограничивает ширину
поля ввода. И если на терминал, то тоже подключается встроенный просмотрщик.
Но каждый следующий оператор Write обнуляет его счетчик выведенных на терминал
строк. (По которому просмотрщик определяет, что выводить уже хватит - экран
заполнен, надо ждать, когда сидящий за терминалом человек велит продолжить.)
Спецформат %% велит этот счетчик не обнулять, а %%% позволяет установить
принудительно: W %%% N;.

                   *  *  *

         "СКРИПТОВОСТЬ" И САМОМОДИФИКАЦИЯ

    Возможность выполнять программу не заранее загруженную (некоторым образом)
в память, а непосредственно читаемую по мере выполнения с некоторого носителя
(например из файла, который обычно называют "командным" или "скриптовым"),
была у Фокала всегда. Изначально. Достаточно переключить на этот носитель
канал ввода: Фокалу без разницы откуда поступают командные строки. (Терминал и
клавиатура "выделенные" только в плане ошибок.) С появлением средств
позиционирования файла (см. выше), появилась и возможность управления порядком
выполнения такой программы. Для чего, как известно, достаточно меток и
переходов к ним - условного и безусловного. А также "к подпрограмме" - с
сохранением адреса возврата.
    Эти фокусы-покусы с файлом, программа из которого в данный момент и
выполняется, возможны, и при том совершенно безопасны, из за упомянутой в самом
начале "строчно-ориентированности" Фокала. Заключающейся в том, что за раз он
всегда читает командную строку целиком. И хранит в некотором входном буфере.
А использует (выполняет) - по-операторно. Так что пока эта строка не кончилась
и не понадобилась новая - операторы в ней могут творить (в том числе с файлом,
откуда она была прочитана) что угодно.
   Но поиск в командном файле метки - только путём прямого его просмотра
(чтения всех строк подряд, пока не встретится) и представляет собой проблему.
Которая, впрочем, элементарно решается с введением формата % в оператор Write.
(См. далее.)

   Подгрузка частей программы по мере необходимости (и удаление уже ненужных)
тоже были в Фокале изначально. А вот возможность "самомодификации" -
переписывания программой самой себя в процессе работы - только при наличии
внешнего носителя, одновременно доступного и на запись и на чтение и имеющего
возможность перемотки в начало. (Впрочем, это могла быть и магнитная лента.)
   Старая (изначальная) метода самомодификации: программа перематывает такой
носитель на начало и пишет туда сформированные ею программные строки. После
чего снова перематывает его на начало; переключает на него канал ввода и
останавливает программу оператором Quit. В результате управление получает
интерфейсная часть интерпретатора. Она читает строки из канала ввода и таким
образом загружает их в память. Главное, чтобы там в конце была прямая команда
по-новой запускающая программу с нужного места.
   Этот приём, хотя и имеет ряд недостатков, продолжает оставаться вполне
действенным. Однако, уже не единственно возможным (см. Modify "...").

   Смысл "скриптовости" - снятие ограничений на размер кода. Особенно когда
существенную его часть занимают предназначенные к выводу тексты, как в
обучающих и демонстрационных программах...
   Смысл самомодификации - тоже в преодолении ограничений языка. Например есть
вещи (как тот же формат в операторе Type) где допускаются только константы, а
нам бы хотелось менять это по результатам, полученным в ходе выполнения
программы...

                   *  *  *

    Оператору Write дополнительно поручено копировать строки из канала ввода
в канал вывода. (Через А2.) Вот как раз чтобы из командного (скриптового) файла
выводить на экран множество текстовых строк, не заключая каждую в кавычки и не
снабжая собственным оператором Type. (А то больно уж набирать такое муторно.)
Или, например, чтобы переписать содержимое одного файла в другой с некоторым
его преобразованием. (Например - в другой кодировке...)
              Write  %формат
   или даже   Write  %формат, N, "метка"
   "Формат" может быть и пустой (состоящий из одного символа %) тогда строчки
копируются как есть. Числовой аргумент N указывает количество подлежащих
копированию строк, а если отсутствует - до конца файла. Текстовый аргумент
указывает "метку": если он есть, эта метка ищется в каждой очередной строке.
И если найдена - работа оператора завершается. (А строка так и остаётся в А2.)

    Этот механизм вполне пригоден так же и для передачи управления к метке в
пределах командного файла. Метка должна быть в строке, предшествующей той,
которой надо передать управление. (И оформить её можно как комментарий.)
А копирование ведётся на имеющееся в операционной системе устройство nul, где
бесследно исчезает любая информация:
      O "nul" zw;  C  открыть устройство nul в самом начале командного файла
      ........
      O zw; O ar; O (0) a; W % "==м1=="; O t; C аналог оператора Goto ==м1==
    Здесь предполагается, что командный файл открыт под псевдонимом А, и что
потом программе понадобится вывод на терминал. И, кстати, файл сперва
перематывается в начало.
    Если спешить особо некуда - всё это вполне приемлемо. Но для ускорения дела
лучше один раз в самом начале найти все метки, запомнить их положение в
переменных, а потом сразу позиционировать файл к нужному месту.
    Можно обойтись и без устройства null, написав: W %! "метка";.

    Копирование строк идёт через А2 (входной буфер оператора Ask). Если он не
пуст, то W % 1; просто выдаст его содержимое в канал вывода, а W %ф_выраж 0;
ничего никуда выводить не будет, а только применит форматное выражение ф_выраж
к текущему содержимому А2 (даже в том случае когда А2 пуст).
    Так как  W % "метка"; оставляет строку с меткой в А2, то перед следующей
аналогичной операцией надо не забыть очистить его: Ask %.

                   *  *  *

    ВСТРОЕННЫЙ ПРОСМОТРЩИК - аналог UNIX`овского more, вообще-то
принадлежность оператора Help. Но автоматически подключается и при выводе на
терминал текстов оператором Write, включая и  W %;. (А вот для оператора Type
для этого надо использовать спецформат %%.)
    Сидящий за терминалом и читающий всё это человек может велеть прекратить
вывод, нажав ESC. И работа оператора завершится. Обнаружить сей факт можно,
посмотрев (с помощью FTEll(1)) - что в буфере клавиатуры? Когда он пуст - там
ноль. Но после нажатия ESC в просмотрщике там будет -1 (что, впрочем, тоже
означает что он пуст).
    Если вывод текста идёт прямо из командного файла:
          W % "метка2" ;  C очередная командная строка
        .....текст...................................
        ..............текст..........................
        ...................всё еще текст.............
               метка2
        ****************; C следующая командная строка
 то до следующей командной строки он в результате "не дотянет". Надо написать:
         W % "метка2"; If(FTEll(1)),0,0; W %! "метка2";
 чтобы распознало эту ситуацию и пропустило недовыведенный остаток текста.
 Но строка то с меткой застряла таки в А2? Значит проще можно:
         W % "метка2"; W %! "метка2";

                   *  *  *

   Оператору Modify дополнительно поручено... Ну, то есть кроме формы:
     Modify  N_стр           -- с числовым аргументом, ввели форму:
     Modify  текст           -- с текстовым, где может быть и "..." и ! и :N
     Modify  N_стр = N_стр2  -- а так же форму с присваиванием
     Modify  N_стр = текст   -- или даже:
     Modify  N_стр = N_стр2, текст2 ,...   -- чтобы дополнить строку
     Modify    !   =  .....                -- чтобы поместить это в А2
     Modify   :N   =  .....                -- или в регистр N
  Т.е. реализовали все возможные варианты. Что позволяет не только перемещать
программные строки между группами, но использовать некоторые из групп программы
для хранения совершенно "посторонних" строчных значений.
   Вообще-то, даже когда формы "с присваиванием" у оператора М еще небыло,
человек, сидящий за терминалом, мог переместить программные строки, например
из одной группы в другую, просто отредактировав номера строк. А вот сама
программа - нет. А форма с текстовым аргументом понадобилась для удобства
работы с отладчиком. (Да и  самомодификация...)

    Отладочный "ПОШАГОВЫЙ" режим (включающийся по '??') - это когда перед
выполнением каждого очередного оператора, не только выдаётся на терминал
его текст, как при ТРАССИРОВКЕ (включающейся по '?'), но и от человека,
сидящего за терминалом ожидается команда - что делать дальше:
 ESC         - остановить выполнение программы
 ВК (Enter)  - выполнить этот оператор, в т.ч. с заходом в подпрограмму
 ТАБ         - выполнить, но без захода (подпрограмму выполнять "молча")
 пробел      - продолжить - отмена пошагового режима, трассировка сохраняется
 Ctrl/пробел - продолжить без трассировки (то же самое с Sh или Alt)
    Этот набор односимвольных команд был дополнен командами:
 '?'                         - выдать мини-справку по этим командам
 alt/(PgUp, PgDn, Home, End) - включить/выключить отладочное окно
 ЗБ, '*', ':', ';'           - ввести и выполнить одну командную строку
(на это время каналы ввода и вывода временно переключаются на клавиатуру и
терминал)
   Отладочное окно - чтобы не портить изображение на экране трассировочной и
другой вспомогательной информацией. А командная строка - чтобы можно было
проверить или даже изменить значения переменных, вывести фрагмент программы,
заглянуть в хэлп...
   Но это таки отладчик, а не интерфейсная часть интерпретатора - ввод новых
"косвенных" строк (с помещением их в память) не предусмотрен. Однако, М "...";
предоставляет и такую возможность.

                   *  *  *

       ФОРМАТНОЕ ВЫРАЖЕНИЕ в операторе W %.....

   Напоминаю: оператору Write дополнительно поручили копировать строки из
канала ввода в канал вывода. (Через входной буфер оператора Ask, он же
"строчный аккумулятор" или А2.) Возможно, с некоторым преобразованием,
предположительно, в более удобочитаемый вид. Вот форматное выражение и
указывает что с этими строчками делать. Задумывалось что-то типа W %буква.
Каковая буква и обозначает "встроенное преобразование". Но исторически так
сложилось, что буквы эти появились вот можно сказать тока-тока...
   В общем: что выросло, то выросло!  Получился отдельный от остального Фокала
язык, который здесь и описывается. (Даже два - еще и язык шаблонов.)

   Текущая (новая, будем считать что "четвертая") его версия, несколько
отличается от предыдущих. Двумя вещами: упразднена конструкция  ? : |
(а '?' стал "постфиксом" как '&' и '\'); и поиск теперь - только с префиксом
'+' или '-'. Ну и шаблоны вернулись с реконструкции...

   Форматное выражение может быть пустым, тогда оператор копирует строки как
есть. Может начинаться с числа, которое, так же как и в "Ask %NNN", указывает
редактору командной строки ширину поля ввода. W %%... будет воспринято как
"спецформат" (см. выше), что-то там такое устанавливающий.
   А вот всё остальное...

   Грамматически, всё форматное выражение представляет собой одно "слово",
ограниченное пробелом, запятой или концом оператора, в т.ч. символом ';'.
(Внутри форматного выражения эти символы могут быть только внутри кавычек.)
Логически, форматное выражение это последовательность операций, в том числе
и составных. А синтаксически состоит из буковок, циферок, скобочек ([{<>}]),
значков _+-*/~.!?:|@=#^$%&\ и уже упоминавшихся кавычек "'` для заключения в
них текстовых констант. (Все кавычки - эквивалентны, а скобки - нет:
используются для разных целей. Вот например круглые заключают в себе составную
операцию...)
   Каждая операция в принципе обозначается одним символом. Но вот '=' и '#'
("установить" и "применить") требуют указать что именно устанавливать или
применять. Символ '%' - тоже: он предписывает обращение к чему-то, и нуждается
в указании - к чему именно обратиться. Идущие подряд цифры тоже образуют
единую конструкцию - числовую константу, а символы, заключенные в кавычки -
текстовую (строчную). То и другое может быть как самостоятельной операцией,
так и нет - случись им стоять после операции, нуждающейся в правом операнде вот
такого типа.

   Основных операций - всего две: '*' и '/' (вставить и удалить). Вернее даже
три: еще и '_' только она ничего не делает. За то всегда выполняется успешно.
(Хотя пожалуй здесь следовало бы еще упомянуть перемещение маркера и поиск
фрагмента строки.)

    Операции выполняются строго слева направо без какого либо старшинства.
Но могут группироваться круглыми скобками: их содержимое считается одной
составной операцией. "+" и "-" считаются "префиксами" - влияют на следующую
после них операцию.  "&" и "\" считаются "постфиксами" - предписывают
выполнить или пропустить следующую после них операцию в зависимости от
успешности и неуспешности предыдущей. Иначе, при неуспешном выполнении
очередной операции, выполнение форматного выражения сразу же прекращается.
  Конструкция  ? : |  предусматривавшая оба варианта и тоже игравшая роль
скобок в предыдущих версиях - теперь упразднена. Символы ":" и "|"
освободились для других целей, а "?" превратился в постфикс, влияющий на ДВЕ
операции, следующие после него (в том числе составные) - одна обязательно
выполняется а другая пропускается.
  "@" - префикс повторения: повторяет следующую после него операцию (чаще всего
составную) либо до первой ошибки, либо так, как предписано "заголовком цикла".
Например: @Y будет раз за разом запускать встроенное преобразование Y пока оно
не вернет признак ошибки. (А так как такого у нас пока нет, то не вернёт
никогда: так и будет каждый раз успешно ничего не делать.) А вот @7+*'/=\'
вставит /=\ ровно семь раз; a  @{1,22=i}(#i*'\-/')  вставит \-/ с монотонно
увеличивающимся шагом столько, сколько в строке поместится. Но опять же не
более 22 раз. (Обращаться осторожно: зациклить навсегда как нечего делать!)

   Операции вставки '*' нужен правый операнд строчного типа, указывающий что
именно вставить (вот как в примерах выше). Иначе она ничего не вставляет.
Операции удаления '/' нужен операнд числового типа, указывающий сколько
символов удалить. При его отсутствии удаляется то, что под маркером. С
префиксами - и + то, что до и после маркера. Операция '*' с префиксами
вставляет свой аргумент перед и после фрагмента, указанного маркером, а без
префиксов - вместо него.
   Таким образом МАРКЕР (в качестве которого - указатель, разделяющий "уже
использованную" оператором Ask часть строки в его входном буфере, и "еще нет"),
имеет еще и размер. И указывает не на один символ, а на фрагмент строки.
   Изначально, когда строка только введена в А2, маркер ставится в её начало и
указывает её всю. После операции перемещения маркера, его размер становится
нулевой; после операции поиска - устанавливается по размеру найденного; после
вставки - вставленного...
   Размер маркера можно принудительно установить с помощью операции '~'
      ~NN       - установить размер маркера = NN (где NN - числовой аргумент)
     +~NN -~NN  - изменить на NN размер маркера
      ~$   ~^   - от текущего места и до конца строки, или до её начала
      ~.        - до метки
   Операции перемещения маркера:
     NN +NN -NN  - на NN позиций вперед и назад, в том числе:
     0  +0       - на начало и сразу после конца маркера
     ^  $        - в начало и в конец строки
     -  +        - на одну позицию вперёд и назад
     =. #.       - установить и применить метку, обменяв её с маркером местами
   Операция =. не изменяет состояние маркера, остальные перемещают его, и
делают его размер нулевым.
   Таким образом, если префиксу не на что действовать, он становится
самостоятельной операцией. Префикс @ - тоже, вот только не действует он лишь
на постфиксы. Вот и образует с ними условное выражение. Где условие - числовое
выражение в его заголовке: "правда" - если >=0.

   Операции поиска: "..." и <...>, теперь обязательно с префиксом '+' или '-'
указывающим направление смещения маркера. Без префикса теперь они только
СОПОСТАВЛЯЮТ своё содержимое с текущим фрагментом строки, не двигая маркер.
(Не сопоставилось - ошибка. Но на этот случай - постфиксы '?' '&' '\'.) Вместо
текстовой константы "...", которая сопоставляется на полное совпадение, может
быть любой другой аргумент строчного типа (см. далее). Конструкция <...>
известна как "шаблон" и сопоставляется более сложным образом (тоже см. далее).
   Операция 'х' (и Х-русская и ИКС-латинская - они же руна "гебо" из фрутарка
со значением 'братство', 'объединение') предписывает на одну следующую
операцию поиска не различать при сопоставлении строчные и заглавные буквы, а
'Х' (заглавная) - не различать так же русские и латинские.

   Числовой аргумент это не только константа в виде последовательности цифр,
но и значение фокаловской переменной со специфическим именем #Б (где Б - любая
буква), или значение выражения, заключенного в фигурные скобки. Например:
  123  #А  {4*X+5*Y}   Запись в переменную #А будет выглядеть как =А
Записывается текущий размер маркера, а с префиксами - размер фрагмента перед и
после него. Используется только целая часть значения переменной или выражения.
   Строчный аргумент это не только константа в виде символов, заключенных в
кавычки (любого типа, но одинаковые), но и содержимое одного из "регистров",
предназначенных для временного хранения текстовых фрагментов. А так же
заключенная в фигурные скобки конкатенация (объединение) нескольких текстовых
констант. Числовые значения там тоже допускаются и указывают коды символов.
Например:  "абвг"  #4  {"деё",Z-6,"жзий"}  Запись в регистр #4 будет: =4
Записывается фрагмент под маркером, а с префиксами - перед и после маркера.
Одновременно в переменную с именем #4 записывается размер этого фрагмента.

   Именно эти "регистры" и указываются конструкцией :N везде, где допустима
строчная константа. (Кроме операторов ввода/вывода.) В том числе и в
операторе Modify с присваиванием, по любую сторону знака =.

   Операции #{...} и ={...} - форматные преобразования числа в строку и
обратно. (Фактически делают то же, что и операторы Type и Ask. Даже в несколько
расширенном варианте.) В фигурных скобках в первом случае выражение
произвольного вида, а во втором - переменная, куда будет записан результат. И
после них может быть % и описание формата в стиле Си-шной функции printf().
А если нету, то используется текущий формат, установленный оператором Type.

   Как уже было сказано, форматное выражение выполняется либо до конца, либо
до первой ошибки. (Типа попытки передвинуть маркер за пределы строки.)
   Но операция "!" предписывает немедленно завершить выполнение форматного
выражения и подавляет вывод строки. С префиксами: "-!" прекращает работу всего
оператора Write, a "+!" - еще и "со скандалом": порождает ситуацию 3.6 или
указанную числовым аргументом.
   Операция "." наоборот предписывает немедленный вывод находящейся в
аккумуляторе строки. С префиксами: "-." - только то, что под маркером, и
"+." - от маркера до конца строки, т.е. "еще неиспользованная" часть.
(Да, несколько нелогично, но так удобнее.)

   И наконец конструкция %N или %Б (где N - цифра, а Б - буква) это аналог
обращения к подпрограмме: следующая после неё операция не выполняется, а
рассматривается как "аргумент". Для '%N' - выполняется содержимое регистра N
как форматное выражение. А "аргумент" доступен там как '#%' или '%%'. В первом
случае - как фрагмент строки, а во втором - он выполняется как операция. Для
'%Б' выполняется встроенное (предопределенное) преобразование. От просто 'Б'
оно отличается вот как раз наличием аргумента. И еще тем, что префиксы '-' и
'+' на него не действуют. (Потому что я так и не придумал, как их учесть внутри
подпрограммы '%N'.)

                   *  *  *

   Встроенные (предопределенные) преобразования можно условно разделить на
"перекодировки" и все остальные.

   Из этих самых "остальных" в настоящий момент реализованы только:
 'R' - реверс байтов (перестановка их в обратном порядке)
 'r' - реверс битов в каждом байте
   ну и ранее упоминавшиеся:
 'х' и 'Х' - которые сами ничего не делают, но для одной следующей после них
операции поиска (сопоставления) предписывают не различать заглавные и строчные
буквы, а так же русские и латинские.
  Они же, но с числовым аргументом ('%хNN' или '%ХNN') указывают с его помощью
разновидность уникода для 'U' (см. ниже).

   Перекодировки, т.е. преобразования букв (заглавных / строчных, русских /
латинских) а так же русских букв между разными кодировками - обозначаются
теми же буковками что и для их распознавания в шаблоне (см. далее)
 'р' и 'Р' обозначают русские буквы,  'л' и 'Л' - латинские, при чем
 'р' и 'л' - строчные, а 'Р' и 'Л' - заглавные. Это относится только к
"текущей" ДОС`овской кодировке CP-866 используемой Фокалом.
   Аналогично, для других кодировок заглавные и строчные буквы обозначаются
заглавными и строчными русскими, а латинскими - любые. Кодировки:
 'D' 'd' 'Д' 'д' -   ДОС    (CP-866)
 'W' 'w' 'В' 'в' -   Win    (CP-1251)
 'K' 'k' 'К' 'к' -   КОИ-8  (ГОСТ-19768.74)
 'I' 'i' 'И' 'и' -   Исо    (ISO 8859.5)
 'U' 'u' 'У' 'у' -   уникод                   (по умолчанию utf-8)
   Х преобразует буквы из текущей кодировки в кодировку Х
а %XY -  из X в Y  (где 'X' и 'Y' - любые буквы из вышеперечисленных)

   Уникод, как известно, может быть разного типа. Двух и четырёх-байтовый
различаются еще и порядком следования байтов. Различаются они обычно с помощью
символа с кодом 0xFEFF "неразрывный пробел нулевой ширины", который для
этого вставляют самым первым. В анализирующем шаблоне (см. далее) он
распознается с помощью %u, автоматически устанавливающем тип входного уникода.
   Но тип уникода может быть так же установлен и принудительно - с помощью
%Хnn, где nn - число (в пределах ста), где единицы указывают тип входного
уникода, а десятки - выходного.
  1 - utf-8,  2 - utf-16,  4 - utf-32, - с нормальным порядком байтов
              6  --//--    8   --//--, - старшим байтом вперёд (т.е. обратный)
  0 - по-умолчанию (для входной - utf-8, а для выходной - как входная).
  Для 3 и 7 внезапно получлась трехбайтная кодировка utf-24.

                   *  *  *

   "ШАБЛОН", он же "регулярное выражение" (конструкция <...>) - это только один
из элементов форматного выражения, но тем не менее отдельный язык. Впрочем,
несложный - чуть расширенный вариант шаблонов UNIX`овского sh. Отличающийся
тем, что '?' и '*' не сами обозначают один любой символ и произвольное их
количество, а наделяют таким свойством следующий после них элемент. И потому
называются "префиксами повторения". Ну и еще тем, что [...] и <...> могут
вкладываться друг в дружку как матрешки, и, соответственно, элементами
перечисления [...] могут быть не только отдельные символы, но и более сложные
конструкции. Ну и "встроенные" шаблоны конешно, типа %Буква...

   Шаблон может быть "анализирующий" и "генерирующий". Анализирующий
сопоставляется с фрагментом строки (например когда с его помощью производится
операция поиска), а "генерирующий" сам формирует фрагмент строки по образчику
того, с чем ранее сопоставился анализирующий. В этом качестве он выступает
когда служит правым операндом операции вставки '*'. Удачно сопоставившийся
анализирующий шаблон оставляет после себя "протокол", который генерирующий и
использует...

    Грамматически, шаблон это аналог строчной константы. Отличается от неё
тем, что в нём нe все символы изображают сами себя, некоторые имеют
специальный смысл. В принципе таковыми можно считать все "значки", вне
зависимости от того, задействованы они в настоящий момент или нет.
    Главный спецсимвол '%' - "экранирующий": он делает другие спецсимволы
(включая самоё себя) - обычными, и некоторые обычные - специальными.
    Следующие по важности это префиксы повторения. Они предписывают повторить
следующий за ними элемент шаблона:
 '*'   - ноль или более раз
 '+'   - один или более раз
 '?'   - ноль или один раз
 {N}   - ровно N раз
 {N,M} - не менее чем N и не более чем M раз, где N и M - числа или
фокаловские выражения, или пропущены и тогда для N - ноль, а для M - скока хошь
 '-'   - сразу после префикса повторения (перед повторяемым элементом)
предписывает применять не "жадный" алгоритм, пытающийся захватить сразу как
можно больше символов, а "ленивый" - действующий противоположным образом.

    Таким элементом может быть:
 - любой "обычный" символ (или '%' плюс специальный) - изображает сам себя
     (но если это буква то с учетом предшествующего шаблону "х" или "Х")
 - конструкция '%' плюс буква - встроенный шаблон (см. далее)
 - символ '^' или '$' - сопоставляются с началом и концом строки
 - символ '!' - сопоставляется с разделяющими строки символами ПС/ВК,
     они же '\n' и '\r' с кодами 10 и 13.
 - символ '.' - сопоставляется с любым символом
 - символ '_' ничего не делает, но это тоже элемент шаблона
 - конструкция <...> - сопоставляется если сопоставились все её элементы
 - конструкция [...] - сопоставляется если сопоставился один из её элементов

    Угловые <...> и квадратные [...] скобки ("последовательность" и
"перечисление") могут вкладываться друг в дружку на любую грубину. Круглые
скобки (...) пока никак не используются (оставлены про запас) но тоже
отслеживаются - т.е. их баланс тоже должен соблюдаться, так же как и для
скобок других видов. Кавычки пока никак не задействованы: <...> тоже своего
рода кавычки, отменяющие специальный смысл кавычек другого типа.

    Внутри "перечисления" [...] префиксы повторения не имеют смысла, символы
'*' '?' '+' - обычные. Конструкция {N} изображает один символ с кодом N.
Но за то как и в UNIX`е есть "диапазон", например конструкция А-Я обозначает
все заглавные русские буквы. Вернее все символы, коды которых находятся в
промежутке между кодами двух указанных. Так что в данном случае, увы, без
буквы Ё, которая в используемой ДОС`овской кодировке - отдельно. (А диапазон
маленьких русских букв здесь вообще разрывный! Его пришлось бы обозначать как
[а-пр-я] или даже [а-еёж-пр-я].)
    Кроме того "перечисление" может быть "негативным": [~...] - сопоставление
с ним считается "удачным" если не сопоставился ни один из его элементов.

                   *  *  *

    Встроенные шаблоны вида %Буква сопоставляются с символами, принадлежащими
к некоторой группе:
 %B %b %Б %б - любая буква, в т.ч. %Б и %б отдельно заглавная и строчная
 %L %l %Л %л - любая латинская    --//--
 %R %r %Р %р - любая русская      --//-- в текущей кодировке
 %D %W %K %I - русские буквы в кодировках ДОС, Win, КОИ-8, ИСО
 %У %у %U %u - уникод, здесь по-другому:
  %U  - любой правильный символ в utf-8
  %u  - один единственный "неразрывный пробел нулевой ширины" с кодом 00FEFF
        но в любой из разновидностей уникода: по нему собственно эти
        разновидности и различаются. Если сопоставился - устанавливает
        внутренний признак типа уникода
 %У %у - только русские буквы, и пока только в utf-8

    а так же
 %Ц %ц %C %c - любая цифра
 %Г %г %G %g - любой символ псевдографики
 %П %п %P %p - пробел и "слепые" символы с кодом меньше чем у пробела
   но
 %С %с %S %s - текстовая константа вида "..."
   а прошлый раз было, но пока еще не восстановлено:
 %Ч - число общего вида
 %Э - правильное фокаловское выражение
 %Ш - правильное форматное выражение оператора W, включая шаблон

  (Подробнее - см. в справке к Фокалу выдаваемой оператором Help.)

                   *  *  *

   Анализирующий шаблон по мере сопоставления с фрагментом строки формирует
"протокол", по которому генерирующий шаблон мог бы соорудить свой фрагмент.
Но который, возможно, может быть интересен и сам по себе: этот протокол
фактически развёртка в линейную последовательность дерева разбора шаблоном
фрагмента строки.
   Поэтому принято (временное) решение вставлять в А2 текущий (последний по
времени) протокол сопоставления шаблона операцией *  с аргументом *. (Т.е.
протокол  можно только вставить, использовать так же как и другие строчные
значения - нет.)

   Организован протокол следующим образом:

 - "последовательность" <...> (в том числе и весь шаблон целиком) фиксируется в
в протоколе в виде пары скобок '<' '>', между которыми - её элементы
 - любой префикс повторения "выгораживает" участок внутри последовательности,
начало которого отмечается символом '*' а конец - '|', и между ними - элементы,
с которыми сопоставилась повторяемая им конструкция. В том числе возможно что
и ни одного.
 - "перечисление" [...] отображается парой скобок '[' ']', в которых
число (номер сопоставившегося элемента), запятая и то, что с ним сопоставилось.
 - элемент шаблона, успешно сопоставившийся с нулём символов отображается '_'.
Это и негативное перечисление [~...] у которого не сопоставился ни один из его
элементов, и пустой шаблон '_', и '^' '$' сопоставляющиеся с началом и концом
строки
 - элемент шаблона, сопоставившийся с одиночным символом 'Х' отображается в
протоколе как '.Х' - это может быть и символ, изображающий сам себя и '.'
(точка) обозначающая любой символ
 - встроенный шаблон вида %Б сопоставившийся с одиночным символом 'Х'
отображается в протоколе как 'БХ'
    А вот встроенных шаблонов, способных сопоставится с чем-то более сложным, у
нас пока только два: %U - сопоставляющийся с символом уникода - отображается в
протоколе как Uчисло, и %S сопоставляющийся со строчной константой в кавычках и
отображающийся в протоколе как S"..."
    Что делать в более общем случае - еще надо подумать...

                   *  *  *

    Генерирующий шаблон (теоретически) должен быть конгруэнтен анализирующему.
Тогда каждому его элементу достанется элемент протокола, записанный
аналогичным элементом анализирующего, которым он бы смог руководствоваться.
Например [...] вставит элемент с тем же номером, каковой отработал в [...]
анализирующего шаблона, '*' повторит свой элемент столько же раз, сколько и
аналогичная конструкция в анализирующем, '.' вставит то, что ей досталось как
есть, а %Б с соответствующим преобразованием (например превратит доставшуюся
букву в заглавную).
    А чтобы количество элементов можно было сделать строго одинаковым, у нас и
есть пустой шаблон '_' (подчеркивание), не делающий вроде бы ничего, но элемент
протокола вставляющий и соответственно потребляющий.

   Ага-ага: должен да не обязан! Очень даже запросто так может быть, что не...
Поэтому будем считать, что генерирующий шаблон сопоставляется с протоколом, а
генерируемый им текст - всего лишь побочный эффект. Ясный пень, что когда
шаблоны конгруэнтны, сопоставление будет один в один. А вот если нет, если
элементу шаблона сопоставляется чужой элемент протокола... На этот случай
разделим все элементы шаблона на "одиночные" и "множественные" - по тому,
сколько элементов протокола они бы хотели заполучить в своё распоряжение.
     Множественные это <...> и *Х, а все остальные - одиночные, включая [...].
(Под * имеются в виду любой префикс повторения, а Х - повторяемый им элемент.)
Соответственно, "множественные" элементы протокола это <...> и *...| но первый
"главнее": второй только "выгораживает" себе его кусочек.
    Настоящим предполагаем, что:
  * множественный элемент генерирующего шаблона может "освоить" любой
доставшийся ему множественный элемент протокола, а вот с одиночными пусть
поступают по-разному:
  - конструкция <...> пусть скромно предоставляет один и тот же элемент
протокола каждому своему элементу
  - а конструкция *Х пусть нагло пытается захватить все доступные элементы
протокола вплоть до ближайшей закрывающей скобки '>'. Но при этом должна
"поделиться" с аналогичными конструкциями, если они есть.
  * одиночные элементы...
  - элементы, изображающие сами себя (обычные символы и экранированные с
помощью % спецсимволы) никак не используют доставшийся им элемент протокола.
  - элементарный шаблон . (точка) вставляет это один в один, как есть. При
чем не только одиночный символ, но и любую скольк угодно сложную конструкцию.
  - элементарный групповой шаблон типа %Б действует так же как . (точка) для
всех символов, кроме букв, которые преобразует к указанному им типу алфавита.
(А на счет не-элементарных встроенных шаблонов еще надо подумать.)
  - элементарные шаблоны _ $ ^ не порождают никакого текста.
  - конструкция [...] если ей попался такой же элемент протокола, отрабатывает
указанный ею свой элемент. А если нет - самый первый.

                   *  *  *

    Ну вот, вроде как всё придумал и даже почти сделал... Так, вроде бы только
кое-какие шероховатости остались. Типа что в конструкции {N,M} шаблона тоже бы
неплохо разрешить присваивание результата фокаловским переменным. Или вот, как
и для всего форматного выражения, надо бы и в шаблоне сделать %N аналогом
обращения к подпрограмме...
    Ан вдруг приходит в голову мысль: а простейшие вещи, типа сравнения двух
строк на полное совпадение с помощью этого замудренного механизма - как?

    Оказывается, у нас нету бинарных операций со строками - все унарные.
Нет, вставить одну строку внутрь другой, или найти совпадающий с ней фрагмент,
это пожалуйста. (Помести её заране в регистр, и...) Даже на полное совпадение
особого труда не составляет. В том числе, по желанию, игнорируя или нет
различия между разными видами буковок. (Если регистр например седьмой, то не
#7 или {:7}, а {:7$}, чтобы и конец строки захватить. А дальше  - постфикс
какой ни будь...)
    А вот сравнить две строки в рассуждении алфавитного порядка их следования
(типа: какая "больше", то есть позже по алфавиту?), или найти наибольшую общую
подстроку - то, что для третьего Фокал задумано как "полукорреляция" (на
предмет вот так доопределить для строк операцию ^)...


  //   Но тут наступило утро и Шахеризада завершила дозволенные речи //
 ================  *  *  *  ===============
  // а всё что дальше - рассуждалка типа "как бы нам обустроить..." //
  // так что далее читать не следует, а то такого можно начитаться...! //


    Как показала практика, все такие описания пишутся для того, чтобы
"с разбегу" преодолеть некий логический барьер. Прошлый раз это были вот те
самые генерирующие шаблоны, а сейчас - как бы организовать доступ к
структурированным данным. Таким, как например поля заголовка пакета им.тов.
Залётова, или записи реляционной базы данных из файла типа *.dbf

   Структура - это вам не строчка: она - набор полей, в каждом из которых - по
такой вот строчке... Или по числу. Или исчо что - ну вот ссылка например...
На что-то...
   Ага-ага: для чисел у нас аккумулятор А1, для строчек А2, а для ссылок
выходит что А3, так? С А1, а вернее с числами работает весь базовый Фокал,
с А2 тоже вроде как научились, а теперь очередь А3 подходит...

   Поэтому мысль номер один - считать тип очередного поля в некую переменную,
а значение в это время нехай сразу окажется в одном из аккумуляторов. Только
вот синтаксически это оформить - как?...

   Ну например  Ask :имя_поля: = Т;  или даже   Ask :имя_поля: = Т, Р, П...
Где Т - тип поля: 0 - число, 1, строка, -1 - ссылка
    Р - размер (для строки) и какие ни будь признаки для остальных типов.
В том числе -1 если ссылка - "нулевая", никуда не указывает. Иначе, например,
размер потока (файла), или записи, на которые ссылка. Следующие переменные,
если есть - еще какие либо параметры. (Порядковый номер поля, его смещение от
начала записи, набор неких аттрибутов...) Но '=' только при двойной
буферизации, иначе ошибка.
   Тащить '=' в оператор Ask, где его раньше никогда небыло, конешно не есть
хорошо, но без этого может быть нежданчик. Если при переходе к следующему
полю тупо тащить его содержимое в А2, а в А1 например его тип, как я хотел
раньше, то как это отличить от просто перехода к следующему табулостопу,
никак не изменяющему содержимое аккумулятора? (Даже если первое исключительно
для файлов с двойной буферизацией, а второе - без таковой. И смешивать их не
предполагается.)


                   *  *  *

            ПРО ЗАПИСИ И ПОЛЯ ОНЫХ

   Итак, запись делится на поля, в каждом из которых - ровно по одному
значению. А вот каковы типы этих значений, привязан ли тип к данному полю,
или может меняться по нашему произволу - в зависимости от того, какое
значение мы в это поле запишем; фиксирован ли и при этом упорядочен ли каким
то образом сам набор полей, или тоже - предмет нашего произвола - пусть всё
это пока остаётся за кадром. Потому что в одних случаях так, а в других эдак.

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

   Да, красиво, изящно, но, увы - не наш случай. У нас язык - всё еще ПЕРВОГО
поколения. (За то максимально простой!) С фиксированными, встроенными в сам
язык типами данных. (Более того - вообще с одним единственным.) Что, впрочем,
совсем не препятствует автоматически преобразовывать значения полей к этому
встроенному типу, а потом обратно. Особенно, если доступно описание полей,
как в том же *.dbf.

   Вообще-то сперва я хотел просто разбить содержимое А2 на поля табулостопами.
А табулостопы эти сделать пронумерованные, или даже именами снабдить,
индивидуальными. С тем, чтобы ставить маркер на начало произвольного, какого
хочу поля, а не только следующего, как сейчас. И плюс к этому - чтение/запись
не "строки" (до ПС/ВК) а фиксированного количества байтов, при чем не взирая
на "непечатные" символы.
   Но увы: просто поставить маркер на начало поля недостаточно. Надо еще чтобы
например то, что выдаст туда оператор Type в соседнее поле не перелезало. А у
нас это - запросто.

   Впрочем, зная границы полей, извлечь, равно как и положить туда любую
числовую или текстовую информацию можно уже сейчас без всяких табулостопов:
форматный механизм оператора Write как раз под это и заточен.
   Однако, с одной стороны сложновато: надо всё-всё-всё про эту структуру
знать. С другой - а как же поля ссылочного типа и навигация по ним?...

   Поэтому пусть вся запись (структура) пусть будет где-то в другом месте, а в
аккамуляторы - только отдельные значения, извлеченные из её полей.

   Подключим к этому делу идею двойной буферизации. "Двойная" она только для
ввода, поскольку у Ask еще и свой буфер есть. А вот этот, второй, нехай будет
привязан к файлу. То есть сперва мы читаем из него туда сразу всю запись
целиком. И уже из него таскаем значения отдельных полей. И в него же пишем.
Причем сразу с автоматическим преобразованием типа к типу поля. А буде захотели
следующую запись, то сперва (если писали и буфер - "грязный") сбросим обратно.
Типа "транзакция"? (Еще и, возможно, с блокировкой этого фрагмента. Некоторые
ОС позволяют...)

   Но тренироваться, как известно, надо на кошках. За сим безо всяких
типизированных полей (и полей вообще) сделаем сперва буферизацию вывода. Для
чего завести буфер для оператора Type и писать не в канал вывода а в него. А
выпихивать из буфера в канал - только по '!'. Включать:  Type %%%  (благо
освободилось), а  Ask %%%  сделать не до конца оператора, как сейчас, а тоже
только до '!', и пусть буфер оно у Type перехватывает. И наоборот тоже.
(А буде надо это дело "спустить на тормозах" - не выдавать в канал вывода, то
'%!'.) За одно решится мелкая но неприятная проблемка с получением текстового
представления даты/времени. А то так исторически сложилось, что в отличии
например от имени найденного файла (ф-ей FTEll), оно, побочный эффект функции
FTMp, сразу в канал вывода. Неединообразно, но ломать всётаки не хочется.

                   *  *  *

    Оказывается, каналов ввода/вывода у нас в Фокале не два, а три!
 - канал ввода  'R'  откуда читают оператор Ask, функция fchr() и сам Фокал
 - канал вывода 'W', куда пишут операторы Type, Write, и функция fchr()
 - и еще канал  'D'  который археологи (в моём лице) раскапывают вот прямо
сейчас! Он указывает на текущий каталог и негласно применяется функцией
ftell() со строчным аргументом при поиске файлов, и оператором Operate при их
открытии. Но никаких других действий с ним пока не предусмотрено. Вернее, все
они - с помощью оператора ! командами операционной системы.

   Итак, где-то там, в тридевятом царстве, в тридесятом государстве, вокруг
нас имеет место быть "внешняя информационная стреда", где обитают
информационные объекты, называемые сильно по-разному: "носители", "тома",
"контейнеры"... И некоторые из них вот прямо сейчас доступны нашей
вычислительной системе и обозначаются в ней буковками: "A:", "B:", "C:"...
(Обязательно с двоеточием - так исторически сложилось.)
   Внутри у каждого такого тома - "внутренняя информационная среда" в виде
набора информационных объектов, для обобщенного обозначения которых нету
подходящего термина: "набор данных" - слишком громоздко и статично; "файл"
(от исковерканного германского "филе" - много) т.е. тоже множество, набор
данных - закрепилось за вполне конкретной разновидностью; "сегмент" - тоже
занято; "поток" - наоборот слишком "динамично" и ассоциируется не с хранением
а с передачей...
   Пока временно остановимся на последнем слове. Тем паче оно уже кое-где
употребляется для примерно сходных целей. Придадим ему несколько расширенное
толкование по сравнению с интуитивно понятным, но пока не будем уточнять и
конкретизировать.

   Эти самые потоки внутри тома или контейнера в общем случае никак не
упорядоченны. (Хотя возможны варианты. Например на магнитной ленте они строго
один за другим.) Но среди них есть один, выделенный, известный как "корневой
каталог". Это обязательно поток таких "записей" (или "структур"), в корорых
есть два следующих поля: первое из них содержит строку - "имя файла", а
второе указывает на поименованный таким образом поток. (Ну и возможно
дополнительные поля со всякими аттрибутами.) Поток, на который указывает поле
номер два, может быть такой же каталог, а может то, что обычно и называется
"файлом". (Хотя есть и другие варианты.)

   Свеженайденный канал 'D' подключен к одному из таких каталогов - "текущему".
Функция ftell() с текстовым аргументом производит чтение всех записей этого
потока подряд, до тех пор, пока не найдёт такую, где значение первого поля не
подойдёт под шаблон, указанный текстовым аргументом. Оператор Operate делает
то же самое, плюс он этот файл еще и "открывает". Я бы сказал, что копирует
содержимое второго поля себе в спецпеременную, обозначаемую "псевдонимом".
(Только неправда это: выполняемые при этом действия куда сложне. Но снаружи
таки выглядит как будто бы оно вот именно так.)

    Другими словами, для одного частного случая, специфическими средствами
операционной системы, реализованы некоторые из возможных действий с некоторыми
полями записей одного из типов потока (известного как "каталог").
    Ага.
    А я хочу - любые возможные действия с любыми полями элементов потока
произвольного типа. Ну или, как минимум (или для начала) чтобы Фокал открывал
с помощью оператора О и использовал не только потоки бестиповых байтов,
искуственно разделенных на строчки по символам ПС и ВК с кодами 10 и 13, но и
хотя бы некоторые другие.
   А так как исходно сам Фокал заточен исключительно под строчки, то видимо
придётся сохранить концепцию главного поля, содержащего текстовую строку. И
по-умолчанию читать только его.

   А эти самые "любые возможные действия" (в дополнение к уже имеющимся
операциям с числами и строчками) должны включать с одной стороны навигацию по вот этим
уже существующим ссылкам. А с другой - создание и уничтожение таких ссылок.
Типа записи другого значения в ссылочное поле. А так же создание новых
потоков и уничтожение существующих. (Не оставляя при этом ни "мусора" ни
висящих ссылок, указывающих на уже несуществующие объекты.)
   И полюс к этому формирование структур данных - либо статическое создание
новых видов записи (и потока такого типа) с произвольными полями, либо
динамическое добавление и/или удаление полей.
   Вроде всё?

   Как - не вопрос. Ну например задействуем таки '%' в операторе Operate.
Пусть просто '%' означает сам А3 (типа: О () А; О % Б; это сделать из
псевдонима Б копию А), а с разными буковками пусть еще что-то значит. Например
%К - один из компортов (на самом дле что ни будь %К1/9600/2н чтобы сразу
указать номер устройства, скорость, количество стоповых битов и контроль по
четности), а %О - описание набора полей,  "метафайл" к тому, указатель на
который в аккумуляторе: для каждого поля - одна запись с фиксированными
полями, типа "имя", "тип", "размер", "смещение", еще признаки какие ни будь...
("nm", "t", "l", "s", "f"...)

   Ага-ага, заполучив каким-то образом  в аккумулятор А3 указатель на поток,
мы теперь сможем открыть его под другим псевдонимом. Или даже под тем же
самым... А прочитав из него очередную запись, а из неё следующий указатель,
проделать опять то же самое... Чем не навигация?

                   *  *  *

   Вот только было бы полезно еще и хоть как-то запомнить путь, чтобы можно
было вернуться обратно. Для этого введём в псевдоним в операторе Ask еще одну
буковку, например 'S'. ('N' там уже есть, чем 'S' хуже?!) Указывающую не
закрывать текущий файл, открытый под этим псевдонимом, при открытии под ним
другого, а как бы "отодвинуть вглубь". А как только этот будет закрыт -
восстановить на место.
   Так можно запоминать не только текущие точки в потоке (смещения в файле),
чтобы потом вернуться к нему обратно, но и наделить этим свойством например
терминал с клавиатурой, чтобы нельзя было закрыть их окончательно...

                   *  *  *

   Ну и зачем нам, спрашивается, после этого канал Д, если мы теперь сами
сможем перебрать все записи каталога и выбрав ту, что понравится, и просто...
А вот даже совсем и не "просто"! Во-первых это будет совсем не такое
элементарное действие, как сейчас открытие файла по его имени:  А "имя" Х;
А во-вторых нешто мы не найдём для чего его приспособить в плане поиска по
базе данных с помощью штатных средств СУБД?...

                   *  *  *

   Итого, у нас фактически остались два вопроса:
 - как файлы (то бишь потоки) открывать не как текстовый/бинарный, а как
какой-то еще (в т.ч. с двойной буферизацией): как-то принудительно, или
автоматически по расширениею (или сигнатуре) распознавать?
 - как описывать именованные типизированные поля с одной стороны так, чтобы
это было развитием идеи табулостопов, а с другой - чтобы они получались
привязаны либо к конкретному открытому под таким-то псевдонимом файлу, либо к
типу потока, о которых тоже надо подумать...


 Ваша оценка:

Связаться с программистом сайта.

Новые книги авторов СИ, вышедшие из печати:
О.Болдырева "Крадуш. Чужие души" М.Николаев "Вторжение на Землю"

Как попасть в этoт список

Кожевенное мастерство | Сайт "Художники" | Доска об'явлений "Книги"