Страница произведения
Войти
Зарегистрироваться
Страница произведения

Руководство по Фокалу


Автор:
Жанр:
Изобретательство
Опубликован:
22.04.2025 — 23.04.2025
Читателей:
1
Аннотация:
Нет описания
 
↓ Содержание ↓
↑ Свернуть ↑
 
 
 

Руководство по Фокалу


Одни пишут стихи, другие прозу.

В том числе фанфики на чужие произведения.

А вот я, как оказалось — тоже фанфик,

но на древний алгоритмический язык...


Руководство по Фокалу


Типа от простого к сложному. Сначала про Фокал вообще, а потом про

конкретную его реализацию, Фокал-1А (или Б) сделанную в рамках проекта по

модернизации этого языка.



А на самом деле еще один своего рода акт ревизии сделанного. И выложенного. А предыдущие: якобы дополнение к гл. 4 книжки о Фокале и еще одно такое-же типа дополнение к 4й главе удалил к коврюжьей матери — что-то не нравится мне, как они были написаны...


Это не оглавление, а этапы развития и реализации тамошних идей

1.0 Базовый Фокал (как я его застал у Муравьёва Николая Ивановича)

1.1 Опен "имя_файла" Псвдоним; — чтобы сохранять и загружать программы под ОС

1.2 Всякие удобства: оператор Хелп, просмотрщик к нему, редактор командной

строки с прибамбасами

1.3 Позиционирование: Open (позиция) Псвд; и функция FTEll() чтобы её получить

1.4 Функции FBip FЦвет FKурсор FTmp FМышь FMAx FMIn — чисто красивости

1.5 Усовершенствование операторов: For — по поводу шага; If — чтобы и в

"прямой" строке (т.е. без меток) — с прицелом на скриптовые файлы, где как раз

все строчки — "прямые"; Go — чисто "за компанию", ну и чтобы цикл без счетчика

в одной (тоже "прямой") строке; Ret — чтобы результат из функции возвращать;

Set и Xecut — по нескольку выражений — выпендрёж чистой воды.

1.6 Дополнительные ключевые слова в операторе Write — чтобы выдавал

результаты работы операторов Set, Open, Do... И другую полезную информацию.

1.7 Усовершенствование функций — расширение их функционала (вместо добавления

новых) за счет:

— переменного числа параметров (что уже было, например в FRnd)

— пропуска параметров (при условии сохранения разделяющих их запятых)

— параметра в виде текстовой константы

1.8 Переменное число параметров для спецфункции FSUbroutine и настоящие

локальные перменные.

1.9 Обнаружение в недрах Фокала числового аккумулятора А1 и осознание самой

идеи "аккумуляторности".

2. Реализация графики (оператор Vizual и функция FViz) и откат к Фок-1Б:

отправка её на переосмысление и модернизацию — в силу излишней примитивности

графических примитивов.

3. Обработка ошибок: оператор Break, усовершенствование Quit и Kill

4.0 Обнаружение в недрах Фокала строчного аккумулятора А2.

4.1 Усовершенствование оператора Ask для работы с А2 — спецформаты, ввод

4.2 Усовершенствование функции FCHr для работы с А2 — редактирование

4.3 Write % — вывод из А2, скриптовые файлы: просмотр текстов и переходы

4.4 Write %форм_выражение; регистры; к параметрам "..." еще и ! и :N

4.5 Усовершенствование оператора Modify — перемещение строк, самомодификация

4.6 Шаблоны (анализирующие) в форматном выражении оператора Write %

4.7 Генерирующие шаблоны, протокол -//-

4.8 Перекодировка символов — встроенные шаблоны, передача параметра в %Х

4.9 Средство расширенной перекодировки %r и %R

5.0 Обнаружение в недрах Фокала потокового аккумулятора А3.

5.1 Механизм табулостопов как попытка деления на поля; спецкомментарий : : ;

5.2 Буферизация вывода и двойная буферизация ввода

5.3 Устройства

5.4 Усовершенствование оператора Operate для нужд навигации

5.5 Описания полей структуры — тоже в виде спецкомментария

6.0 Пересмотр графики — разделение её на "растровую" (функции FTочка FLиния)

и "векторную" (оператор Vizual) — для построения графиков по вычисленным

точкам в любых, каких хотим осях.

6.1 "Коллекции"...

.......

многое из перечисленного уже сделано, кое что — еще нет, в том числе даже

как следует не продумано...

Да, кстати: нумерация этапов — условная. Я её вот только сейчас придумал

чтобы в тексте на неё ссылаться. То есть это (увы) не номера версий: многое

стало понятно только задним числом... (В том числе в результате сочинения вот

таких вот якобы "руководств". Для чего они собственно и пишутся...)

Но сначала (1.0) то есть "базовый" Фокал. Поехали!

Фокал — очень, чрезвычайно простой интерпретируемый алгоритмический язык,

изначально предназначенный на роль калькулятора (программируемого): само его

название происходит от слов КАЛькулятор ФОрмул. Предназначен для решения

небольших задач преимущественно вычислительного характера (ну калькулятор же!),

хотя теперь уже не обязательно. Хорош именно тем, что его можно весь целиком

окинуть одним мысленным взором. (По крайней мере базовую его часть.)

Фокал во-многом похож на Бейсик, но рассчитан вовсе не на начинающих (с

сомнительной "опорой на естественный язык"), а на подготовленных пользователей,

хорошо понимающих что делают; лучше продуман, куда более лаконичен и заточен на

экономию всех видов ресурсов, в том числе и мыслительных.

Так же как и Бейсик, Фокал является языком первого поколения:

— работает с данными только встроенных в язык типов. Более того — с одним

единственным: числами с плавающей запятой. (Двойной точности.)

— для управления порядком действий использует метки и переходы к ним.

(Условный, безусловный и к подпрограмме.)

Так же как и Бейсик, Фокал ориентирован на диалоговый режим, когда

пользователь сидит за терминалом и вводит командные строчки. Строка без номера

выполняется сразу; имеющая в начале номер — помещается в память, чтобы её

можно было выполнить потом — по команде в ненумерованной, "нулевой" "прямой"

строке. (А пронумерованные строчки, соответственно "косвенные".) Строки

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

существующая строка с таким же номером — заменяется на новую.

Эти хранящиеся в памяти строки и являются "программой". Хотя конечно можно

заранее записать множество "прямых" строк в какой ни будь файл и переключить на

него ввод: Фокалу без разницы откуда поступают командные строки. (То есть

"скриптовым" языком он был всегда, изначально.) А вот способен ли на такое

Бейсик — даже и не знаю: сильно от реализации зависит...

Так же как и в Бейсике, в Фокале номера строк служат как метками для

операторов передачи управления, так и используются для всяких манипуляций со

строками: сохранения, удаления, вывода на терминал или на печать.

Но в отличии от Бейсика, строки в Фокале нумеруются не целыми, а дробными

числами. В результате программа получается не сплошным массивом строк, а

разбита на группы, в каждую из которых удобно поместить одну подпрограмму,

выполняющую некоторое законченное действие. Соответственно, целое число

обозначает всю группу, а дробное — одну конкретную строку. А вот когда надо

указать всю программу (в операторах запускающих её выполнение Go, Do, выводящем

на печать — Write или стирающем — Eraze) то используют ключевое слово Ales

("всё").

Так же как и в Бейсике, в Фокале командная строка состоит из "операторов",

каждый из которых выполняет одно законченное элементарное (для данного языка)

действие. Каждый оператор обязательно начинается с ключевого слова. И иногда

только из него и состоит. Например Quit ("завершить", "прекратить", "покинуть")

в косвенной строке останавливает выполнение программы, а в прямой — завершает

работу самого интерпретатора Фокала — позволяет "выйти" из него обратно в

операционную систему, откуда он запущен. (Хотя в оные времена выходить было

просто некуда: Фокал, как и Бейсик, работал на "голой" машине и был сам себе

операционной системой.)

Однако, в отличии от Бейсика, в Фокале все ключевые слова подобраны на

разные буквы алфавита. И могут сокращаться до одной первой буквы. (Все

остальные всё равно игнорируются — до ближайшего символа-разделителя, например

пробела.)

В строке может быть как один оператор, так и несколько — сколько

поместится. В Фокале операторы разделяются символом точка с запятой. Внутри

оператора он не встречается. Части оператора, если надо, разделяются запятыми.

А более мелкие вещи — точками: например так целая часть числа отделяется от

дробной: 3.14159265358979323846 — число Пи.

(А вот в Бейсике — когда спохватились, что надо бы разместить в строке

более одного оператора, точка с запятой оказалась уже занята: с её помощью в

операторе вывода PRINT указывали переход на следующую строку. Так что пришлось

разделять операторы чем попало. В разных реализациях и двоеточием и даже

символом (обратная косая черта). А в Фокале в аналогичном операторе Type для

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

забегать вперёд...)

Элементарное (с точки зрения Фокала) действие это вычислить выражение и

как ни будь использовать его результат. Например оператор Type ("печатать")

выводит его на терминал (или туда, куда направлен канал вывода) а оператор Set

("установить" или Save — "сохранить": без разницы — важна только первая буква)

сохраняет в ячейке памяти — "переменной" чтобы можно было использовать потом.

Или еще говорят что он это значение переменной "присваивает".

Переменные обозначаются "именами" — словами, состоящими из букв и цифр и

начинающимися обязательно с буквы. (Впрочем буквой считается всё, что не цифра

и не разделитель. В том числе и значки: # @ $ & | _ ~.) Сейчас в Фокале

действует ограничение — что переменные различаются только по двум первым

символам. (Остальные если есть — просто игнорируются.) Впрочем, для

калькулятора этого впринципе более чем достаточно. (Фокал ненавязчиво приучает

к лаконичности.)

Кстати, в именах переменных все буквы различаются, а в ключевых словах и

именах функций — нет. Например переменные S s С с — разные, а ключевые слова

S s С с это одно и то же "Set", сокращенное до первой буквы.

"Выражение" это изображение формулы, записанное в одну строку. Знаков

операций — пять: + — * / и ^ (возведение в степень). В остальном от обычной

школьной алгебры отличается разве что тем, что нельзя опускать знак операции

умножения. Потому что там любую величину принято обозначь одной буквой, ну

разве что с индексами (или еще какими штрихами/засечками...), а здесь — словом.

Пусть даже и из одной буквы. И если там что то типа: aX+bY/7c понимается вполне

однозначно, то тут придётся писать: a*X+b*Y/(7*c) потому что иначе как узнать,

что 'a' и 'X' это две разные вещи, а не одна 'aX'? Никак!

И еще: там, в алгебре, формула конечно тоже описывает, какие действия над

входящими в неё величинами надо бы выполнить... Но как ни будь потом: часть

величин, возможно, пока что еще неизвестна. А пока что мы эту формулу малость

того... Ну по-преобразовываем например. А то и в другую формулу подставим...

Чтобы здесь так делать — другой алгоритмический язык нужен. Рефал или Лисп.

Или вот есть такая штука "Дериве"... А в Фокале — всё проще: выражение

предписывает выполнить указанные в нём действия вот прямо сейчас, немедленно.

И все указанные в нём величины уже должны быть — лежать по своим переменным.

А если какой нету — будет ой! (Заругается.) Это — чтобы не вызывало удивления

нечто типа:

Set X=X+1

Здесь вовсе не утверждается, что "икс равен самому себе увеличенному на

единицу", чего и в самом деле быть не может. А велено взять переменную икс,

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

Кстати, переменные с индексами — тоже бывают. И обычно называются

"массивами". Хотя вот именно в Фокале каждая из них существует сама по себе,

независимо от других с тем же именем, но другими индексами.


А вот в других языках место под все одноимённые переменные с разными индексами действительно выделяют сразу одним массивом смежных ячеек памяти — для экономии. Но там подобное надо заказывать заранее. А здесь — нет. Более того: в большинстве языков место под обычные переменные тоже надо заказывать заранее — в виде предварительного их объявления. Здесь же переменная создаётся автоматически в момент первого ей присваивания. В пресловутом Бейсике простыe переменнымe как и в Фокале тоже заводятся автоматически, а вот массивы надо объявлять оператором DIM. Но в одной из реализаций Фокала, когда понадобились по-настощему большие массивы, для выделения под них памяти тоже ввели оператор П. И мы тоже об этом подумаем...))


Так как всё в одну строчку — индексы приходится указывать после имени

переменной в скобках. Если их несколько — через запятую. В Фокале их может

быть один или два. (Ну или ни одного, тогда и скобочек не надо;-) Например:

x(1), x(2), x(3)... Скобки, кстати, любые. А их аж четыре вида: ([{<>}]).

y[1,2], y{2,3}, y<3,7>... Это в других языках скобки специализированные.

(Например в Си для индексов — только [], а {} только для кода, а <> вообще не

скобки, а знаки операций сравнения, которых в Фокале просто нет.) Индекс не

обязательно константа, а выражение произвольного вида: x(/4.5)

но используется только целая часть.

Неприятный побочный эффект: возможность обратиться к одной и той же

переменной как с одним так и с двумя индексами. Да еще и зависящая от

реализации. Впрочем, гарантируется что нулевое значение индекса эквивалентно

его отсутствию. Так что и X(0) и X(0,0) это одна и та же переменная X.

А еще есть функции для вычисления всяких синусов с косинусами. Но только

"встроенные": возможности, как в других языках, сделать из подпрограммы функцию

с содержательным именем в Фокале не предусмотрено. Впрочем, предусмотрена

возможность подгрузить в интерпретатор дополнительные функции (оператором

Load), до сих пор, впрочем, так и не реализованная. (Но вот когда реально

понадобятся какие ни будь функции Бесселя...)

Все встроенные функции называются на букву Ф (чтобы опознавать проще).

T.e. не sin, cos, exp, а FSIn, FCos, FExp. А вот переменные на букву Ф

называть нельзя, увы. (Можно, но использовать их потом не получится. Может

по-позже исправим?)

Здесь, да и в названиях операторов, пишу заглавными те буквы, которые

обязательные: функции, как и операторы, распознаются по первым уникальным

буквам. Но на самом деле конечно могут быть любые: хоть fsin, хоть ФсИн — без

разницы. Более того, для некоторых функций, чтобы например не вспоминать как

правильно: FSUbroutine или FSBr — сделано чтобы правильно было и так и так.

Аргумент функции — тоже в скобках после её имени. Если аргументов несколько,

тоже через запятые.


Ну-с, для начала теории достаточно. Перейдём к практике.

Решать задачи типа 2+2 конечно тоже можно, но неинтересно. Нужно что-то

простенькое, но не совсем тривиальное. Например найти корни квадратного

уравнения А-ИКС-квадрат плюс Бэ-ИКС плюс Це равняется нулю ( A*X^2+B*X+C=0 )

с конкретными А, Бэ и Цэ, которые нам дадут потом.

(Да: по-мне, так это тоже банально. Но искать например корни кубического

уравнения по формуле Кардано — громоздко. Да и не общеизвестно: в школе этого

обычно не проходят.)

Инструмент — интерпретатор Фокала. Это один выполняемый ДОС`овский файл

foc.exe и к нему еще один текстовый файл foc.hlp (или foc-1b.hlp) — справка.

Он же — основная документация. Его наличие не обязательно, но оператор Help

не будет работать.

Еще более необязателен файл _fpks.txt, но если он есть, то в нём сохраняются

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

А в файле sav.f интерпретатор обычно предлагает сохранить программу. И если

он есть — при запуске пытается загрузить её обратно. (Но только если в

командной строке при запуске интерпретатора на этот счет ничего не указано.)



В оные времена Фокал работал на "голой" машине и был сам себе операционной системой. Самое близкое к этому — ДОС, который сидит себе в уголочке памяти, предоставляет файловый сервис и больше ни во что не вмешивается. Всё остальное Фокал делает сам или через функции БИОС`a. Версии под другие ОС планируются после отработки основных концепций и получатся они только ограниченные: например функцию FX реализовать там не представляется возможным...


Впрочем, ДОС`овские программы и под виндой работают — до XP включительно.

Запустим интерпретатор любым способом. Например набрав в командной строке

операционной системы: foc

Что видим: черный экран и на нём * (звёздочка). Это интерпретатор ждёт ввода

очередной командной строки. Хотя сперва мог выдать некоторое "приветствие".

(Чтобы не выдавал — см. в файле справки — строки этак с двадцать пятой: там

еще написано "сам себе ини-файл"...)

Вошли — сразу выйдем, набрав оператор Quit и нажав кнопку ввод. (Достаточно

просто Q.)

Но можно было нажать кнопку ЕСЦ. Тогда, если в памяти есть несохраненные

строки, интерпретатор предложил бы их сохранить, сформировав командную строку,

в конце которой — всё тот же оператор Quit. Её можно малость отредактировать,

например стерев это Q в конце. Или изменив имя файла, в котором сохранять. Или

вообще отказаться, еще раз нажав ЕСЦ — командная строка просто станет пустой.

И тогда по следующему ЕСЦ интерпретатор завершит свою работу.

Quit это в любом Фокале, а ЕСЦ — только в данной реализации.

Вышли — запустим по-новой.

Самой первой всегда пишут такую программу, которая бы проявила хоть какую-то

видимую активность. Например написала на терминале "Ха!" Или "Привет, люди!",

или еще какую подобную фразу, но на буржуйском языке. (С русскими буквами

бывают проблемы. В том числе злонамеренно созданные...) А для микроконтроллера,

у которого никакого терминала нет — хотя бы светодиодом помигала, припаянным к

одному из его выводов...

Написать:

Type 2*2

конечно тоже можно, но лучше всётаки:

Type "Привет, люди!", !

Второй восклицательный знак — тот, что за пределами кавычек, организует нам

переход на следующую строку. А всё, что в кавычках — выводится один в один.

(Якобы как пояснение к выводимым числам.) Запятую, которая разделяет эти два

элемента списка вывода, впринципе можно и не писать: и кавычки и

восклицательный знак и пробелы (которые можно вставлять в любых количествах)

сами по себе символы-разделители.

И, да: если нам нужно чтобы в тексте были кавычки — заключаем его в кавычки

другого типа, благо их три: " ' `

Но мы же хотели программу! Поэтому пишем:

1.1 Type "Привет, люди!", !

А потом:

Go

Или Do; или Go All; или Go 1; или Go 1.1; Эффект будет один и тот же.

Впрочем, нет: если вдруг в каталоге был файл sav.f а мы и не заметили, то в

памяти будет его содержимое, и уж что оно сделает... Вот если бы написали

Do 1.1

то точно бы выполнилась только и исключительно строка с номером 1.1, а вот

по другому... (Go — просто передача управления, а Do — подпрограмме, "взаймы".)

Поэтому перед вводом программы часто пишут Eraze Ales; (разумеется E A вполне

достаточно) чтобы в памяти уж точно ничего "лишнего" небыло.

А мы возьмём и проверим:

Write

или:

Write A

Во втором случае перед текстом программы будет добавлен заголовок — у данной

реализации в виде двух строчек комментариев: номер версии и текущие дата/время.

Вот кстати полезный оператор Comment весь остаток строки после которого

просто игнорируется. Предназначен для написания пояснений. Но можно например

взять и "закомментировать" ненужную пока програмную строчку, просто дописав в

её начале букву Ц — чтобы потом, когда понадобится, заново не вводить. Если

текст программы набирается отдельно — с помощью текстового редактора, то

сделать это ничего не стоит. А вот удалить, а потом вспоминать, что в

удалённой строке было написано, да еще и не ошибиться при её повторном наборе...

Но и в Фокале есть оператор Modify чтобы исправлять уже имеющиеся в памяти

строки, а не набирать их заново.

Ну ладно, побаловались и будя. Решаем уравнение A*X^2 + B*X + C = 0

для чего нам дали три конкретных числа и сказали что вот это и есть А, Бэ и Цэ.

Элементарно — через "дискриминант": D = B^2 — 4*A*C; и если D>=0

то x1= (-A + корень(D))/(2*B)

а x2= (-A — корень(D))/(2*B)

Все эти действия можно проделать и на калькуляторе и на линейке

(логарифмической) и просто с помощью карандаша и бумаги, и даже на счетах.

Вот только корень... Хотя раньше в школе учили и корни тоже вручную вычислять.

(А вот нас — уже нет.)

Но вот следующей задачей будет: решить не одно такое уравнение, а сорок

тысяч штук! Типа кто-то выдаст нам файл, содержащий сорок тысяч комплектов по

три числа в строчке. И вот тут уже бумажкой с карандашом не обойдёшься...

Поэтому не ленимся — пишем:

Set A=.....; Set B=.....; Set C=.....

где вместо точек — конкретные, данные нам числа

Set D = B^2 — 4*A*C; Type D

и если число D — неотрицательное, то:

Type (-A + FSQrt(D))/(2*B)

Type (-A — FSQrt(D))/(2*B)

всё — для одного конкретного набора чисел задачу решили.

Так сказать "единичную" задачу. А теперь решим "массовую": сделав из этого

подпрограмму. Предусмотрительно поместим её не в первую, а например в пятую

группу — просто напишем в начале строчек номера. Но пусть всётаки параметры

А, Бэ и Цэ будут не константами, а пусть программа каждый раз просит ввести их

с терминала. Для этого нам понадобится оператор ввода Ask:

Ask A,B,C; или даже: Ask "введите А, Бэ и Цэ: ", A,B,C

Пишем:

5.1 Ask "введите А, Бэ и Цэ: ", A,B,C

5.2 Set D = B^2 — 4*A*C; Type "Д = ", D

5.3 Type " X1,X2 = "

5.4 Type (-A + FSQrt(D))/(2*B)

5.5 Type (-A — FSQrt(D))/(2*B) !

Восклицательный знак в конце — чтобы на следующую строчку переходило.



В нашей реализации есть пул (мешок) для хранения ранее введённых строк. Доступ — стрелками вверх и вниз. Так что скоре всего ничего набирать заново не придётся, только номера в начале дописать. А если что, то и прямо с экрана стащить можно...


Ага. Но если дискриминант вдруг получится отрицательный, то при попытке

вычислить из него корень — будет ошибка: программа остановится, и

интерпретатор заругается. Впринципе сойдёт, но всё же лучше сделать чтобы оно

само проверяло сей факт и писало что ни будь типа: "корней нет".

Для этого нам понадобится оператор условной передачи управления: If(X)a,b,c;

Он сравнивает значение X с нулём и для трёх случаев, когда оно меньше нуля,

равно и больше — передаёт управление на строки с номерами a, b и c. Здесь

запятые — обязательны, скобки — впринципе тоже. А вот эти самые a,b,c могут

быть вовсе не константами, а выражениями произвольного вида.

Кстати, в правильном, полноценном Фокале произвольное выражение может быть

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

операторах перехода (передачи управления) Go, Do, If — получается "вычисляемый

переход", аналогичный например оператору switch языка Си. И даже когда ввода

ожидает оператор Ask — он тоже вводит вовсе не константу, а ищет в полученной

текстовой строке выражение и вычисляет его. (Ну калькулятор же!)

Константа — только номер в начале нумерованной (косвенной) строки, да еще в

таинственной конструкции "формат" оператора Type.


Формат указывает как выводить числа: ширину поля и количество значащих цифр: %N.M, или это может быть одиночный символ % восстанавливающий формат по-умолчанию. Заметим, N.M это не одно дробное число (как в номере строки) а два целых. Потому что например 7.10 и 7.1 это одно и то же число,


но если написать что: Set pi=3.14159265358979324;

то Type %7.10 pi выдаст 3.141592654

а Type %7.1 pi выдаст 3


Итак, рассматриваем условный оператор

If(X)a,b,c

В "базовом" (1.0) Фокале последние из a,b,c могут быть опущены, и тогда в

