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

Дополнение к гл. 4 книжки о Фокале

Самиздат: [Регистрация] [Найти] [Рейтинги] [Обсуждения] [Новинки] [Обзоры] [Помощь|Техвопросы]
Ссылки:


 Ваша оценка:

   Я тут немножко причесал текущую версию и пытаюсь её выложить, а это - что-то типа описания к ней.
  
  
   Дополнение к главе 4 книжки про Фокал.
   (Как раз к той, где оный Фокал и описан. И которую теперь
   можно и не читать, потому как здесь пересказывается всё заново...)
  
   Надо же на чем-то остановиться?
   Надо, ну хотя бы временно. Вот и давайте остановимся на умеренно стабильной версии 1Б.433 от 09.10.20, назовём её "базовая-2" и подробно опишем её свойства. В сравнении с "базовой-0" версией, за которую мы примем то, что описано в главе 4, к коей этот текст - дополнение.
  
   Кратенько напомню что там было написано.
   - что Фокал - строчно-ориентированный: читает очередную строку и либо кладёт в память (если она с номером) либо выполняет
   - что номер строки - дробное число, и поэтому программа (то, что хранится в памяти) естественным образом разбита на подпрограммы - группы строк.
   - что дробное число (в операторе что-то делающем со строками) указывает одну конкретную строку, а целое - всю группу. А всю программу - ключевое слово Ales.
   - что программная строка состоит из операторов, каждый из которых выполняет одно элементарное (для данного языка) действие
   - что если операторов в строке несколько они разделяются символом ';'
   - что оператор обязательно начинается с ключевого слова (которое можно сокращать до первой буквы, потому что они нарошно подобраны на разные буквы алфавита). И может содержать что-то еще - от оператора зависит. Например выражение. Или еще одно ключевое слово. Или некий таинственный "формат"...
   - что если выражений в операторе несколько - они разделяются символом ','
   - что выражения описывают вычисления по формулам и состоят из операций и операндов. Ну и скобок - для красоты, удобства и для ради изменения порядка действий супротив "естественного". (Но скобки должны быть сбалансированы.)
   - что работает Фокал с одним единственным типом данных - числами с плавающей запятой.
   - что операции (коих ровно пять: '+' '-' '*' '/' '^') указывают арифметические действия над этими числами, а операнды указывают где их взять.
   - что число можно непосредственно изобразить прямо в выражении в виде константы - самым обычным для всех алгоритмических языков способом - в виде последовательности циферок, возможно с десятичной точкой, отделяющей дробную часть от целой, а так же порядком после буквы 'E'
   - что число можно взять из переменной, куда оно ранее было положено на хранение оператором Set (ну или For - он тоже "с присваиванием"). Каковая переменная обозначается "именем" - словом, начинающимся обязательно с буквы и состоящем из букв и цифр. (Впрочем в Фокале буква - всё что не цифра и не разделитель. В том числе ни для чего не используемые символы _:@#$&\|~)
   - что число может быть так же вычислено одной из встроенных функций. Каковые тоже обозначаются именами, но обязательно на букву 'F'. (А вот переменные на эту букву называть нельзя, увы.)
   - что функции, возможно, надо передать аргументы - в скобочках после имени. Если несколько, то через запятую.
   - что переменной тоже "причитаются" аргументы, вернее - индексы, один или два. (Или ни одного.) Это же массив получается! (Одно- или двух-мерный.)
   - что функции - только встроенные, новую функцию (как в других языках) объявить невозможно. Но если очень хочется, то в качестве функции можно использовать подпрограмму - вызвав её с помощью спецфункции FSUbroutine. В том числе ей можно передать один аргумент (он присвоится спецпеременной '&') а по завершении она вернет результат, в качестве которого берётся значение последнего выражения, вычисленного перед возвратом из подпрограммы.
   - что переменные (как в других языках) объявлять не надо, ни обычные ни с индексами - они заводятся в момент первого им чего ни будь присваивания. И занимают память до тех пор, пока все разом не будут уничтожены оператором Eraze
   - что выражение произвольной сложности может быть использовано абсолютно везде, где по смыслу требуется число. (Кроме таинственной конструкции "формат") В том числе в операторах передачи управления, таких как Go, Do, If. (Получается "вычисляемый переход".) И даже когда ждет ввода оператор Ask. Он вводит не просто константу, а именно выражение, вычисляет его и кладёт результат в указанную в нём переменную. Можно сказать, что Ask - "левая часть" оператора присваивания Set.
   - что операторы Set и Xecut просто вычисляют указанное в них выражение, но первый имеющий форму: Set переменная = выражение; кладёт результат выражения в переменную, а второй: Xecut выражение; этот результат просто теряет. Он предназначен для вызова функций с "побочным эффектом", возвращаемое значение которых - "не интересно".
   - что оператор вывода Type выводит результат каждого из перечисленных в нём выражений например на терминал в удобочитаемом виде. А оператор ввода Ask делает всё наоборот - для каждой из указанных в нём переменных, вводит (например с того же самого терминала) и вычисляет одно выражение. А его результат помещает в эту переменную.
   - что в обоих этих операторах допускаются так-же текстовые константы (в виде любых символов в кавычках), которые выводятся один к одному - в качестве комментариев к выводимым числам или приглашений ко вводу.
   - что ранее упоминавшаяся таинственная конструкция "формат" указывает в операторе Type как ему выводить числа. Это либо одиночный символ '%' обозначающий "формат по умолчанию", либо '%' и два целых, разделенных точкой, указывающих ширину поля под число и количество выводимых цифр (т.е. точность).
   - что оператор Operate указывает (с помощью второго ключевого слова) куда производить вывод и откуда ввод. И что при любой ошибке (в том числе - конец файла или другого носителя информации на устройстве, откуда идёт ввод) Фокал автоматически переключает ввод и вывод на терминал, после чего "ругается".
   - что обычно операторы выполняются в "естественном" порядке - так как написаны в строке, а строки - в порядке ввода, или в порядке нумерации. Но есть операторы "передачи управления", меняющие этот естественный порядок. Оператор Goto - просто передаёт управление строке с указанным номером, остаток строки после него не выполняется никогда. Оператор Do передаёт управление подпрограмме - как бы "взаймы": когда подпрограмма завершится, управление получит следующий после него оператор. Условный оператор If тоже как и Go передаёт управление навсегда. If (условие) адрес1, адрес2, адрес3; Но в зависимости от того, меньше нуля значение "условия", равно или больше - по одному из трех адресов. Если последние из них отсутствуют, то в соответствующих случаях выполняется остаток строки.
   - что подпрограммой может быть как отдельная строка так и вся группа - в зависимости от того дробное или целое число было в операторе Do.
   - что возврат из подпрограммы происходит автоматически по достижению её конца, ну или при выполнении оператора Return.
   - что оператор Quit останавливает выполнение всей программы независимо от глубины вложенности вызовов подпрограмм. (В то время как Ret завершает только последнюю.) А в "нулевой" ("прямой") строке (в отличии от "косвенных" - находящихся в памяти) оператор Quit завершает работу интерпретатора (позволяет выйти в операционную систему).
   - что для многократного выполнения чего либо (например действий над каждым из элементов массива) используется оператор For переменная = нач_зн, кон_зн, шаг; который сначала (в качестве начального значения) присваивает указанной в нём переменной значение первого выражения в его заголовке. А потом каждый раз увеличивает его на "шаг" (или на единицу, если третьего выражения в заголовке нет) до тех пор, пока оно не станет больше конечного значения. И каждый раз выполняет остаток строки. Причем как подпрограмму.
   - что для обслуживания (т.е. сохранения, исправления, удаления...) хранящихся в памяти программных строк используются такие же операторы, как и все остальные (а не какие-то специфические "команды" как в подобных интерпретаторах других языков). Оператор Write - как для вывода на терминал так и для сохранения на внешних носителях. Оператор Modify для редактирования и Eraze для удаления. А для загрузки ранее сохраненной программы какой либо специальный оператор не требуется: достаточно переключить на неё канал ввода, как программа автоматически будет читаться и загружаться. А если там встретятся ненумерованные строки - они выполняться в точности так же, как если бы они были введены с терминала. (Что и позволяет использовать Фокал для выполнения "командных" (они же "скриптовые") файлов.) А запуск программы на выполнение осуществляется любым оператором передачи управления - Go, Do и даже If.
   - что оператор Load используется для подгрузки к интерпретатору спецфункций, отсутствовавших в базовой комплектации. (Которые после этого становятся "встроенными".)
   - что для посимвольного ввода/вывода используется спецфункция FCHR, выводящая каждый из своих положительных аргументов, а встретив отрицательный (который должен быть последним или единственным) вводящая один байт и возвращающая число, равное его коду.
   (* Надеюсь, Вы уже догадались, что заглавными я пишу обязательные буквы в именах функций и операторов, а вовсе не выказываю им повышенное уважение. *)
   - что для отсчета временных интервалов используется спецфункция FCLK, возвращающая количество тиков таймера с момента прошлого своего вызова.
   - что для обращения к управляющим регистрам внешних устройств (в т.ч. нестандартных, собственной разработки) используется спецфункция FX, а для их сброса (т.е. приведения в некоторое "исходное" состояние) - оператор Kill.
   - что для назначения реакции на (одно?) внешнее событие используется оператор Break, указывающий какую подпрограмму в этом случае вызывать.
   - что в базовый набор встроенных функций входили корень, экспонента, логарифм, прямые и обратные тригонометрические функции. А так же генератор псевдослучайных чисел - функция FRND, без аргумента выдающая очередное число, а с аргументом - настраивающая генератор. И кроме того функции FABs и FSGn для получения абсолютного значения и знака (-1,0,+1) числа; FITr и FMOd - его целой и дробной части.
   - что для поддержки процесса отладки (неизбежного для любой программы, потому что в любой программе найдётся хотя бы одна ошибка (С)), реализована "трассировка" и "пошаговый режим". Трассировка включается (и выключается) символом '?' перед оператором, а пошаговый режим - двумя вопросительными знаками. При трассировке текст каждого выполняемого оператора выдаётся на терминал, а при пошаговом режиме интерпретатор еще и останавливается, ожидая команды пользователя: выполнить следующий оператор, остановить программу или продолжить без трассировки...
   - что для написания комментариев (пояснений к программе) имеется оператор Comment, весь остаток строки после которого просто игнорируется.
   - что все скобки ([{<>}]) эквивалентны, и все кавычки '"` - тоже. (Что чтобы в текстовой константе изобразить кавычку, константу нужно заключить в кавычки другого типа.)
   - что операторы распознаются по первой букве, имена переменных - по двум, а имена функций по первым уникальным. А лишние символы просто игнорируются.
   - что пробелы - для красоты, и их можно вставлять в любых количествах. Но после ключевого слова обычно требуется какой либо символ-разделитель, например пробел.
   - что запятые (если это не список аргументов функции), тоже в принципе для красоты...
   - что всего может быть до 99 групп и до 99 строк в каждой группе.
   - что к одной и той же переменной можно обращаться как с одним так и с двумя индексами, что к тому-же зависит от реализации. (Впрочем обычно гарантируется, что X(0,0) и X(0) это одна и та же переменная X.)
   - что таймер в машинах разных типов тикает по разному. А чтобы сделать (с помощью функции FX) с внешним устройством что-то осмысленное, надо знать про него почти всё, иначе будет ОЙ!
   - и.т.д. и.т.п. - в зависимости от возможностей аппаратуры и фантазии разработчиков.
   Ну в общем что Фокал это типичный калькулятор (правда программируемый): чуть что - сразу останавливается, ругается и передаёт управление пользователю.
  
   Вот где-то примерно так. Ага.
   Ну так чем-же наша "базовая-2" версия отличается от вышеописанной?
  
   В "негативную" сторону тем, что подгрузка дополнительных функций (и занимающийся этим оператор Load) - не реализована. Сброс внешних устройств (оператором Kill) - тоже. (Впрочем, в используемой архитектуре это не предусмотрено.) Да и обращение к аппаратуре на физическом уровне (функцией FX) - тоже сделано в ограниченном объёме - только к портам ввода/вывода. А к пространству оперативной памяти - нет.
   И, да: злонамеренно сохранены почти все ограничения базового Фокала. В т.ч. распознавание имени переменной только по двум символам и "табу" на букву 'Ф'.
  
   А в "позитивную" - в принципе по четырем аспектам:
   1. доработан ввод/вывод для взаимодействия с файловой системой
   2. сделаны настоящие локальные переменные
   3. реализована реакция не только на (внешние) "события" но и на (внутренние) "ситуации". (В т.ч. на ошибки времени выполнения.)
   4. обнаружен "регистр-аккумулятор" под текстовую строку. И вокруг него реализован механизм работы со строками. Совсем не такой, как планировался для третьего Фокала.
   5. ну и кое-что по-мелочи: введены удобства и красивости; додуманы и доопределены некоторые операторы и функции...
  
   Вот с этого и начнем - с удобств и красивостей.
  
   5.1 Первым делом, первым делом... пианину? Нет, оператор Help! Который бы выдавал справки по операторам и функциям. Заранее заготовленные в отдельном справочном файле. По первой букве указанного в нём ключевого слова.
   Этот самый справочный файл - самый обыкновенный - текстовый. (Чтобы можно было читать и так. Хотя бы потому, что это и есть основная документация на данное ПО. Да и писать удобней.) Но разбит на "секции" с помощью комбинации символов <<Х>> обязательно находящейся в начале строки. (Где Х - первая буква соответствующего ключевого слова).
   Сначала разделы были маленькие - чтобы убирались на один экран. Но потом они разрослись и понадобился встроенный просмотрщик. Чтобы за раз выдавал на экран не больше строк, чем его высота. И ожидал реакции пользователя. (ВК - еще одна строка, пробел - следующий экран. В точности как в UNIX`овском more.)
   А так как выводом у нас занимается еще и Type - ему спецформат %% чтобы и его вывод управлялся этим просмотрщиком. А вывод оператора Write (когда он на терминал) идёт через просмотрщик всегда.
  
   5.2 Далее. Главное из удобств, реально облегчающих жизнь пользователю - "экранный" редактор командной строки. С "карманом" под фрагмент строки и "пулом" (мешком) ранее уже введенных строк. А так же средством захвата в карман фрагмента с любого места экрана. (Подсмотрено в Дос-Навигаторе.)
   Разумеется, эта штука автоматически используется всегда, когда ввод с терминала.
   Было даже введено средство чистить этот пул (оператором Eraze с ключевым словом Пул), и добавлять туда строчку. (Write Пул) Правда, всё это понадобилось только один раз - в некоторой демонстрационной программе, вот это всё и демонстрирующей.
  
   0. Да, кстати, примем решение, что в именах переменных все буквы различаются, а в ключевых словах и именах встроенных функций - нет. То есть например буквы "W", "w", "В", "в" в имени переменной - разные, а в имени функции или будучи первой буквой ключевого слова - одна и та же.
   Эквивалентность русских и латинских в основном очевидна: А-A, Б-B, Ц-C,... Но Q-Я, V-Ж, X-Ь, Y-Ы. Буква Э эквивалентна символу "\", а Ю - "_" которые тоже буквы. И вообще, буква - всё что не цифра и не разделитель.
  
   5.3.0 Красивости типа доопределения функций опираются на то, что у функции теперь может быть переменное количество аргументов. Причем аргумент может быть не только числом, но и текстовой константой. А так же может отсутствовать - просто пустое место между разделяющими аргументы запятыми. Лишние аргументы, если есть, игнорируются.
   Здесь я ничего не придумал - в базовом Фокале всё это уже было - и переменное количество аргументов и их пропуск и текстовая константа в виде строки в кавычках. Правда по-отдельности. А нам это позволило разнообразить функционал.
   5.3.1 Так, одна единственная функция FTMP делает абсолютно всё, что вообще надо делать с датой и временем. Без аргументов она выдаёт текущую дату в виде количества суток с начала тысячелетия. Его дробная часть (доли суток) это время. Используемого в интерпретаторе плавающего числа с двойной точностью (примерно 16 верных десятичных цифр) в принципе хватит для указания моментов времени с точностью до миллисекунды в пределах всей истории человечества, начиная с нижнего палеолита. Такое представление удобно чтобы с ним работать. (Например вычислить временной интервал между двумя такими моментами.) Но совершенно неудобно для восприятия.
   Поэтому сама же функция FTMP с числовым аргументом его и расшифровывает: если он один - выдаёт в канал вывода в виде текста. Или выделяет и возвращает указанную вторым аргументом компоненту: 0 - секунды, 1 - минуты, 2 - часы, 3 - день месяца, 4 - месяц 5 - год. (Все целые, кроме секунд.)
   Если первый аргумент пропущен - FTMP "собирает" дату/время из остальных.
   А если первый аргумент - текстовая константа - FTMP пытается выделить из неё дату/время, в том числе по шаблону, указанному вторым аргументом. А без него - по: "ДД.ММ.ГГ чч:мм:сс". (Его же FTMP использует для преобразования даты/времени в текст.)
   5.3.2 Вторая функция, работающая с временными интервалами - FCLK тоже немного усовершенствована: выдаёт интервал (от предыдущего "момента") не в "тиках" таймера, а сразу в секундах. (А сколько секунд в сутках - каждый, если что, и сам догадается: 60*60*24.) Этот самый "момент" она устанавливает текущим, только если без аргумента. Проще говоря - сбрасывает счетчик. Но может установить его равным первому аргументу. В том числе, если он отрицательный, то как бы переместив его в будущее.
   В этом случае можно заказать "событие" - когда этот момент наступит. (Номер события - второй аргумент.) Или напротив отменить его (если первый аргумент отсутствует). Или вообще отменить все события - если отсутствуют два аргумента, но есть разделяющая их запятая: FCLK(,). Или же событие может происходить периодически - с интервалом, указанным третьим аргументом.
   Ну а если второй аргумент таки есть, но отсутствует - производится тупое ожидание отнесенного в будущее "момента" - полный аналог имеющихся в других языках функций задержки SLIP. Например FCLK(-2.7,) подвесит машину на 2.7 сек.
  
   3.0 "Событие" и "ситуация" - это происшествия, на которые программе надо как-то реагировать: как только оно произошло - вызывается заранее назначенная подпрограмма "реакции" и выполняет по этому поводу какие-то действия. Назначает реакцию и на события и на ситуации оператор Break. (И отменяет тоже.) А искусственно они порождаются: события - оператором Kill, а ситуации - оператором Quit. И тот и другой (кроме номера события или ситуации) могут содержать дополнительные аргументы, которые передаются в качестве параметров в подпрограмму реакции - становятся начальными значениями её локальных переменных. В точности так же, как при вызове подпрограммы спецфункцией FSUBroutine: там первый аргумент - адрес подпрограммы, а остальные... (Да и в операторе Do, вызывающем подпрограмму как процедуру, тоже теперь так сделано.)
   Различаются события и ситуации вот чем:
   Событие - глобальное. Оно происходит где-то во внешнем мире, и реакция на него это аналог прерывания. Собственно ДОС`овскими прерываниями события с номерами 1..255 и являются. А номера внутренних событий Фокала начинаются с 1000. Внутренних это типа нажатия кнопки на клавиатуре, или исчерпание очереди звуков, назначенных на воспроизведение с помощью встроенного динамика. Или вот - истечение временного интервала, назначенного функцией FCLK. (Хотя вот она то как раз может назначить номер события какой ей вздумается.)
   А вот ситуация - локальная. В том смысле, что она возникает где-то в конкретном месте выполняющейся программы. Все "естественные" ситуации - это ошибки. А искусственные, порожденные оператором Quit, это средство прекратить её выполнение. Но если раньше выполнение программы всегда прекращалось полностью и управление получал пользователь, сидящий за терминалом, то сейчас управление получит подпрограмма, предусмотрительно поставившая (с помощью оператора Break) "ловушку" на данную ситуацию. (Так что механизм ситуаций это еще и средство передачи управления, известное как "структурный переход".)
  
   Подпрограмма реакции на событие выполняется "вне очереди" - между операторами основной. Причем в ней самой подобная же реакция на другие события запрещена. (А вот если она передаст управление еще какой либо подпрограмме - там можно.) И как только она завершится - основная программа продолжится со следующего (очередного) оператора. Ничего не заметив.
   Каждому отслеживаемому событию сопоставляется еще и счетчик. Потому что реакция на событие может быть запущена только когда работает "исполнительная" часть интерпретатора. А когда "интерфейсная" часть общается с пользователем (на предмет получения от него очередной командной строки) - нет. А вот подсчет событий, на которые заказана реакция, ведётся всегда.
   Кроме того, некоторые операторы могут выполняться ну очень долго. Например вышеописанное тупое ожидание с помощью всё той же FCLK.
   Этот счетчик передаётся подпрограмме реакции первым аргументом, а сам сбрасывается.
   И вот еще: если "событием" является прерывание процессора, то Фокал его не перехватывает (т.к. в силу собственной неторопливости не успеет обработать), а только фиксирует - наращивая вот этот самый счетчик. А вот учиняет (с помощью функции FCLk например) - вполне без дураков.
  
   С ситуациями всё обстоит по-другому: после возникновения, ситуация "распространяется": происходит рекурсивный выход из всех вложенных вызовов подпрограмм. До тех пор, пока управление не получит "интерфейсная" часть интерпретатора. После чего она "ругается" - выдаёт сообщение об ошибке.
   Или пока у одной из подпрограмм не обнаружится подходящая ловушка. Тогда распространение ситуации прекращается; управление получает указанная в ловушке подпрограмма. После этого ошибка считается исправленной - установившая ловушку подпрограмма нормально завершается.
  
   Поэтому ловушка на событие - глобальная: если указать другую реакцию на данное событие - она отменяет предыдущую. А "нулевая" реакция - вообще отменяет отслеживание данного события. (События - ресурс дефицитный: количество ячеек в таблице отслеживаемых событий - в данной версии всего 16 штук.)
   А вот ловушка на ситуацию - локальная: она, вместе с локальными переменными - личное имущество поставившей её подпрограммы. Точнее: и то и другое размещается в кадре стека возвратов, соответствующего этой подпрограмме. При вызове подпрограммы, на этот стек добавляется новый кадр (в основном для сохранения там адреса возврата), а при возврате из неё - удаляется. И вместе с ним - всё хранящееся в нём вот такое локальное "имущество". Соответственно, при возникновении ситуации, все эти кадры последовательно удаляются со стека начиная с самого последнего, пока в одном из них не найдётся подходящая ловушка...
  
   2.0 Кроме ловушек на ситуации, в кадре стека возвратов могут быть еще и локальные переменные, которые теперь - настоящие. Они - идейные потомки спецпеременной, & через которую функция FSBR передавала параметр в подпрограмму, носят имена &1, &2, ... &N где N - любое число. (Или $1, $2... чтобы как в UNIX`овском sh.) А & превратилась в &0 (она же $ или $0). Используются они для того же самого - передачи параметров в подпрограмму. Только теперь у той же спецфункции FSBR может быть не два аргумента, а сколько угодно. Да и оператор Do теперь это умеет... (Догадался сделать только когда написал вот это. Инерция мышления, однако.)
   При завершении подпрограммы, локальные переменные вместе с её кадром стека ясный пень - удаляются. И становятся доступны предыдущие - в следующем кадре. Впрочем, они и так были доступны: текущие просто их "экранировали". Но если в данном кадре искомой переменной нет - она ищется в предыдущем, предпредыдущем и.т.д. А если так и не найдена - это не считается ошибкой (как для глобальных переменных): просто возвращается ноль.
  
   6.1 Для третьего Фокала было задумано, что самыми глобальными из локальных переменных будут параметры в командной строке, указанные при запуске Фокала. Но они - строчного типа, а мы пока работаем только с числами.
   В принципе до них тоже можно добраться, равно как и до "окружения" (известного как "environ") - набора строчек вида ИМЯ=значение, через которые UNIX`овский sh передаёт запущенной им программе значение некоторых своих макропеременных (тех, которые помечены как "экспортируемые"). Да и другие запускальщики программ как правило тоже. Но сделано это, прямо скажем - по дурацки: повешено на функцию FTEll, на которую свалили всё, что не знали кому поручить. А надо бы для этого ввести отдельное средство, что ни будь типа спецфункции FENviron... Но не будем забегать вперёд.
  
   2.0.1* Таким образом "цепочка контекстов" (если про комплект локальных переменных можно сказать что это "контекст") не статическая, как в других языках ("структурных" и компилируемых, как например Паскаль), а динамическая.
   Ну так там у них в Паскале (и других подобных языках) статическая и динамическая цепочки контекстов существенно конфликтуют. Щас объясню как.
   В этом самом Паскале описания подпрограмм вкладываются друг в дружку как матрешки. Например внутри подпрограммы A могут быть подпрограммы B и C, а внутри B еще и D, E, F... И для всех для них локальные переменные подпрограммы A (например a1, a2, a3) находятся снаружи. И следовательно должны быть доступны во всех перечисленных подпрограммах, если конешно у них нету своих переменных с такими же именами. Ага. А в подпрограммах D, E, F еще и локальные переменные подпрограммы B (например b1, b2, b3). А вот те, что объявлены в подпрограмме C (например c1, c2, c3) - разумеется нет.
   У нас - интерпретатор. У нас переменная ищется в тот момент, когда к ней идёт обращение, то есть во время выполнения программы. Вот в данном случае путем просмотра списков локальных переменных во всех кадрах стека возвратов, начиная с последнего. Да, это долго и дорого, но такова селяви. А у них - компилятор. Он заранее долго-долго что-то химичит с текстом программы, но всё это ради того, чтобы во время выполнения доступ к переменной производился в "одно касание" - единственной машинной командой, сразу лезущей по адресу, где она лежит. С локальными переменными всё просто: используется указатель на текущий кадр стека. (Чаще отдельный, но бывает что и указатель его вершины.) Адрес переменной - это смещение относительно его. А вот как быть если нам в подпрограмме B надо обратиться к переменной a2? Просто относительно того же указателя, но к предыдущему кадру, даже точно зная их размеры - не прокатит! Потому что перед этим подпрограммы B и C могли сорок раз успеть вызвать друг дружку рекурсивно, и вот на сорок первый раз вдруг понадобилось... То есть между кадром подпрограммы A, где вожделенная переменная a2 и текущим кадром активации подпрограммы B, на который смотрит оный указатель, еще сорок кадров активации подпрограмм B и C, пусть даже известного, но (из вредности) разного размера, а главное наваленных неизвестно в каком порядке. (Который, например, от содержания обрабатываемых ими данных, зависит!) Ну и как её искать? Тоже интерпретацией - последовательно просматривая все кадры, пока не найдется нужный... (Благо, если указатель кадра - отдельный, то его предыдущее значение сохраняется в текущем кадре, вместе со значениями других регистров, используемых данной подпрограммой. И все кадры получаются связанными в цепочку. Но это "динамическая" цепочка, как и у нас.) А как же тогда эффективность?!
   Или, чтобы малость облегчить этот процесс, организовать еще и статическую цепочку. (Честно говоря, непонятно как.) Или поступить как в некоторых машинах Лебедева (отличающихся от машин фон-Неймана высокоуровневой архитектурой), например в Эльбрусе: завести не один, а целый массив указателей кадра, с номерами, соответствующими статической вложенности подпрограмм. Соответственно для обращения к локальным переменным используется пара чисел: номер такого регистра и смещение переменной относительно его. (Каждая подпрограмма, зная свой уровень статической вложенности, получив управление, сразу помещает адрес своего кадра стека в регистр с соответствующим номером. Старое его значение сохраняет в своём кадре. А при завершении - вертает всё в зад.) Но там всё это делается аппаратно, а тут - интерпретацией. И всё ради чего? Ради переменных с промежуточными уровнями локальности, которые возможно никому сто лет не понадобятся. В общем не было у бабы заботы - так купила себе порося!
   Вон в Си этой надуманной академическими недоумками проблемы просто нет! А всего лишь не можно вкладывать подпрограммы друг в друга. Блоки (которые "составной оператор"), в начале которых могут быть в том числе и объявления переменных, расширяющих, кстати, текущий кадр стека - это пожалуйста. А вот подпрограммы - нет. А если надо куда ни будь упрятать вспомогательные функции (да и переменные тоже) чтобы не маячили и глаза не мозолили - для этого есть уровень файла с исходным текстом: пометь эти вполне себе глобальные объекты как "static" (на ключевом слове ребята явно сэкономили), и из подпрограмм, помещенных в другие файлы, они видны уже не будут. (А то слов мало, а сущностей много: вдруг в другом механизме вспомогательные детали захочется назвать также.) То есть всего два уровня "локализации": внутри подпрограммы и внутри модуля (файла с исходными текстами) и технологическая потребность удовлетворена, а проблема - не создана.
   А у нас и того нет: имена локальных переменных - предопределенные.
  
   2.1 Однако, самому создать заведомо локальную переменную просто присвоив ей первый раз значение: Set &N=... Можно, но без гарантии: вдруг такая уже есть где-то ниже по стеку? Здесь нам на помощь придёт... Раньше это был только оператор Kill, но он - страшненький ("убить"). Но теперь еще и оператор Do и функция FSUBr. У которых теперь может быть сколько угодно дополнительных параметров - для передачи их в качестве аргументов в подпрограмму. Надо только, чтобы они все по "основной профессии" ничего не делали. Для чего первый (главный!) параметр должен отсутствовать. Тогда следующие помещаются в заведомо локальные переменные. Но "позиционным" методом. То есть учинить таким способом переменную &-тридцать-с-лишним - затруднительно.
  
   5.3.3 Идея воспользоваться побочным эффектом, не дав оператору или функции действовать по прямому назначению - злонамеренно пропустив главный параметр, применима так же и к функции FX позволяющей обращаться к аппаратуре напрямую. И потому еще более опасной, чем оператор Kill: про устройство, которым пытаемся управлять, надо знать почти всё, иначе будет ОЙ!
   В принципе у неё два аргумента - команда и адрес (номер порта). Но если команда - на запись (-1 или -2) то нужен третий - что писать. А если на проверку (0) то тоже нужен третий - маска, указывающая какие биты проверять. Потому что проверка - это считывание порта и выделение отдельных битов с помощью побитового "И" с маской.
   Главный аргумент здесь - команда. Если её пропустить, то функция никуда не лезет, а просто выполняет побитовое "И" между целой частью второго и третьего аргументов.
  
   5.3.4 Как известно, спецфункция FSUBr, передающая управление подпрограмме из середины выражения, еще и должна туда вернуть вычисленный этой подпрограммой результат. Уточним, что возвращает она значение, застрявшее в неком числовом аккумуляторе - внутренней переменной Фокала.
   Если функция никакой подпрограммы не вызывает (в виду отсутствия главного аргумента, указывающего её адрес), то всё равно возвращает содержимое этого аккумулятора. В него попадают значения выражений, правда не всех: которые в операторах передачи управления (Go, Do, If) - нет. (Да и зачем бы нам могли понадобиться эти номера программных строчек?) Но кроме того и некоторые вещи, которые никаким другим способом не получить. Например счетчик количества строк, принадлежащий упомянутому в п.5.1 просмотрщику. (Что пригождается в демонстрационных программах, выводящих на экран обширные тексты.)
  
   5.1.1 Если пользователю надоест читать выдаваемый просмотрщиком текст, то он нажмет кнопку ESC и просмотрщик прекратит. Но демонстрационной программе надо как-то узнать о сём прискорбном факте. (И, например, промотать остаток текста чтобы добраться до следующих за ним команд.) Здесь нам поможет буфер клавиатуры...
   В нём помещается ровно одно число - либо признак что он пуст, либо код нажатой кнопки. (Еще одно нажатие и будет ошибка 0.2 "переполнение буфера клавиатуры"!) Впрочем, забирает его оттуда только функция FCHR, а все остальные только очищают. (И просмотрщик - тоже.) А позволяет проверить функция FTEll. (Потому что если он пуст - FCHR будет висеть и ждать нажатия кнопки.)
   Ну так на то, что буфер клавиатуры пуст, одинаково указывают и 0 и -1. Но все всегда помещают туда 0, а -1 - только просмотрщик в случае если пользователь таки нажал ESC.
  
   1.0 Заметим, что кроме аккумулятора под число, хранящего последний результат, в Фокале есть аналогичная вещь для объектов ввода/вывода - специально для уже упоминавшейся функции FTEll. Которая должна сообщить текущую позицию в файле. Вот только в каком? Наоткрывать их можно десятка полтора. А если в "текущем", так их - два: те, на которые указывают каналы ввода и вывода. Вот и было решено, что это тот, к которому было последнее по времени обращение. Пусть даже и совершенно фиктивное...
  
   4.0 ...И еще один аккумулятор - для хранения (и использования) текстовой строки. Заметим, он не придуман, организован, реализован (как для FTEll), а обнаружен. Потому что, как оказалось, был всегда...
  
   3.1 Страшненький оператор Kill искусственно порождает "событие". Его исходная функция - сброс аппаратуры в некое "исходное" состояние, хотя и не реализована, но зарезервирована за оператором без аргументов. А пока он ничего не делает.
   Первый аргумент оператора указывает номер события, которое надо породить. (А без него он тоже ничего не делает.) А вот второй и следующие аргументы - параметры этого события: некоторые прерывания, которые учиняет сей оператор, суть обращения к операционной системе (или BIOS`у). Им полагается передать параметры (как правило через регистры процессора) и потом получить результат (через них-же). Оператор не функция - значение не возвращает. (Возвращает однако - через числовой аккумулятор: либо 0, либо код ДОС`овской ошибки - если признак Ц в слове признаков процессора установлен - так все прерывания обычно об ошибке сообщают.) К тому же их здесь сразу несколько. Ну и куда же их девать, если не разложить по локальным переменным ТЕКУЩЕЙ подпрограммы? (Есть языки, где функция может вернуть сразу несколько значений, но это явно не наш случай. Да и там они как правило сразу раскладываются по переменным.) Поэтому дополнительные аргументы этого оператора перед прерыванием и помещаются в регистры, а после из них - в локальные переменные. А лишние, или если оператор ничего не делает (и не собирается - за отсутствием первого аргумента) то сразу в переменные.
   Но всё равно оператор - страшненький. Обращаться осторожно!
  
   3.2 Внутреннюю "ситуацию" искусственно порождает оператор Quit. В принципе он делал это всегда, но теперь ему можно указать - какую именно. Плюс параметры, для передаче подпрограмме реакции.
   Оператор Quit без аргументов по-прежнему останавливает выполнение программы, причем так, что интерфейсная часть интерпретатора "не ругается". Для этого он порождает ситуацию с номером -1. На которую не поставить ловушку. Потому что в операторе Break отрицательными числами обозначаются "события".
  
   3.3 Ситуации, возникающие естественным образом - это ошибки, которые интерпретатор обнаруживает в ходе выполнения программы. Номер ошибки (и вообще ситуации), так же как и номер строки - дробное число. В результате чего ошибки разбиты на группы по типам:
   1 - синтаксические ошибки (типа дисбаланс скобок)
   2 - арифметические ошибки (типа деление на ноль)
   3 - ошибки ввода/вывода (типа конец файла)
   4 - прочие ошибки времени выполнения (типа не найдена строка программы)
   5 - исчерпание или недоступность ресурсов (типа не хватает памяти) И в дополнение к ним группа ноль:
   0 - действия пользователя (типа он нажал Ctrl/Ц)
   Например ошибка 4.3 - нет переменной: попытка взять значение из переменной, в которую еще ничего не клали. И соответственно места в памяти под неё не выделяли. Или уже освободили - оператором Eraze.
  
   ### Ограничение текущей версии: не более 100 групп ситуаций и не более 100 ситуаций в группе. Т.е. под это, как и для хранения номера строки, выделено всего по одному байту.
   Впрочем, для нумерации программных строк в данной версии выделено больше. И смело можно использовать не две, а до четырех десятичных цифр.
  
   3.4 А вот номер события - целый, и при том отрицательный. (Хотя, если так подумать, то пожалуй и для них стоило бы ввести группы...)
  
   3.5 Реакция как на "событие", так и на "ситуацию" заказывается и отменяется оператором Break. Например:
   B N=R -- установит на событие (или ситуацию) с номером N реакцию в виде вызова подпрограммы R. Здесь N и R - числа, точнее выражения, их вычисляющие. (В приведённом виде это будут значения переменных с именами 'N' и 'R'.)
   B N -- установит "нулевую" реакцию (для события - просто её отменит), а
   B N = ; остаток строки -- установит в качестве реакции остаток текущей строки, который сейчас разумеется выполняться не будет.
  
   2.2 Так как имя локальной переменной состоит в основном из её номера... Вернее номера позиции при "позиционной" (а других не держим) передаче параметров в подпрограмму. ...То к ним ко всем разом можно обращаться как к одномерному массиву: не только &N где N - число, но и &(NN), где NN - выражение произвольного вида.
   Что, кроме всего прочего, даёт доступ и к локальным переменным с экзотическими номерами -1, -2 и.т.п. И которые как правило используются для служебных целей. В частности в &(-1) при передаче параметров помещается их количество. Точнее - индекс последнего из параметров. А в &(-2) - номер ситуации (ошибки) на которую сработала ловушка.
  
   2.2.1* Бывает еще "ключевая" или "именная" передача параметров. Это когда у подпрограммы их большая куча. Мы их все, может, и знать не знаем - передаём только некоторые, написав: имя=значение; а у всех остальных остаются значения по-умолчанию. Но это же надо заранее "объявить" в подпрограмме все эти параметры, указав для каждого из них имя (при чем желательно "говорящее") и его значение на случай если ничего не передадут. Всё это явно не для Фокала. Тут мы принципиально ничего заранее не объявляем и не описываем!
  
   3.6 Для каждой ошибочной ситуации предусмотрена текстовая строка, которую интерфейсная часть интерпретатора выдаёт в качестве сообщения об ошибке.
   Оператору Quit такая тоже причитается. Для чего один из его аргументов может быть текстовой константой. В т.ч. пустой - важно само её наличие. Которое, кстати, предотвращает выход из интерпретатора в случае если Quit выполняется не в "косвенной" строке (нумерованной, сохраненной в памяти) а в "прямой" - введенной с терминала или из командного (он же "скриптовый") файла.
   Это сообщение, а так же программная строка, вызвавшая ошибку, сохраняются во внутренних переменных интерпретатора. И при необходимости могут быть получены функцией FTEll (см. далее), на которую свалили всё, что не знали кому поручить.
  
   *** Чтобы плавно перейти от локальных переменных и ситуаций, которые уже практически полностью рассмотрены, обратно к "удобствам и красивостям", а главное - к тому, как додуманы и доопределены некоторые из операторов (и функций), рассмотрим типовой пример: надо преждевременно выйти из цикла For.
   Типа с его помощью мы последовательно перебираем... Ну не важно что, да хоть сами целые числа и ищем подходящее. Каковым пусть будет число 22. А попутно что-то с ними делаем (ну хоть на терминал выводим). И вот - нашли! Дальше перебирать не надо, цикл надо завершить.
   Дополнительное условие: надо чтобы всё это было в одну строчку. (Это мы скриптовые файлы писать метим!)
  
   6.2 Как известно, тело цикла (остаток строки после оператора) - подпрограмма. Просто перейти оттуда куда либо оператором Go (как в других языках) - не поможет: по завершении строчки, куда перешли, управление вернется заголовку цикла. А оператор Ret послужит аналогом "continue" других языков - управление вернется заголовку цикла сразу.
   Кроме того, все три выражения в заголовке цикла вычисляются один раз - перед его началом. (А не каждый раз, как например "условие" и "шаг" в аналогичном операторе языка Си.) Так что цикл с плавающей верхней границей или переменным шагом средствами самого языка не сделаешь. (Это для Си подобное - рабочий момент, а для такого программируемого калькулятора, как Фокал - редкая экзотика.)
   Типовое решение - установить параметр цикла больше конечного значения, нехорошо тем, что в данном случае он и содержит номер найденного элемента. (Можно конешно скопировать в другую переменную, но не изящно это.)
   Применим вышеописанный структурный переход, например по ситуации 17:
  
   F; B 17; F i=1,1000; T i; I(i-22)0,,0; Q 17,""
  
   Что видим? Самый обычный цикл до тысячи; самый обычный оператор Type распечатывающий параметр цикла; оператор If, решающий что искомое число уже найдено, со странными номерами строк...
   Так же видим что ловушку без реакции ставит оператор B 17; а саму ситуацию порождает оператор Q 17,"" с дополнительным аргументом в виде пустой текстовой константы - вот как раз чтобы, если это всё это в "нулевой" строке - не вывалиться из интерпретатора.
   А самый первый в строке оператор For почему-то состоит только из одного ключевого слова...
  
   5.4.1 Оператор For.
   Как известно, остаток строки после оператора For - тело цикла, выполняется как подпрограмма. Много раз - пока...
   Ну так в форме с одним только ключевым словом - один единственный раз. Вот для такого случая как здесь у нас. А то, после срабатывания ловушки на ситуацию, происходит выход из подпрограммы, которая её поставила. Вот мы и сделали подпрограмму в виде одной только текущей строки.
   Задумаемся, а до каких пор выполняется тело цикла? Ну ясный пень, что пока значение параметра цикла не достигнет конечного значения. Однако, не вполне понятно, что значит "достигнуть"? Станет больше конечного значения (ну или меньше, если шаг отрицательный) - это да. А что делать если мы сразу установили начальное значение близко к конечному - надо ли выполнить тело цикла хотя бы один раз? Принимаем решение, что если ближе чем на пол шага - тогда эта итерация цикла будет последней. Ну и если вышел за конечное значение - тоже на пол шага или больше.
   Если конечное значение не указано - тело цикла выполняется один раз. (Как подпрограмма, естественно.) Если не указан только шаг - то он всегда устанавливается либо +1 либо -1 в зависимости от того, что больше - начальное значение или конечное. А чтобы цикл никуда не двигался (чтобы например искусственно в теле цикла сделать этот шаг переменным) надо честно указать шаг равный нулю.
  
   5.4.2 Оператор If.
   Как известно, оператор If в зависимости от значения выражения, указанного в качестве условия, передаёт управление по одному из трех адресов. Но если последние из них отсутствуют, то в соответствующих им случаях выполняется остаток строки.
   Во-первых решено, что вычисляется только один из них, а остальные два - нет. Потому что это могут быть полноценные выражения, в том числе содержащие спецфункцию FSUBr, что фактически вызов подпрограммы.
   Во-вторых решено, что в операторе пропущены могут быть не только последние адреса, но и любые. И в соответствующем им случае тоже выполняется остаток строки после оператора.
   Ну и в третьих решено, что нулевой номер относится к следующей программной строке: выполнение текущей просто завершается. А некорректный номер строки (например -1) эквивалентен его отсутствию - приводит к выполнению остатка строки после оператора. Что позволяет вообще обойтись без переходов куда либо: в зависимости от условия остаток строки либо выполняется, либо нет.
   Например: if(x)0, ,0; t "X==0"! или: if(x-y)0, ,0; t "X==Y"!
   if(x) ,0; t "X!=0"! if(x-y) ,0; t "X!=Y"!
   if(x)0,0; t "X> 0"! if(x-y)0,0; t "X> Y"!
   if(x)0; t "X>=0"! if(x-y)0; t "X>=Y"!
   if(x) ,0,0; t "X< 0"! if(x-y) ,0,0; t "X< Y"!
   if(x) , ,0; t "X<=0"! if(x-y) , ,0; t "X<=Y"!
  
  
   5.4.3 Оператор Go.
   Остался в точности таким же как и был. Но для него решено, что некорректный номер строки по прежнему вызывает ошибку, а вот нулевой - относится к текущей строке. Что позволяет (вместе с вышеописанным оператором If) сделать цикл в пределах одной строки, в том числе введенной с терминала или скриптового файла.
  
   5.4.4 Для операторов передачи управления Go, Do и If решено, что оператор Do передаёт управление "точно" - в точности той строке, которая ему указана. Её отсутствие вызывает ошибку. А вот операторы Go и If передают управление "примерно": указанной строке, или следующей после неё, если она отсутствует.
  
   5.4.5 Решено, что в операторе Ret тоже может быть выражение. Если данную подпрограмму вызвали с помощью спецфункции FSUBr - его значение она и вернет. А если нет - оно просто попадёт в аккумулятор для чисел. В точности как значение выражения в операторе X.
  
   5.4.6 О! В операторе X за каким-то контрабасом разрешили не одно выражение, а несколько - через запятую. (И в операторе Set тоже, видимо за компанию.) В числовом аккумуляторе остаётся значение последнего.
  
   *** Уж коли речь зашла об аккумуляторах, то может пора вспомнить что их у нас вдруг оказалось три. И остальные два напрямую связаны с вводом/выводом.
  
   1.0.1 Вспомним, что у Фокала есть один канал ввода, откуда берёт данные оператор Ask, функция FCHR и "интерфейсная" часть интерпретатора, когда она хочет очередную командную строку; и один канал вывода, куда пишут операторы Type и Write, функция FCHR а "интерфейсная" часть выдаёт звёздочку - традиционное приглашение Фокала ко вводу очередной командной строки. И еще оператор Ask выдаёт туда двоеточие - своё приглашение ко вводу. Или текстовые константы, буде они в нём есть - с той же самой целью.
   Решено, что оная звёздочка (равно как и двоеточие оператора Ask), предназначается только и исключительно пользователю, сидящему за терминалом. За сим и выдаваться они будут только и исключительно на терминал, вне зависимости от того, куда указывает канал вывода. Но только в том случае, если ввод - с клавиатуры. И текстовые константы оператора Ask тоже. Причем наличие текстовой константы, даже пустой, подавляет вывод двоеточия.
  
   1.0.2 Вспомним, что вышеупомянутые каналы переключал оператор Operate. ("Оперировать", "манипулировать"...) Причем набор устройств ввода/вывода был фиксированный. Поэтому они обозначались вторым ключевым словом. И путаницы, что подключить к каналу ввода а что - вывода, не возникало. (Клавиатура и перфосчитыватель - только на ввод; терминал, перфоратор и принтер - только на вывод.)
   А вот теперь в нашем распоряжении файловая система. И файлов в ней столько!.. Но чтобы файлом воспользоваться, его сперва надо "открыть", а потом, когда надоест - "закрыть". А еще к файлу прилагается "текущая точка", оно же "указатель ввода/вывода". Если бы файл был магнитной лентой, то это была бы магнитофонная головка. Её можно передвигать. Перематывая ленту.
   Вот только на настоящую магнитную ленту писать получается только в конец. Вернее конец автоматически образуется там, куда пишем. А у файла такого нет: пишем поверх того что было, а то что после этого места - не пропадает.
   А еще, один и тот же файл можно открыть как для чтения так и для записи. А так же для того и другого одновременно.
   И всё это пришлось поручить оператору O, дав ему в помощь функцию FTEll чтобы сообщала текущее положение указателя чтения/записи. Впрочем, у него как раз название подходящее: можно трактовать и как Open - "открыть". А что значит "tell" - понятия не имею: в Си-шной библиотеке ввода/вывода такая функция сообщает положение указателя.
  
  
   1.1 Оператор Open обязательно содержит второе ключевое слово. Которое теперь "псевдоним" открытого файла. Он может быть на любую букву алфавита.
   Если ничего кроме этого самого псевдонима в операторе O больше нет - он как и раньше переключает канал ввода или вывода. На файл, открытый под этим псевдонимом. А так как файл может быть одновременно открыт и на чтение и на запись, то какой канал переключать определяем по остальным символам оного псевдонима: если там среди них есть буковка "R" (намек на слово read - "чтение"), то переключаем канал ввода. А если нету - то вывода.
   Если перед псевдонимом вдруг есть текстовая константа, то это имя файла, который надо открыть. Если псевдоним уже занят - открытый под ним файл сперва закрывается. А если текстовая константа пустая - то только закрывается.
   Если кроме имени файла там еще выражение в скобках - то указатель чтения/записи сразу ставится в указанную им позицию. Если есть только скобочки с выражением - то только позиционируется. А если скобочки эти - пустые, то не делается ничего. Но всё равно это операция ввода/вывода (хотя и фиктивная) и в "аккумулятор" для функции FTEll этот объект таки попадает.
   Кстати, сообщает что туда попало - сам же оператор Oper - если в нём псевдонима нет. Так же как и W - в форме, пригодной для обратного ввода. (А то W O как вывалит до кучи весь список чего наоткрывать успели!...)
   Как именно открывать файл - тоже показывают дополнительные буковки псевдонима. Которые, в отличии от других ключевых слов, не все игнорируются. В данной версии задействованы: R W A B T N, указывающие на чтение, запись, добавление, "бинарный" режим открытия файла и соответственно "текстовый". А буква N велит не очищать (при вот данной операции) уже упоминавшийся аккумулятор под текстовую строку. (Чтобы учинять с ним разные фокусы.) А то при переключении каналов ввода/вывода он обычно автоматически очищается.
  
   ### Форма, когда имя файла ПОСЛЕ псевдонима, (через знак =) как это описано в главе 12 оной книжки (к коей вот это всё - якобы приложение), здесь не реализована.
  
   1.2 Когда Фокал еще только-только запущен, уже задействованы: Т - терминал и К - клавиатура. А так же: Л - стандартный принтер и Ц - стандартный компорт. Ну и разумеется каналы ввода, вывода и вывода ошибок - I O E. Это их ДОС автоматически открывает.
   У нас все-таки не UNIX - терминал с клавиатурой доступны не только через стандартный ввод/вывод (вот эти самые I O E), но и напрямую. Но уж коли ДОС всё это предоставил - пусть будут доступны.
  
   ## Вот только толку от них - ноль! Даже меньше: из за того, что буква Ц оказалась вдруг занята, приходится автоматически открывать не все указанные в командной строке файлы (как это было первоначально задумано), а только два первых. И это при том, что драйвер компорта в ДОС`е примитивно-примитивный - для любой реальной задачи придётся делать свой. А с перенаправлением ввода/вывода вообще засада... Так что с этим пока поосторожнее.
  
   1.3 Ежели при запуске интерпретатора в командной строке указаны имена еще каких-то файлов (а что там еще может быть?) то первые два открываются автоматически под псевдонимами А и Б. Причем на А сразу же переключается канал ввода. На предмет программку оттедова загрузить...
   А если нету - делается попытка открыть файл "sav.f" в текущем каталоге - тоже под псевдонимом А и с той же самой целью. Потому что когда пользователь пытается выйти из Фокала, нажав ESC, перед выходом ему предлагается сохранить там программу. Да и переменные тоже. (Это если он программу правил.)
  
   *** Ну это чтобы следующий сеанс работы был продолжением предыдущего.
  
   5.2.1 С той же самой целью содержимое пула (мешка) ранее введенных с помощью редактора строк при выходе автоматически выгружается в файл "_fpks.txt" а при запуске загружается обратно.
   Но правда только если такой файл в текущем каталоге уже есть. Или если есть переменная среды FPKS. Где, кстати, может быть указано и какое-то другое имя: FPKS=имя_файла.
  
   5.2.2 Экранный редактор командной строки, облегчающий пользователю её ввод, завершает редактирование по нажатию кнопок ВК (она же "ввод" или "Enter"), и ESC (она же АР2 или "эскейп"). В первом случае редактирование строки завершается "успешно" и она передаётся тому, кто её у редактора запросил. (А это может быть оператор Ask и сам интерпретатор, вернее его "интерфейсная" часть.) А по нажатии ESC - "неуспешно". (Запросившему возвращается ноль.) Но это только если строка пуста. Если нет - она только очищается.
   Оператор Ask, получив от редактора ноль, просто оставляет значение переменной без изменения. (Еще и на терминал выводит.) А вот сам интерпретатор пытается завершить свою работу. (Ну пользователь же эскейп нажал - выйти наверно хочет!) Но сперва проверив - не редактировал ли пользователь программу, действенно напоминает ему что надо бы сохранить результаты его трудов! Формирует и предлагает пользователю примерно такую командную строку:
   o "sav.f" sw;o s;w a;w s;t "o k"!;o "" s;q После чего считает свою миссию выполненной. (Признак этот сбрасывает.)
   Разумеется, пользователь может сделать с этой предложенной ему командной строкой всё что хочет. В т.ч. заменить имя файла на своё собственное. Или стереть лишнее, или, если ему не надо ничего сохранять - стереть всю эту строку нафиг, еще раз нажав ESC. И еще раз нажав ESC таки выйти!
  
   5.3.5 Функция FTEll задумана чтобы просто сообщать положение указателя чтения/записи для открытого файла. (Того, который поучаствовал в последней по времени операции ввода/вывода (пусть даже и фиктивной) - потому что его псевдоним только в операторе Oper и больше нигде.) Но на неё еще свалили всё, что не знали кому поручить.
   - сначала - еще и сообщать длину файла (когда ф-я FTEll - с аргументом)
   - далее, для терминала, не имеющего ни длины ни текущей точки (есть аналоги, но с ними имеют дело функции FKурсор и FVizual), FTEll запрягли сообщать признаки удержания в нажатом виде "интересных" кнопочек: Cntrl, Alt, Shift.
   - и плюс к тому - проверять односимвольный буфер клавиатуры. В принципе туда лазит и функция FCHR. Но она код нажатой кнопки оттуда заберет, а сам буфер очистит. А если он был пуст - будет ждать до морковкиного заговенья, пока пользователь соизволит нажать какую либо кнопку. А FTEll просто проверяет.
   - потом еще ей поручили искать файлы в текущем каталоге. По имени, указанному аргументом в виде текстовой константы. (С пустой - следующий.)
   - и сообщать об этом, ею же самой в прошлый раз найденном файле - тоже размер, а кроме того и время создания. (Или -1, если небыл найден.)
   - и в довершении всего - еще и добывать из недр интерпретатора разные всякие текстовые строки. И складывать их в третий из вышеупомянутых аккумуляторов, тот, что вот как раз для хранения текстовой строки. (В т.ч. элементы "программного окружения", это которое environ.)
   Как же всё это удалось? Ой, и не спрашивайте! (В хелп загляните.)
   Ну так я сам заглянул - всё просто: FTEll(,,X). Если X - число, то получаем параметр из командной строки, если строка - имя переменной из environ, то её значение. Если таковая есть. Иначе возвращает -1.
  
   4.0.1 Этот самый якобы аккумулятор для хранения одной текстовой строки - ни что иное, как входной буфер оператора Ask.
   В принципе у Фокала два таких буфера. Второй принадлежит самому интерпретатору. Он вводит туда очередную командную строку и пока всю её не выполнит - за следующей не лезет. Так что операторы, в этой, уже находящейся в памяти строке, могут творить в том числе с файлом, откуда она взята, всё что им вздумается. Благо, в строке их помещается много...
   Оператор Ask тоже вводит за раз целую строку, а "потребляет" из неё только одно выражение, в то время как их в ней может быть несколько. Соответственно, буфер снабжен указателем, отделяющим уже использованную его часть от еще нет.
   Функция FCHR, вводящая байты поштучно, читает их прямо из файла (или из уже упоминавшегося буфера клавиатуры) только в том случае, если буфер оператора Ask - пуст. А если нет - берет символ оттуда.
   По-другому некрасиво получается.
   Но и так - тоже не всё хокей... В общем нужны средства что-то с этим буфером сделать. В первую очередь - очистить. Чтобы следующее число (или выражение) было с начала следующей строки...
  
   4.1.1 Для очистки оного буфера (он же "строчный аккумулятор) ввели конструкцию "формат" и для оператора Ask: теперь Ask % - чистит этот свой буфер, делая его пустым.
  
   5.2.3 Хотим чтобы сидящий за терминалом пользователь совсем обленился: не вводил сам значения, которые от него хочет оператор Ask, а получал в уже почти готовом виде и мог просто согласиться, или чуть подредактировать.
   Ну и кто подобное сможет организовать? Конечно же сам оператор Ask! Делает же это интерпретатор, предлагая в ответ на нажатие ESC почти готовую строку для сохранения программы. Чем оператор Ask хуже?!
   Для этого надо проделать следующий фокус-покус: текст, который редактор выдаст в редактируемой строке пользователю, должен быть помещен вот в этот самый буфер, но так, чтобы он остался пустым. ВЫГЛЯДЕЛ пустым! (Что оказывается совсем не то же самое.) Надо чтобы указатель начала еще неиспользованной части указывал на конец буфера. Типа всё содержимое уже использовано. Что, впрочем, сделать не так уж просто: как только Ask или FCHr берёт из буфера последний символ, он же его сразу и очищает до состояния "нету буфера". (FCHr(,0) или FCHr(,) возвратит после этого -1.) А нам надо чтобы был. Надо либо принудительно туда этот указатель поставить, либо...
   Вот для этого теперь у Ask есть спецформат %%. Он тоже очищает буфер, но главное: после него (и до конца оператора, или до !) Ask начинает работать так же как Type, но не в канал вывода, а вот в этот свой буфер. (Т.е. выражения вычисляются и результат преобразуется в текст по текущему формату оператора Type, а текстовые константы выводятся как есть.) Спецформат %%% делает то же самое, но сохраняет "уже использованную" часть. (Т.е. позволяет добавлять к тому, что туда поместил предыдущий оператор.)
   /// примечание: раньше это было до ближайшей запятой - из соображений, что
   одного выражения вполне достаточно (а ! просто игнорировался)
   и сейчас я весь в раздумьях - как лучше и логичнее? \\
  
   4.1.2 Оператор Ask вводит строку в буфер (если он конешно пуст), но сам же её и потребляет. Чтобы только вводил, но ничего с этой строкой не делал - форма оператора Ask с абсолютно пустым списком ввода. (Весь оператор из одного только ключевого слова.)
   Внимание: в операторе и в самом деле не должно быть абсолютно ничего. Одна приблудная запятая испортит всю малину. Но таки может быть формат вида %N. (Где N - числовая константа.) Что бы это ни значило.
  
   5.2.4 Сейчас формат %N в Ask работает только для терминала - указывает редактору ширину поля ввода. Каковое простирается от того места экрана, где стоял "маркер" (он же "курсор") - указывающий текущую позицию. И до правого края экрана. Или же ровно на N символьных позиций.
   Последнее - для того, чтобы на экране можно было нарисовать табличку и что-то вписывать в её графы: поставил (функцией FCS она же FKурсор) курсор в начало поля, указал его ширину и вводи, не трогая соседние поля...
   Причем, такой формат полагается всем операторам ввода, в т.ч. Modify...
   А табличку эту нехай сама FKурс нарисует - символами псевдографики...
  
   *** Но про управление экранным курсором - попозже. Сейчас мы заполучили текстовую строку в аккумулятор, и надо срочно изыскать хоть какие-то средства, чтобы можно было хоть что-то с нею там сделать.
   Аналогом экранного курсора у нас здесь вот этот самый указатель, отделяющий "уже использованную" часть буфера от "еще нет". (Так и хочется назвать его тоже "маркером".) Так, если по-минимуму, нам надо читать/писать символ, на который он указывает; двигать его куда захотим и определять - сколько есть символов до него и после.
   Уже читает из буфера (и вот как раз ровно один символ) функция FCHR. Есть мнение поручить ей и всё остальное.
  
   4.2.1 Функция FCHR (с аргументом -1) берет из оного буфера код очередного символа и сдвигает указатель "текущего места", отделяющий уже использованную часть строки от еще нет... Но символ всё равно остаётся в буфере, и ничего не стоит "возвратить" его назад, просто сдвинув указатель обратно.
   Это делает сама же FCHR() но без аргументов. И за одно сообщает - сколько там в буфере еще осталось не введенных символов.
   Она же: FCHR( ,N) - двигает не на одну, а сразу на N позиций. В том числе и в другую сторону, если N - отрицательное. Или никуда, если 0. Только сообщает.
   Она же: FCHR( , ) - измеряет буфер в другую сторону: сообщает сколько в нём "уже введенных" символов.
  
   *** теперь для полного комплекта нам недостаёт средства поместить любой произвольный код в текущее место текстового буфера.
  
   4.2.2 Хотим получить в виде числа код символа "а". (Нет, ну ей богу - надоело постоянно заглядывать в таблицу кодов символов!) Это сделает FCHR("а").
   А если в текстовой константе их будет два: FCHR("аб"), то второй получим следующий раз. Для чего поместим его... А вот в текущее место буфера и поместим! Прямо поверх того, что там уже было. И все следующие аргументы функции, если они есть - тоже.
  
   *** теперь не хватает только возможности вывести то, что получилось...
  
   4.3 Если мы затеяли написать что ни будь демонстрационно-обучающее, выводящие на экран длинные тексты, то никакой памяти хранить всё это не напасешься! Однако, здесь мы никуда не спешим: делать что либо всё равно надо в темпе восприятия пользователя. Потому подобное есть смысл выполнять прямо из файла, где эта программа хранится. Вон в первых ЭВМ вообще памяти для программ не было (её и для данных то не хватало), вводили снаружи (типа с перфоленты), и ничего... И мы тоже не баре.
   В общем это и будет "скриптовый" (он же "командный") файл. Ничего сложного: Строки из файла выполняются в точности так же как и введенные с терминала. С линейным алгоритмом вообще никаких проблем нет. Разветвленный конешно требует меток и операторов перехода к ним, но в этом качестве прекрасно сойдут файловые адреса (положение указателя чтения/записи файла) которые можно получить с помощью FTEll, хранить в переменных (т.к. это просто числа) и применять для позиционирования с помощью оператора Oper. Это и будет аналог оператора перехода. В хелпе про всё про это - целый раздел (под N 7).
   Однако, применительно к выводу текста есть проблема: его придётся выводить в точности так же, как и программе загруженной в память: каждую строку заключить в кавычки, поместить получившуюся текстовую константу в оператор Type и еще снабдить символом ! для перехода на следующую строку.
   Хотя казалось бы - чего проще: текст хранить в файле "как есть" (так его и набирать и потом править куда удобней) и просто копировать в канал вывода нужное количество очередных строчек. Ну может быть с некоторым преобразованием. Или же просто "как есть". (А дальше будут опять команды.)
   Ан, в Фокале нету подходящего средства!
   Ну не побайтно же копировать в цикле, с помощью FCHr! (Хотя можно, и вполне работает.)
   Это дело решили поручить оператору Write, указав преобразование в виде конструкции "формат". (Который прямо на глазах становится всё более и более таинственным.) Или без преобразования, если формат пустой - состоит из одного только символа %.
   В общем теперь W % N выдаст в канал вывода N строк. Прочитав их из канала ввода через тот же самый входной буфер оператора Ask.
   Но вот если буфер не пуст, а вывести велено ровно одну строку, то единственное, что сделает W % 1 - выдаст содержимое буфера в канал вывода.
  
   *** ага, вот и вожделенный вывод, которого нам так не хватало.
  
   4.3.1 Но практика показала, что так оно конешно лучше, чем каждую строку в виде текстовой константы в отдельный оператор Type. Но подсчитывать строчки - тоже не фонтан. И поэтому для оператора Write с форматом была придумана "метка".
   Это параметр в виде текстовой константы, которую W % "..." ищет внутри каждой введенной в буфер строки, и обнаружив - прекращает работу. Строка остаётся в буфере, а количество скопированных строк - в числовом аккумуляторе. (Откуда его не трудно достать функцией FSUBr().) Если указаны и метка и количество - то что сработает раньше. Если ни то ни другое - то до конца файла. Будет ошибка. Или, если ввод с терминала - пока пользователь не нажмет ESC. Тогда ошибки не будет. Если указано отрицательное количество строк - то тоже до конца файла, но ошибки не будет.
  
   5.4.7 А сам оператор Write (без формата) тоже немножко доопределён - чтобы он выдавал не только программные строки, но и другие сведения о внутреннем состоянии интерпретатора (например значения переменных). Причем в форме, пригодной для последующего обратного ввода. Что именно - указывается вторым ключевым словом:
   - А - как и раньше - вся программа и перед нею заголовок (кто и когда вывел).
   - S - результат работы оператора Set (т.е. значения переменных)
   - O - результат работы оператора Open (т.е. псевдонимы открытых файлов)
   - B - результат работы оператора Break (т.е. реакция на события)
   - D - результат работы оператора Do (т.е. состояние стека возвратов)
   Последнее имеет смысл применять либо в какой то из подпрограмм, либо в пошаговом режиме встроенного отладчика.
   Еще есть ключевое слово П (от слова "пул"), заставляющее поместить в этот самый пул содержимое строчного аккумулятора. Так как "пул" это мешок, только по нерусски, то и ключевое слово М делает то же самое.
   Аргумент в виде числа - как и раньше указывает строку или группу строк.
   Аргумент в виде текстовой константы - указывает имя переменной. В результате чего оператор выдаёт значения не всех переменных, как с ключевым словом Set, которых может быть очень много, а только с указанным именем.
  
   5.4.8 Точно так же сделано и в операторе Eraze: теперь он тоже может удалить не только все переменные разом (когда без параметров), но и одну конкретную, указанную текстовой константой.
   С ключевым словом Пул или Мешок оператор очищает этот самый пул редактора,
   С ключевым словом Break - обнуляет счетчики все отслеживаемых внешних событий.
   С ключевым словом Ales оператор удаляет всё что можно: кроме переменных и программных строк, еще и все псевдонимы открытых файлов. Но кроме тех, на которые указывают каналы ввода и вывода.
  
   4.4 А теперь к "...." добавим еще и ! указывающий на строчный аккумулятор. Чтобы строку из него можно было использовать везде, где и строчную константу. Кроме операторов Ask и Type, где восклицательный знак уже испокон веку используется для обозначения конца строки и перехода на следующую.
  
   5.4.9 В том числе в операторе !, который в этом случае будет выглядеть как !! Как и в UNIX`е, это обращение к операционной системе. Ей в качестве командной строки передаётся вся строка после восклицательного знака. (А теперь вот - и из аккумулятора.)
   Потому что например создать новый файл Фокал может, а удалить после того как попользовался и он стал не нужен - нет. И переместить/переименовать - тоже нет. Но всё это можно сделать командами операционной системы.
   Этак можно и утилиту какую запустить (Если ей конешно памяти хватит.) Текстовый редактор например. Или аналог Нортон-командера - по каталогам полазить. (А редактор в нём встроенный. И просмотрщик...) Сам Нортон или например Дос-навигатор - вряд-ли, а Волков-командер - как с куста!
  
   *** В результате, теперь можно сказать, что Фокал немножко научился работать и со строками. Правда хранить их по-прежнему приходится снаружи - в виде файлов...
   Потому что совсем немножко он и раньше умел: посимвольно, таская их функцией FCHR и сохраняя как числа в элементах массива. (Что конешно же стрельба из пушек по воробьям.)
   Однако, и сейчас, если мы например организуем диалог с пользователем, для исправления некой даты/времени, то прочитать набранную пользователем строку и преобразовать её обратно в число - запросто. А вот чтобы использовать в качестве начального значения то, что выдаёт FTMP - временный файл понадобится. (Который потом еще и удалять придётся.) Потому что FTMP(t) выдаёт текстовый результат прямо в канал вывода.
   Да и для самомодификации программы - тоже временный файл. Конешно сейчас это уже не очень нужно. Но всё равно остались места, где только и исключительно константы. (Вон этот самый таинственный "формат".) А вдруг понадобится сделать его таким, как вычислила программа...
  
   6.3 Самомодификация программы - это круто! Эффектно, эффективно и чрезвычайно опасно. Ну прямо цирковой номер: выполняющаяся программа на ходу переписывает и исправляет сама себя! Да, в Фокале такая возможность была всегда. (В отличии от...) Хотя и ограниченная. Но с появлением доступа к файлам, в которые можно сначала записать, а потом перемотав в начало прочитать написанное...
   Ну так берем такой временный файл, формируем в нём какие нам надо программные строки, перематываем в начало, переключаем на него канал ввода и останавливаем выполнение программы оператором Quit. Управление получает интерфейсная часть интерпретатора; она читает и помещает в память всё то, что мы вот только сейчас написали. Главное - не забыть в конец добавить команду, которая бы запустила программу заново с нужного нам места.
  
   *** А нельзя ли как-то без временного файла обойтись? Например немножко доопределив оператор Modify, каковой как раз программные строчки исправляет...
   Можно.
  
   5.4.10 Оператор Modify.
   Предназначен для того, чтобы пользователь мог исправлять программные строки, а не вводить их заново. Реализован простейшим образом: всего лишь для каждой из строк, ну например указанной ему группы, по очереди запускает редактор.
   Сначала он позволял исправлять только текст строки; потом и её номер - чтобы можно было например переместить строку в другую группу. Но это же получается что теперь он (частично) выполняет функции интерфейсной части интерпретатора. Вот пусть и...
   Было решено:
   - во-первых разрешить в Modify параметр в виде текстовой константы. (И "..." и ! разумеется.) Чтобы делал с этой строкой в точности то же самое, что и интерфейсная часть: если там в начале есть корректный номер - помещал в память, а если нет - сразу выполнял. И всё это без манипуляций с файлами и остановки программы. (А то локальные объекты теряются.)
   - во-вторых ввести форму "с присваиванием". Якобы для перемещения программных строк из одной группы в другую: Mоve N = M; (где N и M - числовые выражения.) В результате чего строка с номером M перемещается в группу N, или просто получает другой номер.
   - и наконец в третьих разрешить в форме с присваиванием использовать строчные константы. (В левой части присваивания - только !.) Что даёт возможность перемещать программные строки в аккумулятор и обратно.
  
   *** Обратим внимание, что на грамматическую правильность помещаемые в память программные строки никто никогда не проверяет. (Проверяет "исполнительная" часть интерпретатора - в по ходу выполнения.) Так что храниться там может абсолютно что угодно. Только на выполнение не запускай!
  
   ### К сожалению не всё что угодно: только "печатные" символы (а не произвольный код), потому что конец строки определяется по символу с кодом 0. Поэтому же в операторах Ask и Write не реализован для чтения из файлов формат %N - чтение не построчно, а порциями по N байт.
   И кроме того - ограничение на длину строки. (По размеру текстового буфера, в данной версии - 122 символа.)
  
   *** Это же аналог переменных для строчных значений получается!
   Обратим внимание: для хранения числа используются "переменная", которая идентифицируется строкой (хотя пока это и только первые её два символа), а вот аналог переменной для хранения строки идентифицируется числом. Тоже ограниченным до двух (ан нет, уже четырех) десятичных цифр до и после запятой.
  
   *** Ага-ага: хранить, вводить/выводить и применять строчные значения для специфических целей (например пропикать азбукой морзе с помощью FBIp) научились. А вот средства анализа и преобразования - крайне слабые.
   Однако, еще не вечер. Что там у нас с форматом в операторе W, якобы сложным и таинственным?
  
   4.5 Формат в операторе Write (делающий из него совершенно другой оператор!) до сей поры представлялся одним только символом % указывающим, что строки копируются как есть.
   Но было задумано что преобразования указываются буквами. (Например %К преобразует русские буквы из кодировки КОИ-8 в текущую - ДОС`овскую, известную как cp866, а %W соответственно из виндовой cp1251.) А %Число так же как и в Ask ограничивает размер поля ввода - это если для терминала. А для файла - чтобы указывало размер порции, вводимой за раз.
   Ни то ни другое до сих пор не реализовано. А реализованы некие "операции". Операции типа удалить, вставить, передвинуть маркер... Каждая, как и полагается, один символ. Вот эти самые буковки, если и когда будут - тоже будут такими операциями.
   Выполняются эти операции без какого либо старшинства (такого, как у арифметических операций в фокаловских выражениях) - просто слева направо. И "левым операндом" каждой является вот этот самый регистр-аккумулятор - содержащийся в нём текст и/или вышеупомянутый маркер, указывающий его фрагмент. Но некоторые могут иметь так же и правый операнд. Типа "число" или "строка". В простейшем случае это числовая константа в виде последовательности циферок, или строчная константа в виде любых символов, заключенных в кавычки.
  
   4.5.1 "Маркером" таки назовём этот самый указатель, отделяющий уже использованную часть входного буфера оператора Ask, от еще нет. Он указывает начало "фрагмента" и дополнен его размером. После сдвига маркера этот размер равен нулю. А после поиска фрагмента - равен размеру найденного. Но операцией ~ этот размер можно установить искусственно. Или изменить.
   Маркер можно поставить в начало и в конец строки (операциями ^ и $), сдвинуть на указанное количество позиций или переместить путем поиска. Операцией поиска служит "строка", сама же и указывающая что искать, если не является чьим-то операндом. А так же "шаблон" - конструкция, заключенная в угловые скобки <...>, где некоторые символы выполняют служебные функции. (В отличии от строчной константы, в которой каждый символ изображает сам себя.) Операцией сдвига служит "число" - тоже если не является чьим-то операндом.
   Как сдвиг маркера так и поиск могут быть как вперёд (к концу строки), так и назад - к началу. Это указывают префиксы + и -. Они именно префиксы, действующие на различные операции. (Хотя и родились из идеи, что числу, указывающему на сколько сдвинуть маркер, нужен знак - направление.)
   Сами + и -, когда не префиксы (например перед ^ $ или _) указывают сдвиг вперёд и назад на одну позицию.
   Например команда ~N устанавливает размер фрагмента ровно N символов независимо от того, какой он был до этого. А вот +~N и -~N увеличивают его на N символов в сторону конца и соответственно начала строчки. Уменьшают только если само число N отрицательное. Чего можно добиться, написав {-N} - вот тут минус это действительно знак. А само N в фигурных скобках может быть полноценным фокаловским выражением.
  
   4.5.2 Основных операций - всего две: это * вставка (замена) и / удаление. Вставке нужен строчный операнд (что вставить), а удалению - числовой (сколько удалить). Без него / удаляет фрагмент под маркером. А с префиксами - и + то что до маркера и после. Вставка с префиксами - и + вставляет соответственно не вместо указанного маркером фрагмента строки, а перед и после.
  
   4.5.3 "Число" (как в качестве чьего либо правого операнда, так и самостоятельной операции сдвига маркера) может быть не только числовой константой в виде последовательности циферок, но и целой частью значения из фокаловской переменной со специфическим именем #X (где X - любой символ, допустимый в имени переменной и потому считающийся буквой).
   Кроме того это может быть целая часть значения полноценного фокаловского выражения, заключенного в фигурные скобки. Причем там, в этих скобках, допустима так же конструкция =Х присваивающая вычисленное фокаловской переменной с именем #Х.
   Сама конструкция =Х (вне фигурных скобок) присваивает переменной #Х размер фрагмента под маркером. А с префиксами - и + соответственно до маркера и после него - до конца строки.
  
   4.5.4 "Строкой" (как в качестве чьего-то правого операнда, так и операции поиска) тоже может быть не только константа "...", но и значение переменной со специфическим именем #N (где N - цифра 0..9). Вернее "переменной" - это на будущее, а пока что - "регистра". Впрочем при записи в регистр операцией =N в переменную со специфическим именем #N автоматически кладется длина сохраняемого в регистре фрагмента: =N помещает в регистр фрагмент под маркером, а с префиксами - и + части строки до и после маркера.
   Аналогично числу, строка тоже может быть сформирована с помощью фокаловского выражения, заключенного в фигурные скобки. Но увы - ничего сложнее строчной константы "..." (или ! обозначающего конец строки) первый Фокал предоставить не способен. (Это для третьего Фокала придумано, как доопределить арифметические операции + - * / и ^ для работы со строками...) Но и это имеет смысл: указать конец строки ! ВНУТРИ "..." (как Си: \n) Фокал не позволяет (там это будет обычный восклицательный знак). Так что конструкция {...} даёт возможность объединить несколько "..." в одну строку. Включив туда так же и символы указанные их кодами (в виде числовых констант или фокаловских выражений). Но чтобы отличить строку от числа, надо чтобы первым в {...} было что ни будь специфически строчное, например текстовая константа, пусть даже и пустая.
  
   4.5.5 А еще у нас есть программные строки, в которых (см. 5.4.10) позволительно хранить всё что нам вздумается (если не пытаться ЭТО выполнить: "хоть горшком назови, только в печь не ставь!"). Но у них уж больно сложные наименования - в виде дробного числа. Однако поскольку в {...} можно написать полноценное числовое выражение, то осталось придумать как отличить номер программной строки от кода символа.
   Для этого в обиход вводится символ : двоеточие. Слово для его обозначения есть с русском языке хорошее - опричь (что значит "другой"). Во времена Ивана Грозного "опричниной" (в противоположность "земщине") называлась территория, где был введен другой, новый способ комплектования армии (используемый, кстати, и по ныне). А "опричниками" (не имевшими с вышеупомянутой "опричниной" ничего общего) называлась избранная тысяча личной охраны (то, что потом называлось гвардией), в которой, в отличии от других армейских соединений, было запрещено местничество и куда, кстати, царь приглашал бояр лично, и лично назначал на должности. (А уж понаписали то потом про всё это... Только идиот поверит. Однако же верят, что показывает, какие же эти люди - идиоты, не способные к критическому мышлению. Или, если таки способны - злонамеренные сволочи.)
   Ну так вот: в {...} просто выражение ХХХ будет как и раньше обозначать символ с кодом, равным его целой части, а :ХХХ - программную строку с соответствующим номером.
  
   5.4.11 По введении в обиход символа "опричь", дадим оператору Modify доступ и до вышеупомянутых регистров форматного механизма. Указывая их :N (где N - одна цифра 0..9).
  
   5.3.0.1 Введем так же возможность использовать содержимое регистров форматного механизма везде, где допустима текстовая константа, добавив к "..." и ! еще и :N (где N - одна цифра 0..9).
   Но это - кроме операторов ввода/вывода, где ! испокон веку указывал конец строки. Там : теперь будет указывать "табуляцию": переход к следующей позиции табулостопа. Были вишь в пишущих машинках такие выдвигающиеся железочки: при нажатии на кнопку ТАБ каретка ехала до следующей выдвинутой. Что очень помогало машинисткам печатать всякие таблицы. В дисплеях сделали позицией табулостопа тупо каждую восьмую. А у нас в Фокале, помнится, тоже был где-то специальный механизм для установки этих табулостопов где вздумается. Только он без дела ржавеет... Ну так пусть пользу приносит! (Чем мы хуже кота Матроскина?)
  
   4.5.6 Конструкция #{...} где внутри фигурных скобок опять фокаловское выражение, преобразует его значение в текстовую строку - как это делает оператор Type. И в результате тоже получается "строка", пригодная к использованию как для вставки операцией * так и самостоятельно - в качестве операции поиска.
   Формат для преобразования берётся тот же самый, что и в операторе Type. Но можно указать свой - после % по правилам Си-шной функции printf(), которая на самом деле это преобразование и производит. А ежели в нём указаны звездочки, обозначающие поля переменной величины, то через запятые после оного формата должно быть подобающее количество выражений, чтобы эти конкретные значения вычислить.
   Парная к #{...} конструкция ={...} производит обратное форматное преобразование (с помощью Си-шной функции scanf() разумеется). Длина преобразованного фрагмента попадает в числовой аккумулятор. И после ={x} сдвинуть маркер за пределы: ={x}{fsbr()} или, например, удалить это: ={x}~{fsbr()}/
  
   4.5.7 Операция может кончиться неуспешно. (Например подходящий фрагмент не найден. Или сделана попытка передвинуть маркер дальше конца строки.) Тогда отработка формата сразу и завершается. Но это если после операции нет "постусловия".
   Постусловий два: в случае успешности & и соответственно неуспешности \ выполнения операции, они запускают после неё еще одну. А иначе пропускают её. Если одной операции мало - можно несколько штук заключить в круглые скобки. Третье постусловие ?...:....|, предусматривающее сразу оба случая, само себе скобки.
  
   4.5.8 Образцово-показательная операция _ успешно выполняется всегда. Потому что ничего не делает. И вообще предназначена на роль пробела, который в форматной строке употреблять нельзя (ибо разделитель) - на нём формат сразу и закончится.
  
   4.5.9 Если мы напишем что-то типа "вася"*"Василий" то эта конструкция найдёт и заменит только одно слово "вася" (если в обрабатываемой строке оно вообще есть). А если нам надо все, а в строке их может быть несколько, то нам понадобится префикс повторения: @("вася"*"Василий") который циклически выполняет одну, следующую после него операцию. Как всегда до первой ошибки. Но только одну - поэтому и пришлось использовать круглые скобки. (Здесь операций две: поиск и замена найденного.)
   Префиксу повторения можно так же указать число повторений - в виде числовой константы, значения переменной #Х или выражения в фигурных скобках. Если в них встречается =Х, то переменная #Х станет параметром цикла. (Или последняя из них, если несколько.) И будет изменяться от количества повторений до нуля. Но если =Х в @{...} есть, а выражения нету - цикл таки будет без ограничения количества итераций, а переменная #Х станет их счетчиком - будет изменяться "в плюс". Впрочем, значение счетчика повторений цикла доступно и так - через числовой аккумулятор.
   В конструкции @{...}, в отличии от просто {...}, может быть несколько выражений - через запятую. Тогда первое становится начальным значением, второе конечным, а третье - шагом. В точности как в операторе For.
   Но заголовок цикла @{...} может быть и "строчного" типа. Тогда его содержимое сопоставляются с обрабатываемой строкой, начиная от маркера. И если не сопоставилось - цикл завершается. (А размер сопоставившейся части тоже попадает в числовой аккумулятор.)
   Кроме обычных текстовых констант "..." и восклицательного знака, обозначающего символ, разделяющий строки (с кодом 10, в на языке Си его еще пишут как '\n') а так же обращений к регистрам #N и "программным" строкам :NN (где NN - фокаловское выражение), ну и чисел, обозначающих коды символов (тоже фокаловские выражения, но без предшествующего двоеточия, которое, кстати, если в одиночку, то обозначает '\t' - код табуляции), там еще допускаются ^ и $ обозначающие начало и конец строки. (Не путать с ! - символом '\n'.)
   Сюда так и просятся конструкции (что ни будь типа %Б, %Ц...) обозначающие любую букву и цифру, любой символ из перечисленных (что ни будь типа [...]) и префиксы повторения (типа * + ?) - как в "регулярных выражениях" (они же "шаблоны"). Но пока чего нет того нет.
  
   4.5.10 Если сразу же после префикса повторения идёт постусловие, а операция, которая должна бы выполняться в цикле - отсутствует, то получается условное выражение. (Вот выполнился бы цикл хоть один раз - значит условие "истинное".) В принципе условным выражением можно считать любую операцию и постусловие после неё, но там "условием" является успешность её выполнения, что несколько другое.
   Кстати, тот же самый цикл выполнен "успешно" если завершился по исчерпании количества повторений. А если по ошибке, то нет. И к самим постусловиям & и \ это тоже относится, вернее к операции в них. Пропускают то они её всегда успешно.
  
   *** Пусть нам надо что-то вычислить и в переменной сохранить; или что-то такое сделать, вызвав фокаловскую подпрограмму (ф-ей FSUBr разумеется), но чтобы при этом ничего в обрабатываемой строке не попортить. Запросто: пишем вот такое условное выражение с образцово-показательной ничего не делающей операцией _ и всё: @{.....}&_
  
   4.5.11 Операцией ! можно немедленно завершить отработку формата. Попутно запретив вывод. Впрочем, перед этим операцией . (точка) можно учинить его принудительно.
   Префиксы на две эти операции тоже очень даже действуют: -! позволяет вообще завершить работу оператора Write, а +! еще и завершить "со скандалом" - с ошибкой 3.6 (или какой указано с помощью числового аргумента).
   Для . префиксы напротив предписывают вести себя более скромно: +. выдаёт в канал вывода не всю строку в аккумуляторе, а только ту её часть что после маркера. ("Еще не использованную". Mы не забыли, что маркер, это одновременно и указатель места, до которого строка в буфере уже якобы использована предыдущими операторами Ask?) А -. вообще только то что под маркером.
  
   *** Вот например надо "промотать" часть текстового файла, чтобы добраться до... Ну это мы таки взялись писать упоминавшийся в 4.3 скриптовый файл, и нам надо передать управление вперёд. Если бы назад - не было бы проблем: запомнили бы файловый адрес в переменной, а сейчас позиционировали бы туда оператором О. Но и вперёд - не проблема: в предшествующей нужному для нас месту строке напишем некую "метку" и прочитаем все строки до неё оператором W % "метка";. Вот только куда всё это девать? Оператор Write не просто читает, а копирует!
   А вот: в операционной системе есть такое полезное устройство nul, где бесследно исчезает любая записанная туда информация. Вот можно туда. Но можно проще: W %! "метка"; и всё - теперь он их только читает и никуда не пишет.
   Или еще проще: надо подсчитать количество строк в файле. Для чего промотать его до конца. Но это же ошибка "конец файла" будет! Да, конешно - можно поставить ловушку... Но укажем в качестве количества строк -1 и ошибки не будет: O (0) f; O fr; W %! -1; O k; T FSBr(),"строк";. А количество скопированных оператором строк взяли из числового аккумулятора (упоминавшегося в 5.3.4 и далее).
  
   *** А вот с выделением фрагмента у нас дела обстоят как-то хило: фрагмент можно найти (операцией поиска) или установить некоторого размера, указанного числовым параметром. И всё.
  
   4.5.12 За сим на помощь маркеру решено было призвать "метку". Ввести операцию "установить" =. её там, где сейчас маркер и "применить" #. в результате чего метка и маркер меняются местами. (Длина маркера при этом становится равной нулю. Коль надо её сохранить - запомним в переменной: =x#.~#x )
   А так же ~. установить размер фрагмента между меткой и маркером (ну или с префиксами - добавить к). И за одно установить до начала ~^ и до конца ~$ строки.
   Нет, запомнить положение маркера можно было и раньше - в переменной: -=x и установить размер фрагмента от запомненного места до текущего - тоже. Вот примерно вот так: -=y^{fmin(x,y)}~{fabs(x-y)} - но уж больно это громоздко...
  
   4.5.13 Конструкции %N производит применение содержимого регистра N в качестве форматного выражения, что эквивалентно вызову подпрограммы. А %{:NN} или %{NN} использует для этого одну из программных строк.
   Но кое что здесь определенно недодумано. Поэтому на всякий пожарный после такой конструкции лучше бы добавить пустую операцию _ (а то есть мысль сделать передачу в такую "подпрограмму" аргумента...)
  
   ### фокаловское выражение в фигурных скобках может содержать в себе в том числе обращение к спецфункции FSUbr - фактически вызов фокаловской подпрограммы, где может быть что угодно. В том числе и оператор Write %.., что вполне допустимо. Но надо иметь в виду, что строчный аккумулятор - один на всех. И прилагающиеся к нему "регистры" - тоже. А ведёт W % себя... Ну не то что бы по свински, но буфер после себя оставляет пустым. (Если на "метку" не наткнулся.) Однако, если велеть ему скопировать ровно ноль строк: W %... 0 то ничего - только обрабатывает что в буфере и ничего не портит.
  
   4.5.14 Итак: круглые скобки (...) объединяют несколько операций в одну; угловые <...> заключают в себе "шаблон", фигурные {...} - фокаловское выражение, а квадратные [...] пока не задействованы - зарезервированы под что-то, не дающее числового результата. (При нижеследующем формальном описании грамматики они обозначают необязательность; а символ | разделяет варианты.)
   А вот кавычки (коих три: " ' `) у нас все эквивалентны, но опять же не взаимозаменяемы - их самих можно заключить в кавычки другого типа. (Деваться некуда: все три непарные.)
   Основных операций всего две: *[строка] и /[число] - вставить и удалить.
   Операций поиска тоже две: строка и шаблон
   Операций сдвига маркера три: число ^ $ и в дополнение к ним #. + -
   Операция установки ширины маркера всего одна: ~[число | ^ | $ | .]
   Операций с меткой три: =. #. ~. установить и применить к
   Установить и применить: =Y #Y применимы так-же к "регистрам" (Y=0..9), переменным (Y - буква), а если Y это {...} то получаются преобразования меж строкой и числом по указанному в фигурных скобках Си-шному формату. Причем "установить" не на что особо не влияет (кроме того, что устанавливается), а "применить" даёт либо число либо строку.
   Дополнительных операций две: ! прекратить и . вывод
   А "пустая" образцово-показательная операция всего одна: _
   Ко всему к этому еще два префикса направления: + -
   префикс повторения: @[число | {заголовок_цикла}]
   и три постусловия: &операция \операция ?посл_операций[:посл_операций]|
   При том что круглые скобки: (посл_операций)
   А шаблон...
  
   4.6 А шаблоны-то у нас, оказывается, опять не реализованы! Ни анализирующие ни тем более генерирующие. Каковые, по задумке, будучи в позиции чьего-либо операнда, должны формировать фрагмент текстовой строки из частей фрагмента, найденного предшествующим анализирующим шаблоном. "Опять" потому, что они, как и графика, уже были, но теперь тоже пересматриваются. Так что оная "базовая" версия оказалась и в этом месте урезанной.
  
   *** Вот мы наконец почти полностью рассмотрели этот самый "сложный и таинственный" формат в операторе Write - средство обработки текстовых строк.
   *** Заметим, что строка это вовсе не то же самое, что число. С числом мы работаем как с единым целым - с помощью арифметических операций. Причем исходные числа (операнды выполняемой операции) уничтожаются и на свет появляется новое число - её результат. Ну так и со строкой мы обычно пытаемся поступать подобным образом, а это неправильно. Может быть это как раз и хорошо, что в Фокале мы столь ограничены в средствах, что вынуждены работать с одной единственной строкой в аккумуляторе. Не уничтожая её и создавая новую, а постепенно преобразуя вот таким вот вышеописанным способом.
  
   *** Ну и наконец остальные красивости.
   Ввели...
  
   5.3.6 ...функцию FBip - чтобы издавать звуки.
   В данной версии - только с помощью встроенного динамика. Он позволяет издавать звук указанной частоты. Возможно так же управление его длительностью. Громкостью - нет.
   Соответственно, у функции может быть два аргумента: частота и длительность. Если длительность указана (аргументов - два), звук ставится в очередь на воспроизведение и функция возвращает её общую длительность. (Исчерпание этой очереди - событие 1004.) Если аргумент только один - звук указанной частоты включается (или выключается) навсегда, а очередь очищается.
   Тот и другой аргументы могут быть текстовой константой (и "..." и !), но не оба одновременно. В роли частоты это ноты, а в роли длительности - азбука морзе. В последнем случае скорость (вернее длительность "точки") можно указать третьим аргументом.
   Имеется так же возможность узнать оставшуюся длину очереди не только в секундах но и в "элементах" (в т.ч. нотах или буквах); получить и скорректировать частоты для нот. (А то оказывается звукоряд бывает разный - "натуральный" и "темперированный", да и нота ЛЯ, относительно которой все эти частоты, тоже в разных редакциях разная - то 440 Гц, то 435.)
   Ноты обозначаются буквами A B C D E F G причем с ноты ЛЯ; номер октавы - цифрой непосредственно перед нотой; #-повышение частоты на пол тона; + и - увеличение и уменьшение длительности в два раза, а * - в полтора. Все остальные символы обозначают паузу. В том числе . (точка) - максимально короткую. (Один тик таймера = 1/18 секунды.)
  
   5.3.7 ...функцию FColor она же ФЦвет - чтобы указывать цвет выводимых символов. Включая цвет знако-места в текстовом режиме. Или цвет выводимых точек в графическом. Для 16 и 256-цветных режимов, использующих "палитру", FCOLor позволяет её инспектировать и корректировать.
  
   5.3.8 ...функцию FCS она же ФКурсор - чтобы двигать курсор по экрану.
   FKурс(X,Y) ставит курсор (он же маркер) в позицию X строки Y и возвращает код символа под курсором. Значение пропущенных или отсутствующих параметров не меняет. (Например FK(1) ставит курсор в начало текущей строки, а FK(,1) - в первую строку, оставляя в той же позиции.) Для некорректно заданного параметра сообщает его правильное значение. Например FK(,-1) - текущий номер строки.
   При наличии третьего параметра позволяет установить FK(x,y,цвет) или только считать FK(x,y,) цвет знако-места.
   Аргументы, в виде текстовой константы, рассматривает как команды. С их помощью можно рисовать линии и рамки символами псевдографики, прокрутить область экрана (между текущим и указанным положениями курсора), очистить её (или заполнить указанным символом - не обязательно пробелом), а так же скопировать фрагмент строки в текстовый аккумулятор.
   Есть так же возможность управлять формой курсора, в т.ч. сделать его невидимым.
  
   5.3.9 ...функцию FMышь - чтобы хоть как-то использовать мышку.
   Позволяет включить/выключить мышиный маркер; переставить его в указанное место экрана; получить его текущие координаты, перемещение (с прошлого раза) и признаки нажатости кнопок. Что именно - указывается первым аргументом.
   События по манипуляциям с мышкой назначаются функцией FViz. Потому что привязаны к области экрана - "окну".
   Управление формой мышиного маркера не реализовано. Требует наличия мышиного драйвера. Причем третью координату - "колёсико" поддерживает под ДОС`ом далеко не любой из них!
  
   5.3.10 ...функцию FViz - для управления дисплеем. Но в основном - чтобы сообщать его параметры, типа длины строки и количества строк на экране. На эту функцию свалили всё, что хоть как-то связано с видеосистемой. В том числе назначение "окон" для отслеживания пересечения их границ мышиным маркером, и/или нажатия в их пределах мышиных кнопок.
   Параметров много и поэтому они делятся по группам.
   0 - видеорежим, от признака текстовый он или графический до размера шрифта и таблицы табулостопов.
   1 - размеры экрана
   2 - параметры видеостраницы
   3 - текстовое окно, включая привязку к нему мышиных событий
   4 и следующие - для графических видеорежимов - были реализованы в версиях Фокал-1А, а из 1Б удалены. Вместе с оператором Viz, отображавшим графические примитивы (которые оказались излишне примитивными) и возможностью переключать видеорежимы. Однако Фокал способен работать в любом. (Потому как пользуется функциями BIOS`а. Вот только в графических текстовый вывод BIOS`а ну очень медленный. И символы на экране распознаёт плохо. По этому с отладочным окном (см.далее) в них ну просто беда...)
  
   5.3.11 Функции FMAx и FMIn всего лишь возвращают самый большой и самый маленький из своих аргументов. Введены чтобы играть роль логических операций: если принять положительное число за "истину" а отрицательно за "ложь", то смена знака (унарный минус) будет играть роль инверсии, операция FMIn логического умножения (она же "И" или "конъюнкция"), а FMAx соответственно - логического сложения (она же "ИЛИ" или "дизъюнкция"). Однако, это может быть не просто бинарная логика, а например трехзначная - с нейтральным элементом 0 - "не знаю" Или "нечеткая", когда логическое значение меняется плавно и обозначает достоверность (или вероятность). (По желанию: хоть обычным образом, хоть как это принято у математиков - в диапазоне 0..1, с нейтральным элементом 1/2.)
   Человек внутри себя рассуждает именно с помощью нечеткой логики. Потому что бинарная - извращение, приводящее к парадоксам.
  
   5.3.12 Функциям FSQrt и FLOg (корень и логарифм) теперь причитается второй аргумент, указывающий степень корня и основание логарифма. (А без них, как и раньше, корень - квадратный, а логарифм - натуральный.)
  
   5.3.13 У функции FATan тоже может быть второй аргумент: очень часто её используют в форме FATan(X/Y) что для Y близкого к нулю работает плохо (а на ноль вообще делить нельзя, но арктангенс-то определен и равен Пи/2). Вот на такой случай и сделано FATan(X,Y).
  
   5.5 Для встроенного в интерпретатор режимов трассировки и пошагового выполнения решено, что
   - во-первых эти признаки будут локальные (но наследуемые). То есть трассировка автоматически отключится при выходе из подпрограммы, в которой включена.
   - во-вторых что ошибки группы 0 (вызванные действиями пользователя) при включенной трассировке переводят интерпретатор в пошаговый режим
   - в третьих что подлежащий выполнению оператор выдаётся на терминал не после или в процессе, а ДО его выполнения. Чтобы в пошаговом режиме пользователь мог решить, что делать дальше.
   - и наконец в четвертых - в пошаговом режиме реализована возможность набрать и выполнить командную строку.
   Ввести строку с номером (чтобы дополнить программу) нельзя, но для этого в нашем распоряжении оператор М.
  
  
   *** Ну что, - вроде бы всё? На последок вспомним всякие мелочи.
   Описанный выше интерпретатор Фокала...
   - работает под ДОС`ом в текстовом режиме. В графических - тоже, только сам переключать их опять не умеет.
   - к какому либо размеру экрана не привязан, хотя полагает его 80х25: самая краткая справка, которую оператор Хелп выдаёт без аргументов, помещается целиком именно на экране такого размера
   - использует ДОС`овскую кодировку; буквами считает и русские и латинские и вообще всё, что не цифра и не разделитель (включая псевдографику, хотя вот её бы и не надо). В именах переменных все буквы различаются. В ключевых словах и названиях функций все буквы эквивалентны. То есть и заглавные и строчные и русские и латинские. Их соответствие вполне очевидное: А-A, Б-B, В-W, Г-G... Но: Ж-V, Я-Q, Ы-Y; и "Е" и "Ё" одинаково эквивалентны Е-латинской, впрочем так же как и "Ь" и "Ъ" букве "X". А для Ш Щ Ч Э Ю соответствующих им латинских букв нет. Впрочем и операторов на эти буквы нет. (А вот разделы файла справок...)
   - с "прочих" символов (числящихся буквами) # $ & : @ \ | ~ начинается комментарий, как и с оператора Coment. (Важно в UNIX`е, где выполняемый файл, не в кодах (бинарный) а интерпретируемый, должен начинаться со строки в которой два первых символа (они же "сигнатура") обязательно #! а дальше - командная строка, с помощью которой следует запустить для данного файла интерпретатор. (Иначе его будет выполнять sh.) А вот для самого интерпретатора эта строка должна выглядеть комментарием. Остальные "прочие" символы - просто "до кучи" - вдруг где-то они тоже что-то значат?)
   - имена переменных распознаёт по первым двум символам, остальные игнорирует и даже не хранит.
   - имена функций (которые все обязательно начинаются на букву Ф) распознаёт по первым уникальным буквам, остальные тоже игнорирует. Причем одна и та же функция может иметь несколько альтернативных имён: чтобы мучительно не вспоминать - "логарифм" это как - FLOG, FLG или FLN?
   - ключевые слова - и название оператора и второе ключевое слово распознаёт по первой букве, остальные игнорирует до ближайшего символа-разделителя
   - только в "псевдониме" оператора Open некоторые буквы что-то значат
   - если в операторе может быть и второе ключевое слово и выражение, а оно вдруг начинается с переменной, то чтобы оператор не принял его за ключевое слово, полезно заключить выражение в скобки
   - а вот у функции, если аргумент ровно один, скобки в которые он заключён, можно опустить
   - все скобки ([{<>}]) эквивалентны, но не взаимозаменяемы
   - все кавычки '"`тоже
   - номер строки в группе (как и номер группы) - до четырех цифр.
   - индексов у переменной с индексами - не более двух. Исключительно целые. Причем когда их два - их значения не более 127 если со знаком или 255 если только в плюс.
   - первый символ имени локальной переменной может быть как & так и $ (чтобы как в UNIX`овском sh) - они эквивалентны. Глобальные переменные тоже могут начинаться с этих символов (но второй - буква!), и там это разные символы
   - переменную на букву Ф завести можно, но получить обратно присвоенное ей значение - не получится: интерпретатор будет искать функцию с таким именем. (Вопрос, а не отдать ли приоритет именам переменных, чтобы у функций можно было сделать "выколотые точки"? Или хотя бы искать переменную, если функции с данным именем нет, а не сразу учинять ошибку? Рассматривается.)
   - в буфер клавиатуры (для функции FCHr) при нажатии "спецкнопок" типа F1..F12, стрелок, Ins, Del, Home, End PgUp, PgDn, а так же "обычных" кнопок но вместе с Alt, попадают коды типа "тысяча с лишним". Какие именно - можно узнать экспериментально, набрав в командной строке t fch(-1);g 0; и понажимав на них. В том числе удерживая кнопки Ctrl, Alt и Shift. (Для остановки - нажать Ctrl/Ц)
   - редактор командной строки работает только в режиме "вставки", режим "замены" не реализован. Использует обычные (для всех подобных редакторов) спецкнопки и их комбинации с Ctrl, Alt и Shift. (А вот Ctrl/X, Ctrl/C, Ctrl/V не поддерживает.)
   - но кроме этого, отрабатывает нажатия спецкнопок, при удержании одновременно всех трех: Ctrl, Alt и Shift: Ins - запускает захват текстового фрагмента с экрана; Home включает, а End выключает отладочное окно. (А кнопку Del не нажимать, а то будет перезагрузка компьютера!)
   - отладочное окно предназначено для отладки программ, формирующих изображение на экране - чтобы отладочная информация (прежде всего результаты трассировки) его не искажали. Для этого часть экрана, попавшая под окно, сохраняется, а после его отключения - восстанавливается на место. Вывод на терминал блокируется, если курсор стоит за границами окна. Само окно работает в двух режимах (переключаются так же: Ctrl/Alt/Shift/Home и отличаются видом рамки): при двойной рамке окно находится на экране постоянно, а при одинарной убирается с него перед выполнением каждого очередного оператора, а после - восстанавливается обратно. (Результат работы оператора оказывается на основном экране, а результаты трассировки и команды пользователя, если режим пошаговый - в окне. Временно убрать окно (до нажатия любой кнопки) - Ctrl/Alt/End (то есть без Shift). В графических режимах это текстовое окно почти бесполезно.
  
   Ой, чуть не забыл - про "спецформаты": %% и %%%.
   В операторах ввода/вывода естественно, коих три: Ask, Type и Write.
   Ну просто формат - одиночный символ % для Type устанавливает формат по-умолчанию; для Ask очищает ейный входной буфер, а для Write велит строчки передавать со входа на выход.
   Спецформат %% предписывает Type присуседиться к просмотрщику - выдавать на терминал не просто так, а с его помощью (чтобы пользователь читать успевал); Write %% N принудительно устанавливает в N счетчик уже выведенных строк просмотрщика; а для Ask он тоже чистит его буфер, после чего организует в нем (из списка ввода/вывода в операторе) начальное значение для предъявления пользователю.
   Спецформат %%% для Ask делает то же самое, но буфер не чистит - позволяет добавить к тому что там уже есть.
   Ну вот явно же просится такой спецформат и для Type...
  
   *** Ну вот теперь - вроде всё.
  
   Резюме.
   Заявленную в почти-заголовке цель - остановиться и просто описать достигнутое - достигнуть не удалось: в процессе описания выявилось масса недоделок и просто несообразностей, которые пришлось срочно устранять. Так что "вторая-базовая" версия у нас теперь будет 1.6 (т.к. 1.5 уже было).
   В процессе написания сего описания пришлось даже ввести новую концепцию: задействовать символ : как специальный. (На ряду с уже задействованными + - * / ^ в качестве знаков операций, = присваивания, % для дополнительных указаний оператору (формат вывода например) и ! чтобы завершить что-то (например строку), а так же в качестве намёка на строчный аккумулятор. (Уж коли он наряду с текстом в кавычках тоже числится строчной константой).
   Символ : тоже сразу приобрёл два значения: табуляция (в пару к ! в роли конца строки) и "опричь" (другой) - другой смысл чего-то: там, где регистры форматного механизма оператора Write % доступны непосредственно (как #N и =N т.е. в самом ейном формате) :число указывает на программную строку; а там где непосредственно доступны программные строки - :N (где N - одна цифра) - вот как раз на оный регистр.
  
   В процессе развития Фокала обнаружены существовавшие и ранее внутренние объекты: буфер клавиатуры, числовой аккумулятор, строчный аккумулятор (он же входной буфер оператора Ask). И добавлены средства для работы с их содержимым. В основном со строчным аккумулятором. В результате Фокал приобрел более продвинутые возможности по обработке текстовых строк. Хотя вовсе и не такие, как изначально планировалось.
   Кроме того был доработан механизм вызова подпрограмм, добавлены средства взаимодействия с файловой системой и реакции на ошибки. А так же кое что по мелочи.
  
   Если провести аналогию между языками программирования и транспортными средствами, то исходный (базовый-0) Фокал выглядел как велосипед с моторчиком даже на фоне такого относительно простого языка-легковушки как Паскаль или Си (хотя последний - что-то типа УАЗика, для езды по бездорожью). И вот теперь Фокал дорос до мопеда. (Но надеюсь всё-же не мотоцикла, которому подавай персональный гараж, а на межэтажную лестничную площадку уже не закатишь...)
  
  
   p.s. Из опыта написания программ работающих со строками. (Вот в частности СОРТ.f - сортировка - на предмет тезаурус получить...)
  
   1. Внимание: не надо оставлять маркер в конце строчного аккумулятора - он будет выглядеть пустым!
   Добавил в оператор Write ключевое слово Reg - чтобы показывал содержимое регистров форматного механизма и содержимое аккумулятора. Ну так сделал там предупреждение на этот случай, и всё равно несколько раз на этом обжегся...
   Однако, может Вы хотите выдать содержимое строчного аккумулятора пользователю, сидящему за терминалом, чтобы он его малость поредактировал:
   W %$ 0;A
  
   2. Соображения по поводу "?". Фокал - язык максимально лаконичный. За сим все используемые в нём символы должны вызывать правильные ассоциации. Ну так вопросительный знак должен ассоциироваться с отладкой. Оператор такой у нас уже есть. Более того - был всегда. Включает трассировку и пошаговый режим. (Я кстати его маненько поправил - вот в частности чтобы по "?" мини-справку по своим командам выдавал, а то сам путаюсь.)
   А вот как на счет ключевых слов в других операторах? Не очень.
   Пока только Operate ? сообщает куда направлены каналы ввода и вывода. Причем пишет не в канал вывода, а только и исключительно на терминал. (Иначе какой от этого толк?) В том числе и в пошаговом режиме: там, если например назать кнопку "забой" (она же "Backspace") можно ввести (и выполнить) обычную командную строку. Причем на это время ввод и вывод временно переключаются на терминал. Ну так там Operate ? сообщает не этот тривиальный факт, а то, куда они указывали.
   Еще был (и надеюсь, опять будет) такой оператор Vizual - для визуализации результатов вычислений. Проще говоря - графики строить. Но пока удалён на реконструкцию: уж больно примитивными оказались реализованные в нем прошлый раз обычные графические примитивы - точка, линия, окружность, полигон... (Кстати, указываемые вторым ключевым словом.) График построить (или например гномика нарисовать) конешно было можно, но сложно и громоздко. А нам надо чтобы "одной левой". Ну так этот оператор (и, кстати, одноимённая с ним функция FViz) умел переключать режимы работы дисплея: чтобы нарисовать например линию, его режим должен быть графический а не текстовый. А чтобы узнать, какие у этого дисплея вообще есть режимы, в нём тоже было ключевое слово "?".
   И это пока всё.
  
   p.p.s. О коварстве "очевидных" вроде-бы вещей. Таких как конец строки.
   (пофилософствовать со злости потянуло:-)
   Вот как, спрашивается, текстовый файл делится на строчки? А по-разному, от системы зависит...
   Были времена (и системы) когда файл был сам себе базою данных. И состоял из записей, в том числе разного размера, за которые отвечала не только система, но иногда и аппаратура. Вот там каждая строка была отдельной записью...
   Но со времён UNIX`а всё куда проще: файл это безструктурная последовательность байтиков. Некоторой длины. Виртуальная модель перфоленты с возможностью дописывания и исправления уже записанного. А за структуру, буде она нужна, отвечают прикладные программы, которые с этим работают. Ну и как же текст и его деление на строчки? Возможны варианты. Самый простой - указывать конец строки символом, который в ней заведомо не встречается. Например 0. Или вот как в UNIX`е - 10 (он же 012 или 0xA или '\n'). Почти тот же, но другой: один в один хранить то, что собираешься вывалить на печатающее устройство. Другой потому, что там вообще-то не коды символов а команды. В том числе команда "напечатать такую-то буковку". И у разных печатающих устройств они могут быть сильно разные. Вот в UNIX`е и решили, что пусть эти команды драйвер устройства формирует. Логично. Но в ДОС`е решили не так. Вернее в ДОС тупо и бездумно тащили "решения" из других систем (сперва CP-M копировали, а потом элементы UNIX`а) и получилась эклектика, которая до добра не доводит. (Из ДОС`а всё это один в один переселилось в винду...)
   Дело в том, что самое первое печатающее устройство - телетайп, украденный на почте. Пишущая машинка с дистанционным управлением. А у неё, если пишет не на узенькую ленточку в одну строку, а таки на лист бумаги - для перехода на следующую строчку нужны две команды: ВК и ПС - возврата каретки, когда она ставится в начало той же строки (вдруг понадобится для особой жирности эту строчку поверх еще раз напечатать), и перевода строки, когда бумагоопорный валик проворачивается. Вот этими двумя командами с кодами 10 и 13 строки в ДОС`е и завершаются. (И еще неизвестно в каком порядке.) В UNIX`е пошли на разумный компромисс, но у нас-то ДОС, здесь - как и полагается у семи нянек... Ну так для ДОС`овского Си (на котором Фокал и написан) ввели два режима открытия файлов - "бинарный" и "текстовый", используемый по-умолчанию, когда при чтении символ с кодом 13 изымается, а при записи - добавляется обратно.
   Ага-ага. А вот если мы читаем строчку (например для каких-то своих целей в строчный аккумулятор с помощью оператора Ask с пустым списком ввода), то где будет после этого указатель чтения/записи? Логично предположить что после крайнего из этих двух символов. А как бы не так! Запросто после первого из них.
   То-то я демонстрационную программку никак отладить не мог... Нет, она вполне себе работала, но иногда вдруг раз и... То есть писана от она была лет десять назад - вот хотел про новомодные (тогда) свежевстроенные красивости рассказать (и продемонстрировать). Последняя - доступ к файловой системе. Вернее оно-то (оператор Operate) как раз было самое первое, с чего началась модернизация языка, причем в такие незапамятные времена... Но не зрелищно. А чтобы зрелищно - типа давайте содержимое каталога на экране отобразим и в подкаталоги залезать разрешим... (Ну и файлы просматривать.) А что, для этого почитай всё есть. А что ничего кроме самого строчного аккумулятора еще нету (ни его форматных преобразований, ни возможности сохранить строку где-то еще), так не беда: текстовые строки и во вспомогательном файле хранить можно... Если мимо тех мест, где они начинаются не промахиваться...
   В общем, пока не догадался открывать этот вспомогательный файл в "бинарном" режиме, так оно и глючило... Может этот самый "текстовый" режим вообще упразднить?
 Ваша оценка:

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

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

Как попасть в этoт список
Сайт - "Художники" .. || .. Доска об'явлений "Книги"