Я тут немножко причесал текущую версию и пытаюсь её
выложить, а это - что-то типа описания к ней.
Дополнение к главе 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=имя_файла.