соответстующих случаях выполняется остаток строки. А в "продвинутом" (1.5)

можно пропустить любые — при условии сохранения запятых. Тот же эффект даёт

отрицательный номер строки. Нулевой — тоже не ошибка, а предписание ничего не

делать. Всё это — чтобы можно было использовать оператор If вообще без каких

либо реальных номеров строк. Например:

If (X) 0, ,0; Type "икс равен нулю"

If (X) ,0 ; Type "икс не равен нулю"

Так как a,b,c — могут быть какие хотим выражения, в том числе и такие у

которых запросто могут быть "побочные эффекты" (например изменятся значения

некоторых переменных), то вычисляется только одно из них. Остальные — просто

пропускаются.

Да, условный оператор Фокала (по сравнению например с Бейсиком) не очень

удобен: сравнивать два числа приходится вычитанием одного из другого. Операции

сравнения > >= < <= = и <> (не равно), как там — куда нагляднее. Но разрешить

их только в условном операторе, как это там и было сделано — нарушение

принципов! (Да и неоправданное усложнение грамматики, а значит и

программы-интерпретатора — бесцельный расход ресурсов, которых и так в обрез.)

А разрешить их везде — так они потащат за собою "логический" тип значений, а

он — еще и дополнительные "логические" операции, не применимые больше ни к

чему. (А за ним и другие типы потянутся...) И всё это ради чего? Ради пошлого

удобства и наглядности... Нам такого счастья не надо!

К тому же бинарная логика — ущербная. Фокал ненавязчиво приучает к

трёхзначной. Или даже к "нечеткой". Но вот логические операции таки нужны.

И если за "инверсию", "отрицание" сойдёт смена знака, то на роль И и ИЛИ в

(1.4) ввели функции FMIn и FMAx просто выдающие самый маленький и самый

большой из своих аргументов. С них кстати и началась эта эпопея (1.7) с

синтаксическим сахаром — что мол надо разрешить функциям иметь произвольное,

какое угодно количество параметров. Де лишние они пусть просто проигнорируют...

Но вернёмся к нашим баранам. Итак, добавляем:

5.25 If(D) 6.1

6.1 Type "корней нет" !

И всё — "массовая" задача решена.

Надеюсь, всё понятно?

И то, что номер строки это вовсе не два целых числа (как склонны думать

некоторые, бейсиком ушибленные), а одно вещественное? Поэтому строчка с

номером 5.25 не окажется в конце группы, а встанет между 5.2 и 5.3

Обратим внимание: наша программа расползлась на две группы. Если запускать

её командой Go, то группа 6 тоже будет выполнена — вслед за группой пять,

чего нам совсем не надо. Поэтому либо запускаем Do 5; либо в конец пятой

группы помещаем еще одну строку с оператором Quit, останавливающим программу.

Но он же остановит нам её и после обработки первой же строки из сорока

обещанных тысяч. Поэтому от 5.6 Quit; воздержимся, a таки напишем: 5.6 Ret

Этот оператор возврата из подпрограммы мы просто забыли написать. Или даже

скорее поленились: понадеялись на то, что группа строк (да и отдельная строка)

и без того "естественная подпрограмма" — возврат управления происходит по

достижении её конца автоматически — даже если там нет оператора Ret. Но это

когда обращение идёт к группе (или к одной строке). А у нас тут запускается

"вся" программа, вот и...


Можно констатировать факт, что и следующая задача тоже уже решена процентов

на восемьдесят. Чего там не хватает? Двух вещей: переключения каналов ввода и

вывода на файл с исходными данными и на тот куда поместить результат. А так же

цикла по строчкам этих файлов.

С циклом всё просто: у каждого файла есть указатель чтения/записи. Сразу

после открытия он в самом начале. А при каждом чтении (или записи) смещается

на размер прочитанного (или записанного). Так что вполне достаточно нужное

число раз вызвать нашу подпрограмму в группе 5:

3.7 Do 5; Go 3.7

и всё.

Такой цикл не завершается никогда. Но в данном случае программа не

"зациклится": как только строчки в файле закончатся — произойдёт ошибка

"конец носителя" и программа остановится сама собою. Что нам и надо.

(В смысле: сойдёт для сельской местности.) И даже каналы ввода и вывода сама

собою на терминал переключит. (Правда заругается.)

А открытые файлы при выходе из Фокала сами собою корректно закроются.

Но сначала их таки надо открыть и переключить на них каналы ввода и вывода:

3.1 Op "файл_с_данными" XR; Op X

3.2 Op "файл_под_результат" YW; Op Y

Вот собственно и всё.

А всё остальное у нас уже написано. Вот правда надо бы скопировать в файл

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

Добавим:

5.15 Type A,B,C

И вот теперь действительно всё — вторая задача тоже решена. Ну разве что

вписать правильные названия файлов с исходными данными и результатом. И,

разумеется, запустить.

Ну и конечно объяснить, что мы такое сделали. Поэтому еще немножко теории.

У Фокала есть один канал ввода и один канал вывода. Оперирует ими оператор

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

Из канала ввода читает оператор Ask (построчно), функция FCHR (побайтно),

ну и сам интерпретатор, когда ему нужна очередная командная строка (тоже

построчно). А в канал вывода пишет оператор Type, функция FCHR и еще оператор

Write. Вот такая у Фокала простенькая система ввода/вывода.

Оператор Modify позволяющий редактировать уже находящиеся в памяти строчки

формально тоже к ней относится. Но не использует каналы ввода/вывода, а

напрямую общается с пользователем — через терминал и клавиатуру. И потому

сильно зависит от реализации.


У нас для этого есть редактор командной строки (1.2) со всяческими прибамбасами, типа "кармана" под фрагмент строки, пула ранее введенных строк и возможности захватить фрагмент с экрана. Ну так Modify использует его напрямую, а например Ask — только когда он на том конце канала ввода.))


Но нам сейчас интересно что у этих каналов на другом конце.

В оные времена, когда Фокал работал на "голой" машине, у неё было всего

несколько устройств ввода/вывода, которые и указывались в операторе Операте

вторым ключевым словом: терминал (Tty) и его клавиатура (Kbd); принтер (Lpt);

так называемая "перфостанция" — ленточный перфоратор (Prf) и пеофосчитыватель

(Rd). И всё.

Долговременное хранение информации (и программ и данных) — на перфоленте.

Главное — каждое из этих устройств работало либо только на ввод, либо

только на вывод. Поэтому путаницы не возникало. И оператор О выглядел так:

Operate Устройство (Например: O L; — переключить вывод на принтер.)

Сохранить находящуюся в памяти программу (или вывести её на печать) — тем

же самым оператором Write, что и вывод её на терминал. Только сначала надо

переключить канал вывода туда, а потом — обратно: O P; W A; O T

Загрузить — еще проще: просто переключить канал ввода на считыватель: O R

(только сперва ленту туда поставить) и Фокал всё прочитает оттуда сам.

В точности так же, как и с терминала читает: ему без разницы. Но вот когда

ленточка кончится — произойдёт ошибка "конец носителя" и Фокал сам собою

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

Поэтому изящнее (чтобы не ругался), когда последняя команда на ленте делает

это сама. Для этого выводим так: O P; W A; T "O K"!; O T

А можно было и "G" дописать — тогда бы только что загруженная программа

сразу бы и запустилась...

А вот теперь у нас вместо перфоленточки, на которой можно карандашом

написать всё что посчитаешь нужным, смотать и положить в коробочку, например

из под сухого "вафельного" торта, присланного тётушкой к очередному празднику

(размеры как раз подходящие) — то, что называется "файл". Впрочем, от

перфоленты качественно мало чем отличается: такая же в точности

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

(Которые мы в операторах Type восклицательными знаками обозначаем.) Разве что

перемотать можно. (Перфоленту — тоже можно, но вручную.) А еще можно писать

поверх того, что уже написано. (Вот это для перфоленты — увы...) Но сначала

надо "открыть", а потом — "закрыть". Ага. Но и перфоленту в считыватель тоже

сначала надо поставить, а потом... Но это же вручную! А тут операционная

система сделает всё это за нас: найдёт в виртуальной коробочке (по имени),

поставит в виртуальный считыватель... Только команды ей подавай.

Главное то, что там считыватель был один, а здесь таких вот "виртуальных

считывателей" — много. (Как минимум несколько.) Фиксированные ключевые слова

для их обозначения не годятся.

(1.1) Поэтому еще в незапамятные времена было принято решение, что во-первых

возложить все эти действия по открытию/закрытию (а потом и позиционированию)

на всё тот же оператор Oper. А во-вторых что второе ключевое слово в нём

теперь будет и не ключевым словом вовсе, а "псевдоним" открытого файла.

Соответственно открываем файл так: Ope "имя_файла" Псевдоним;

Тоесть меж первым и вторым ключевым словом — текстовая константа. Закрываем в

точности так же, только константа — пустая. А переключаем на него канал ввода

или вывода — как и раньше.

А оператор Write с ключевым словом Oper запрягли сообщать — какие именно

файлы на настоящий момент открыты. При чем сообщает он это так, чтобы

получились команды Фокала, которые как раз и откроют эти файлы, если не на

терминал выдавать, а где ни будь сохранить и следующий раз — выполнить. (1.6)

Но файл можно открыть как на ввод так и на вывод. Да и одновременно на то

и другое — тоже. Поэтому в том ключевом слове, которое "псевдоним",

игрорируются не все буквы после первой. Некоторые (R W и еще A) указывают как

именно открывать: на ввод, на вывод, или на добавление — тоже на вывод, но в

конец, добавляя к тому, что в файле уже было. А при переключении — какой из

каналов имеется в виду. Задействованы так же буковки B и T указывающие что файл

открывается как "бинарный" и как "текстовый" (по умолчанию).

Псевдонимы T K L остаются закреплёнными за терминалом, клавиатурой и

принтером. Добавляются еще I O E для стандартных ввода, вывода и вывода

ошибок. А псевдонимы А и Б резервируются для двух первых аргументов в командной

строке, которой запущен интерпретатор: первый — предположительно файл с

программой, а второй — файл под протокол. Поэтому, если есть, открываются на

ввод и добавление соответственно. И на А сразу же переключается канал ввода.

Если аргументов в командной строке нет — делается попытка найти файл "sav.f"

и тоже открыть под псевдонимом А...

(1.3) "Позиционирование": Oper (позиция) Псевдоним;

"Позиция" — смещение указателя ввода/вывода от начала файла в байтах.

Ноль — начало файла, -1 — конец. Скобки обязательны — по ним и.

А чтобы получить текущую позицию — пришлось добавить отдельную функцию,

которую по аналогии с Си-шной назвали FTEll. Но сразу возникла проблема: как

ей указать, позиция в каком из имеющихся в наличии открытых файлов нам нужна?

Указывать ей в качестве аргумента псевдоним из оператора Open было бы

нарушением принципов: опять некий специальный тип данных, который придётся

разрешить использовать везде... Нет уж! Поэтому (по аналогии с FSUbr — см.

далее) было решено, что это будет тот файл, который поучаствовал в последней по

времени операции ввода/вывода. В том числе и фиктивной, как O () X;.

(Позиционирование которое ничего никуда не позиционирует. Потому что в круглых

скобках ничего нет.) Забегая далеко вперёд (5.0) скажем, что вот так и

появился на свет "потоковый" аккумулятор А3...

А потом свежеизобретенную функцию FTEll загрузили дополнительной работой:

сперва сообщать не только позицию в файле, но и его размер — указав ей это с

помощью аргумента. Вернее самим фактом его наличия: свой аргумент, если он

есть, FTEll не просто никак не использует, но даже и не вычисляет!

А потом, с аргументом в виде текстовой константы, запрягли FTEll искать

файлы в текущем каталоге по указанному ею имени. Или "шаблону" — как это

принято в операционной системе, когда вопросительный знак (какового в имени

файла быть не может) заменяет собою один любой символ, а звёздочка — сколько

угодно. Но под шаблон могут подойти имена нескольких файлов. Найти следующий -

с аргументом в виде пустой строки. Если не нашла — вернёт -1.

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

(Вернее конечно есть, но это позиция курсора на экране. И ею занимается другая

функция — FKурсор она же FCS.) И запрягли опрашивать буфер клавиатуры и

нажатость "интересных" кнопок (Ctrl, Alt, Shift).

Потом еще кое что придумали, но это лучше в хелпе посмотреть...

Кстати, пока не забыли: а не помешает ли нам "приглашение" в операторе Ask

в строке 5.1? В смысле — не удалить ли оттуда текст "введите А, Бэ и Цэ: ",

чтобы его не выдало нам куда ни будь сорок тысяч раз. (И, кстати — куда?)

А вот это — от реализации зависит: в базовом Фокале (1.0 и даже 1.1) лучше

бы удалить — во избежание. Но потом было сделано так, что оператор Type выдаёт

свои текстовые константы куда угодно (в смысле — пишет в канал вывода), а вот

оператор Ask — только и исключительно на терминал. И только в том случае, если

канал ввода указывает на клавиатуру. Так что ничего страшного.

Кстати, если этого самого "приглашения ко вводу" в виде текста в кавычках в

операторе Ask нету, то он выдаёт в качестве приглашения символ двоеточие.

Чтобы "подавить" это — надо таки написать: "" (в кавычках — пусто).

Аналогично, оператор Type после вывода очередного числа, добавляет пробел

(чтобы числа не сливались), от которого решительно невозможно избавиться.

Вернее это раньше было невозможно, а сейчас его вывод откладывается до вывода

следующего числа или до конца оператора. А текстовая константа (в том числе и

пустая) его отменяет.

А вот если пользователь ввёл строку, в которой несколько чисел, а оператору

Ask нужно только одно — остальные останутся до следующего раза. А следующий

оператор Ask всё равно тупо выдаст это самое приглашение ко вводу, не смотря на

то что числа для него уже есть и вводить ничего не нужно. Забегая вперёд (4.1)

Ask может очистить свой входной буфер (A %) и тем самым проигнорировав

оставшееся с прошлого раза — велеть вводить по-новой.



* * *


Итак, задачку-то мы решили, но использовали (и продемонстрировали) далеко

не все средства даже базового (1.0) Фокала. Поэтому произведём ревизию,

посмотрим — что осталось за кадром?

Кстати, у нас (1.2) есть оператор Help — так он без аргументов выдаёт

краткую справку (на один экран) — самое начало файла foc.hlp, где как раз всё

что есть в Фокале и перечислено.

Но мы перечислим по-новой:

— вычисления: Set — вычислить выражение и сохранить результат в переменной

Xecut — тоже вычислить выражение, а результат — проигнорировать

Надо бы "екзекут" — выполнить, да буква Е уже занята под более нужный оператор

Eraze. А Xecut — для выражений с "побочным эффектом" (это может быть например

звук, издаваемый с помощью функции FBip), когда нужен вот этот вот побочный

эффект, а возвращаемый функцией результат — нет. Но он тоже не пропадает, а

застревает в некотором "числовом аккумуляторе", он же А1.

— ввод/вывод: Ask — ввод

Type — вывод

Write — вывод текста программы (и другой полезной информации)

Operate — управление вводом/выводом (переключение каналов)

Modify — редактирование строк программы

— обслуживание программы: Go, Do — запуск на выполнение

Write — вывод

Modify — исправление

Eraze — удаление

Eraze команда опасная, поэтому если и Go, и Do, и Write и Modify без

аргументов действуют на всю программу (почти так же как с ключевым словом All)

то Eraze без аргументов удаляет только переменные, освобождая таким образом

память перед новым запуском программы, а всё остальное — не трогает.

— управление порядком выполнения программы: Quit — останов программы

If — условный переход

Go — безусловный переход

Do — вызов подпрограммы

Ret — возврат из подпрограммы

For — цикл со счетчиком

Цикл со счетчиком позволяет что-то сделать указанное (фиксированное) число

раз. Выглядит так: For сч = нач_зн, кон_зн, шаг ; остаток строки...

Где сч — любая переменная, в т.ч. можно и с индексом. Далее — три выражения.

(Или меньше.) Они вычисляются один раз — в самом начале. Начальное значение

сразу присваивается переменной-счетчику. Далее многократно выполняется остаток

строки. (Кстати — как подпрограмма!) А значение переменной-счетчика каждый раз

увеличивается на величину шага. До тех пор пока не станет больше конечного

значения. (С (1.5) решено, что больше чем на пол шага.) Но после цикла

остаётся то, которое было на последней его итерации.

Например (шутка): написать "ку-ку" столько раз, сколько укажут времени

Ask "который час?",N; For i=1,N; Type "ку-ку"!

Если остатка строки под тело цикла мало — его вполне можно вынести в

отдельную группу, написав Do N_группы. Или, если столько места под него

ненадо — продолжить в другой строке, написав Go N_строки;. А вот завершить

таким методом выполнение цикла, просто выйдя за его пределы (как это делают в

других языках) — не получится: тело цикла — подпрограмма. И каждая

программная строка — тоже "естественная подпрограмма". Куда бы мы ни передали

из цикла управление, по завершению строки управление вернётся заголовку цикла.

Поэтому чтобы досрочно из него выйти — следует установить значение счетчика

цикла больше конечного.

Но можно так же остановить программу оператором Quit. Или выполнить т.н.

"структурный переход", породив тем же самым Quit "ситуацию". Но это уже (3).

— другие операторы: Coment — коментарий

Load — подгрузка спецфункций (не реализован)

Kill — ("убить") — сброс (не реализован)

Prepare — распределение памяти (не реализован)

Break — реакция на прерывания (начиная с (3))

? — трассировка и ?? — пошаговый режим

(примечание: отделять ? или ?? от следующего оператора ; необязательно)

Трассировка заключается в том, что текст выполняемых операторов выдаётся на

терминал — чтобы отслеживать по какому пути идёт выполнение программы.

В пошаговом режиме очередной оператор выполняется только после нажатия кнопки

на терминале. Так можно сделать еще один шаг, (в т.ч. как с заходом в

подпрограммы так и без), остановить программу, или продолжить — как с

трассировкой так и без.

В данной реализации из пошагового режима можно так же ввести "прямую"

командную строку Фокала.

Ну вот: для (1.0) операторы — все.

А мы ничего не забыли?

Как запустить программу на выполнение — понятно: любым оператором

передачи управления: Go, Do и даже If.

А остановить как?! (Если сама не останавливается...)

Организовать какую либо ошибку.

— если ввода ждёт оператор Ask — ввести ему некорректное выражение, например

содержащее несуществующую переменную

— если не ждёт, то нажать любые две кнопки: буфер клавиатуры всего на один

символ — будет его переполнение.

— сейчас (начиная с (1.2)) можно еще нажать комбинацию кнопок Cntrl/Ц

(А то например вот такую программку: 1.1 Type fchr(-1)!; Go 1.1

сообщающую коды нажатых кнопок, по другому не остановишь: функция FCHr берет

эти коды из буфера клавиатуры всяко быстрее чем мы нажимаем эти кнопки!)

Ну да, еще — встроенные функции:

FSQrt FExp FLOg — корень, экспонента, логарифм

FSIn FCOS FTAn — тригонометрические прямые

FASin FACos FATan — -//— обратные

FABs FItr FMOd FSGn — модуль, целая и дробная часть числа, знак

FCLk FRnd — таймер, генератор случайных чисел

FCHr FX — посимвольный ввод/вывод, обращение к внешним устройствам

FSUbr — обращение к подпрограмме как к функции (а не к процедуре — как Do)

Вот это и есть весь "базовый" Фокал.



* * *


(1.9) а так же (1.8), (1.7) и (1.4)

То, что на этапе (1.4) сначала напридумывали еще функций:

FTMp FBip FKурсор FЦвет FMышь — дата/время, звук, курсор, цвет, мышь

и сразу же (хотя у меня и написано, что это (1.7)) взялись расширять их

функционалность не вводя новых имён, а "маневрируя" параметрами, коснулось

и уже существующих. Но если для корня и логарифма — всего лишь решили

указывать основание вторым аргументом, то на FSBr отыгрались по полной!

Спецфункция FSUbroutine ("подпрограмма": sub — "под", а routine — рутина

и есть!) это такое-же как и оператор Do обращение к подпрограммме — передача

управления группе или даже отдельной строке "взаймы", т.е. с возвратом: когда

эта группа или строка завершится — управление перейдёт не к следующей за ней,

а к следующему после Do оператору. (Там в конце даже наличие оператора Ret не

обязательно.)

FSUbr отличается от Do тем, что во-первых обращение идёт из середины

вычисляющегося выражения. И после возврата управления, его вычисление будет

продолжено. Во-вторых в подпрограмму передаётся параметр, указанный вторым

аргументом функции FSUbr (первый — номер строки или группы), и возвращается

результат, который и используется в дальнейших вычислениях.

Параметр попадает в спецпеременную & a возвращается значение, которое

было вычислено в последнем перед возвратом из подпрограммы операторе.

Надо это так понимать, что каждый оператор, вычислив очередное выражение,

помещает его результат в некоторую скрытую в недрах Фокала переменную — вот

как раз на такой случай. Эту переменную и будем называть "числовым

аккумулятором" или А1. (Потому что в последствии были обнаружены и другие

подобные аккумуляторы А2 и А3 для значений других типов.)

Казалось бы, оператор, просто делает своё дело, и никакого значения

"вернуть" не может (даже в том случае, когда ему есть что "сказать") — для

этого функция нужна. Вот и пришлось к оператору Опер, перемещающему в частности

указатель ввода/вывода файла, прилагать функцию FTEll, сообщающую его

положение. А к оператору Vizual, переключающему в частности видеорежим работы

терминала, фунцию с таким же именем, сообщающую его. (Ну и еще кое что

по-мелочи...) Потому что заводить спецпеременные — некрасиво это!

Но, как оказалось, оператор таки может положить некий побочный результат

свой деятельности вот в этот аккумулятор. Который, в отличии от переменных,

создаваемых по мере надобности, существует всегда. Но извлечь его оттуда может

только функция FSUbr... Поэтому первое, что ей разрешили, это при отсутствии

первого аргумента не вызывать никакой подпрограммы, а только возвращать

содержимое А1.

Далее: спецпеременная &, куда попадает передаваемый подпрограмме параметр,

в (1.0) — кроме экзотического имени в остальном скорее всего — самая что ни на

есть обыкновенная. (Сам в оные времена проверить не догадался, а и нигде

ничего такого особенного про неё не написано. Значит наверно да.)

Обратим внимание, что все переменные Фокала по нынешним меркам — глобальные.

Здесь нету концепции "операторных скобок", заключающих в себе и ограничивающих

локальную "область видимости". В которой и "обитали" бы эти самые локальные

переменные.

Вот в Фортране — есть. Более того — там вообще нет глобальной среды. А есть

только отдельные подпрограммы, каждая из которых существует сама по себе (и

компилируется отдельно), а между ними — межпланетный вакуум. И кусочки такой

вот глобальной среды приходится создавать искусственно — в виде т.н.

"коммон-блоков", описывая их в каждой из подпрограмм. Типа если имена

одинаковые, то значит и коммон-блок один и тот же. Впрочем, и подпрограммы

друг к дружке тоже по именам обращаются. А что они друг про дружку думают -

в плане списка параметров, и про состав этих самых коммон-блоков — пусть об

этом у компоновщика голова болит! (Того, который разместит всё это в

оперативной памяти и связи меж ними установит...)

Вот в Си внешняя среда очень даже есть: это образ вот той самой оперативной

памяти машины, где все программные объекты и находятся. И фрагменты кода,

оформленные как подпрограммы, и фрагменты данных — как массивы и отдельные

переменные. Вот они существуют всегда, потому и глобальные. Но фрагмент кода,

получив управление, может за одно и получить взаймы еще некоторое количество

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

размещенные там переменные и будут локальными. Да и параметры подпрограмме

тоже передаются через взятые вот так же взаймы ячейки памяти.

А в Фортране параметр — окошко в область данных вызывающей программы. Через

которые можно не только взять оттуда число, но и обратно положить. Или даже

передать управление. (Это если в ячейке области данных — адрес еще какой-то

подпрограммы — вот ей.) В Си для этого честно передаются адреса (указатели).)

А в Фокале нету ничего другого, кроме вот этой вот "внешней среды":

программа, хотя и делится на группы, но эти её части ничем друг от дружки не

отгораживаются. За ненадобностью. И все переменные, соответственно, общие.

Общедоступные.

Но если подпрограмма, запущенная как функция (с передачей ей параметра),

сама захочет обратиться еще к кому-то тоже как к функции — с помощью FSUbr

(даже не обязательно к самой себе), то новый параметр попадёт всё в ту же

переменную & и бывшее там значение будет утрачено, что не есть хорошо.

Чтобы это исправить — надо брать память под переменную & так же "взаймы" как

и в Си, а при возврате из подпрограммы — возвращать. То есть сделать её

по-настоящему локальной.

Так и было сделано.

А под маркой того, чтобы разрешить передавать функциям сколько угодно

параметров (де лишние они проигнорируют), для FSUbr сделана была возможность

завести не одну единственную локальную переменную & а сколько угодно. Но

под номерами вместо имён: это же вообще-то параметры, переданные "позиционным"

способом. (В противоположность "именному".) Вот пусть именем переменной и

будет номер позиции: &0 &1 &2 &3... Вернее сначала сгоряча (и по аналогии с

макропеременными UNIX`овского sh, которые все начинаются с символа $) сделано

было $0 $1 $2 $3... (Ну забыл я, забыл как в Фокале переменная под параметр

называется!) А потом решено было разрешить чтобы и так и так.

И уж коли все они уже пронумерованные, то пусть одновременно будут и

элементами массива &(). (Одномерного.) А в его элемент &(-1) пусть FSUbr

помещает количество переданных параметров. Вернее номер последнего из них.

А то пропускать параметры тоже дозволено (при условии сохранения разделяющих

их запятых).

От обычных переменных, локальные отличаются только тем, что если переменной

нет, то это не ошибка — возвращается ноль. Доступны переменные последнего

"комплекта", но если в нём данной переменной нет — ищется в предыдущих.

Так что теперь и с рекурсией — никаких проблем. В качестве примера

рекурсивной программы обычно приводят вычисление N-факториала. (Хотя по-мне

так его куда проще с помощью цикла: Set x=1; For i=1,N; Set x=x*i

Но традиции...) Так что:

7.1 If(&) 7.2,7.2; X &*FSBr(7.1, &-1); Ret

7.2 X 1 ; Ret; Coment Вызывать: FSBr(7.1, N)

Или лучше и нагляднее: 7.1 If(&) 7.2,7.2; Ret &*FSBr(7.1, &-1);

7.2 Ret 1

Но это только после (1.5) когда оператор Ret малость усовершенствовали вот как

раз на такой случай...

(1.7) улучшение функций. Подробности — в хелпе, здесь только обзор:

— немножко "синтаксического сахара": если у функции ровно один аргумент, то

скобки вокруг него можно опустить: вместо FBip(440) (нота Ля) — FBip 440

— эта самая FBip издающая звуки, сразу была сделана так, чтобы можно было

указать не только частоту, но и длительность. Тогда звук ставится в очередь

(чтобы не отвлекать основную программу). Предусмотрена и возможность проверки

длины остатка этой очереди. А без указания длительности очередь сбрасывается

и звук включается (или выключается: FBip 0) немедленно. Функция доработана

так, что если вместо первого аргумента (частоты) указана текстовая константа,

то это ноты. А если в место второго (длительности) то это текст для передачи

("пропикивания") азбукой морзе. Правда без скорости передачи тоже не обойтись:

пришлось указывать её третьим аргументом. Для нот предусмотрена так же

возможность корректировать их частоты. А то есть "натуральный" звукоряд, а

есть "темперированный"...

— функция FЦвет (она же FCOLor) устанавливающая цвет выводимых символов,

доработана чтобы править VGA-шную палитру. (Но пока там сделано еще не всё что

задумано — отложено до повторной реализации графики.)

— функция FKурсор (она же FCS), перемещающая курсор по экрану, доработана

для выполнения и других действий с монитором — от определения текущих

координат курсора (а так же размеров экрана, что впрочем потом передали фунции

FViz); считывания кода символа под курсором и изменения цвета знакоместа, до

рисования линий символами псевдографики и прокрутки текущего окна вывода.

(Указание размеров и положения которого сразу возложили на функцию FViz.)

— функция FTmp, сообщающая текущие дату и время в виде целой и дробной части

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

манипуляции с датой/временем, включая преобразование её в удобочитаемый вид и

обратно.

— функция FМЫшь (она же FMUso) всего лишь включает/выключает отображение

мышиного маркера, а так же сообщает его текущие координаты и нажатость кнопок.

Заказ порождения связанных с мышью событий — по нажатию кнопок в заданной

области экрана, а так же по пересечению её границ — там же где и указание этих

областей — функцией FViz.

— функция FTEll, сообщающая текущее положение указателя чтения/записи того

файла, который поучаствовал в последней по времени операции ввода/вывода (т.е.

попавшего в аккумулятор А3) доработана... Да свалили на неё всё, что не знали

кому поручить! Включая поиск файлов в текущем каталоге, "добывание" элементов

командной строки и "среды" (которая environ), и даже текста сообщения о

последней по времени ошибке. Так уж исторически сложилось: она была самая

первая...

— функци FX, позволяющая обращаться к управляющим регистрам аппаратных

устройств (а в данной версии только к портам ввода/вывода) и потому весьма

опасная (мой шеф, помнится, придумал "запирать" её функцией FF от случайного

использования) доработана чтобы когда ни к чему не обращается (за отсутствием

первого аргумента, как раз и указывающего что делать) производила побитовые

операции. Впринципе она их и с устройством производит: не только читает

оттуда и пишет, но и проверяет признаки. (Например определяя готовность

устройства к следующей операции.) А нам например надо чтобы считав функцией

FTEll() коды нажатости спецкнопок клавиатуры, выделить оттуда состояние

кнопки "левый Shift". Пожалуйста: FX(,FTEll(),2)

— функция FCHr производящая побайтный ввод/вывод (ввод — если её аргумент

отрицательный: он должен быть последним или единственным) доработана чтобы

могла вернуть обратно уже введённый символ...



* * *


(4.0) (4.2)

— функция FCHr, производящая побайтный ввод/вывод, пишет-то конечно

непосредственно в канал вывода, а вот читает... А вот читает она

непосредственно из канала ввода только в том случае, если пуст входной буфер

оператора Ask. А если не пуст — то из него, пока не исчерпается.

Этот самый оператор ввода Ask читает за раз целую строку (ну Фокал же

строчно-ориентированный!) а вот использует для каждой указанной в списке ввода

переменной только одно число (или выражение). А еще не использованную часть

строки хранит в некотором буфере... Вот оттуда и вынуждена брать очередные

байты FCHr, а то некрасиво получается.

И вот ей поручили вернуть уже взятый байт обратно — фактически сдвинуть

назад указатель текущего байта в этом самом буфере. Пожалуйста: FCHr() (т.е.

без аргументов). И за одно пусть сообщает сколько символов теперь в буфере

осталось.

Ага.

— А не на один, а сразу на N байтов — слабо?

— Запросто: FCHr(,N).

— Ха, а если N отрицательное?

— Ну значит в другую сторону.

— А узнать, сколько там в другую сторону (якобы уже использованных)?

— Тоже можно: FCHr(,).

А общая длина строчки в буфере соответственно: FCHr(,)+FCHr(,0).

Ладно.

— А если аргумент в виде текстовой константы? FCHr ведь коды символов

возвращает, не так ли?

— Ну значит FCHr("Б") вернёт код буквы Б. Но только одной.

— А если их будет несколько: FCHr("БВГД") — код буквы Б вернёт, а остальные

куда?

— А вот туда — в этот самый буфер, чтобы вернуть их при следующих обращениях.

— А если константа будет пустая FCHr("")? А если вслед за ней еще несколько

параметров?...

— А этот самый буфер — не переменная ли, случаем, под одну текстовую строку?

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

которая аккумулятор А2?

— Именно!

Однако, пока что к нему не хватает средств ввода и вывода: мы же с

текстовыми строками работать прицелились? А тексты — вещь громоздкая. Поэтому

храним их исключительно где-то там, за пределами Фокала — в виде файлов

например. А внутрь тащщим только по одной строчке...



Нет, даже вот это — уже большой прогресс! В оные времена по одному символу с помощью FCHr приходилось таскать и под видом массива чисел хранить...))


(4.1)

Вводит-то строку оператор Ask (это же вообще-то его входной буфер!), но

вводит и сам же использует... А сделаем так, что если его список ввода

АБСОЛЮТНО пуст, то только вводит. Если конечно этот его буфер тоже пуст.

А сделать его пустым (на случай, если остаток строки нам почему-то не

нужен) сдедующим незамысловатым образом:

Ask %

(Ничего не поделаешь — изобразительные средства Фокала ограничены, пришлось

стащить "формат" из оператора Type.)

Далее. Если ввод с терминала, то было бы полезно иметь возможность не

заставлять пользователя вводить (ну то, что надо ввести) целиком, "с нуля", а

предложить нечто, с чем он мог бы просто согласиться. Ну или немножко

исправить. Вот как при нажатии кнопки ЕСЦ Фокал предлагает нам командную

строку для сохранения программы... И такая возможность есть! Надо чтобы этот

текст уже лежал в этом самом буфере, но при этом он выглядел пустым. Не был

пустым, а ВЫГЛЯДЕЛ. А пустым он будет выглядеть, если в нём больше нету ни

одного неиспользованного символа. То есть если упомянутый маркер стоит в самом

его конце. Как туда поставить маркер? Да запросто: FCHr(,-FCHr(,0)).

А вот как туда попадёт нужный нам текст? Может по-разному.

Например мы вводим строчки из одного файла, предлагаем пользователю их

поредактировать, и то что получилось сбрасываем в другой файл (когда придумаем

средство вывода). Ага, разбежались! При переключении канала ввода вот с того

самого файла на терминал, А2 автоматически очищается. Ну так надо чтобы не

очищался: добавим ко всяким RWABT в псевдониме открытого файла еще и буковку N.

А вот мы хотим, чтобы это было число, которое вот сейчас и вычислим...

Для этого переведём оператор Ask в особый режим, когда он работает в точности

как Type, но пишет не в канал вывода, а в А2. С помощью "спецформата" %% тоже

очищающего А2 и спецформата %%% — не очищающего (только урезающего по текущую

позицию маркера) — чтобы можно было добавить к тому что там уже есть.

Действует этот особый режим работы оператора Ask до его конца или до ! как

раз и означающего конец строки.



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


Возникнув, эти самые спецформаты %%, %%% и даже %%%% принялись мигрировать,

в том числе обратно в операторы Type и Write.

Впрочем используются они там для вспомогательных целей. Вот есть у нас

такая штука — встроенный просмотрщик. (Аналог UNIX`овского more.) Всего лишь

подсчитывает переданные ему для вывода на экран строчки. И как вывел N штук,

останавливается и ждёт что по этому поводу скажет сидящий за терминалом

пользователь. А он может нажать либо ввод, тогда просмотрщик выдаёт еще одну

строчку, либо пробел — тогда следующий экран, либо ЕСЦ — тогда вывод должен

быть прекращен.

Придумано это для оператора Help когда тексты у него стали дюже длинные.

Для оператора Write он тоже автоматически подключается. А вот для Type — нет.

Но подключается с помощью %%, а с помощью %%% еще и задействуется автотабуляция

чтобы выводимое выстраивалось в стройные колонки. (Прибамбас для вывода

значений переменных с помощью Write Set.) Для чего в недрах Фокала есть

таблица табулостопов (доступ к которой — как всегда с помощью функции FViz).

Например: For i=1,1e9; Type i; будет долго-долго вываливать на экран

числа от одного до миллиарда, при чем рассмотреть что либо не представляется

возможным. А вот: For i=1,1e9; Type %% i; — поэкранно. Но нажатие кнопки ESC

это процесс не прекратит: это надо проверить с помощью FTEll(0) и сделать

например так: For i=1,1e9; Type %% i; If(FTEll(0)),0,0; Set i=1e9

А если там написать: Type %%% "ой", "мама", "еще", "только", i

то каждый из элементов этого списка вывода будет на экране с позиции очередного

табулостопа.

Однако сейчас (5.1) у нас есть : (двоеточие) как раз и предписывающее

переход к следующему табулостопу — эффект тот же самый даже без привлечения

просмотрщика. Так что спецформат %%% в Type стал ненужен. И автор строит

хищные планы...

У этого самого просмотрщика есть счетчик выведенных им на экран строк. Ну

так обычно для каждого очередного оператора Help или Write счет ведётся с

нуля. Оператор Type его тоже сбрасывает — чисто на всякий случай. А вот со

спецформатом %%% — нет. Write %% тоже нет, а Write %%% N принудительно

устанавливает. (Уж не знаю зачем — авось пригодится...)

Спецформат вида %число в операторах ввода, а это Ask и Write с форматным

выражением, указывает "размер записи". Тоесть предписывает прочитать не

"строку" — до символа конца строки, а "запись" имеющую фиксированный (указанный

вот этим числом) размер.



* * *


(4.3) Миграция формата % в оператор Write.

Сброс содержимого А2 в канал вывода будет выглядеть так: Write % 1;

Правда при условии что А2 заведомо не пуст. Иначе потащит строку из канала

ввода. Или лучше: Write % ""; — тогда не потащит...

Однако придумано это для совершенно других целей: облегчения написания

скриптовых файлов. В том числе выдающих на экран длинные тексты, типа

всяких руководств и демонстрашек...

(Да что там греха таить: W % N "метка"; было придумано и реализовано

при попытке похвастаться красивостями (1.4) — написать демонстрационную

программку, рассказывающую о всяких FBip, FЦвет... объясняющую как их

использовать и сразу же демонстрирующую что они делают и как это выглядит.

С одной стороны тексты с объяснениями довольно длинные, а с другой — алгоритм

вовсе не тупо линейный... Там же выявилась необходимость хоть как-то управлять

просмотрщиком, который и выдаёт эти тексты на экран.)

"Командным" или "скриптовым" файлом называют программку, которая

выполняется прямо "с колёс" — по мере чтения из файла, где содержится.

Следует заметить, что "скриптовым" языком Фокал был всегда, изначально: ему

совершенно без разницы откуда поступают командные строки — с терминала или с

какого другого носителя. Терминал "выделенный" — только в плане ошибок.

Да, это куда медленнее, чем из памяти, но не всегда это важно: вот как раз в

демонстрационно-обучающих программах тексты надо выдавать на экран в темпе

восприятия пользователя...

И, кстати, а как выдавать тексты? Элементарно, с помощью оператора Type.

То есть написать текст, а потом каждую строчку заключить в "скобочки": T" и "!

Да, всего по два символа в начале и в конце строки. Но всё равно править такой

текст потом весьма неудобно. (Или надо их убрать, поредактировать и вставить

по-новой.) А нельзя ли без этого обойтись? Можно: поручим оператору Write

(который на строках (программных) как раз и специализируется) еще и копировать

строки из канала ввода в канал вывода. С некоторым преобразованием — для

удобочтитаемости. Каковое традиционно укажем "форматом" вида: %Буковка, который

придумаем потом. А пока % как и в Type — по-умолчанию, то есть один в один.

Сначала — просто указанное количество строк (или до конца файла). Например:

Write % 5

если бы вот это был бы командный файл, выдал бы вот эту и следующие 4 строки

в канал вывода (на терминал, или куда он направлен). Потому как интерпретатор

тоже читает программу построчно: вот прочитал строчку с оператором Write % 5

и указатель ввода/вывода стоит на начале следующей после него строки. Дальше

он вот эти пять строк выведет, а потом в файле будет опять командная строка:

Coment вот в данном случае комментарий...

Но каждый раз строчки подсчитывать... Как минимум чревато ошибками. Поэтому

следующая мысль: копировать до "метки" — уникального текстового фрагмента,

который заведомо не встретится в выдаваемом тексте. Укажем его оператору Write

текстовой константой и поместим в первую после конца текста строку, типа:

=========================== метка_123 =================== (для заметности).

И пусть он ищет такой фрагмент в каждой очередной строке. Если найдено — стоп.

(А строку с меткой уже не выводит.)

Это, кстати, решает еще одну важную проблему: управление порядком действий

в командном файле.

Чтобы можно было писать полноценные программы, одной линейной

последовательности действий — недостаточно. Минимальные средства управления

порядком действий это метки и переходы к ним: в том числе и к подпрограмме

(с запоминанием обратного адреса и последующим возвратом управления по нему).

Кстати, в самом Фокале именно такой минимально-необходимый набор и реализован.

Получить и запомнить адрес текущего места в файле (а вернее начала следующей,

еще пока не считанной строки) — не проблема: Set X=ftell(); (При условии,

что предшествующая операция ввода/вывода была именно с этим файлом. Если

что, для гарантии можно вставить фиктивную — типа: Oper () Псевдоним; )

Так что реализовать что возврат из подпрограммы, что цикл, который — переход

назад: Oper (X) A; или If(условие_повторения_цикла) 0,0; Oper (X) A;

(Наш командный файл скорее всего будет указан первым аргументом в командной

строке при запуске Фокала и следовательно открыт под псевдонимом А.)

Проблема — организовать переход вперёд: откуда бы нам знать смещение

интересующей нас строчки?! Единственная возможность — читать все строчки

подряд и искать в каждой из них вот эту самую "метку". Вот Write % "метка"

это и делает. (А без этого — побайтно читать файл функцией FCHr и с чем-то

сравнивать. Можно. Но сложно, громоздко, ненаглядно...) Единственное: оператор

Write %; не просто читает, а копирует. Но вот есть в операционной системе

такое полезное устройство nul где бесследно исчезает любая записанная туда

информация... Откроем его заранее, например под псевдонимом Z: O "nul" ZW

и тогда преход к метке будет: O Z; W % "метка"; O T; или сначала: O (0) A

но это если эта самая "метка" — раньше (до текущего места). При чем заведомо

раньше, а то найдёт саму вот эту строку... Просто и универсально, увы, не

получается.

А начиная с (4.4), где появились форматные выражения, возиться с

устройством nul уже не обязательно: W %! "метка"; и всё.

Далее: а КАК Write % копирует строки?

Через А2.

Да, в результате этого возникают некоторые дополнительные проблемы, чем

если бы у Write был собственный буфер. Но за-то это одновременно и средство

вывода строки из аккумулятора. И не только...

В частности, при обнаружении метки, содержащая её строка так и остаётся в

А2. И перед следующей операцией приходится его очищать: Ask %; Но и это

можно применить для пользы дела. Например:

Если текст длинный — больше размера экрана, для его вывода подключается

встроенный просмотрщик. Дающий пользователю возможность в том числе прекратить

просмотр текста. (Нажав кнопку ЕСЦ.) В результате файл, откуда его читаем, не

будет перемотан до следующей командной строки. И с этим надо что-то делать.

Узнать, что пользователь нажал этот самый эскейп (ESC), мы можем проверив

содержание односимвольного буфера клавиатуры. (Получить его: ftell(0) при

условии что предыдущая операция ввода/вывода была с терминалом или

клавиатурой.) По-идее он должен быть пуст — содержать ноль. Но если в

просмотрщике был нажат ЕСЦ, то там будет -1, что впрочем тоже означает, что

он пуст. Так что:

Ask %; Write % "_метка_"; Oper () T; If(FTEll(0)),0,0; Write %! "_метка_";

Но ведь если первый оператор Write отработал полностью, то строка, содержащая

текст "_метка_" уже в аккумуляторе. Поэтому можно куда проще:

Write % "_метка_"; Write %! "_метка_";

(Второй оператор Write или найдёт эту самую метку сразу, или таки перемотает

до неё файл.)


Резюме (типа подведём итоги): С одной стороны к (4.3) Фокал стал куда

"более скриптовым". (Это мы еще не рассматривали, как организовать стэк для

адресов возврата из подпрограмм — чтобы обеспечить их неограниченную

вложенность — как на случай, когда вся они в одном файле, так и в разных, при

чем с сохранением возможности автономного их выполнения. Впрочем, это

рассмотрено в разделе <<7>> файла справки (выдаваемой оператором Help),

непомерно в результате разросшимся...)

А с другой — обнаружились средства работы с текстовыми строками. Которых

уже достаточно для решения многих задач невычислительного характера.


* * *

пример ?


* * *

Но средства эти пока что непомерно хилые.



* * *


Прежде чем перейти к (4.4) и начать рассматривать более продвинутые

средства для борьбы с текстами, сделаем лирическое отступление — вспомним, что

мы там, в том обширном списке пропустили?

(1.2) всякие удобства, включая оператор Help, уже упоминавшийся просмотрщик

к нему (всего лишь выдающий текст поэкранно) и редактор командной строки с

прибамбасами. Включающими в себя сначала "карман", чтобы положить туда

фрагмент строки — дабы вставить в другом месте. Потом возможность захватить в

этот карман (или сразу вставить в строку) кусок текста с любого места экрана.

(Слизано с ДосНавигатора.) Потом пул (мешок) ранее введенных строк. В том

числе с возможностью сохранения его содержимого между сеансами работы (в том

самом файле _fpks.txt). Потом еще и "отладочное окно" — чтобы трассировочная

информация не затирала изображение на экране. Удобство, честно говоря,

сомнительное, а в графических режимах работает и вовсе безобразно. В то время

как использование для аналогичных целей файла протокола (того самого для

которого псевдоним Б) так и не вышло из стадии эксперимента.

Как всем этим пользоваться — см.<<1>>, для чего подать команду: Help 1

Или открыть файл foc.hlp (или какой он у текущей версии?) любым редактором

или просмотрщиком и найти там метку <<1>> (обязательно в начале строки).

Оператор Help просто выдаёт на экран разделы этого самого файла справки,

(который вполне себе текстовый), разделенные вот такими метками.

Соответственно: Help АБВГД ищет метку <>. Без аргумента Help выдаёт

краткую справку на один экран. (Самое начало файла.) А если метка не найдена,

то оглавление (список помеченных строк). Кроме <<#>> — эта часть текста в

файле справки — "закомментирована". В первом же из таких разделов — описание

организации самого файла справки.

(1.5) доработка операторов:

Что оператор If доработан для использования в "нулевой" строке, и

оператор Go с ним за компанию — см. выше. (Для Go нулевой номер строки велит

перейти к началу текущей, а для If — наоборот завершить текущую. А вот пропуск

номера строки или -1 — никуда не переходить, а передать управление следующему

после If оператору. И что из трёх выражений в операторе If вычисляется только

одно, а остальные — нет.)

Что шаг в операторе For, если он опущен, определяется автоматически, в

зависимости от того что больше — начальное или конечное значение (а не как

в (1.0) тупо устанавливается +1). Что цикл прекращается, когда значение

счетчика выйдет за конечное значение более чем на пол шага. Что опущен может

быть не только шаг, а вообще всё: For; — тогда тело цикла (остаток строки)

выполняется ровно один раз, но тоже как подпрограмма. Что тоже имеет смысл:

может служить границей распространения ситуации (см.(3)).

Что оператор Do, так же как FSBr, может иметь любое количество

дополнительных параметров, которые тоже передаются подпрограмме и становятся

там начальными значениями её локальных переменных.

Что оператор Do передаёт управление указанной в нём строке точно, а Go и

If — примерно. И если строки с точно таким номером нет — это для них не ошибка.

Что оператор Ret может содержать одно выражение, которое так же как в Xecut

просто вычисляется. (А результат попадает в А1.) Смысла в этом ровно никакого.

Ну разве что наглядно видно, что возвращается — вот это. И еще гарантия, что

никто уж точно не вклинится между вычислением и возвратом и не успеет изменить

значение в аккумуляторе. Может — реакция на событие: (см.(3)) она выполняется

"вне очереди" между операторами, но (вроде бы) А1 сохраняет.

Что оператор ! так же как и во многих утилитах UNIX`а позволяет обратиться

к операционной системе: весь остаток строки — это её команда. Можно например

удалить файл (!del имя_файла): создать то его Фокалу запросто, а удалять не

умеет. Можно перебраться в другой каталог (!cd имя_каталога); можно посмотреть

его содержимое (!dir). А можно запустить например Волков-Командер (!vc) — чтобы

лазить по каталогам с удобствами. А в нём еще и просмотрщик, и текстовый

редактор... ДосНавигатор (dn) запустить скорее всего не удастся — памяти

не хватит. И НортонКомандер (nc) скорее всего тоже. А вот шедевр товарища

Волкова — вполне.

Что оператор Coment игнорирующий всё до конца строки, может нынче

начинаться не только с буквы Ц, но и с "букв" @ # $ & ~ | :

В том же самом UNIX`е есть такое полезное правило, что если выполняемый файл

начинается с символов #! то дальше идёт текстовая строка, указывающая как

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

интерпретатор Фокала.) Но #.... должно для неё выглядеть комментарием.

Вот и. А остальные символы, не являющиеся в Фокале служебными — за компанию.

(1.7) Усовершенствование функций (что мы выше упустили)

Что корню квадратному FSQrt и логарифму FLog с помощью второго параметра

можно указать основание. (В (1.0) логарифм был только натуральный, а корень

только квадратный. И то, при наличии операции возведения в степень...)

Что FATan(X,Y) эквивалентен FATan(X/Y) но гораздо лучше "ведёт себя" в

области значений Y близких к нулю.

Что FMOd (дробная часть числа), с двумя аргументами: FMOd(X,Y) — остаток

от деления X на Y нацело.

Что FRnd (без аргументов — генератор случайных чисел в диапазоне от -1.0

до +1.0, а с числовым аргументом — установка генератора в некоторое состояние),

принимает так же аргумент в виде текстовой строки: FRnd("пароль"), а так же

с аргументом -0 — устанавливает генератор по текущей дате/времени.

(Примечание: здесь использован тот факт, что в данной реализации у чисел

с плавающей запятой мантиса — в прямом коде. И существует два отдельных

значения +0 и -0. А не в дополнительном, где ноль — единый. Возможно, в

других реализациях этот фокус не получится!)

Что FX (доступ к управляющим регистрам внешних устройств) без первого

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

выполняющий побитовое И над вторым и третьим аргументами, с некоторого момента

тоже воспользовался вот этим вот фокусом. И теперь:

FX(, +А, +Б) — И -> А&Б по прежнему проверка битов

FX(, +А, -Б) — И-НЕ -> А&~Б сброс битов

FX(, -А, +Б) — ИЛИ -> А|Б установка битов

FX(, -А, -Б) — ИСКЛ.ИЛИ -> А^Б инверсия битов

Для одного аргумента (при первом отсутствующем):

FX(, +А) -> ~А — инверсия всего 32-х разрядного значения

FX(, -А) -> lg2(А) — номер старшего значащего бита (двоичный порядок)

(а мантиса — в числовой аккумулятор А1: вдруг пригодится)

(2) Про графику говорить особо нечего: оператор Vizual рисовал графические

примитивы, указанные вторым ключевым словом — точку, линию, окружность,

полигон... Однако они оказались излишне примитивны: калькулятору нужно совсем

не это, а возможность не особо напрягаясь, рисовать графики по вычисленным

точкам. В том числе в каких угодно осях, включая и сами оси...

И вообще видимо имеет смысл разделить графику на "растровую" — нижнего

уровня, рисующие вот такие вот графические примитивы по точкам. И включающую

две функции FТочка и FЛиния, организованные по аналогии с функцией FКурсор,

для текстового экрана. И "векторную" — вот этот самый оператор Vizual,

графическими примитивами для которого будут: начало координат, ось, точка

графика и коллекция вот таких точек, составляющая один график.

(3) Обработка ошибок.

Ну это совсем просто: ошибка вызывает прекращение выполнения программы и,

прежде чем "диалоговая" часть интерпретатора, общающаяся с пользователем,

выдаст ему нелицеприятное сообщение, производится последовательный выход из

всех вложенных подпрограмм. Что известно как "спуск по стэку". Оператор Quit

делает в точности то же самое, разве что никто не ругается.

Кто вдруг не знает, "стэк" это "стопка", "кипа". Для наглядности

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

вытащить (и не засунуть!) — положить можно только сверху, взять — тоже. Так

же работает магазин у пистолета или, например, у автомата Калашникова. Поэтому

организованная таким образом память называется еще "магазинной", в

противоположность очереди, или более общей "вагонной" памяти, где как у

состава на рельсах сортировочной станции вагончики могут как добавляться, так

и забираться с обоих концов.

Во всех (уважающих себя) алгоритмических языках вот по такому "стэковому"

принципу организована та самая штука, где сохраняются сведения о вызывающих

друг дружку подпрограммах. Потому что понадобятся эти "адреса возврата" тоже в

обратном порядке от того, как их туда складывали: самый последний — первым.

У Фокала тоже есть такой стэк. И знать о нём совсем не вредно: это в его

ячейках как раз и размещаются списки локальных переменных.

И вот в текущую (самую верхнюю) ячейку этого самого стэка возвратов,

принадлежащую выполняющейся в данный момент подпрограмме, оператор Break

помещает "ловушку" на ошибку или на "ситуацию". (Различаются они только

номерами.) Эта ловушка содержит две вещи: номер ситуации и "реакцию" на неё:

Break N_ош = N_стр_реакции; Возможно "нулевую" (т.е. ничего не делать):

Break N_ош; Можно сразу несколько номеров ситуаций: Break N1,N2,N3 = реакц;

Реакция — адрес подпрограммы, которая и будет устранять последствия ошибки.

Но это может быть и остаток текущей строки: Break N_ош = ; другие операторы...

Тогда в этот раз он разумеется не выполняется.

А оператору Quit разрешили порождать ситуацию не только с тем волшебным

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

аргументом (если он есть).

А остальные аргументы (если есть) — будут переданы вот этой вот

подпрограмме реакции — станут начальными значениями её локальных переменных.

А еще может быть аргумент в виде текстовой константы: сообщение об ошибке,

которое интерпретатор и выдаст пользователю (если не найдётся подходящей

ловушки). В прямой (нулевой) строке наличие текстового аргумента, даже

пустого, предотвращает завершение работы интерпретатора. (А то оператору Quit

только дай!...)

Ловушка поставленная оператором Break, плюс ситуация, порожденная оператором

Quit и составляют в совокупности упоминавшийся "структурный переход".

Позволяющий в частности досрочно выйти из цикла. Однако, выглядит всё это

примерно так:

— подпрограмма ставит ловушку и выполняет какие-то действия. Если всё

нормально, ловушка никак не влияет. И с завершением подпрограммы, удаляется

так же как и локальные переменные.

— если одно из действий (в чём бы оно ни заключалось), породило ситуацию,

ловушки на которую не предусмотрено, выполнение подпрограммы сразу аварийно

завершается. Ситуация "распространяется" на подпрограмму, вызвавшую данную,

потом на предыдущую... Пока таки не встретится подходящая ловушка, или пока

управление не вернётся к "диалоговой" части интерпретатора, и та матерно не

сообщит пользователю, что обо всём этом думает.

— если возникла ситуация, а ловушка — подходящая, она "срабатывает";

выполняется реакция, якобы исправляющая последствия этой ошибки,

и столь предусмотрительная подпрограмма сразу же нормально завершается

(возвращает управление в точку её вызова) так, как будто никакой ошибки небыло.

Однако, а это ли нам нужно? Например в строке 3.5 у нас цикл, где мы

что-то ищем в большущем массиве А[], циклически перебирая его элементы. И вот

наконец нашли! Рассматривать остаток массива не требуется; параметр цикла как

раз и указывает найденный элемент:

3.5 Break 12; For i=1,N; If( годится_ли{ А[i] } ) 0,0; Quit 12

Здесь "годится_ли{...}" — конечно условность. Но это вполне может быть вызов

подпрограммы (с помощью FSBr) и в самом деле проверяющей — годится этот

элемент или нет. Всё хорошо, просто замечательно, вот только строки 3.6, 3.7 и.

т.д. выполнены уже не будут — выход из подпрограммы произойдёт немедленно. А

мы сделаем так:

3.5 For; Break 12; For i=1,N; If( годится_ли{ А[i] } ) 0,0; Quit 12

(См. чуть выше — там где (1.5).)

Ошибки 12 в Фокале заведомо нет.

Примечание: номера ошибок (и ситуаций) — дробные числа: ошибки тоже

объединяются в группы. И можно поставить ловушку как на одну конкретную,

так и на всю их группу.

Имеющиеся в наличии группы ошибок:

0 — действия человека за терминалом (например 0.3 — нажата Ctrl/C)

1 — синтаксические ошибки (например 1.4 — дисбаланс скобок)

2 — арифметические ошибки (например 2.1 — деление на ноль)

3 — ошибки ввода/вывода (например 3.2 — конец файла)

4 — прочие ошибки (например 4.3 — нет переменной)

5 — недостаток ресурсов (например 5.1 — не хватает оперативной памяти)

Полный список ошибок см.<<6>>

Однако, кроме "ситуаций" (внутренних) бывают еще и внешние по отношению к

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

Или кончился отправленный на воспроизведение звуковой фрагмент... Можно

конечно заставить программу всё время опрашивать — не кончился-ли? Но вообще-то

вот именно на такой случай и существуют прерывания. "События" Фокала это их

аналог.

Ловушку на событие ставит всё тот же оператор Break. Отличие в том, что

номера событий — отрицательные. И целые. Ловушки — глобальные. Реакция

обязательна: её отсутствие как раз и предписывает прекратить отслеживание

данного события.

Так как интерпретатор Фокала — медленный, события именно что

"отслеживаются": с каждым из них связан счетчик. И при возникновении события

он наращивается. А реакция запускается если он не-ноль при первой же

возможности (которая может представиться очень не скоро): подпрограмма реакции

запускается "вне очереди" после конца выполнения очередного оператора. (А

некоторые выполняются — ну очень долго! Например: Set x=FCHr(-1) ждёт до

тех пор пока пользователь кнопку не нажмёт, или пока байт из линии связи не

поступит.) В самой реакции выполнение реакции на другие события запрещается.

(Вот если она в свою очередь вызовет другую подпрограмму — там уже можно.)

Счетчик событий передаётся подпрограмме реакции первым аргументом. (Попадает

в локальную переменную &.) И сбрасывается.

Искусственно создать событие можно с помощью оператора Kill. Это без

аргументов он должен бы устраивать сброс компьютера, что в данной архитектуре

неприемлемо. И поэтому не реализовано. А вот первый аргумент указывает ему

номер порождаемого события. (Знак числа игнорируется.)

Так как под ДОС`ом первые 255 событий соответствуют аппаратным прерываниям

компьютера, а некоторые из них — обращения к функциям БИОС`a и операционной

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

процессора, и через них же возвращают результат, то в операторе Kill второй

и следующие аргументы сначала помещаются в регистры, а потом — в локальные

переменные текущей подпрограммы. (Без первого аргумента — сразу в переменные.)

Признак C, через который обычно указывает успешность обращения — в А1.

Номера собственных событий Фокала начинаются с 1000.

Полный список событий — см.<<6>> — там же где и список ситуаций.




* * *


(4.4) Форматное выражение в операторе Write %_форматное_выражение_

"Встроенные" форматные преобразования вида %Буковка — должны были быть

прежде всего "перекодировками": преобразованиями символов из виндовой

кодировки, попадающейся буквально на каждом шагу, в ДОС`овскую, с которой

только и работает Фокал. (Ну и обратно разумеется.) Чуть по-реже встречается

"уникод", КОИ-8 еще реже, а бывают и вовсе экзотические... А еще преобразование

меж большими и маленькими буквами (заглавными и строчными), меж русскими и

латинскими...

Правда с виндовыми текстами большая проблема: "сверхдлинные строчки".

Вишь конец строки они делают только в конце абзаца — де программное

обеспечение этот текст само на строчки разобьёт при отображении. (А то и шрифт

не "моноширинный" и ширина окна, куда всё это будет выводиться, неизвестно

какая... А места в памяти у них там под виндой немеряно, не то что у нас — с

ограничением на размер буфера в 122 байта...)

Но пока эта проблема не решена, пока суд да дело... Надо всётаки уметь

делать с текстовой строкой в А2 ну хоть что-то! Тем паче "маркер", указывающий

"текущую точку" в А2 у нас уже есть — был всегда, так же, как и сам А2. Ну

хотя бы кое-что вставить (туда где маркер): *"кое-что" и соответственно

удалить: /12 (в данном случае — 12 символов). И, разумеется, передвинуть

маркер как вперёд +3 так и назад -4. Совершенно необходимо так же уметь ставить

маркер в начало ^ и в конец $ строки. И иметь возможность это самое кое-что

найти: +"кое-что" и -"кое-что" тоже двигаясь в разные стороны от текущего

положения маркера. Всё это — хотя-бы с константами.

Это и будет самый минимальный набор операций: вставка, удаление, перемещение

и поиск. (Который сперва был только в одну сторону — без плюсов и минусов, как

впрочем и перемещение.) Но в результате плюс и минус превратились в префиксы.

А маркер приобрёл еще и размер — чтобы указывать найденный фрагмент, а не

только его начало. И теперь делит строку на три части: то что под маркером, то

что перед ним и то что после. Основным операциям, (которых по прежнему всего

две: * и /) эти префиксы теперь указывают к чему их надо применить. Так: +*

вставляет перед маркером, -* после, а без префиксов — вместо. А удаление, если

ему не указали количество символов, удаляет одну из этих трёх частей. А вот

если указано, то... Например в А2: "123456789 абвгдеёжз 123456789 ийклмнопр"

маркер стоит вот в этой позиции: ~~~~~ (попал он сюда: ^13~5)

/3 удалит:


* * *

-/3 удалит:


* * *

+/3 удалит:


* * *

Сразу и заранее предупреждаю: форматное выражение оператора Write это

всегда одно "слово": как только встретился конец оператора, например точка с

запятой, или запятая, разделяющая его части, или даже просто пробел — значит

форматное выражение кончилось. (Эти символы могут быть там только в составе

текстовых констант. То есть в кавычках.)

Поэтому в роли пробела там символ '_' (подчеркивание) — пустая операция,

которая ничего не делает, за-то всегда успешно.



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

Вернее, дело обстоит так: чтобы немедленно прекратить выполнение форматного выражения есть операция '!', которая еще и вывод текущей строки запрещает. (А если надо всё наоборот: строку вывести, а выполнение форматного выражения продолжить — есть операци '.' (точка).) А для того, чтобы в зависимости от успешности/неуспешности выполнения очередной операции поступить по-разному — есть "постфиксы" '&' '' '?'. Например в: 'x&y' 'xz' 'x?yz' операция y выполняется если x выполнилась успешно, а операция z — если нет. Но во всех случаях обработка строки после этого продолжается. А вот если её таки надо завершить, то: 'х&'. (Другие варианты тоже грамматически правильны, но смысла не имеют: 'x\' это то же самое, что просто 'x'; 'x?y' то же самое что просто 'xy'; а 'x?z' делает то же что и 'х&'.

Но это мы таки сильно забежали вперёд... Вернёмся к нашим баранам.


Вот например надо во всех копируемых строчках заменить "Вася" на "Петя".

Запросто: Write %"Вася"*"Петя" (изначально '+' был необязателен)

W %^+"Вася"*"Петя" (^ чтобы точно с начала строки)

— Ага, один раз. А если слов "Вася" в строке несколько?

— Нам нужен префикс повторения!

Ага.

— Но префиксы обычно действуют только на одну, следующую за ними операцию.

А у нас их здесь две: найти и заменить.

— Значит еще нужны скобки, группирующие несколько операций в одну.

Вот где-то так: Write %@(+"Вася"*"Петя")

Замечательно.

— А когда этот наш цикл кончится?

— Когда +"Вася" больше ничего не найдёт.

Ага.

Отсюда идея, что если очередная операция выполнилась неуспешно, то на

этом — всё: выполнение форматного выражения прекращается. (Ага: это для текущей

строки прекращается. Write сбрасывает её в канал вывода, берёт следующую из

канала ввода и всё по-новой...)

— А если всётаки надо продолжить?

— На этот случай введём "постфиксы" '&' и '' которые берут этот самый статус

успешности/неуспешности предыдущей операции и либо выполняют либо пропускают

следующую. То есть '&' если да, а '' если нет. А еще универсальный '?' сразу

для обоих случаев.

(Сначала я его было сделал как скобки — по аналогии с условным выражением

? : языка Си, задействовав в качестве закрывающей скобки символ '|', но потом

таки одумался: просто две следующие операции, а скобки у нас и так уже есть.)

Кстати о птичках, то есть о скобках: префиксы + и — (в отличии от @)

действуют на операции только "персонально". Поэтому +(...) не даст никакого

эффекта: префиксу действовать не на кого. И он выполнит свою исходную миссию:

сдвиг маркера на одну позицию.

Ах, да: уж коли у нас маркер приобрёл размер, то надо бы и его научиться

устанавливать: ~45 (вот — в 45 символов). А так же ~$ и ~^ — от текущего места

до конца и до начала строки. (А еще ~. — до "метки", но про метку — потом...)

А кроме этого операция вставки устанавливает маркер по размеру

вставленного, поиска — по размеру найденного, а вот перемещение и удаление его

размер обнуляют.

В том числе и сдвиг на 0 позиций. Но просто 0 или -0 оставляют маркер там,

где он был (только размер у него теперь будет нулевой, как и после любого

перемещения), а вот +0 таки его сдвигает — на размер самого маркера.

А что это мы всё без молока да без молока? В смысле — исключительно с

константами? У нас здесь язык программирования, или я где?! У нас же

переменных полно — пусть пользу приносят! (Чем мы хуже кота Матроскина?)

В общем текущее положение маркера... А вернее размер любой из трёх частей,

на которые он делит строку в А2, сохраняем в переменной следующим образом: =Х

+=Х и -=Х чтобы потом применить (вместо числовой константы) как #Х (где Х -

одна буковка). И пусть в миру (то есть в самом Фокале) и имя у этой переменной

будет такое-же: #Х. Мы еще не забыли, что всякие # $ & |... там тоже считаются

буквами, и такое имя — вполне допустимо?

Получились еще две операции: '=' и '#' — "сохранить" и "применить",

нуждающиеся в уточнении, где именно сохранять (и что потом применять). В том

числе текущее положение маркера в некотором тихом месте: =. чтобы потом

обменять их с маркером местами: #. т.е. это вот та самая, уже упоминавшаяся

"метка". Штука, кстати, весьма удобная...

Вот, значит, какая задача: копируем строки с помощью оператора Write %...

и если в строке есть символ : (двоеточие), то само это двоеточие удалить, а

текст перед ним перенести в конец строки и "отгородить" от её содержимого

символом ; (точка с запятой). (Это мы ассемблерную программу покалечить хотим:

превратить все метки в комментарии.)

— Ага, разбежались! Надо где-то сохранять вот этот переносимый фрагмент

строки. (А то придётся таскать по одному символу...)

Поэтому по аналогии с переменными для чисел, введём "регистры" для таких

вот текстовых фрагментов. И обозначим их цифрами 0..9, а в остальном — всё то

же самое. И сразу договоримся: что если пишем фрагмент в седьмой регистр =7 то

в переменную #7 пусть автоматически записывается его размер. Вот теперь:

Write %+":"/-=1-/$*';'+*#1

Непонятно? А навставляем _ (аналог пробела), пронумеруем и прокомментируем:

Write %___+":"___/___-=1___-/___$___*';'___+*#1

0 1 2 3 4 5 6 7

0. думаем, что изначально маркер стоит в начале строки (так оно и есть)

1. ищем символ "двоеточие" (если не найдём, то на этом всё и кончится)

2. удаляем найденное

3. сохраняем в регистре 1 всё, что перед маркером (было перед двоеточием)

4. всё что перед маркером — удаляем

5. ставим маркер в конец строки

6. вставляем туда точку с запятой

7. после того что под маркером (а там только что вставленная ';') вставляем

фрагмент, ранее сохраненный в регистре 1

Пробуем.

Фокус не получился — факир был пьян! Нет, с терминала-то всё нормально,

а вот из файла... Там оказывается в конце каждой строки символ перехода на

следующую. (Даже два: перевод строки и возврат каретки.) Вот после них мы

этот текст и пристроили! А редактор командной строки, с помощью которого мы

набираем строчки на терминале, оказывается таковых не вставляет. Надо бы не

конец строки искать, а вот их...

Забегая вперёд, скажу, что это будет выглядеть: +{!}

Write %___+":"___/___-=1___-/___+{!}___*';'___+*#1

1 2 3 4 5а 6 7

Пробуем. (Но сейчас уже только с файлом)

Опять не слава богу! Нет, всё сработало, кроме последней строки, где

оказывается нету перехода на следующую. Вот там +{!} ничего не нашла и

соответственно операции 6 и 7 уже не выполнились.

Ладно, пишем так:

Write %___+":"___/___-=1___-/___$___-{!}____-*';'___+*#1

1 2 3 4 5 5б 6 7

Здесь: 5 — ставит маркер в конец строки и 5б — ищет этот самый переход на

следующую, но от конца к началу, а если не нашла, то конструкция _ (не

делающая ничего содержательного) просто не даст прекратить выполнение.

Ну вот сейчас наконец-то всё отработало правильно...


Выражения: {...}

{!} — это вот как раз было оно, причем экзотического "текстового" типа...

Для универсальности одних переменных недостаточно! Тем более, что в нашем

распоряжении вся хилая мощь КАЛькулятора ФОрмул! Но фокаловское выражение

"изолируем" от форматного с помощью фигурных скобочек. И используем как

числовой параметр целую часть его значения. В {...} кроме выражения (которое

там должно быть только одно) могут быть символы _ (подчеркивание) в роли

пробела и конструкции =Х ("присваивание", где Х — буква) чтобы при

необходимости сохранять вычисленное в переменных.

Разумеется, в таком выражении ничто не мешает использовать функцию FSBr,

которая вообще-то обращение к подпрограмме. И таким образом для каждой

очередной строчки выполнять любой код... Но даже если она вернёт ноль — размер

маркера таки попортит... (Впрочем, дело поправимое: =Х{fsbr(....)}~#X)

Выражение текстового типа устроено в точности так же, но отличается тем,

что начинается с чего-то однозначно текстового, результатом имеет строку, а

в присваивании =N должна быть цифра, указывающая номер регистра чтобы её

сохранить. Единственная операция — сцепление элементов ("конкатенация")

специально никак не обозначается. А числовые значения, которые там тоже могут

присутствовать, обозначают коды символов.

Ради них по большому счету это и затеяно: ну нету в Фокале концепции

"экранирующего символа". Как например в Си (и вообще в UNIX`е), где это

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

включая самоё себя и огораживающие эту константу кавычки, так и коды,

графических обозначений просто не имеющие. (И потому известные как "слепые".)

Так уж исторически сложилось, что коды символов в Фокале сами по себе: с

ними работала исключительно функция FCHR, а текстовые константы тоже сами по

себе — исключительно для общения программы с пользователем. И вместе сошлись

они вот только сейчас. Вот и.

Еще в текстовом выражении кроме констант вида "..." и кодов символов,

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

#N и :NNN, где N — номер регистра, а NNN — номер программной строки.

(N — одна цифра, а NNN — выражение общего вида).


Конструкция {...} понравилась и тут же нашлось множество желающих её

использовать: @{...} #{...} ={...} %{...}

Префикс повторения конечно же самый первый! (Куда уж без него...) Нет,

если всего лишь надо указать количество повторений — нет проблем. Например:

@12+*'*' вставит ровно двенадцать звёздочек. (+* а не просто * потому, что

оно не вставить, а заменить норовит — то, что под маркером. А под маркером то,

что вставлено в прошлый раз, т.е. предыдущая звёздочка.)

А вот полноценный заголовок, с переменной-счетчиком, начальным, конечным

значением и шагом — всё как в операторе For i=0,44,2;.... — @{0,44,2=i}....

— Ага-ага! А как же запятые?! Их же в форматном выражении — низзя...

— Так ведь в фигурных скобочках. Там в выражении и они и в обращении к функции

могут быть и между индексами... Ну ладно, поелику это и в самом деле вроде как

нарушение принципов, от сего момента заменим все запятые на '|' хотя пока

(временно!) разрешим оба варианта. Тогда аналог For i=0,44,2;.. — @{0|44|2=i}..

— н-да, а с запятыми-то лучше смотрится... Привычнее...

И к тому же символ | это ведь буква? Ну да — не цифра и не разделитель.

И назначать её разделителем только вот ради этого случая... В общем нехорошо

получилось! Вернём-ка мы всё это в зад. Но попозже...

Заголовок цикла может быть и в виде текстовой константы. Он сопоставляется

с фрагментом строки начиная с текущего положения маркера. И отличается

возможностью дополнительно содержать ^ и $ указывающих начало и конец строки.

Если учесть, что конструкция из заголовка цикла и постфикса (без "тела

цикла") — суть условное выражение, где заголовок цикла отрабатывает ровно один

раз, проверяя условие. То это получился некий сурогат шаблона. Которых тогда

еще небыло. К тому же строчное значение, не будучи чьим-то правым операндом,

даже без + (как сейчас) так и норовило пройтись по всей строке...

Конструкция %{...} это обращение к одной из программных строк, номер

которой указан выражением в скобках. Выражение — только числовое. (Хотя по

аналогии с :номер_строки в строчном выражении и может начинаться с двоеточия.)

Смысла в текстовом выражении в данном случае нет ровно никакого: к подпрограмме

в регистре N обращаемся: %N, а модная нынче "лямбда" (где она есть), дающая

анонимную подпрограмму, нужна вовсе не для того, чтобы эту подпрограмму сразу

же и выполнить (что нам мешает сделать эти действия просто так?), а чтобы

заховать куда-то на черный день, или передать кому-то в виде параметра...

Как и любой уважающей себя подпрограмме, этим тоже полагаются параметры.

Хотя бы один. В качестве которого — следующая после %N или %{...} конструкция.

Она не выполняется и доступна в "подпрограмме" как текстовый фрагмент (без

ограничивающих её скобочек или кавычек, если конструкция составная) с помощью

#%. Хотя можно и выполнить: %%.

Обратим внимание: кроме %{...} и %N может быть еще и %Х (где Х — буква).

— Ну и чем же это %Х отличается от просто Х?

— А вот этой самой возможностью получения следующего за ним параметра.

Вообще-то сложилось забавное положение: всё наше форматное выражение

задумано как Write %Буква; — каковая буква укажет одно из встроенных

преобразований, которых до сих пор... (Вот именно!)

А Write %_какие_то_операции_; — это чисто от бедности. Однако, если среди

них эта самая Буква таки встретится... То должна отработать то, что ей

поручено — применительно к тому фрагменту строки в А2, который в этот момент

под маркером. Для чего сделано так, чтобы в самом начале — когда строчка

только-только прочитана, маркер стоял в её начале и включал её всю.

Но теперь вдруг появилось два варианта: просто Буква и %Буква, отличающийся

вот как раз наличием параметра... Которого, как оказалось, нам так не хватало!

Дело в том, что предполагалось, что все эти встроенные преобразования будут

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

(ДОС`овской)... А вот захотим мы преобразовать из одной кодировки в другую...

Можно конечно и в два этапа — сначала в текущую ДОС`овскую, а потом из неё. Но

честно говоря, это не есть хорошо: в ДОС`овской может например не быть

некоторых символов, которые есть в этих двух — в обоих. А тут пожалуйста: %Xy.

Но выглядеть это должно: Write %_%Xy; потому что спецформаты %% и %%%

успели и в оператор Write мигрировать! Да и Write %Число тоже что-то такое

значит, по-этому лучше бы: Write %_Число или Write %+Число...

Отвлечемся на секундочку:

— мы же говорили, что везде, где по смыслу требуется число, в Фокале допустимо

выражение. Даже когда ввода ожидает оператор Ask... А вот тут что — не число

разве? Константа!

— ну так это и есть "таинственная конструкция формат", однако.

— да, но всё-же... Вот если это самое число (что бы оно там ни значило) мы

получим только в процессе вычислений?

— да, нехорошо получается... Однако вот как раз для этого и есть такая вещь,

как "самомодификация" — хорошо, что мы наконец о ней вспомнили...

В общем получается, что параметр "определяемым пользователем" аналогам

подпрограмм %N и %{:NNN} достался чисто "за компанию" — исключительно ради

соблюдения принципа, что если что-то можно кому-то то можно и всем. Далее,

заметим, что префиксы + и — на % не действуют. (Чисто потому, что я так и не

придумал, как передать в подпрограмму факт наличия перед ней такого префикса.

Параметр-то этот, и то со скрипом...) Поэтому встроенное преобразование Х в

форме -Х и +Х можно примерить к тому что и до и после маркера, а %Х — нет.

А к перекодировкам мы еще вернёмся...

Конструкции #{...} и ={...} — форматные преобразования.

— еще более форматные, чем те форматные выражения, коие щас рассматриваем?

— ага!

Преобразуют число, вычисленное заключенным в {...} выражением в его

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

={...} переменную. Там же вслед за ней должен (может) быть формат, указывающий

как это сделать. (Если нету — используется тот, что и в операторе Type.)

Организованный на Си-шный манер — как в ейных функциях printf() и scanf():

% ширина_поля буковка. (Без пробелов, естественно; "буковка" — одна буква, а

"ширина_поля" — числовая константа, состоящая из циферок. Буковка: d i o u x

для целых чисел, e f g — для плавающих, c s — пока ровно один байт: ширину

игнорирует. (А зря-а-а!)

Вообще-то у нас такими вещами оператора Ask и Type занимаются. Но что

делает например Ask? Берёт (да — тоже отсюда) ВЫРАЖЕНИЕ и вычисляет его!

Причем выражение это должно быть грамматически правильное. А все константы в нём

исключительно десятичные... (Ну калькулятор-же!) Ага. А в его аналоге ={...}

выражение нельзя, но за-то можно и восьмеричное число и шестнадцатиричное...

Подробнее — грамматику см. <<Ш>>

Сейчас надо наконец закончить с форматными выражениями. И тогда можно будет

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

упоминавшейся "самомодификации"...

Там и осталось-то: . и !

— потому что все остальные символы уже использовали?

Ну в самом деле: пробел, запятая и точка с запятой — табу! Кавычки и

цифры — для изображения констант. Буквы — под будущие "встроенные"

преобразования. Плюс и минус — префиксы, * и / основные операции; ^ и $

перемещение в начало и в конец строки; = и # установить и применить; круглая

"Абезьяна" (она же "сАбака") — @ префикс повторения, & ? постфиксы (провека

условия). Подчеркивание _ аналог пробела, надчеркивание ~ (она же "тильда") -

установка размера маркера; процент % известный своей склонностью устраивать

заговоры, тайные общества и прочие форматы — и тут чем-то подобным занимается

(вот сейчас только обсуждали); скобки... А вот скобки, в отличии от кавычек,

все используются по-разному: круглые () для группировки операций в одну

составную; фигурные {} для заключения в них фокаловских выражений, квадратные

[]... Вот квадратные пока что зарезервированы на будущее. (На них обширные

планы, хотя пока что весьма и весьма туманные...) И наконец угловые <> которые

по сути своей и не скобки вовсе, а скорее кавычки-"ёлочки" (остальные -

"лапки")... Вот как раз в таком качестве (4.6) к делу и пристроены...

Ну так что осталось? Да, всего четыре символа . : ! | (если не считать

некий "забой", ввести который — затруднительно, а использовать — стрёмно).

Восклицательный знак: "прекратить немедленно!". Завершить выполнение форматного

выражения (а с префиксами — и всего оператора), при чем вывод вот этой строки

отменяется. Точка — всё наоборот: вывести текущую строку и продолжить дальше.

Символ | планируется использовать для возврата конца строки — на случай если

вдруг ввели слишком много: вот есть у винды такое уже упоминавшееся неприятное

свойство — делать сверхдлинные строки с целый абзац размером... А символ :

(двоеточие ) — так же как и в операторах ввода/вывода для перехода к другому

полю... Но пока что оба — резерв главного командования.

Ну-с, с форматными выражениями практически всё — всё готово к пришествию

встроенных преобразований и шаблонов.



* * *


А в это время в замке у шефа...

В смысле в самом Фокале...

В самом Фокале вдруг осознали, что кроме "..." оказывается есть еще две

конструкции, которые в операторах ввода/вывода Ask и Type тоже считаются

строчными константами: ! обозначающий конец строки и тихой сапой пролезшее туда

двоеточие, предписывающее переход к следующему полю... Вернее табулостопу,

каковые вдруг завелись, чтобы оператор Write со вторым ключевым словом Set мог

выдавать значения переменных в несколько колонок. (А то по одной штуке в

строке, да еще когда их много — типа массивы какие ни будь...)

Вернее осознали не само их наличие (тоже мне новость!) а что таки есть куда

их запрячь: обозначать аккумулятор А2 и регистры форматного механизма :цифра.

(Как мы видели, в самом форматном механизме всё наоборот: :NNN это одна из

строк программы, правда только в {...}, а регистры: #N и =N. Но там-то они

как раз доступны непосредственно...

Вспомним, что нынче текстовые константы могут быть не только в Ask и Type,

но и еще в некоторых других операторах. (Начиная с Operate.) А так же могут

передаваться как параметры некоторым функциям — тем, которые "знают" что с этим

делать. Ну так теперь всем им можно не только "..." передать, но и содержимое

А2 или регистра.

И за одно, чтобы служба мёдом не казалась, добавили оператору Write

ключевое слово Reg — чтобы сообщал: а что это у нас сейчас в этих регистрах

находится? (А то делать что либо вслепую... Неправильно это!)

За одно, ему-же, и оператору Eraze — тоже аргумент в виде текстовой

констаны ("..." ! :N) чтобы сообщали/удаляли не все переменные разом, а токмо

вот этой вот якобы константой указанные.


(4.5)

А оператору Modify (который теперь можно смело называть "Move") сделали

форму "с присваиванием": чтобы он мог перемещать программные строчки из одной

группы в другую. А за одно между регистрами и аккамулятором А2, или например

записать в любое из них текстовую константу (буде она справа от знака =).

Потому как вдруг обнаружили, что программные строчки от любых других строк

ровно ничем не отличаются. И их грамматика проверяется только и исключительно в

процессе выполнения. А в остальное время там можно хранить абсолютно всё что

вздумается. Хотя вот пока только и исключительно "печатное". А то попадётся

байт с кодом ноль — и привет: все будут думать, что строка на нём кончается.

(Так что и в этом смысле пресловутый уникод — вещь предельно похабная: любой

пробел включает байт с кодом ноль.)

Кстати, ему (оператору Modify, а не уникоду) и без присваивания разрешили

использовать аргумент в виде текстовой константы — так же как и операторам

Eraze и Write. Но если для Eraze и Write таким образом указываются имена

переменных — чтобы не все скопом удаляли / выводили на печать, а только

указанные, то оператору Modify, занимающемуся вообще-то говоря редактированием

строк программы... И чтобы вдруг текстовая константа (которая нынче и не

константа вовсе) — дело пахнет керосином! Заговором! Самомодификацией!

Впрочем, предлог был совершенно невинный — всё дело, мол, в отладчике.

Встроенного отладчика в Фокале небыло никогда — за ненадобностью. Каждая

строчка и без того "естественная подпрограмма". Запускай их поштучно

оператором Do и смотри, что они сделают. А чтобы посмотреть, куда оператор If

передаст управление — есть "трассировка". Включается (и выключается)

оператором ? (вопросительный знак).

Гладко было на бумаге...

Вернее вот как раз на бумаге (когда терминалом была пишущая машинка) весь

этот неудобочитаемый результат трассировки, можно было, оторвав этот листок

(длинною метра полтора) и разложив на столе, анализировать со всеми удобствами.

В том числе писать на нём всё что вздумается, стрелки например на выведенном

туда же тексте программы, как на карте рисовать: "айнэ колоннэ марширт рейхс,

цвайте колоннэ марширт линкс!...", или например выделять интересные места

цветными маркерами...

Твёрдая копия — великая вещь! Но бумаги не напасёшся. И шумно.

Но с пришествием дисплеев стало с этим делом как-то совсем кисло. На экране

маненький кусочек бумажного листа помещается. (Ну что такое — 24 строчки?!)

И не напишешь ничего, и даже руками не потрогаешь — только глядеть... И, глядь,

всё интересное, не успеешь оглянуться, уже уползло за край экрана!

(Надо бы конешно сваливать трассировочную информацию в файл — вот второй

в командной строке как раз для этого: под протокол. Но вот как-то до сих пор

не.)

Выход — вот этот вот "пошаговый режим" (включается — ??) — та же самая

трассировка, но на выполнение следующего оператора Фокал ждёт разрешения от

пользователя. Впрочем я сделал, что он сначала выводит текст оператора, потом

ждёт и только дождавшись — выполняет. (Мне так показалось логичней.) А еще я

сделал возможность ввести и выполнить командную строку. "Прямую". Чтобы

например посмотреть значение переменной. Распечатать кусочек кода. Или даже в

хелп заглянуть... (И включаемое оттуда же "отладочное окно" — чтобы этим

изображение на основной части экрана не портить...)

Но в отличии от диалоговой части интерпретатора, нумерованных строчек там

вводить нельзя. Не понимает оно такого авангардизма. Поправить уже

существующие строчки и с помощью Modify можно. При этом можно даже номер

строки изменить. Но добавить новую — увы. Вот и пришлось сделать из оператора

Modify полный аналог диалоговой части интерпретатора...

Но теперь с помощью этой фичи программа может править сама себя!

Смертельный номер! Русский самоубийца! Только у нас! Только один раз!

(Или что там еще кричат цирковые зазывалы в подобных случаях?) Но и в самом

деле самомодифицирующийся код это эффектно, эффективно, смертельно опасно.

При том, что возможность для самомодификации программ была в Фокале всегда.

Изначально. Но при отсутствии перезаписываемых и перематываемых внешних

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

заранее написанный код. Для этого надо переключить канал ввода на содержащий

его носитель и остановить программу. Управление получит диалоговая часть

интерпретатора и всё это сама собой подгрузит. Но там в конце должна быть

прямая команда чтобы запустить программу по-новой.

А при наличии файловой системы уже на уровне (1.1) даже при отсутствии

позиционирования, можно было окрыть файл на запись, записать туда нужные

команды, добавить прямую строку для повторного запуска, закрыть и открыть на

чтение. После чего тоже переключить на него канал ввода и остановить программу.

А вот теперь даже внешний носитель без надобности.

Зачем нужен этот цирковой фокус? А вот для тех самых (уже немногих)

оставшихся случаев, когда (увы-увы) в программе должна быть именно константа.

Например завершить работу интерпретатора находящаяся в памяти программа

никак не может: для этого надо выполнить Quit из прямой строки, а у неё — все

косвенные. А вот теперь — пожалуйста: 10.7 Modify "Quit"

Чуть более сложный случай: установить формат для оператора Type — %N.M где

N и M не константы, а числа, находящиеся в переменных с такими вот именами:

Ask %% "Type %", N, ".", M; Write %^ 0; Modify !

Здесь мы воспользовались спецрежимом оператора Ask, предназначенных для

формирования "начального значения" для предъявления пользователю. В котором он

работает в точности как Type, но весь вывод попадает не в канал вывода а в А2.

Однако, маркер при этом всё время в конце, все символы в А2 — как бы "уже

использованные", а сам он — выглядит пустым. Поэтому мы сначала сдвигаем

маркер хотя бы на одну позицию назад (а в данном случае ставим в начало А2) и

только потом используем как текстовый аргумент в операторе Modify, который это

и выполнит.

Обратим внимание на нелепость пресловутой "опоры на естественный язык". Нет,

для самых-самых начинающих это еще имеет смысл — при условии что это их родной

язык, а не хрен знает чей. Далее оно только мешает. А в данном примере

целенаправленно вводит в заблуждение: оператор "Ask" здесь ни у кого ничего не

спрашивает, "Write" никому и ничего не сообщает, "Type" ничего не печатает, а

"Modify" соответственно ничего не модифицирует. Гораздо понятнее в данном

случае будет, если написать: A %% "T %"N'.'M; W %^ 0; M !

Наблюдая этот цирк, мы проглядели самое интересное: теперь у нас,

оказывается, есть переменные не только для чисел но и для строк. Правда

напрямую из форматного механизма записать туда ничего нельзя (ибо нефиг!),

только с помощью оператора Modify (он же Move). Но ничто нам не мешает с

помощью FSBr... (См. выше.)



* * *


О кодировках символов и встроенных преобразованиях для оных

Буковки, циферки и прочие знаки препинания для машины — просто числа

(коротенькие целые), а картинки — изображения буковок, которые мы и видим на

экране или на бумаге, просто этими числами пронумерованы. Способов, которыми

нумеруются буковки — превеликое множество. Для каждого языка выдумана свой

собственный. И часто не один. Для русского — штук пять или шесть. Вот они то и

называются "кодировками".

(Не путать кодировки со шрифтами! Разные шрифты это разные картинки для

одной и той же буквы, а разные кодировки — разные числа для её обозначения.)

Та, которую использует Фокал это 866-я или "ДОС`овская" кодировка.

(Далеко не лучшая, но зато в ней есть все символы псевдографики.) Еще

постоянно приходится буквально запинаться об "виндовую" — 1251, и так

называемый "уникод", которого к тому же еще и несколько разновидностей.

Частенько встречается так-же КОИ-8, активно использовавшаяся у нас годах в

семидесятых — девяностых. (По ГОСТу 19768-74, а семибитная КОИ-7 аж еще с

шестидесятых: ГОСТ 13052-67.) В UNIX`e (а Linux это тоже разновидность UNIX`а)

это стандарт де-факто. Братья-славяне давятся кодировкой 855, тоже ДОС`овской,

где славянских (в том числе не нужных в русском языке) букв больше чем в 866,

но расположены они весьма неудобно, а псевдографика — не вся. Комитет ИСО

было расстарался эти самые кодировки хоть как-то стандартизировать, и пытаясь

угодить всем, как тот слон из басни, учинил их аж шестнадцать штук. Но в

основном для себя любимых. А для нас — ISO 8859.5 старшая часть которой почти

без изменений и вошла в уникод, но сама по себе что-то не больно используется.

Ну так там псевдографики вообще нету.

Общее у всех перечисленных в том, что все они — расширения так называемой

ASCII. И все (кроме уникода) — восьмибитные. Семибитный ASCII у них у всех -

четыре младших "страницы" по 32 символа. (В семибитном КОИ-7 — три.)

Представляет так же некоторый интерес ебздиковская производная ДКОИ,

использовавшаяся на машинах ряда ЕС-ЭВМ. (Сдуру содранных с IBM-360/370, что

небезызвестный Дейкстра назвал величайшей победой запада в холодной войне.)

И совсем уж древняя и кстати безымянная кодировка (хотя кое кто называет её

"УПП") по ГОСТ 10859-64, интересная в частности тем, что её можно было

использовать не только в семибитном, но и в шести, пяти и даже четырёхбитном

вариантах. Но содержащая только заглавные буквы и заточенная под Алгол-60.

Заслуживает упоминания так же некая кодировка "МИК" минского НИИ ЭВМ. И та,

которая используется в яблочной машинке "Макинтош". (Две последние — расширение

ASCII, а две первые — свои собственные.)

Из кодировок, не содержащих русские буквы, интерес представляют например

Radix-50, включающая всего 40 символов, но позволяющая упаковать их по три

штуки в два байта. Еще телетайпетная МТК-2 (вот там то как раз русские буквы

есть) и разные виды "транслита"...

Можно подумать, что вон мол буржуи хорошо со своей ASCII устроились...

Впринципе сначала действительно так и было задумано. Однако, латинский алфавит

недостаточен ни для одного языка, который его использует: там всегда есть

дополнительные буквы, при чем у всех языков — разные. (А кому дополнительные

буквы не нужны, тем латинский алфавит не годится вообще! Потому как на-поверку

пишут они иероглифами, которые и изображают непроизносимыми комбинациями из

латинских букв. Или таки получаются слова, но совершенно другого языка — вот

например как для аглицкого.) А в этом самом ASCII место после латинского

алфавита, где бы эти дополнительные буквы можно было бы разместить (хотя и

только для кого-то одного), уже давно занято другими, тоже нужными символами.

Без которых например в языке Си ну никак не обойтись. Да и сама эта ASCII,

действительно организованная более-менее разумно — вовсе не американская, как

нас всех уверяют, а предположительно у фашистов стащена. Их нативная,

известная как "ебздик" (EBCDIC) просто безобразна и тоже существует минимум в

шести несовместимых друг с дружкой вариантах. В точности как и сам аглицкий

язык: исходный примитивизм (та самая "простота", которая хуже воровства),

желание здесь и сейчас облегчить себе жизнь, неизбежно порождает хаос и

переусложнение на следующем уровне.

Но потом появился "уникод"... Типа решили взять вообще все какие ни есть

символы и пронумеровать двадцатиразрядными (!) числами. Нет, сначала — 16-и

разрядными, что было бы еще ничего. Но потом спохватились, что шестидесяти

пяти тысяч (!) позиций для самых редких китайских иероглифов может и не

хватить... (Хотя, полагаю, скорее беспокоились о заделе на будущее.) Но как ни

тужились, смогли заполнить только около ста тысяч позиций из этого миллиона...

А сначала вообще пытались тупо узаконить имеющий место хаос, просто выделив под

каждую буковку два байта и разместив в одном её код, а в другом — номер

национальной кодировки. Наплевав на то, что большинство из них различается

всего несколькими символами...

Так что двух байтов под символ уникода видите-ли мало, а четыре — явный

перебор, да и расточительно, но бывает. Ну так оно еще может быть как старшим

так и младшим байтом вперёд! А главное — в старших байтах частенько — ноль.

В общем — маразм крепчал...

Однако, разумные люди нашлись и там — придумали то, что называется Утф-8,

вещь вполне приличную, но увы — существенно неравномерную...

Однако, это только способы записи (хранения, передачи) вот этих вот номеров

символов. Не путать с самой нумерацией! А вот сама она — гигантизм и

узаконенный хаос, пусть даже и немножко причесанный: нормальный человек

способен с толком использовать дай бог тысячу символов; ну китайцу,

предположим, нужно раз в десять больше. Но там штук сорок одних только

звёздочек (тогда как нужна только одна), да и псевдографики невесть сколько

сортов, а вот кое-чего, самого, казалось бы, очевидного — нет! Нет, я понимаю,

что им надо на что-то тратить избыточную производительность: кризис

перепроизводства всё нарастает... (Это когда даже если все ходят в рванье и

впроголодь, полки в магазинах всё равно ломятся. Потому что потребителям,

которые всё это и производят, денег раздают меньше, чем стоит то, что они

произвели...)

Но эти растратчики ресурсов нам не указ! Нам нужно совсем другое...

Вот в данном конкретном случае нам нужна такая (промежуточная,

вспомогательная) кодировка, которая была бы не просто перечнем символов (как

тот же уникод), а позволяла бы легко распознавать их "сорта", а так же

преобразовывать их друг в дружку. Вот например как в КОИ-8, где все виды

букв — в одном и том же алфавитном порядке. В результате определить тип

буквы — по одному или двум старшим битам, а преобразовать — их изменением.

Что предполагает такую же "страничную" организацию. Но во-первых пусть

каждый алфавит занимает свою страницу целиком. (А не как в ASCII.) А во-вторых

пусть страница будет не 32, а 64 символа. Что, полагаю, будет достаточно не

только для любого алфавита со всеми его необходимыми расширениями (но без

излишеств!), но и для слоговых типов письменности. Для алфавитных письменностей

в первой половинке страницы разместим "основные" буквы, а во второй -

"дополнительные", типа нашей Ё, которыми, если что, можно пожертвовать,

преоброазовав их в "основные". А все полезные значки, включая цифры, скобки и

прочие знаки препинания — в "нулевую" страницу. При чем из "слепых" символов

только те, которые по-настоящему необходимы для организации текста. А вовсе

не для управления удалённым оборудованием, как в той же ASCII...

Это:

— пробел — для разделения слов

— ПС (перевод строки — n) для разделения строк

— ** (код 0х17) "конец абзаца" — для разделения абзацев (параграфов)

— ПФ (прогон формата — f) для разделения страниц

а так же:

— ГТ (горизонтальная табуляция — t) для разделения полей (ячеек таблицы)

— ВТ (вертикальная табуляция — v) её аналог, но по вертикали

и кроме того:

— ВШ (возврат на шаг — b) для наложения символов

— ВК (возврат каретки — r) для наложения строк

— ** неразрывный пробел нулевой ширины — для образования лигатур

— ЗВ (звонок a) для привлечения внимания (хотя по-мне он тут лишний)

— АР2 (авто-регистр 2, он же "эскейп") начало сложных многосимвольных команд

Еще на нулевой странице предполагается заразервировать три кода (2 3 и 4)

под начало и конец текста, а так же конец файла, пакета или что там у нас?...

И еще три (отличающиеся от них одним битом) — под команды переключения страниц.

(Символы с кодами 3 и 4 — это небезызвестные Cntrl/Ц и Cntrl/Д.)

Вот предположим у нас линия связи. Некоторые, работающие в асинхроном

режиме, например по "старт-стопному" протоколу, постоянно находятся в

состоянии когда нету передачи данных. А те, которые в синхронном — должны

постоянно что-то передавать в линию, обозначая "наличие несущей". (Пропадание

которой означает обрыв канала связи.) Ну так в этом качестве передаётся

неразрывный пробел нулевой ширины с кодом 0х16, начало собственно передачи

обозначается символом с кодом 1, а конец (и переход вот вот в такой режим,

только поддержки синхронизации) — соответственно 4.

Начало файла обычно изображать не надо, надо только конец: некоторые

файловые системы выделяют место с точностью не до байта (как UNIX), а только

до блока. Поэтому код 4 там востребован, а код 1 — нет.

Но полученный из линии код 1 как правило указывает на начало заголовка

пакета. Тело пакета начинается с кода 2 и завершается кодом 3, после чего

например хвостовик с контрольной суммой. (Это от пользователя, сидящего за

терминалом, ничего такого не требуется, а вот от оборудования — да.) А мы

будем считать, что код 3 (Cntrl/Ц) переводит систему в некий управляющий

режим. Где можно, например, указать тип кодировки, или способ её упаковки, или

что-то еще нужное и важное. (Вдруг например китайцы захотят перевести систему

в режим три байта на иероглиф...) После чего код 2 опять переведёт систему в

состояние текста.

А три (из четырёх в нулевой странице ASCII) команды — резервируются для

переключения страниц: да, описываемая кодировка — сугубо вспомогательная,

условно девяти— или даже двенадцати-битная (я еще не решил) и "наружу"

вытаскивать её не планируется... Однако, это вполне возможно. Но в имеющем

нынче хождение восьмибитном байте помещается только четыре страницы. Три из

которых предполагается сделать переключаемыми. (А нулевая — соответственно нет.)

На "непереключаемый случай" в качестве страницы 1 — псевдографика, а 2 и 3

— "рукописный" (системный) алфавит — русские буквы в алфавитном порядке в

первой половине страницы и несовпадающие с ними по написанию латинские — во

второй на тех же позициях. Плюс специфические буквы разных языков в свободных

позициях, в соответствии с их произношением. (Буква Q — под Щ, как и

полагается.) Однако для заглавных и строчных букв это соответсвие несколько

отличается...

— Ишь размечтался! Когда это еще понадобится? А вспомогательная кодировка

нужна уже сейчас. И сейчас в ней пока что шесть страниц: по две на алфавит,

псевдографика и вышеописанная "нулевая". (Куда упихано всё нужное из ASCII,

что не буква — на вышеописанных принципах.)

Однако, к такой кодировке требуется и средство проверки/изменения вот этих

битиков, по-возможности универсальное. Потому что если каждую из кодировок мы

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

преобразование (из указанной в текущую или наоборот) — это еще можно придумать:

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

А вот если попутно понадобится сделать что-то еще... (Например все заглавные

буквы в перекодируемом тексте сделать строчными.) Не знаю как это обозначить

так, чтобы самому не запутаться. Вот и нечего огород городить: промежуточная

кодировка с вышеописанными свойствами и честные побитовые операции!

Забегая вперёд, скажем, что это средство уже таки есть. Даже два.

— два средства?

— ну вот так оно получилось — ей богу совершенно неожиданно. Сперва "реверс"

понадобился: чтобы байтики в обратном порядке переставить. А так же битики в

каждом байте указанного (маркером) фрагмента. (Заметим, это РАЗНЫЕ операции:

чаще всего нам надо переставлять буковки — превратить "ТОПОР" в "РОПОТ",

а "ROMA" что значи Рим в "AMOR", что значит любовь. Но бывает что надо и

битики...)

Вот и ввели две операции 'r' и 'R' — для каждого байта в отдельности и

для всего фрагмента разом. Ага. А если (в форме '%r' и '%R') передать им еще и

аргумент... А вот ничего путного в этом случае не получается. (Группами по N

байт, чтобы один вид уникода в другой преобразовывать... Глупости! Префикс

повторения нам на что?!) В общем решили поручить временно безработному '%r'

выполнять те самые побитовые операции. Указав такую операцию аргументом. Но

куда конь с копытом, туда и рак с клешнёй! Тоже безработный '%R' возжелал

делать всё то же самое, но не с каждым байтом персонально, а разом со всем

фрагментом!

А еще были сделаны встроенные операции 'о' и 'О' ("объединение"), которые

сами по себе ничего не делают — только предписывают следующей после них

операции поиска не различать большие и маленькие буквы, а так же русские и

латинские. Их вариант '%о' и '%О' — тоже указывает разные вещи. Например

включить или выключить (с аргументами + и -) это самое отождествление навсегда.

(И если например включено '%о+' то теперь 'о' его временно выключает.) А с

числовым аргументом (типа '%о123') указывают вид уникода: входного — единицы и

выходного — десятки. Сейчас (а это уже не первый вариант) 1 — utf-8, 2 — utf16,

4 — utf32... А три? Три — трёхбайтный вариант! Далее 5 — опять utf16, но другим

концом вперёд. То было младшим, а это — старшим. 6 и 7 — аналогично. 8 и 9

сейчас тоже utf16, но без т.н. "суррогатных пар", каковые в данном случае

просто игнорируются, но это не окончательно: про транслит забыли!... А вот

нулевое значение — признак, что пока неизвестно. То есть для входного-то

по умолчанию utf-8 (а для выходного — такой же как входной), но если встретися

символ с кодом FEFF (неразрывный пробел нулевой ширины) вот тут мы и узнаем...

Для входного. А вот как узнать, что мы узнали — увы, пока не придумано.

Очень даже придумано! *<%О> (Но 'О' здесь — обязательно О-русская).

Забегая вперёд: это так называемый "шаблон". Он же "регулярное выражение".

При чем шаблон — "генерирующий". Потому что является правым аргументом

операции вставки * (звёздочка). А конструкция %О в нём вот как раз и генерит

текст типа "%о123", указывающий установленный сейчас вид входного и выходного

уникода. (А если бы 'О' была латинская — выдавало бы что-то типа: "%о+" или

"%о-" — состояние признака объединения заглавных букв со строчными. Или "%О+"

"%О-" — -//— русских с латинскими, буде 'О' была заглавная.)


Других встроенных операций (не перекодировок) на настоящий момент пока тоже

не придумано. Но — договорились... (Кто и с кем? Ну естественно я с самим

собой!) ...Что пусть все перекодировки обозначаются исключительно русскими

буквами: маленькая (строчная) — к нам, в текущую кодировку, которая, напомню,

у нас ДОС`овская — 886-я. А заглавная — соответственно от нас в этой буквой

указанную. Потому как преобразовывать приходится исключительно русские буквы:

латинские во всех кодировках — одна и та же ASCII... Это чтобы небыло

путанницы. А латинские соответственно зарезервируем под другие встроенные

преобразования, буде они когда появятся...

Выбор самих буковок... В основном очевиден: 866 досовская — 'Д'; 1251

виндовая — 'В' ('W'конешно смотрится лучше...); КОИ-8 — 'К'; уникод — 'У';

Исо-8859.5 — 'И'; тоже досовская 855 для братьев-славян — 'С'; ебздиковская

производная ДКОИ — 'Е'... А для кучи национальных вариантов никаких букв не

напасешся — так и придётся выдавать их во временное пользование. (Так же как в

операторе Опер мы буковки вновь открытым файлам "сдаём в аренду".) То есть

придумать, как описывать если не кодировку целиком, то хотя бы отличия такого

варианта от одного из базовых. Или хотя бы загружать заранее заготовленное.

(А то у нас оператор Load так до сих пор никак и не...) Но потом.

Ой, а про "полукорреляцию" то совсем забыли!

Чуть выше я сказал, что мол других встроенных операций (не перекодировок)

на настоящий момент пока больше не придумано... Оказывается, это не совсем так.

В отличии от поиска, операция '%п' (вернее 'п'-латинское — ну ведь

договаривались же!) ищет не весь указанный ей в качестве аргумента фрагмент

целиком, а только одинаковые слова — самый большой совпадающий кусок. И не во

всей строке, а только в её части, выделенной маркером. А просто 'п', которой

аргумента не положено — "автокорреляционная функция" указанного ей фрагмента с

самим собою. Но центральный её максимум — игнорирует. (Разумеется, сам с собою

фрагмент заведомо совпадает, но это неинтересно.) Если буква заглавная — то

критерии более строгий: игнорируются так же наложения частей одного слова. То

есть в "мамамама" 'p' найдёт слово "мамама" а 'P' — нет. (А слово "мама" — да.)

А вот название досталось по-наследству от проекта Фокал-3. Там

предполагалось пойти обычным путём полиморфизма: ввести еще один тип данных

"строка". (Даже два: еще и "пустое" значение, любая операция с которым вызывает

ошибку. Оно содержится в переменной, которой еще ничего не присвоили.) Хранить

значения всех этих типов в одних и тех же переменных, обрабатывать одними и

теми же операциями... Проект возник, когда придумалось как доопределить для

строк имеющиеся в Фокале пять операций: ну то, что строка+строка это их

сцепление ("конкатенация") — тривиально; что умножение и деление строки на

число это её размножение (повторение указанное количество раз) и разрезание на

части — не трудно догадаться буквально в течении пяти минут. А потом еще минут

за десять додумать конкретику: что будет если числовой операнд нецелый, меньше

единицы и вообще отрицательный? (Отрицательный для умножения — реверс. Он же

для операции унарный минус. А для деления — отчитываем символы не с начала

строки а с конца.) Главное: что бы могла значить операция вычитания двух строк?

Конструкция оператора If такова, что числа приходится сравнивать вот как

раз путём вычитания. Ну так пусть и строчки — тоже: пусть операция бинарный

минус ищет с какой позиции одна строка входит в другую как подстрока. (Если

первая строка входит во вторую — результат будет отрицательный, а если ни одна

не является частью другой — то ноль.) Интересно, что при вычитании строки из

самой себя получится 1, а числа — ноль. Ну значит и вычитание из самого себя

пустого значения должно давать -1. Нам же надо, например, определить тип

аргумента, переданного в функцию. А ведь данный аргумент может быть и пропущен.

Это сейчас у нас локальная переменная под него просто не создаётся...

Ну а дальше всё просто: унарный плюс пусть даёт размер строки — заведомо

числовое значение; умножение и деление одной строки на другую (а не на число)

тоже что-то такое-этакое. Например замена символов и сопоставление с шаблоном.

Для чего второй аргумент должен быть строкой специального вида. (При чем для

умножения — это последовательность пар шаблонов: анализирующего и

генерирующего. А генерирующие шаблоны реализованы вот только сейчас, и то пока

что в первом приближении.) А вот к чему приткнуть возведение в степень?

Вот операции ^ и было решено поручить поиск одинаковых общих подстрок.

Вернее первой самой большой. А чтобы эту операцию можно было применять и для

"автокореляции" (с некоторыми предосторожностями: отрезав от левого операнда

первый символ) сдвиг второго операнда относительно первого — только вправо.

Соответственно полная корреляция: FMAX(x^y, y^x)... Вот это название операции

'П' и досталось...

А то оказывается, что с "хирургическими" методами у нас полный ажур, а вот с

"постановкой диагноза"... Такую простую вещь как нахождение в двух строках

одинакового фрагмента (но заранее неизвестного) придётся делать пещерными

методами — таская по одному символу с помощью FCHR...

Ну а с 'Б', 'б', 'Р', 'р', 'Л', 'л' как со средством преобразования всех

букв в заглавные, строчные, русские и латинские я решил малость повременить:

с учетом 'о' и 'О' — подумать надо... Да и с шаблонами как-то согласовать.



* * *


(4.6) Вот, кстати, как раз про эти самые шаблоны...

Обратим внимание, что при всём кажущемся обилии конструкций форматного

выражения, основных операций — по пальцам пересчитать:

— две для перемещения маркера

— собственно перемещение на N символов — указывается числом

— поиск (сопоставление с содержимым А2) — указывается строкой

— две для изменения содержимого А2 — вставка и удаление: * /

— для / число — правый операнд, указывающий сколько удалить

— для * строка — правый операнд, указывающий что вставить

— еще одна для принудительной установки размера маркера: ~ (тильда)

— для ~ число — правый операнд, указывающий сколько установить

— и еще две константы ^ и $ для установки маркера в начало и в конец.

Все остальные — вспомогательные: либо поставляющие нам вот этот вот правый

операнд — числовой или строчный, либо выполняющие "административные" функции

как @ ( ) & ? . ! % либо указывающие направление + -

Правда в группу констант, "помогающих" ~ (тильде) устанавливать размер

маркера, тихой сапой пролезла еще и . (точка), ни с того ни с сего взявшаяся

изображать еще и "метку"... Но в целом: вот это — всё.

Строка здесь участвует всего в двух действиях: поиск и вставка. Но поиск

совершенно тупой — путём сравнения на равенство, а вставка... Ну константа-же!

Пусть даже и взятая откуда-то. Никакого анализа. И синтеза...

А вот есть у нас еще один вид как-бы кавычек — "ёлочки" <> — сам бог велел

учинить с их помощью что-то такое-эдакое: тоже текстовую константу, но

специального вида. Отличающуюся от той, которая заключается в кавычки-"лапки"

(где каждый символ изображает сам себя) тем что здесь некоторые будут иметь

специальный смысл. И первый из них как всегда главный баламут всея Фокала %

теперь в роли экранирующего символа: отбирающего этот самый специальный смысл

у других специальных символов (включая самого себя) но придающий его некоторым

из "обычных". А на роль "специальных" сразу же взялись претендовать абсолютно

все, кто не буква и не цифра. Ну может быть за исключением кавычек-лапок...

(Дело-то типа уже в кавычках происходит, где им специального смысла не

полагается.) Но и то того гляди передумают...

Да, речь идёт про "шаблон", он же "регулярное выражение". Простейшее из

которых испокон веку используется операционными системами при поиске файлов:

символ '?' которого в имени файла быть не может, заменяет собою один реальный

символ, а '*' — любое их количество: от нуля и более. Впрочем, в Дос`е и винде

шаблон этот совершенно идиотский. (Лично для тупого Билла писаный, которого

папа с мамой деньги сторожить посадили?) Ну текстовый файл на букву А найти

еще можно: "А*.txt" а попробуйте найти такой, где эта буква была бы в середине

имени: "*А*.txt", или даже "?*А*.txt" — чтобы уж точно не в начале?

Не тут то было!

Правильный шаблон только в UNIX`е. (Люди не для тупого начальства и не на

продажу — для себя писали!)

Да, алгоритм сопоставления с шаблоном — дорогостоящий, типа перебор с

возвратами. Когда (если он "жадный") первая звёздочка сперва захапает себе все

символы. Глядь, а остаток шаблона ("А*.txt") не сопоставляется! Приходится

отдавать. Но поштучно. И каждый раз заново пробует — ну может вот сейчас уже?

А следующий метасимвол делает то же самое... Если алгоритм "ленивый" — всё

наоборот: каждый метасимвол пытается спихнуть всю работу на следующих.

И только если ничего не получается...

Вот наш шаблон — это немножко улучшенный вариант UNIX`овского. Отличающийся

тем, что во-первых в нём есть еще и "встроенные" шаблоны вида: %Буква (например

%Ц — любая цифра, а вот %Б — не любая буква, а только заглавная). А во-вторых

? и * не сами по-себе заменяют один или несколько символов, как в шаблонах

файловых систем, а действуют на следующую после них конструкцию, предписывая

повторить её 0-1 и 0-много раз. То есть превращаются в "префиксы повторения".

(Вместо * теперь *. где точка вот как раз и изображает собою любой символ.)

И этих префиксов повторения — больше: '+' означает 1-много, {N} — ровно N раз,

а {N,M} — не менее чем N но и не более чем M. (При чем N и M — фокаловские

выражения.) При их пропуске — ноль и много (типа бесконечность).

Так что: '*' эквивалентна {,} '+' — {1,} a '?' — {,1}



((Впрочем, вряд ли я открыл Америку: вроде бы и в других языках, где есть "регулярные выражения" тоже так сделано? Ну хотя бы потому, что ничего сильно другого и не придумаешь...))


В UNIX`овском шаблоне, кстати, есть еще и конструкция [...] заменяющая

собою тоже один символ, но не любой, а один из перечисленных внутри этих

скобок. При чем если коды символов идут подряд (как буквы или цифры) то можно

не перечислять их, а указать "диапазон": [А-Я]. А если нужен символ '-' то

велено указывать его первым или последним. Потому что порядок перечисления -

несущественен. (Не то, что у нас...)

Но диапазон — для людей хорошо представляющих, как у них кодировка устроена.

Впрочем, для пиндосов, использующих ASCII, и представлять особо нечего:

латинские буквы — строго подряд в алфавитном порядке. Правда маленькие

отдельно, большие тоже отдельно. Дополнительных букв нету. Цифры — тоже подряд.

А остальные символы и перечислить можно. Всем остальным сложнее: мало того,

что у них есть дополнительные буквы, которые невесть как расположены, так еще

и с основными бывает такая чехарда!...

У нас например буква Ё — дополнительная. В КОИ-8 её вообще нет. Вернее есть

только в совсем полном варианте (где и псевдографика предусмотрена), которого

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

— в порядке чужого алфавита. При чем первой стоит буква Ю, такая же круглая,

как "Абезьяна" (она же "сАбака") @ на которую она приходится, Ш и Щ приходятся

на скобочки — квадратные или фигурные, буква Э между ними, а последним стоит

твёрдый знак! При чем заглавный — самым последним. И им частенько приходится

жертвовать: этот код, состоящий из всех единиц, очень часто применяется для

специальных целей... Так что "все" русские буквы в КОИ-8 выглядят как [ю-ъЮ-Ч]

ну или может [ю-ъЮ-ЧёЁ], или даже [ю-ЧёЁ].

В виндовой 1251 всё вроде бы тип-топ: русские буквы в правильном алфавитном

порядке. (Всё как мы любим!) Буква Ё — есть, хотя и дополнительная (ничего не

поделаешь: букв у нас 33, а 2^5 это 32 — только 32 буквы в один ряд убираются).

Ан — буква 'Я' приходится на тот самый код, который для специальных целей. А

ведь это не 'Ъ' (с которого слова никогда не начинаются) — ею не пожертвуешь!

Ну и какой вредитель/недоумок-либераст-дерьмократ это придумал?!

Да, с тех пор прошло более тридцати (!) лет и за это время большой кровью,

(разные аспекты жизни зеркально отражают друг дружку!) затратив невесть

сколько усилий, таки удалось "перевоспитать" большую часть программного

обеспечения... Полагаю, что это была не ошибка а таки диверсия. Потому что

например в более ранней ДОС`овской 866 кодировке с этим всё в порядке. Там

другая проблема (хотя и куда более мелкая): маленькие русские буквы — разрывные.

Типа: [А-Яа-пр-яЁё]. Связано это тоже можно сказать со своего рода диверсией:

псевдографика размещена по-идиотски, а передвинуть её на другое место

невозможно — зафиксирована аппаратно! То есть только в этом месте знакогенератор

не рисует промежутков между буковками...

А вот братьям-славянам с их 855 кодировкой — совсем кисло: там буквы

раскиданы как попало. При чем в основном парами: заглавная-строчная, но не все.

Впрочем нативная американская с неприличным названием и производные от неё

(наша ДКОИ например) — ничуть не лучше и вполне это неприличное название

оправдывают: надо же было учинить такое непотребство!...

Наши собственные, о которых уже мало кто помнит, были продуманы куда лучше.

Например уже упоминавшаяся безымянная "ГОСТ`овская" (известная так же как

"УПП" — от слов "устройство подготовки перфокарт") заточенная под пробивание

дырочек в этих самых перфокартах, была принципиально семибитная: восьмой бит

всегда дополнение до нечетности. "Четными" могли быть только два кода: все

нули и все единицы — место где на перфокарте еще ничего не пробито и где был

"забит" ошибочный символ. Но она была организована так, что могла быть

использована и в шести— и в пяти— и даже в четырёх-битном варианте! Да, буквы

— только заглавные. При чем по "типографскому" принципу: латинские только те,

которые не совпадают по написанию с русскими. (И это — не недостаток: вот

именно так оно всегда и должно быть!) В шестибитном варианте (а у многих машин

размер слова тогда был кратен шести: так для арифметики лучше) там были только

русские буквы. В пятибитном букв уже небыло но были все знаки математических

операций — всё что нужно для калькулятора. И даже в четрёхбитном (!) там было

всё (включая пробел) для записи числовой информации.

Однако, в семье — не без урода. И вот такой урод нашелся и подгадил: в

кодировке отсутствует вопросительный знак! Нет, люди потом как-то

приспособились, благо свободных кодов там более чем достаточно... Однако,

случай вопиющий и требует расследования. По некоторым сведениям, эта кодировка

была привязана к некоторому АЦПУ-128 (алфавитно-цифровому печатающему

устройству, возможно "барабанного" типа), а оно в свою очередь делалось под

язык Алгол-60. Где знаков математических операций — куча, а вот вопросительный

знак никак не используется. Вот нашелся же безголовый исполнительный кАзёл,

делающий всё тупо по ТЗ! А кто это ТЗ писал — он то куда смотрел, чем думал?...

— а про ёлку-то ты не забыл, боярин дубовый?

— когда жёлудь спелый...

Нет, так не годится! Пробуем по-новой:

В UNIX`овском шаблоне, кстати, есть еще и конструкция [...] которую не

грех и позаимствовать. И если считать, что весь шаблон это последовательность

"элементарных", где например любая буква изображает только сама себя,

конструкция %Б — любую букву (но только одну), %Ц — любую цифру, а точка вообще

любой символ, то в <...> должны сопоставиться все элементы, а в [...] — только

один. Любой. А так как сами <...> и [...] тоже элементарные, то ничто не мешает

им вкладываться друг в дружку как матрёшки... Кстати, префиксы повторения

действуют только в <...> потому что это — последовательность. А [...] — выбор.

И в результате, кстати, вырабатывает одно единственное число — номер выбранного

элемента. В то время как <...> для каждого своего элемента пишет в протокол...

— они что, еще и, как милиция, протоколы составляют?

— да! А иначе как же шаблон "генерирующий" сможет чего ни будь сгенерировать

по обращику того, с чем предшествующий ему анализирующий сопоставился?

Вот — по этому самому протоколу...

Мы еще не забыли, что строчное значение участвует у нас в двух операциях:

поиск и вставка? Вот когда поиск — шаблон анализирующий, а когда вставка -

генерирующий. Но это мы таки забегаем вперёд: на уровне (4.6) (и долгое время)

были одни только анализирующие. Как передать от них информацию генерирующим

было непонятно — книжку, чтобы осмыслить это, писать пришлось! (По принципу:

не понял сам — объясни другому...) Но оказывается всё просто: шаблон, вспрочем

как и самое обычное арифметическое выражение — это ведь фактически дерево.

А их запись в программе — развёртка этого дерева в линию (одномерную

последовательность). Ну так процесс сопоставления тоже порождает дерево, хотя и

несколько другое. И можно сразу получить его линейную развёртку — прямо в

процессе сопоставления. А не возиться ни с какими списками, а потом еще

городить средства их обработки, порождая нечто LISP`о-подобное... Просто надо

писать её по стековому принципу: каждый элемент последовательности прежде чем

вписать себя в протокол, должен запомнить положение указателя его текущего

места. И если ему не удалось сопоставиться — восстановить его как было.

Впрочем, каждому даже и не надо, а надо тому, кто его вызывает — тому фрагменту

программы, который интерпретирует выбирающую конструкцию [...] а так же

префиксы повторения...

Впрочем, ближе к делу! То есть к грамматике.


Напомним, что шаблон это в некотором смысле аналог "....". Анализирующий

шаблон некоторым образом "сопоставляется" с символами строки в А2 от того

места, куда указывает маркер. Если не сопоставился — ошибка. (И тогда

выполнение форматного выражения завершается. Если конешно нету постфикса '&',

'' или '?'.) Но при наличии префикса '+' или '-' маркер сдвигается и попытка

сопоставления предпринимается по-новой. В чём собственно и заключается

операция поиска.

Элементами шаблона являются следующие конструкции:

— '_' (подчеркивание) — всегда успешно сопоставляется с нулём символов

— '.' (точка) — успешно сопоставляется с любым символом — ровно адын штука

— '^' и '$' — сопоставляются с началом и концом строки. Если поставить их в

середине шаблона, то он гарантированно не сопоставится ни с чем

— '!' — сопоставляется с СИМВОЛОМ перехода на следующую строку. Таковой может

найтись и посередь строки в А2, хотя конешно обычно стоит в её конце.

Потому что механизм ввода/вывода именно по нему и завершает ввод. (А вот

редактор командной строки при вводе с терминала его вообще не добавляет.)

— любая буква и любая цифра изображает сама себя (но с учетом 'o' и 'O')

— конструкция, состоящая из символа '%' и любого значка, не являющегося

буквой или цифрой, изображает вот этот значёк. (В том числе %% — сам

символ '%'.)

— конструкция, состоящая из символа '%' и буквы — один из встроенных шаблонов

— конструкция, состоящая из символа '%' и цифры — то же самое, что и <....>

если бы содержимое угловых скобок заранее поместили в указанный цифрой

регистр.

— конструкция <....> сопоставляется, когда сопоставились все заключенные в

ней элементы, а конструкция [....] — когда хотя бы один (следующие после

сопоставившегося игнорируются).

— конструкция (....) тоже вполне допустима, но игнорируется.

Главное: внутри <....> перед любой из таких элементарных конструкций может

стоять префикс повторения. А после — постфикс (через двоеточие). Каковых

постфиксов до сих пор никаких не реализовано. (И даже оное двоеточие пока не

отрабатывается.) А префиксов — очень даже: это уже упоминавшиеся '*', '?', '+'

и конструкция {....}. А между ними и повторяемым элементом может так же быть

еще и '-', предписывающий использовать "ленивый" (а не "жадный") алгоритм.

А вот внутри [....] никаких префиксов нет: '*', '?' и '+' — самые обычные

символы. А конструкция {....} изображает символ с кодом, вычисленным

содержащимся в ней выражением. Но за-то в [....] есть "диапазон" и "отрицание".

Уже упоминавшийся диапазон образуется из двух не-групповых (т.е. изображающих

один символ с конкретным кодом) конструкций. А вот "отрицание" это символ ~

(тильда) сразу после открывающей скобки. (Что тоже, кстати, слизано из UNIX`а.)

Тогда [~...] становится "негативным": сопоставляется с нулём символов в

случае, если не сопоставился ни один из его компонентов.

Вот собственно и вся грамматика.

У "генерирующего" шаблона она такая же в точности. Вот только его смысл...

(4.7) Смысл его (генерирующего шаблона) в том, что он, будучи правым

операндом операции вставки '*', должен сформировать для неё фрагмент строки,

руководствуясь "протоколом", который ему оставил предшествующий анализирующий.

Нет, если весь он состоит из символов, изображающих сами себя, то и никакой

протокол ему не понадобится. (Но чем он тогда отличается от константы в

обычных кавычках?!) Протоколом руководствуется прежде всего префикс повторения:

он должен повторить свою конструкцию ровно столько раз, сколько и парный к

нему в анализирующем. Во-вторых — конструкция [....] — она должна выбрать тот

же вариант, что и. В третьих — '.' (точка) — она должна вставить тот же самый

код, что и. Ну и некоторые встроенные шаблоны, о которых мы что-то совсем

забыли...

(Ничего, скоро вспомним!)

По идее анализирующий и генерирующий шаблоны должны быть конгруэнтны. Чтобы

каждой из конструкций досталось то, что записала парная к ней — такая же.

Должны, да не обязаны! Поэтому смотрим что пишет в протокол какая из

конструкций и что потребляет. (Впринципе протокол этот — почти текстовый.

И его вполне можно вытащить и посмотреть, вставив спецоперацией **. А парная к

ней спецоперация // делает протоколом удаляемый ею фрагмент под маркером. Но

обе — чисто для отладки и не уверен что будут сохранены в следующих версиях.)

— "последовательность" <...> (в том числе и весь шаблон целиком) фиксируется в

в протоколе в виде пары скобок < >, между которыми — её элементы.

И "потребляет" такую же последовательность, последовательно предлагая её

элементы каждому из своих членов. (А буде кому недостанется...) А получив

только один элемент — предлагает его каждому из своих членов.

— любой префикс повторения "выгораживает" участок внутри последовательности,

начало которого отмечается символом '*' а конец — '|', и между ними — элементы,

с которыми сопоставилась повторяемая им конструкция. В том числе возможно что

и ни одного. "Потребляет" последовательность до '|' или до '>' (если конешно у

него там не указано предельное значение), предлагая тому что повторяет,

следующий очередной элемент на каждой итерации.

— "перечисление" [...] отображается парой скобок [ ], в которых число (номер

сопоставившегося элемента), запятая и то, что с ним сопоставилось. "Потребляет"

такую же конструкцию. И только тогда выбирает указанный в ней элемент (а не

первый). А вот если это число больше, чем у него элементов... (Даже и не знаю:

пока будем считать, что не вставит ничего.)

= а вот если перечислению в генерирующем шаблоне достался не вышеописанный

элемент протокола, а что-то другое, вот тут и начинается самое интересное.

Но пока что не реализованное. То есть это и должен быть настоящий выбор: к

доставшемуся конструкции [...] элементу протокола последовательно применяются

все её компоненты, пока очередной не подойдёт. Однако, подошедший элемент

должен иметь возможность сгенерить не только то, что он описывает, но и что-то

другое. И вот тут нам бы как раз и пригодились обещанные постфиксы, до сих пор

не реализованные...

Постфиксы были бы полезны и для анализирующего шаблона. Например шаблон

типа: :@ч описывающий число с плавающей запятой,

запишет в протокол не точную его конструкцию, а абстрактный факт "@ч",

указывающий что имело место быть число. (Шаг к синтаксическому разбору?)

А в генерирующем, обнаружив эту абстракцию, заменить её на что-то конкретное...

— элемент шаблона, успешно сопоставившийся с нулём символов отображается '_'.

Это и негативное перечисление [~...] у которого не сопоставился ни один из его

элементов, и пустой шаблон '_', и '^', '$' сопоставляющиеся с началом и концом

строки. Предложенный ему элемент протокола "потребляет" но никак не использует.

— элемент шаблона, сопоставившийся с одиночным символом 'Х' отображается в

протоколе как '.Х' — это может быть и символ, изображающий сам себя и '.'

(точка) обозначающая любой символ. Символ, изображающий сам себя, предложенный

ему элемент протокола никак не использует, а вот '.' (точка), которая сама-то

сопоставляется хотя и с любым символом, но только с одним — вставляет что

угодно. В том числе получив '<' — всё вплоть до закрывающей скобки '>'.

— встроенный шаблон вида %Б сопоставившийся с одиночным символом 'Х'

отображается в протоколе как 'БХ'. Шаблон '.' (точка), получивший это,

символ Х и вставит. А вот если это достанется другому шаблону на вроде %В,

то вот тут как раз и начинается самое интересное. Потому что все эти %Б %В %Г

%Д... описывают некие группы символов и по сути являются аналогом конструкции

[...]. Следовательно: если символ попал в диапазон, описанный как %Б, то %В

должен вставить символ из своего диапазона, но с тем же номером.

Ну и что же, спрашивается, они все значат?

На текущий момент %Б это любая заглавная буква, а %б — строчная.

Соответственно %Р %р — русские буквы, а %Л и %л — латинские. В текущей 866-й

кодировке и с учетом предшествующей шаблону операции 'o' или 'O'. (Русскими

буквами считаются так же четыре дополнительные: Ё, Э-не_оборотное, И-латинское

с двумя точками и У с надчеркиванием.) Было так-же задумано, что латинские

буквы %B %R %L обозначают любую букву соответствующего типа. Однако сейчас, с

учетом 'o' и 'O' это по-видимому уже не нужно...

%Ц %ц %C %c — одна цифра; %Г %г %G %g — символ псевдографики; %П %п %P %p

пробел, а так же любой "слепой" символ с кодом меньше чем у пробела, но %П %п

еще и знаки препинания . , : ; ! ? тоже указывающие конец слова. При чем %p %п

ровно один символ, а %П %P — вся последовательность идущих подряд символов этой

категории.

%V %v — одно правильное число, закодированное в стиле utf-8 без попыток

распознать код FEFF даже если тип входного уникода — 0. %U — один любой символ

уникода (указанного типа — вот как раз "с попытками", если он ноль) %u — один

единственный код FEFF "неразрывный пробел нулевой ширины" по которому все

всегда тип уникода и определяют. В протокол пишется как Uчисло.



Вот надо честно сказть, что сочинение вот таких вот описаний (а это уже далеко не первое) — это фактически ревизия сделанного — так же, как написание шпаргалки при подготовке к экзамену. Вот написав это, сейчас я вдруг увидел, что заглавные %Ц и %Г тоже надо бы как и %П сделать чтобы не с одним символом сопоставлялись, а с последовательностью этой категории; %Ч соответственно сделать правильное число со знаком, мантисой, порядком, десятичной точкой и возможно предшествующими пробелами. Уж коли мы заикнулись про постфиксы — сделать чтобы после двоеточия конструкция хотя бы игнорировалась; вспомнить про '#' и '='... Для #N (где N — цифра) сделать что в регистре N константа (а не шаблон как %N), а #X — просто код, указанный числом в соответствующей переменной. Впрочем, у нас в [...] для этого есть заключенное в {} выражение. Ну тогда пусть значение уникода. Соответственно, =N — помещение в регистр того, что уже сопоставилось, а после двоеточия — того, с чем сопоставился данный конкретный элемент. Соответственно =Х после двоеточия — размер этого элемента, а просто так — текущее смещение относительно начала строки в А2... А еще возможность присваивания в конструкции {...} Но не всё сразу — пока что только запишем всё это в "недоделки".


Уже упоминавшиеся (в связи с перекодировками) %В %в, %К %к, %И %и, %У %у

обозначают русские буквы в соответствующей кодировке — заглавные и строчные.

(%С %с, %Е %е, %М %м — тоже, но пока не реализованы.) Потому как латинские

у всех у них одни и те же. (Кроме "Е", где вообще черт знает что. Но там можно

будет использовать 'O'.)

Осталось еще %S %s — единственная (пока) сложная конструкция:

— %s сопоставляется со строчной константой в кавычках, а

— %S со сбалансированной скобочной структурой. Где, кстати, скобки, попавшие

внутрь кавычек "погоды не портят". Отображаются в протоколе как $/....../

где ..... — любые символы, / / — тоже любые символы, которые среди них не

встречаются. Но все они должны быть "печатные". При наличии "слепых": &"....."

где, как и в Си-шной текстовой константе используется экранирующий символ .

Отличие только в том что числа NNN, только десятичные (или восьмеричные, если

начинаются с нуля) и могут отделяются от следующих символов запятой. (Если там

дальше тоже запятая, или цифры — то обязательно.)

Остальные сложные вещи, такие как правильное фокаловское выражение,

правильное форматное выражение и правильный шаблон — это на будущее...

А по-поводу нарушения фокаловских принципов конструкцией &"..." протокола...

Подумать таки надо. Но внутреннее представление всё равно другое а это

(операции ** и //) временно....




* * *


Итак, еще раз про встроенные шаблоны вида %Х (где Х — одна буква) как

анализирующие так и генерирующие.

Договоримся, что любая конструкция вида %буква это обязательно встроенный

шаблон. Даже в том случае, если таковая буковка никак не задействована,

анализирующий просто ни с чем не сопоставится, а генерирующий ничего не

сгенерирует, проигнорировав доставшийся ему элемент протокола.

АНАЛИЗИРУЮЩИЕ встроенные шаблоны делятся на две группы: первые

сопоставляются с одним символом, вторые — с некоторой сложной конструкцией,

типа плавающее число (с мантисой и порядком), грамматически правильное

выражение, или хотя бы сбалансированная скобочная структура...

Из них пока реализован один только %S сопоставляющийся с текстом в

кавычках. Проблем — две: первая — как зафиксировать такую конструкцию в

протоколе и вторая, более сложная — в универсальности. Первая проблема

порождается спецоперациями ** и // позволяющими "вытащить" протокол на свет

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

виде. Она вполне решаема. Вот только решения эти мне что-то не больно-то

нравятся. (Ну да: введением таки только и исключительно для протокола текстовой

константы с экранирующим символом.) Вторая заключается в том, что например мы

считаем "скобками" а что — нет? Мы то в Фокале их числим четыре вида: ([{<>}]),

а где ни будь в другом месте <> скобками не считаются. А еще кому-то

понадобится выделять фрагменты, заключенные в '/' и ''. Ну и что делать?

Получается, что встроенному шаблону, анализирующему скобочную структуру, надо

передавать параметры! Интересно, как? Хотя у нас вон там еще "постфикс" никак

не задействован... А еще есть проблема рекурсивности, которую надо

рассматривать отдельно: скобочки же могут вкладываться друг в дружку как

матрёшки на любую глубину... А если скобочка в кавычках, то скобкой она типа

не считается? Да и кавычки (не у нас!) во первых могут быть парные, а

во-вторых иметь в комплекте экранирующий символ...

Но отложим это (до следующей версии).

Пока что нас интересуют те встроенные шаблоны, которые сопоставляются с

одним символом, таким как буква, цифра, разделитель, символ псевдографики...

Но это анализирующий — сопоставляется. А парный к нему генерирующий должен

вставить нечто, получившееся из того, что получил от него через протокол.

Два крайних случая: символ, изображающий сам себя, себя же и генерирует,

игнорируя информацию из протокола. А шаблон . (точка), сопоставляющийся с

любым символом, и вставляет тоже что угодно. Один в один. При чем любую, сколь

угодно сложную конструкцию. Ага. А все остальные?

Перечислим их (честная ревизия кода, а не что я на этот счет думаю)

"объединение" (буква О может быть как русской так и латинской)

%о (строчная буква) — не различать заглавные и строчные буквы

%О (заглавная) — не различать русские и латинские

Инвертирует текущее состояние этого признака. (С учетом алгоритма

сопоставления, идейка, честно говоря сомнительная.) Следов в протоколе не

оставляет.

В генерирующем шаблоне (как это уже было написано выше) вставляет

конструкцию типа %о+ указывающую в каком этот признак состоянии.

"кодировки" (буквы — исключительно русские)

%Д %д — ДОСовсская 866

%В %в — виндовая 1251

%К %к — КОИ-8

%У %у — уникод

%И %и — ИСО 8859.5

%М %м — макинтош

Заглавная буква -> русские заглавные, строчная -> строчные. Латинские

буквы у всех перечисленных совпадают с латинскими текущей кодировки.

Планируется так же сделать:

%С %с — славянская 855

%Е %е — ебздиковская производная ДКОИ.

И вот у последней всё не так! Но пока не сделано, а там — подумаем...

(С учетом %О выделить можно любые буквы, а вот с прочими символами как?)

"типы букв" (в текущей кодировке)

%Б %B — любые заглавные

%б %b — любые строчные (но это если признак %о не установлен)

%Р %R — заглавные русские

%р %r — строчные русские

%Л %L — заглавные латинские

%л %l — строчные латинские (аналогично для признака %О)

другие классы символов (тоже в текущей кодировке)

%Ц %ц %C %c — любая цифра

%Г %г %G %g — любой символ псевдографики

%П %п %P %p — пробел или любой "слепой" символ с кодом меньше пробела

если русская (%П %п) — плюс то, чем может завершиться слово:

знаки препинания '.' ',' ':' ';' '!' '?' и конец строки

если строчная (%п или %p) — один такой символ,

а если заглавная (%П или %P) — все сколько их подряд

уникод

%U — аналог . (точки) — один символ текущего уникода

%V -//— — один правильный символ utf-8

%u %v — символ с кодом FEFF (неразрывный пробел нулевой ширины) по которому

обычно определяют тип уникода

Как уже говорилось ранее, уникод различаем "входной" и "выходной". В

данном случае входной — для анализирующего шаблона, а выходной — для

генерирующего. Устанавливаются операцией %О с числовым аргументом, но могут

быть установлены и с помощью вот этих вот встроенных шаблонов. При чем %u

определяет и устанавливает тип входного уникода только когда он

"неопределённый" — равен 0, а %v — всегда (т.е. переопределяет его заново).

На текущий момент (03.01.24/14:00) это всё что есть.

Вспомним, что генерирующему шаблону может быть передано через протокол?

— ноль символов: _

— одиночный символ: .x Аx Unnn, ! где: х — вот этот самый символ,

— группа символов: $/xxx/ &"xxx" А — буква, n — цифра

— выбор: [nn,Y] Y — любая из описанных

— последовательность: <...> здесь конструкций

— повторение: ...*...|... ... — их последовательность


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


Итак, что видим? Видим деление символов на ранее упоминавшиеся классы

(буквы, цифры, псевдографика...) с выделением подклассов среди букв. (Те, что

обозначены как "кодировки" — тоже буквы, при чем русские.) Вернее

соответствующий анализирующий шаблон выделяет из входного потока символ

относящийся к этому классу и соответственно генерирующий должен вставить в

выходной поток тот же самый символ, преобразовав к своему подкассу. А если

преобразование не требуется, то там вполне можно поставить шаблон . (точка).

Ага. Вопрос только в том, что ему делать, если достанется символ

относящийся к другому классу?

— преобразовать к своему классу (а это вообще возможно?)

— вставить как есть

— не вставлять ничего

Последний случай имеет смысл если есть возможность выбора. Например в виде

конструкции [...] не парной к аналогичной конструкции анализирующего... Сейчас

то в этом случае тупо используется первый её элемент. А надо, чтобы подходящий!

Впрочем, возможен компромис: за пределами вот такой вот конструкции пусть

всё "чужое" вставляет один в один (как точка), а внутри — только "своё".

За одно разрешается и еще только наклёвывающяся проблема с перекодировками:

псевдографику (если она есть) тоже нужно перекодировать, а если нету — как-то

изобразить ("транслитерировать"). При чем существуют кодировки (не

реализованная пока ДКОИ например), где не только русские но и латинские буквы

закодированы принципиально по-другому. Да и все остальные символы тоже...

А преобразование символов между классами (например букв в цифры или в

символы псевдографики) пока что не представляется мне вомзожным.

Поэтому вот так и сделаем: за пределами конструкции [...], которой пока

что еще и нет, любой встроенный "одиночный" шаблон пусть вставляет все

"не-свои" символы один в один — как . (точка). И в роли "постфикса" — тоже.

(Которого тоже пока что нет — это после двоеточия.)




* * *


В общем будем считать, что про шаблоны пока всё.

— что там у нас еще осталось?

— а вот:

(4.9) %r и %R — близнецы братья (кто более матери-истории ценен?...)

Ну даже настоящие близнецы не совсем похожи — и тут так же. Напоминаю:

в начале был реверс — перестановка в обратном порядке: битиков в байте и самих

байтиков в указанном маркером фрагменте. (А битиков — тоже в каждом байте

фрагмента, вообще-то.) Вот это они по наследству и получили: %r работает

последовательно с каждым байтом фрагмента, а %R с ним со всем как с единым

целым. Но делать умеют почти одно и то же: свои операнды они рассматривают не

как буквы, цифры и прочие знаки препинания, а как наборы битов. (Они же

беззнаковые целые числа.)

А всё потому, что никак не получалось придумать — как одновременно указать

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

латинскими, и, главное — всё это еще и для других кодировок, где тоже всё это

есть... Нет, по-отдельности — пожалуйста. А вот всё вместе... Сделать-то не

проблема, а вот как указать, что имено надо делать, да еще так, чтобы и самому

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

там все нужные нам преобразования производились изменением буквально одного -

двух битов. Чтобы там все буквы стояли единообразно: младшие ну например шесть

бит указывали конкретную букву, а все старшие были бы её признаками: седьмой

бит — заглавная или строчная, восьмой — русская или латинская. А если битов

больше, то и на другие алфавиты можно замахнуться...

Ага, замечательно. Но с одной стороны не все буквы — буквы: меж ними и

цифры встречаются, и прочие знаки препинания. А с другой — нужен инструмент,

чтобы эти битики признаков переворачивать. (Без инструмента и вошь не

раздавишь: ноготь нужон!) И инструмент по-возможности универсальный. То есть

не только нужные для этого побитовые операции, но и например арифметические...

Вот и.

А если учесть, что у нас есть еще и кошкой драный уникод, который в один

байт ну никак не укладывается... В общем делаем так: берём очередную буковку

(сколько бы байт она ни занимала), тащим её в тихое место, и уже там что-то с

нею делаем. А сделав — кладём назад. Или сохраняем про запас... Где? Да хоть

в переменной, хоть одном из регистров...

Образчик этого самого "тихого места" (операционный стэк) дают нам

советские программируемые микрокалькуляторы. А так же язык Форт и/или

ПостСкрипт если кто о них знает. Но там этот самый стэк — произвольной глубины,

а у калькулятора Б3-34 — ограниченной: это не компьютер и ресурсов у него в

обрез. А у нас хоть и компьютер, но инструмент-то мы делаем сугубо

вспомогательный! Так что операционный стэк — пока что те же самые четыре

ячейки. (По крайней мере пока — а потом посмотрим. Но там этого вполне хватало,

думаю, что и здесь хватит.)

Стэк, если кто забыл, это "стопка" или "кипа" — способ обращения как у

пистолетного магазина: последний засунутый — лежит сверху и взять можно только

его. Но если взял — теперь наверху засунутый перед этим... Вот только у нас не

патроны, а числа, а ячеек (пока) всего четыре: при засовывании нового,

последнее пропадает, а при вытаскивании — размножается. (Но полагаться на это

таки не следует: вдруг в будущем ячеек добавим?) Главное: каждая операция берёт

свои операнды со стэка и туда же помещает результат. Кстати, очень удобно!

Не 2 + 3 = как на обычном калькуляторе, а 2 3 + и всё. Правда между 2 и 3 надо

что-то такое, чтобы указывало что ввод одного числа кончился и начался

следующего. Там была такая кнопка со стрелкой вниз — проталкивающая верхнее

число дальше в стэк. Хотя годится и любая операция — всё что угодно, что не

циферка. У нас например _ (подчеркивание), как всегда ничего не делающее.

А удобно потому, что всякие формулы, чуть сложнее 2+2 например (5+4)/(3+2)

на обычном калькуляторе считать запаришься: промежуточные результаты придётся

на бумажку записывать, а потом вводить заново. А тут: 5 4 + 3 2 + / и всё.

Кстати, это — "обратная польская безскобочная запись" называется...

В общем у наших близнецов %r и %R всё в точности то же самое: что сделать,

передаём им в виде аргумента... Мы еще не забыли, что вообще-то всё это -

внутри форматного выражения оператора Write и выглядит: Write %_%r"операции"

То есть операцию можно передать конечно и одну, а можно вот так вот: несколько

в кавычках или скобках. Любых. Но как-то интерпретатор отреагирует на другие

виды скобок, вложенные например в Write %_%r(....); которые вполне могут быть и

не сбалансированными... Так что уже лучше в кавычках.

Итак, у обоих — операционный стэк из четырёх ячеек и одни и те же операции:

& | ^ ~ побитовые: И, ИЛИ, исключающее ИЛИ, инверсия; + — * / арифметические.

Деление — целочисленное: даёт два числа — частное (сверху) и остаток (под ним).

Чтобы до него добраться (да и вообще) — операции реогранизации вершины стэка:

[ дублирование верхнего числа, ] обратная к ней — удаление его нафиг. А так же

: обмен пары и @ обмен всех четырёх — засовывание самого верхнего под низ.

Стэк у нас таки не стопка как в ПостСкрипте, а магазин фиксированной ёмкости.

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

Но тогда была бы возможна ситуация, когда на стэке ничего нет, а мы пытаемся

выполнить с этим какую-то операцию — пришлось бы как-то на это реагировать...)

Главное отличие: операции , . ; (запятая, точка, точка с запятой) — взять

очередной i-ый байт из обрабатываемой последовательности, положить его обратно

(удалив со стэка), а так же обменять их местами. (Кстати, операция 'i' сообщает

нам индекс текущей позиции, а 'l' — сколько еще осталось.) Договорились, что

если ,, или даже ,,, то значит надо взять за раз два и три байта. Или если ..

и ... то соответственно положить. А если взяли один, а положили три... Да,

тоже можно: обрабатываемый фрагмент соответственно в этом месте раздвигается.

Но уж коли мы взяли три, то i на эти три байта и продвинется... (А надо взять

еще раз, но один. И за ненадобностью выкинуть со стэка уже упоминавшейся

операцией ']'.)

Для "старшего братца" %R, который берет всю эту входную последовательность

целиком, все эти детские игры с многоточиями разумеется совершенно никчему.

Казалось бы, этим можно и ограничиться... Ну добавив еще обращения к

переменным (=X #X, где X — любая буква) и регистрам (=N #N, где N — цифра),

и еще вот эти самые перекодировки — в виде обозначающих кодировку буковок:

в промежуточную — строчных, а из неё обратно — заглавных... Ну еще константы

конечно. Не только десятичные (битики указывать, они не больно удобны), а как

в Си: 0х... 16-ричная, 0о... 8-ричная и 0b... двоичная. А еще текстовые

константы в виде кода буковки. Эти не 'в кавычках', а с помощью кавычки: 'А.

Дело то уже в кавычках происходит! Да и буковка нам как правило нужна только

одна. Хотя конечно с ними можно как с точками...

Ага, а что делать, если попадётся вовсе не буковка, а цифра, знак

препинания или упаси господи вообще какой ни будь неизвестный науке зверь,

которых дофига в уникоде? Их же отличать как-то надо! Да, конечно они

отличаются — битами признаков в старшей части... Но надо же не просто отличить,

но и поступить по-разному... Поэтому без условных операций ну никак не

обойтись!

Их две: ? и для случаев "да" и "нет", пропускающих в противном

случае следующую вслед за ними операцию. Которая может быть и составной, для

чего (разумеется!) используются круглые скобки. Сами-то по себе они ничего не

делают (от слова "вообще"!) — исключительно указывают вот такой вот условной

операции что именно пропускать. Без открывающей скобки она просто "не

догадается", а без закрывающей — пропустит вообще всё. (Но и вложенность

скобок тоже отслеживает, поэтому лучше использовать их парами. Хотя ничто не

мешает...) А перед условной операцией должен быть типа "предикат"?...

Никто никому ничего не должен! (Если не занимал, конечно...) Просто каждая

операция вырабатывает еще и "логический" результат. И почти всегда он

"нейтральный" — /0/. (Фокал ненавязчиво приучает нас к тренарной логике!) Даже

бездельник _ (подчеркивание) и то вырабатывает нейтральный результат. И одни

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

городить аналог if-else: ?(...)(...) этот фокус и так получится, а чтобы

последняя операция заключенной в скобках последовательности при случаем могла

что-то проверить для следующей после них условной операции...

...как вот эти самые "предикаты": < <= > >= = и <> (не равно) а так же ><

(включение), которые не-нейтральный результат и вырабатывают: /+/ — если

условие выполняется и /-/ если нет. Они, ничего не изменяя, сравнивают два

верхних операнда. А если перед ? такого предиката нет, то (при /0/) он сам

проверяет, что верхний операнд на стэке не-ноль.

Обратим внимание, что если сразу вслед за знаком '=' у нас буква или цифра,

то это присваивание, а если что угодно другое — то предикат: сравнение на

равенство.

Логические константы? Да они у нас тоже есть: M и S ("Младший" и "Старший"

потому что... а вот не будем забегать вперёд!) дающие "негативный" (/-/) и

"позитивный" (/+/) логический результат соответственно. А "нейтральный" (/0/)

нам даст любой бездельник.

Логические операции? Ишь чего захотели: они же бинарные! Поэтому — где ни

будь в другом месте. К тому же значений — три, а у нас здесь все числа

исключительно беззнаковые. Хотя можно (операцией 'F') сохранить логический

результат в числовом аккумуляторе А1 (мы про него еще не забыли?), и сразу же,

пока никто не испортил (А1 — что твой проходной двор!) положить на стэк его

содержимое (операцией 'а'). Но негативный результат превратится в число -1 и

будет выглядеть как единицы во всех разрядах. Однако, если нас такое устроит...

Итак: 'a' 'A' (любого алфавита) — прочитать на стэк число из аккумулятора

А1 и соответственно его туда записать. А 'f' и 'F' — сохранить в А1 логический

признак результата предыдущей операции и установить обратно.

Общий принцип такой: заглавная буква — наружу, на экспорт, строчная -

внутрь. Аккумулятор А1 снаружи данного механизма? Да! Значит пишет туда 'F', а

берёт оттуда и устанавливает соответственно 'f'.

Но ведь операция инверсии — унарная? Да! И таковых у нас аж целых три:

p n c (все буквы маленькие латинские) — "позитив", "негатив" и "циклатив".

— позитив 'p' оптимистично превращает нейтральный результат в позитивный.

— негатив 'n' переворачивает всё кверх ногами (оставляя нейтральный как был)

— циклатив 'c' это здешний пессимист, желающий видеть всё хуже, чем оно есть,

и для этого последовательно понижающий оценки: позитивную до нейтральной,

нейтральную до негативной... А вот негативной деваться некуда — занимает

оставшуюся свободной позитивную позицию.

Зачем такие сложности? А вот:

— Никогда ничего сама не проверяющая операция Х выполнит операцию Х только

если /-/, nХ — только если /+/, и соответственно cХ — только если /0/.

— Операция ? проверяющая в "нейтральном" случае на ноль — чтобы ничего не

проверяла, должна быть употреблена как p? — сработает если /+/ и /0/;

np? — если /-/ и /0/. И наконец cp? — если НЕ-/0/.

— А следующая за ней операция соответственно сделает всё наоборот.

Вот такая она — троичная логика в проекции на двоичную.

Логические константы 'M' и 'S' это еще и префиксы-модификаторы для

операций обращения к регистрам #N и =N — чтобы они делали это по "вагонному"

принципу. (Именно префиксы: сам то логический результат выполнения предыдущей

операции ни на кого, кроме ? и никакого влияния не оказывает.)

Так-то #N всего лишь берет из регистра "текущий" i-ый байт, а =N пишет

поверх него. (Если он есть. А буде нету — размер строки увеличивается и

вырабатывается /+/. А буде нету чего прочитать — читается ноль а логический

результат — /-/.)

Как уже говорилось, смещение текущего байта и количество оставшихся можно

получить операциями 'i' и 'l', а установить? ('I' и 'L') Увы — нет. То есть

если бы мы байтиками и ограничились, то никаких проблем. Но мы же разрешили

(сами себе) таскать по нескольку байт, при чем в обе стороны. В результате

элементарно запутаться... Да и зациклиться. Поэтому всё строго последовательно.

(Нет, мне не жалко, я ведь сделаю... Но пеняйте на себя!)

А вот по "вагонному" принципу байт добавляется в строку самым младшим и

самым старшим соответственно. В точности как вагончики к стоящему на рельсах

составу (пока он еще без локомотива). И забираются в точности так же. (А буде

забирать нечего — то ноль и негативный логический результат.)

Старший братец %R делает всё в точности то же самое, но пишет не один

байт, а всё значение, которое у него в верхней ячейке стэка. (А оно может быть

какого угодно размера — от нуля байтов и больше — неограниченно, хотя и в

пределах разумного.) А вот читает и удаляет при обращении по вагонному

принципу "номинальное" количество байтов, которое устанавливается командой 'N'.

А вот считать, что ранее установлено — командой 'i'. (Потому как 'n' занято,

а индекс текущего байта обрабатываемого фрагмента для %R смысла не имеет: он

берёт его весь целиком.) Соответственно 'l' и 'L' — определяет вот этот самый

размер операнда (в байтах) и принудительно его устанавливает — либо урезая

строку, либо напротив добавляя ей ведущие нули.

(Впрочем, "ведущие" это старшие биты — в конце строки, а отнюдь не в её

начале, где у нас биты младшие. А вот когда строка как текст выдаётся на

печать, то слева оказываются как раз младшие, самые первые буквы.)

А размер только значащей части (без ведущих нулей) сообщает операция 'л'.

Еще модификаторы 'M' и 'S' действуют на предикат >< (включение), так то

(без них) определяющий что один из операндов является частью другого. Для %r

где все операнды одинаковые, это просто сравнение на равенство. А вот для %R...

Кстати, операция побитовая: количество битов, на которое надо сместить

первый операнд относительно второго, чтобы они совпали, помещается в

аккумулятор А1. (Если второй операнд меньше первого и сдвигать пришлось

его — число отрицательное.) Причем, даже если совпадения нет, в А1

смещение для самого крупного из совпадающих фрагментов.

А вот с модификаторами 'M' и 'S' операция >< ничего и никуда не двигая,

определяет, сколько бит совпадают с младшего и старшего конца операндов.

Результат всё в так же в А1.

"Номинальный" размер операнда для %R нужен еще и при сдвигах. О которых

мы совсем забыли! Как же это побитовые операции и без сдвигов?...

Сдвигов у нас два вида: обычные << >> на указанное первым операндом количество

битов, и "циклические" N (где N — одна цифра) — на фиксированное. При

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

циклическом — задвигаются с другого конца. Для %r где размер операнда

фиксированный, всё очевидно. Для %R при сдвиге к старшим разрядам ничего не

теряется — увеличивается размер операнда. Теряется при сдвиге к младшим.

И при циклическом сдвиге >N если размер операнда меньше номинального, он

принудительно делается равным ему.

Сдвиги <0 и >0 которые ничего никуда не сдвигают, работают как своего рода

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

считают количество нулевых битов с младшего и со старшего края операнда и

помещают результат в А1.

К сдвигам примыкает операция 'm' создающая "маску" в виде указанного

аргументом количества единиц. С её помощью можно выделить часть значения.

Например 6m& — только код буквы, обнулив все её признаки. Или 4m<7& выделить

только код типа алфавита — из соображения что он занимает 4 бита.

А главному баламуту всея Фокала символу % на пару с $ поручены операции

деления и умножения арифметики полиномов в кольцах Галуа. Где роль сложения

(а за одно и вычитания) выполняет ^ (исключающее ИЛИ).

Столь сложное и интрирующее название на самом деле скрывает за собою вещь

простую до тривиальности. Только малоизвестную: в школе этого не проходят. За

ненадобностью: лентяйская арифметика — без переносов между разрядами. (Поэтому

и сложение с вычитанием — там одна и та же операция.) И потому ни для чего

по большому счету не нужная — так, игра ума. Однако, почти как настоящая...

Вот и приспособили её для подсчета контрольных сумм для некоторой хранимой или

передаваемой по линиям связи информации — чтобы проверять: а не испортилась ли?

Небезызвестная "циклическая контрольная сумма" (она же CRC) ни что иное, как

остаток от деления информационного пакета, рассматриваемого как одно длиннющее

целое, на некую константу, называемую обычно "образующим полиномом". На эту

роль выбирается тамошнее простое число: в лентяйской арифметике тоже

встречаются числа, которые нацело ни на что не делятся. Кроме единицы и самоё

себя.

Деление обычной арифметики с переносами еще лучше бы подошло. Но это

считать куда проще. Особенно аппаратно: предположим информация приходит из

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

сдвиговый регистр размером с вот этот вот "образующий полином" и

последовательно загоняем в него эти приходящие из линии битики. Но не просто

так, а берём те разряды сдвигового регистра, для которых в образующем полиноме

единички, и определяем для них не-четность. (С помощью кучи элементов

исключающее ИЛИ.) И если количество единичных битов там таки нечетное, то

входной бит инвертируем. (Еще одно исключающее ИЛИ.) Вот и всё! В начале в

сдвиговом регистре нули, в конце, после прохождения всего пакета — вот эта вот

циклическая контрольная сумма.

Еще есть операция 'h' подсчитывающая код Хеминга а за одно и определяющая

четность операнда (т.е. четное или нечетное там количество единичных битов).

Код Хеминга это контрольная сумма, позволяющая исправить одну ошибку (и

выявить — две). Он весь построен на идее четности: берем и делим биты операнда

на группы так, чтобы каждая из них включала примерно их половину. И

подсчитываем для каждой из них вот эту самую четность. Если испортился

(перевернулся) ровно один бит, то он испортит четность только тех групп, в

котороые входит. Разницу (результат исключающего ИЛИ) между правильной

контрольной суммой и новой, подсчитанной по вот такому испорченному операнду

обычно называют "синдромом ошибки". Чтобы этот самый синдром ошибки оказался

двоичным номером испорченного бита, группы выбираем так: пронумеруем все биты

операнда, поставив каждому в соответствие двоичное число. Групп сделаем ровно

столько, сколько разрядов в номере самого старшего бита. (Плюс еще одну,

включающую все биты операнда.) И каждой поставим в соответствие один из

разрядов номера. Бит операнда будет входить в те группы, для которых в его

номере в соответствующих им разрядах — единички. (Вот эти группы, а вернее

подсчитанные для них контрольные биты он, если что, и испортит!)

А теперь небольшой финт ушами: поручим хранить эти контрольные разряды тем

битам, в номере которых только одна единичка!

Вообще-то обычно контрольная сумма (и код Хеминга тоже) хранится либо совсем

отдельно, либо в конце. А надо бы — вот так, распределённо. Ну так эти биты

(уж коли они в конце или отдельно) из общего счета битов надо бы исключить.

Или туда биты контрольной суммы и прятать. Но пока не сделано ни то ни другое.

(Так что толку от операции h пока что не предвидится.)

Операция 'r' и 'R' — генератор случайных чисел. (Тот же самый, что и FRNd.)

Отлиличаются тем, что 'r' выдаёт случайное число размером в 1 байт, а 'R' — во

весь операнд (для %R — "номинального" размера).

Осталось управление порядком действий.

— условные операции ? и мы уже рассмотрели

— скобки ( ) для образования составной операции — тоже

— цикл в виде пары { } фигурных скобок. Открывающая просто указывает точку,

куда перейти, встретив закрывающую. Помещает свой адрес во внутреннюю

переменную. Поэтому '}' переход только назад. Если '{' небыло — к началу

командной последовательности. Вложенные циклы сделать нельзя — переменная

только одна. Но напоминаю, что инструмент — вспомогательный: сложные

программы здесь писать не предполагается.

— средств обращения к подпрограмме — тоже нет. Равно как и возможности

вычислить фокаловское выражение. В котором можно было бы вызвать спецфункцию

FSUbr и таким образом задействовать любые операторы Фокала. Не могу

представить, для чего бы это могло понадобиться применительно к каждой буковке,

обрабатываемого %r фрагмента. Хотя сделать впринципе можно: в виде конструкции

#{...}

— остановить выполнение — операцией '!'. Но для %r выполняющей одну и ту же

последовательность операций для каждого байта обрабатываемого фрагмента, это

всего лишь переход к следующему.

— совсем прекратить выполнение — по ошибке: злонамеренно выполнив деление на

ноль (хоть / хоть %). При этом %r или %R завершается неуспешно. И при

отсутствии постфикса завершает выполнение форматного выражения. (Для очередной

строки.) А для %r под маркером останется фрагмент строки, который уже успели

обработать.

Все неизвестные команды ошибок не вызывают, а просто игнорируются, что

наверно неправильно.

— А лошадь мэра родила двухголового телёнка

— Чо?!

— Ничо — про перекодировки, ради которых всё и затеяно, опять забыли!

Эти самые перекодировки обозначаем исключительно русскими буквами: чтобы не

путаться, и еще потому, что преобразованию подвергаются только русские,

латинские в младшей части — один в один ASCII. (Хотя для ебздиковской

производной ДКОИ это, как оказалось, совсем не так!) Согласно ранее

объявленному принципу: строчная — к нам, заглавная — на экспорт. (Ну скромные

мы!) А все латинские соответственно для обозначения других операций.


Здесь был обещан список кодировок и их обозначений. Но он в точности такй-же как и чуть выше:


Д д — ДОСовсская 866 В в — виндовая 1251 К к — КОИ-8

У у — уникод И и — ИСО 8859.5 М м — макинтош

а еще планируется сделать (но пока не сделано)

С с — славянская 855 Е е — ебздиковская производная ДКОИ.


А еще для табличного преобразования символов — шифровки/дешифровки методом

замены одних символов на другие (по табличке) нам оказывается нужен доступ к

этой самой табличке по индексу. Табличкой пусть пока побудет строчка букв в

одном из регистров. Чтение — операциями: b w g d

запись B W G D

размер элемента: 1 2 3 4 байта.

Обратим внимание: буквы латинские, но в порядке русского алфавита.

Выглядит так: bN и BN (N — цифра) Реализовано пока только для %r и только как

обращение к регистру. (BХ и bХ где Х — буква, было бы обращение к переменной

#Х с индексом... Может сделать? И для %R тоже...)



* * *


Недописано

Впрочем и сам Фокал — тоже

Далее планируется сделать наконец буферизацию вывода, включаемую в

операторе Type с помощью спецформата %%% после чего он будет писать не в канал

вывода а вот в этот свой буфер — до особого указания: Type ! после чего всё там

накопившееся разом... (Типа "транзакция".) А до того Ask %%% сможет этот буфер

перехватить и себе присвоить, превратив его в А2. (А если А2 был не пуст — всё

это соответственно теряется.) Но все функции с текстовым "побочным эффектом",

включая пресловутую FTMP (ради которой, честно говоря, всё это и затеяно)

продолжают писать не в канал вывода, а вот туда — тоже до особого распоряжения:

Ask ! Но и Type тоже сможет это перехватить... Вот только пока непонятно: это

будет один и тот же А2, или "дружба — дружбой, а табачок — врозь"? В смысле

если Type затеял вот это самое %%% то что будет делать Ask %% — перехватит у

него буфер или будет таки писать в свой А2 (до конца оператора)? И с другой

стороны Type %%% дописывает то, что в А2 или таки начинает "с чистого листа"

(пустого буфера)?... В общем тут думать надо, прикидывать — а что будет если

каналы ввода и вывода направлены на разные файлы, но одинаково требующие чтобы

чтение и запись происходили сразу порциями-транзакциями...

А пока — продолжение следует.

ред.2.00 от 22.4.25

 
↓ Содержание ↓
↑ Свернуть ↑
 



Иные расы и виды существ 11 списков
Ангелы (Произведений: 91)
Оборотни (Произведений: 181)
Орки, гоблины, гномы, назгулы, тролли (Произведений: 41)
Эльфы, эльфы-полукровки, дроу (Произведений: 230)
Привидения, призраки, полтергейсты, духи (Произведений: 74)
Боги, полубоги, божественные сущности (Произведений: 165)
Вампиры (Произведений: 241)
Демоны (Произведений: 265)
Драконы (Произведений: 164)
Особенная раса, вид (созданные автором) (Произведений: 122)
Редкие расы (но не авторские) (Произведений: 107)
Профессии, занятия, стили жизни 8 списков
Внутренний мир человека. Мысли и жизнь 4 списка
Миры фэнтези и фантастики: каноны, апокрифы, смешение жанров 7 списков
О взаимоотношениях 7 списков
Герои 13 списков
Земля 6 списков
Альтернативная история (Произведений: 213)
Аномальные зоны (Произведений: 73)
Городские истории (Произведений: 306)
Исторические фантазии (Произведений: 98)
Постапокалиптика (Произведений: 104)
Стилизации и этнические мотивы (Произведений: 130)
Попадалово 5 списков
Противостояние 9 списков
О чувствах 3 списка
Следующее поколение 4 списка
Детское фэнтези (Произведений: 39)
Для самых маленьких (Произведений: 34)
О животных (Произведений: 48)
Поучительные сказки, притчи (Произведений: 82)
Закрыть
Закрыть
Закрыть
↑ Вверх