Кронос - автоматизированное рабочее место профессионального программиста

Д.Н.Кузнецов, А.Е.Недоря, Е.В.Тарасов, В.Э.Филиппов

КРОНОС - общее название семейства 32-разрядных процессоров, разработанных авторами в ВЦ СО АН СССР, с системой команд, оптимизированной для компиляции и исполнения Модула-2 программ.

Модула-2 - модульный язык программирования, созданный Н.Виртом в Цюрихе (Швейцария) в Высшей Технической Школе (ETH).

Процессоры семейства КРОНОС (в дальнейшем просто КРОНОС) могут быть оснащены большим объемом оперативной (до нескольких десятков мегабайт) и дисковой памяти (до нескольких сотен мегабайт). Описываемые в статье программные средства работают на машинах, оснащенных процессорами КРОНОС, в минимальной конфигурации 0,5 Мбайт ОЗУ и 5-10 Мбайт дисковой памяти и обеспечивают 2-4 автоматизированных рабочих места для профессиональных программистов. Компьютеры на базе процессоров КРОНОС могут быть укомплектованы различными типами алфавитно-цифровых терминалов, программные средства универсальны по отношению к типу используемого периферийного оборудования.

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

Идеология создания автоматизированного рабочего места (АРМ) программиста базируется на следующих принципах:

  • удобство интерфейса;
  • модульность;
  • открытость системы;
  • интегрированность результатов.

Мы раскроем содержание этих принципов и коснемся форм их реализации на машинах КРОНОС.

Удобство иннерфейса и экстерфейса

Удобство интерфейсов АРМ <-> ПРОГРАММИСТ (экстерфейс) и ПРОГРАММА <-> АРМ (иннерфейс) - главнейший принцип из положенных нами во все разрабатываемые системы. Поскольку в условиях использования оборудования различных типов для создания такого интерфейса не удается жестко ориентировать программные средства на тот или иной его тип, необходимо создать самонастраивающиеся средства, позволяющие максимально полно использовать возможности каждого конкретного типа оборудования, обслуживающего рабочее место программиста. При этом недопустимо просто минимизировать используемые программными средствами возможности терминалов, клавиатур и других устройств, так как это отрицательно скажется на удобстве работы программиста.

Именно традиционное решение о минимизации используемых возможностей привело в широко распространненых системах (таких, как UNIX, RSX, RT11) к существенным неудобствам в интерактивном общении пользователя с системой. Попытки же преодолеть такие неудобства и максимально полно использовать возможности того или иного терминала приводят к появлению неконтролируемого обилия программных средств, ориентированных на конкретный тип периферийного оборудования. (Всем, например, печально известно многообразие экранных редакторов для конкретных терминалов в каждой традиционной системе). Универсальные программные средства традиционных систем отличаются излишней громоздкостью, вызванной в основном тем, что каждая утилита содержит в себе свои собственные средства настройки на конфигурацию периферии. Все это не позволяет создать достаточно комфортабельные программные средства для работы программиста. И лишь в более поздних версиях традиционных ОС была начата работа по унификации аппаратных возможностей периферийного оборудования.

Мы преодолеваем эти проблемы, используя хорошо зарекомендовавшую себя технику абстрактных типов данных. Вся работа с конкретными типами устройств инкапсулирована в различные реализации одного и того же определяющего модуля, поддерживающего интерфейс всех программных средств с устройствами такого класса. Все программные средства как самой системы, так и прикладного программного обеспечения (ПО) оперируют с устройствами только в терминах такого определения. Этим дополнительно достигается полное единообразие работы пользователя с различными (даже еще не знакомыми ему) программными средствами. Программист живет в стабильной обстановке, и у него не возникает потребности постоянно помнить мелкие правила типа: "Какой кнопкой раздвинуть строку в ЭТОЙ утилите", которые зачастую в огромном количестве необходимы при работе на наимоднейшей персональной технике. Добавьте сюда еще окончание: "... для данного типа терминала", и вы получите классическую ситуацию, когда профессиональный уровень программиста определяется не его умением создавать качественное программное обеспечение, а суммой усвоенных мелких правил.

В качестве примера абстрагированного интефейса в Приложении 1 приведен простой модуль работы с вводом/выводом в стиле UNIX, используемый многими утилитами системы.

Вторым важным свойством программных средств является возможность в любой момент работы единообразным для всех программных средств способом получить толковую и понятную подсказку об имеющихся возможностях на родном для программиста языке. В качестве примера приведем кусочек подсказки, выдаваемой редактором при нажатии кнопки "HELP":

{ПРЕФИКС} КОМАНДА [АРГУМЕНТЫ]
переход в командный режим: GOLD GOLD
ПРЕФИКСЫ:
"!" -- начать поиск с начала файла
"$" -- искать в диапазоне строк
"#" -- рассматривать диапазон как прямоугольник
"=" -- вставить прямоугольник с вертикальной раздвижкой
ОБЛАСТЬ всегда маркируется как прямоугольная, но используется, по умолчанию, как диапазон строк.
Префиксы #,= заставляют редактор произвести операцию с прямоугольной областью.
КОМАНДЫ: (допустимые префиксы)
[ -- начало области
] -- конец области

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

Все интерфейсные средства имеют два лица: обращенное к человеку (экстерфейс) и к ПО (иннерфейс). Необходимость обеспечить качественную реализацию обоих лиц интерфейсных средств вызвана использованием описываемого АРМ программистами, основным видом деятельности которых является создание нового программного обеспечения.

Открытость системы

Необходимость обеспечить комфортабельную работу программиста выдвигает к программным средствам АРМ требование открытости системы. Мы понимаем открытость системы как возможность создавать (разрабатывать, реализовывать, отлаживать) модульное ПО, удовлетворяющее двум следующим критериям. Первый критерий: для работы любого ПО необходимы только те программные и аппаратные средства, которые непосредственно использованы в нем. Поясним это на примере. Если программист создает программу для встроенного применения, которое не предполагает работу с файлами и дисковой памятью и не пользуется имеющейся файловой системой, ему гарантируется возможность получить работающий как на АРМ, так и в целевых условиях программный продукт, в который не входит файловая система и все к ней прилагающееся.

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

Второй критерий: любое созданное программистом ПО автоматически наращивает программные средства АРМ и входит в их состав на равных правах с базовыми программными средствами. Этот критерий положен в основу принципа интегрируемости.

Интегрируемость

Интегрируемость программных средств, как и УДОБСТВО ИНТЕРФЕЙСА, имеет два лица: обращенное к пользователю и обращенное к ПО.

В первом случае удачным примером интегрированной системы может служить компилирующий редактор (в стиле TURBO-PASCAL COMPILER фирмы Borland). Обеспечить такую возможность имеющимися программными средствами не составляло труда и заняло у нас около недели. Текст модуля, обеспечивающего компиляцию в редакторе с немедленной визуализацией ошибок приведен в Приложении 2.

Примером внутренней интегрируемости являются все программные средства нашего АРМ, построенного как модульная расслоенная иерархическая система. Опыт эксплуатации машин КРОНОС как АРМ программиста в различных коллективах показал, что большинство создаваемого программистами ПО выглядит не только как черные ящики (toolbox), но и как набор библиотек, реализующих абстрактные типы данных. Эти библиотеки удается использовать в другом программном обеспечении, они постоянно пополняют набор "строительных материалов", используемых программистами КРОНОСа для написания своих программ. Примером таких библиотек явлются популярные модули работы со строками, приведенные в Приложении 3.

Модульность

На сегодня модульность программного обеспечения достигается средствами языка Модула-2, не уступающими средствам языка Ада, а по нашему мнению, в некоторых аспектах превосходящими последние.

Не расширяя средств языка, нам удается максимально параметризовать наше программное обеспечение вынесением варьируемых действий в процедурные параметры абстрактных операций в стиле языка CLU. В качестве примера в Приложении 4 приводится модуль повторно-входимого загрузчика, используемого на КРОНОСе. Заметим, что из-за отсутствия процедурного типа в языке Ада написать такой модуль (сохранив повторно-входимость и простоту настройки) не представляется возможным.

Заключение

Остается заметить, что все программные средства АРМ профессионального программиста КРОНОС ориентированы на мультипроцессорное, многозадачное и многопользовательское применение. Они интегрированы в оперционную систему Excelsior II. В данный момент ведется разработка мультипроцессорной версии ОС Excelsior.

Авторы благодарят И.В.Поттосина за плодотворные обсуждения изложенных принципов.

Приложение 1

DEFINITION MODULE StdIO; (* Д.Кузнецов (Leo) 19-Apr-86  *)

FROM SYSTEM    IMPORT   ADDRESS, WORD;
IMPORT ASCII;

(* Модуль  экспортирует  процедуры  стандартного  ввода-вывода в
   стиле UNIX.  *)

CONST EOF = ASCII.FS;   CR = ASCII.CR; LF = ASCII.LF;

PROCEDURE print(format: ARRAY OF CHAR; SEQ args: WORD);
(* format     ::= { текст | "%" формат | "%%" | "\n"
                    | "\r" | "\l" | "\\" } 0c.
   Комментарии к значению формата см. в модуле Image.
*)

TYPE Stream=INTEGER; (* Stream < 0,  если поток не открыт. *)

VAR StdIn, StdOut, StdErr: Stream;

PROCEDURE Open  (s: ARRAY OF CHAR): Stream;
(* Открывает файл с именем -s- *)
PROCEDURE  LookUp(s:  ARRAY  OF CHAR): Stream;
(* Ищет  файл  с  именем -s- в директориях, на которые указывает
   path, и если находит, то открывает его. *)
PROCEDURE Create(s: ARRAY OF CHAR): Stream;
(* Пытается  открыть файл -s-; если файл не найден, создает файл
   с таким именем. *)
PROCEDURE Close(s: Stream): INTEGER;
(* Закрывает файл с именем -s-, в случае неудачи Close(...)<0.*)
PROCEDURE Seek(s: Stream; offset: INTEGER; origin: CARDINAL): INTEGER;
(* Позиционирует  файл  на  -offset-  байтов относительно начала
   (origin=0),  текущей  позиции  (origin=1)  или  конца  потока
   (origin=2).  Возвращает реальную позицию в потоке, на которой
   теперь  стоит  указатель.  Начальная  позиция  в потоке имеет
   номер 0:
                   Seek(s,0,0)=0
   Seek(...)<0 - свидетельствует об ошибке ввода/вывода. *)
PROCEDURE sRead (s: Stream; a: ADDRESS; lengh: CARDINAL): INTEGER;
(* Читает из потока -s- -lenght- байтов и кладет их адресу  -a-.
   Выдает  число  прочитанных  символов  (в  том  числе  0, если
   встретился  конец  файла),  либо номер ошибки, если результат
   отрицательный. *)
PROCEDURE sWrite(s: Stream; a: ADDRESS; lengh: CARDINAL): INTEGER;
(* Пишет в поток -s- -lenght- символов, взятых по адресу -a-. *)
PROCEDURE GetC(s: Stream): CHAR;
(* Читает  один символ из потока -s-.  *)
PROCEDURE PutC(s: Stream; char: CHAR);
(* Кладет символ -char- в поток -s-. *)
PROCEDURE GetS(s: Stream; VAR str: ARRAY OF CHAR): INTEGER;
(* Символ  за  символом  читает  строку  из  потока -s- в -str-;
   выдает  число  прочитанных  символов, либо номер ошибки, если
   результат  отрицателен.  Результат  равен  нулю  при  попытке
   чтения за концом файла. *)
PROCEDURE PutS(s: Stream; str: ARRAY OF CHAR);
(* Кладет в поток -s- строку -str- до 0c или до конца массива *)
PROCEDURE BusyGetC(s: Stream): CHAR;
(* Читает символ из потока; если нет символов, выдает 0c *)
PROCEDURE Why?(r: INTEGER; VAR s: ARRAY OF CHAR);
(* Здесь  -r- - результат неудачного выполнения любой процедуры.
   Выдает в -s- причину неудачи. *)
PROCEDURE File?(s: Stream): INTEGER;
(* Выдает  номер  файла,  которому соответствует поток, либо -1,
   если поток открыт на терминал. *)
PROCEDURE IsTTY?(s: Stream): BOOLEAN;
(* TRUE, если поток открыт на терминал *)

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

PROCEDURE SetMode(ControlC: BOOLEAN);
(* ControlC   -   интерпретация  ^C  как  прерывания  исполнения
   процесса.  Устанавливается, только  если  StdIO  -  терминал.
   Терминал   остается   в   указанном  состоянии  до  следующей
   установки даже после окончания процесса. *)
PROCEDURE Write(Ch: CHAR);
(* Выводит один символ в файл стандартного вывода. *)
PROCEDURE Read(): CHAR;
(* Читает   один   символ  из  файла  стандартного  ввода.  Если
   стандартный ввод открыт на терминал, то некоторые контрольные
   символы имеют особую интерпретацию. Среди них:
   ^C - посылает сигнал прерывания процесса.
   ^S - немедленно  остановить  процесс вывода на терминал. Этот
        символ  используется  как  человеком,  так  и некоторыми
        конструкциями   терминалов   (при  выполнении  операций,
        требующих   существенных   затрат  времени  -  например,
        раздвижка строк).
   ^Q - возобновить вывод на терминал.
   ^O - переключиться на латинский регистр (в стандарт ASCII-7).
        Это символ SI в стандарте ASCII.
   ^N - переключиться  на  национальный регистр (в данном случае
        кириллица КОИ-8). Это символ SO в стандарте ASCII.
   ^F - переключить (инвертировать) перекодировку больших букв в
        маленькие   и  маленьких  в  большие.  Используется  для
        терминалов, не имеющих клавиши CAPITALS с фиксацией)
   Специальный смысл интерпретации этих символов можно выключить
   внешним по отношению к данному модулю способом. *)

PROCEDURE BusyRead(): CHAR;
(* Проверяет,  был  ли введен из стандартного ввода хотя бы один
   символ,  и  если  такой  символ  имеется,  то выполняется как
   операция Read, иначе возвращает значение 0c. *)
PROCEDURE Pressed(): INTEGER;
(* Возвращает число уже нажатых на клавиатуре кнопок. *)
PROCEDURE Peek(n: INTEGER): CHAR;
(* Позволяет   заглянуть   вперед   на  -n-  символов  в  потоке
   стандартного ввода, открытом на клавиатуру. *)
PROCEDURE ReadStr(VAR S: ARRAY OF CHAR);
(* Осуществляет  ввод  строки  из  файла  стандартного  ввода. В
   случае,  если  этот файл - терминал, допускает редактирование
   вводимой  строки  в режиме экранного однострочного редактора.
   Допустимы  движения  по  строке с помощью стрелок 'вставка' и
   'удаление    символов',   а   также   использование   старого 
   содержимого  строкового  массива, предоставленного для ввода.
   Для   терминала   ввод   осуществляется   модулем  Edit,  см.
   комментарии к нему. *)
PROCEDURE AskStr(Txt: ARRAY OF CHAR; VAR S: ARRAY OF CHAR);
(* Выдает  в  стандартный  вывод  приглашение  -Txt-  с  помощью
   процедуры   WriteString,   после   чего  считывает  ответ  из
   стандартного ввода с помощью ReadString. *)
PROCEDURE Confirm(prompt: ARRAY OF CHAR; VAR s: ARRAY OF CHAR);
(* То  же,  что  и  AskStr, но с показом предыдущего содержимого
   строки. *)
PROCEDURE WriteString(S: ARRAY OF CHAR);
(* Выдает в файл стандартного вывода содержимое строки символ за
   символом.  Концом  строки  считается  либо  физический  конец
   предоставленного  массива,  либо  символ 0c, в зависимости от
   того, что встретится раньше. *)
PROCEDURE Show(S: ARRAY OF CHAR);
(* Эквивалентна последовательности: WriteString(S); WriteLn. *)
PROCEDURE ShowAndWait(S: ARRAY OF CHAR);
(* Эквивалентна        последовательности:
       WriteString(S); dummy:=Read();    WriteLn;
   Значение  введенного  символа  несущественно  и игнорируется.
   Используется для приостановки вывода до нажатия любой клавиши
   на клавиатуре. *)
PROCEDURE Query(s: ARRAY OF CHAR): BOOLEAN;
(* Печатает    приглашение    -s-    и   возвращает   TRUE   для
   'y','Y','д','Д',   введенного   с   терминала;  FALSE  -  для
   'n','N','н','Н'. *)
PROCEDURE QueryLn(s: ARRAY OF CHAR): BOOLEAN;
(* r:=Query(s); WriteLn; RETURN r *)
PROCEDURE WriteLn; (* Эквивалентна Write(NL);
   Замечание.
   При  выводе  на  последовательное  устройство внутрисистемное
   соглашение  о завершении строки в файлах с помощью символа NL
   конвертируется  в  вывод последовательности символов возврата
   каретки (CR) и перевода строки (LF). *)
PROCEDURE SetCrs(Line,Col: CARDINAL);
(* Осмыслена, только  если   стандартный   вывод   направлен  на
   терминал,   иначе   игнорируется.  Вызывает  позиционирование
   курсора в указанные строку и столбец. *)
PROCEDURE Bell;
(* Порождает  звуковой  сигнал, если стандартный вывод направлен
   на  терминал  и терминал обладает звуковым генератором. Иначе
   игнорируется. *)
PROCEDURE Home;
(* Позиционирует  курсор  в  верхний  левый  угол  экрана,  если
   стандартный вывод направлен на экран, иначе игнорируется. *)
PROCEDURE Clear;
(* Стирает содержимое экрана от позиции курсора до конца экрана,
   если    стандартный   вывод   направлен   на   экран,   иначе
   игнорируется. *)
PROCEDURE ClearLine;
(* Стирает содержимое строки от позиции курсора до конца строки,
   если    стандартный   вывод   направлен   на   экран,   иначе
   игнорируется. *)
END StdIO.

Приложение 2

DEFINITION MODULE mcPublic; (* Ned 20-Oct-87. (c) KRONOS *)

FROM SYSTEM     IMPORT  WORD;

TYPE
  PRINT   = PROCEDURE (ARRAY OF CHAR, SEQ WORD);
  ERROR = PROCEDURE (INTEGER,INTEGER, ARRAY OF CHAR, SEQ WORD);
  GETLINE = PROCEDURE (VAR ARRAY OF CHAR);
  PRAGMA  = ['a'..'z'];

TYPE
  INFO = RECORD
           name  : ARRAY [0..79] OF CHAR;
           lines : INTEGER;
           errors: INTEGER;
           time  : INTEGER;
           iotime: INTEGER;
           codes : INTEGER; (* in words *)
         END;

PROCEDURE enterCompiler(cpu: INTEGER; VAR mm: CHAR);
PROCEDURE compile(print,info: PRINT; error: ERROR;
                     getline: GETLINE;
                       maxer: INTEGER; VAR info: INFO);
PROCEDURE pragma(p: PRAGMA; onoff: BOOLEAN);
PROCEDURE exitCompiler;

END mcPublic.

DEFINITION MODULE exComp; (* Leo 22-Dec-87. (c) KRONOS *)

PROCEDURE Compile;
PROCEDURE NextError;

END exComp.

IMPLEMENTATION MODULE exComp; (* Leo  22-Dec-87. (c) KRONOS *)

IMPORT Config, exHead, exMain, exMem, Terminal;
FROM EditScr    IMPORT  pushandclearinfo, pop;
FROM ASCII      IMPORT  EOF;
FROM SYSTEM     IMPORT  WORD;
FROM Strings    IMPORT  Str1;
FROM Image      IMPORT  image0, image;
FROM FileNames  IMPORT  ChangeExt, AppExt?, AppExt, codext
                      , symext;
FROM Keyboard   IMPORT  Pressed?, ReadKey, break;
FROM Clock      IMPORT  miliClock;
IMPORT FROM mcPublic;
FROM mcScan     IMPORT  Fault;

VAR  sou: ARRAY [0..79] OF CHAR;
  noerrs: INTEGER;
    Last: INTEGER;
      mm: CHAR;
     cpu: INTEGER;
      en: INTEGER;

    errs: ARRAY [0..15] OF
          RECORD
            line,col: INTEGER;
            txt: ARRAY [0..63] OF CHAR;
          END;

    head: ARRAY [0..81] OF CHAR;
    bump: ARRAY [0..81] OF CHAR;
     msg: ARRAY [0..81] OF CHAR;
     str: ARRAY [0..81] OF CHAR;

   BREAK: BOOLEAN;

PROCEDURE print(VAL format: ARRAY OF CHAR; SEQ arg: WORD);
BEGIN
  Str1(str,"<<< "); image(str,format,arg);
  Terminal.print("%s",str); Terminal.ClearLine;
  Terminal.print("\r");
END print;

PROCEDURE error(l,c: INTEGER; VAL format: ARRAY OF CHAR;
                                                SEQ arg: WORD);
BEGIN
  IF BREAK THEN RETURN END;
  IF (l>=0) & (c>=0) & (noerrs<=HIGH(errs)) THEN
    errs[noerrs].line:=l;
    errs[noerrs].col :=c;  image0(errs[noerrs].txt,format,arg);
    INC(noerrs);
  END;
END error;

PROCEDURE info(VAL format: ARRAY OF CHAR; SEQ arg: WORD);
BEGIN
  IF BREAK THEN RETURN END;
  image0(bump,format,arg); image0(msg,head,bump,noerrs);
  print("%s",msg);
  WHILE Pressed?() DO
    IF ReadKey()=break THEN BREAK:=TRUE; Fault(79) END;
  END;
END info;

PROCEDURE getline(VAR s: ARRAY OF CHAR);
  VAR sz: INTEGER;
BEGIN
  IF exMem.cur>Last THEN
    s[0]:=EOF; s[1]:=0c;
  ELSE
    exMem.get(s,sz); exMem.jump(exMem.cur+1);
  END;
END getline;

PROCEDURE header(VAL s: ARRAY OF CHAR);
BEGIN
  pushandclearinfo;
  image0(head,"%%-32.32s MODULA-2<%c> errors %%$2d %-15.15s>>",
         mm,s);
END header;

PROCEDURE result(VAL res: INFO);
  VAR str: ARRAY [0..79] OF CHAR; time: INTEGER;
BEGIN
  IF noerrs>0 THEN RETURN END;
  IF BREAK THEN
    pushandclearinfo; print("Компиляция ПРЕРВАНА"); pop;
    WHILE Terminal.Pressed()=0 DO Terminal.WaitUntilPressed END;
    RETURN
  END;
  image0(str," строк %d   время  %$2dcp + %$2dio"
            ,res.lines,res.time,res.iotime);
  Str1(sou,res.name);
  IF res.codes=0 THEN AppExt(sou,symext);
  ELSE AppExt(sou,codext); image(str,"   код %dw",res.codes)
  END;
  image(str,'  "%s"',sou);
  pushandclearinfo; print(str); pop;
  WHILE Terminal.Pressed()=0 DO Terminal.WaitUntilPressed END;
END result;

PROCEDURE parser;
  VAR r: INTEGER; res: INFO;
BEGIN
  exMain.GetName(sou);
  IF Terminal.Cursor      (0)#0 THEN END;
  IF Terminal.LowIntensity(1)#0 THEN END;
  IF Terminal.Reverse     (1)#0 THEN END;
  header(sou);
  pushandclearinfo;
  compile(print,info,error,getline,8,res);
  pop;
  IF Terminal.Cursor      (1)#0 THEN END;
  result(res);
  IF Terminal.Reverse     (0)#0 THEN END;
  IF Terminal.LowIntensity(0)#0 THEN END;
END parser;

PROCEDURE Compile;
  VAR ch: CHAR; cur: INTEGER;
BEGIN
  noerrs:=0; BREAK:=FALSE;
  FOR ch:='a' TO 'z' DO pragma(ch,TRUE) END;
  cpu:=Config.SysInfo.CPU;
  IF NOT (cpu IN {1,2,5,6}) THEN
    print('Неужели Джек уже спаял Kronos2.%d',cpu); RETURN
  END;
  cur :=exMem.cur;
  Last:=exMem.last;
  exMem.jump(0);
  enterCompiler(cpu,mm);
  IF mm='0' THEN
    exHead.message('Слишком мало памяти для копмилятора',TRUE);
    RETURN
  END;
  parser;
  exitCompiler;
  exMem.jump(cur);
  exHead.finish;
  en:=0; NextError;
END Compile;

PROCEDURE GetError(n: INTEGER; VAR l,c: INTEGER;
                                         VAR t: ARRAY OF CHAR);
BEGIN
  IF (n<0) OR (n>=noerrs) THEN
    Str1(t,""); l:=-1; c:=-1; RETURN END;
  Str1(t,errs[n].txt);
  l:=errs[n].line;      c:=errs[n].col;
END GetError;

PROCEDURE NextError;
  VAR txt: ARRAY [0..79] OF CHAR; l,c: INTEGER;
      str: ARRAY [0..79] OF CHAR;
BEGIN
  IF noerrs>0 THEN
    GetError(en,l,c,txt);
    exMain.JumpTo(l,c);
    image0(str,"<<< %-60.60s ОШИБОК: %d >>>",txt,noerrs);
    IF Terminal.HighIntensity(1)#0 THEN END;
    exHead.start(str);
    IF Terminal.HighIntensity(0)#0 THEN END;
    WHILE Terminal.Pressed()=0 DO Terminal.WaitUntilPressed END;
    exHead.finish;
    en:=(en+1) MOD noerrs;
  END;
END NextError;

BEGIN
  noerrs:=0; en:=0;
END exComp.

Приложение 3

DEFINITION MODULE Strings; (* Leo 18-Oct-85 *)

(* Модуль поставляет процедуры работы со строками.
  1.  Все  операции  работают  со строками длиной <= 256 байтов,
      т.е.  в  массивах размера > 256 байтов используются только
      первые 256 позиций.
  2.  Любая  строка  перед  началом работы с ней должна (!) быть
      проинициализирована операциями Str0, Str1, Str2.
  3.  Если в ходе какой-либо операции диапазон указанных позиций
      выходит  за  пределы  строки, то он усекается по диапазону
      индекса строки.
  4.  Если при операции какая-то часть информации оказывается за
      пределами физической строки, эта часть теряется.
  5.  Операции,  выдающие строку как результат (например, второй
      аргумент  операций  FirstWord,  DelWord),  не  требуют  ее
      предварительной инициализации. *)

PROCEDURE Str0(VAR S: ARRAY OF CHAR);
(* Инициализация пустой строки *)
PROCEDURE Str1(VAR S: ARRAY OF CHAR; Contence: ARRAY OF CHAR);
(* Инициализация  строки с начальным содержимым -Contence-. *)
PROCEDURE Str2(VAR S: ARRAY OF CHAR);
(* Эквивалентно Str1(S,S) *)
PROCEDURE Len(VAR S: ARRAY OF CHAR): CARDINAL;
(* Выдает длину строки *)
PROCEDURE App(VAR S: ARRAY OF CHAR; Ch:CHAR);
(* Прицепление в конец строки литеры *)
PROCEDURE AppStr(VAR S: ARRAY OF CHAR; A: ARRAY OF CHAR);
(* Прицепление в конец строки другой строки *)
PROCEDURE DelCh(VAR S: ARRAY OF CHAR; i: CARDINAL);
(* Удаление  символа  в  указанной  позиции  и  подтяжка  хвоста
   строки. *)
PROCEDURE DelStr(VAR S: ARRAY OF CHAR; i: CARDINAL; len: CARDINAL);
(* Удаление -len- символов в указанной позиции и подтяжка хвоста
   строки. *)
PROCEDURE InsCh(VAR S: ARRAY OF CHAR; i: CARDINAL);
(* Вставка пробела в указанную позицию и сдвижка хвоста строки.*)
PROCEDURE InsStr(VAR S: ARRAY OF CHAR; i: CARDINAL; len: CARDINAL);
(* Вставка  -len-  пробелов в указанную позицию и сдвижка хвоста
   строки.*)
PROCEDURE PoolStr(VAR S: ARRAY OF CHAR);
(* Подтягивает строку влево, удаляя ведущие пробелы *)
PROCEDURE Word(VAR S: ARRAY OF CHAR; n: CARDINAL;
               VAR W: ARRAY OF CHAR);
(* СЛОВО  -  последовательность подряд идущих литер, отличных от
   пробела, конца или начала строки.
   Выдает  -n-е  слово в строке -S- в массив -W-, если оно есть,
   иначе - пустую строку.
   Внимание!  Аргументы не могут быть одним и тем же массивом!*)
PROCEDURE WordCount(VAR S: ARRAY OF CHAR): CARDINAL;
(* Подсчитывает число слов в строке *)
PROCEDURE DelWord(VAR S: ARRAY OF CHAR);
(* Удаляет из строки первое слово *)
PROCEDURE GetWord(VAR S: ARRAY OF CHAR; VAR W: ARRAY OF CHAR);
(* Эквивалентно: FirtsWord(S,W); DelWord(S) *)
PROCEDURE FirstWord(VAR S: ARRAY OF CHAR; VAR W: ARRAY OF CHAR);
(* Эквивалентно: Word(S,0,W).
   Выдает  первое  слово в строке, если оно есть, иначе - пустую
   строку. *)
PROCEDURE SubStr(VAR S: ARRAY OF CHAR; start,finish: CARDINAL;
                 VAR W: ARRAY OF CHAR);
(* Вырезает из строки указанную подстроку *)
PROCEDURE SetStr(VAR S: ARRAY OF CHAR; start,finish: CARDINAL;
                     W: ARRAY OF CHAR);
(* Вставляет в строку указанную подстроку *)
PROCEDURE AppLn(VAR s: ARRAY OF CHAR);
(* Эквивалентно App(s,CR); App(s,LF) *)
PROCEDURE Truncate(VAR S: ARRAY OF CHAR; L: CARDINAL);
(* Удаляет хвост строки. Len(S)=L *)
END Strings.

DEFINITION MODULE Image; (* Leo  04-Apr-87. (c) KRONOS *)

(*Модуль поставляет процедуры для работы с числовыми строками*)

FROM SYSTEM    IMPORT   WORD;

CONST    ok=0;
      dummy=-1;
    illegal=-2;
   overflow=-3;
  underflow=-4;
invalidbase=-5;

PROCEDURE PeekNum (VAR s: ARRAY OF CHAR; pos: INTEGER;
                   VAR w: WORD): INTEGER;
PROCEDURE PeekReal(VAR s: ARRAY OF CHAR; pos: INTEGER;
                   VAR w: REAL): INTEGER;
(* Считывает  из  строки -s- число -w-, начиная с позиции -pos-.
   Возвращает номер позиции следующего за числом символа и < 0 в
   случае неудачи.
   minInt=2147483648=-2147483648
         =20000000000b=-20000000000b
         =20000000000c=-20000000000c
         =80000000h=-80000000h.
   Все эти варианты считаются правильными.*)

PROCEDURE GetNum (VAR s: ARRAY OF CHAR; VAR n: WORD): INTEGER;
PROCEDURE GetReal(VAR s: ARRAY OF CHAR; VAR n: WORD): INTEGER;
(* Эквивалентно: FirstWord(s,n);
   -n-  может состоять из любых базовых чисел, например:
        123456789
       -1
        0ABCDEFh
        177b
       -177b
        377c
   Возвращает   значение   больше  или  равное  нулю,  если  все
   нормально.*)

PROCEDURE image(VAR s: ARRAY OF CHAR; format: ARRAY OF CHAR;
                SEQ w: WORD);
(* Добавляет  в  строку  -s-  число  -w-,  записанное  в формате
   -format-.

   format     ::= { текст | "%" формат | "%%" | "\n"
                    | "\r" | "\l" | "\\" } 0c.
   текст      ::= "любая последовательность символов ASCII-8,
                   кроме '%','\' и 0c ".
                   Если требуется символ '%', предварите его '%':
                   '%%' преобразуется в просто '%'.
                   Если требуется символ '\', предварите его '\':
                   '\\' преобразуется в просто '\'.
   формат     ::= { модификатор } база.
   база       ::= ("d"|"h"|"b"|"s"|"{}"|"i").
   ширина     ::= цифра { цифра } | "*".
   точность   ::= "." цифра { цифра } | ".*" .
   модификатор::= ( space | "$" | "-" | "+" | "#" | ширина
                    | точность } .
   d   (Decimal)       - десятичное;
   h,H (Hexidecimal)   - шестнадцатеричное
                         (h,x -- "A".."F" прописные)
   x,X (Hexidecimal)   - эквивалентно 'h'
                         (H,X -- "a".."f"  строчные)
   b,B (Octal)         - восьмеричное;
   o,O (Octal)         - эквивалентно 'b'
   s,S (String)        - строка;
   c,C (Char  )        - одиночный символ
   {}  (set)           - битсет;
   i,I (bIn)           - двоичное;
   f,F (Float)         - вещественное число в формате:
                           [+|-]dddddddd.ddddd
                             |__  n1  __|_ t _|
                         Число  цифр  до  запятой n1 - минимально
                         необходимое   для  представления  числа.
                         Число  цифр  после  запятой t - задается
                         точностью (по умолчанию 6).
   e,E (Exponent)      - вещественное число в формате:
                           [+|-]d.ddddddE(+|-)dd    или
                           [+|-]D.DDDDDDe(+|-)DD
                         Число цифр до запятой - 1.
                         Число цифр после запятой - t - задается
                         точностью (по умолчанию 6);
                         (формат 'e' -- 'E' в результате;
                         формат 'E' -- 'e' в результате).
   g,G (General)       - вещественное число в формате:
                         если  FLOAT(TRUNC(число))=число,  то  в
                         формате dddddd; иначе в формате 'f' или
                         'e' - какой короче.
   #                   - показывает  число  с указанием основания
                         (например:   image(s,"%$10#h",12abcdefH);
                         эквивалентно image(s,"012ABCDEFh") );
   -                   - число пишется слева в поле установленной
                         ширины;
   +                   - показывает  число  со знаком, независимо
                         от знака числа;
   $   (zero)          - дополнить до нужного количества разрядов
                         ведущими нулями;
   space               - выставляется     знак,     если    число
                         отрицательное, иначе пробел;
   точность            - задает   число  значащих  цифр  после(!)
                         запятой в форматах 'f','e','g' или число
                         символов строки в формате 's';
   ширина              - задает  общую(!)  ширину  поля (с учетом
                         возможных  символов  основания,  знака и
                         т.п.);     если     указанной     ширины
                         недостаточно      для      представления
                         выводимого      значения,     происходит
                         автоматическое    расширение   поля   до
                         минимально   необходимого;  если  вместо
                         спецификации   ширины   и/или   точности
                         указаны   '*',   то  значения  ширины  и
                         точности   берутся   из  соответствующих
                         аргументов.  Внимание! Значения точности
                         и   ширины   должны  быть  из  диапазона
                         [0..255];   в   противном   случае   они
                         принимаются   равными    значениям    по
                         умолчанию.
   Модификаторы  '$' и '-',   естественно, не совместимы.
   Модификаторы  '+' и space, естественно, не совместимы.
   Модификатор  точность  может быть использован только с базами
   's','f','e','g'.
   Модификаторы  '+'  и  space  могут быть использованы только с
   базами 'i','f','e','g'.
   Модификаторы  '$',  '+'  и space НЕ могут быть использованы с
   базами 's','{}','c'.
   \n  обозначает CR+LF
   \r  обозначает CR
   \l  обозначает LF
   ВНИМАНИЕ! \n, \r, \l обрабатываются ТОЛЬКО в формате, но не в
   строках-аргументах!!! *)
END Image.

Приложение 4

DEFINITION MODULE osLoader; (* Leo 29-Jan-88. (c) KRONOS *)

FROM SYSTEM     IMPORT  ADDRESS;
TYPE
      DFT;      (*    DATA  FRAME TABLE    *)

CONST   (*      РЕЗУЛЬТАТЫ  ОПЕРАЦИЙ       *)

  ok=0; dftoverflow=1; geterror=2; nomemory=3; wrongtime=4;

VAR SYS: DFT; (*  системная DFT Excelsior  *)

PROCEDURE lookupModule(dft: DFT; name: ARRAY OF CHAR;
                                VAR G: ADDRESS): BOOLEAN;
(* Ищет  модуль  в  DFT.  Если  находит,  заносит  значение  его
   глобального   регистра   в   -G-  и  возвращает  TRUE.  Иначе
   возвращает FALSE, значение -G- не изменяется. *)

TYPE  allocProc=(*    addr     size *)

PROCEDURE (VAR ADDRESS, INTEGER);
(* При size>0 пытается отвести непрерывный кусок памяти размером
   в  size  слов.  Возвращает  NIL  в случае неудачи. При size<0
   утилизует ранее отведенный кусок размером в ABS(size) слов.*)

getProc=
         (*    name         code        size    allocate  *)
PROCEDURE(ARRAY OF CHAR,VAR ADDRESS,VAR INTEGER,allocProc,
      VAR ARRAY OF CHAR): BOOLEAN;
     (*   cause       *)

(* Если  удалось  "добыть"  кодофайл для -name-, размещает его в
   памяти, пользуясь процедурой -allocate-, выдает его CodePtr в
   -code-  и  возвращает TRUE. Иначе - FALSE и причину неудачи в
   текстовом виде в -cause-.

   ВНИМАНИЕ! -get- возвращает размер выделенной памяти в -size-.
   Именно:  кусок  code..code+size-1  будет  утилизован в случае
   неудачной загрузки. *)

lookupProc=
         (*    name         glo   *)
PROCEDURE(ARRAY OF CHAR,VAR ADDRESS): BOOLEAN;
(* Процедура поиска во внешних DFT (см. ниже). *)

PROCEDURE Load(VAR  dft: DFT; (* результирующая -dft- *)
  VAR
    name: ARRAY OF CHAR; (* имя загружаемого модуля/сообщение *)
  lookup: lookupProc;    (* процедура поиска во внешних DFT   *)
     get: getProc;       (* процедура размещения кода модуля  *)
   alloc: allocProc;     (* процедура выделения памяти        *)
 dftsize: INTEGER;       (* макс. размер дерева импорта       *)
       ): INTEGER;       (* результат загрузки                *)

(* В  случае неудачи (результат#ok) возвращает причину неудачи в
   текстовом  виде  в  массиве  -name-,  поэтому   рекомендуется
   описывать -name- как минимум 80-байтовым:
        name: ARRAY [0..N] OF CHAR;  HIGH(name)>=79.
   Инициализация  формируется  как  отдельная  структура  внутри
   -dft-   и  может  быть  исполнена  как  процедура  с  помощью
   -Call(dft)-.

   Процедура  -lookup- используется для поиска модуля во внешних
   DFT. Мы рекомендуем такую форму:
     PROCEDURE lookup(name,G): BOOLEAN;
     BEGIN
       IF lookupModule(SYS,name,G) THEN RETURN TRUE END;

       IF lookupModule(MY0,name,G) THEN RETURN TRUE END;
       IF lookupModule(MY1,name,G) THEN RETURN TRUE END;
       . . .

       RETURN FALSE;
     END lookup;
   Замечание.  Процедура -lookup- используется дважды в процессе
   загрузки  -  при чтении кодофайлов и при размещении глобалов.
   Поэтому  она  должна обеспечивать стабильный (не меняющийся с
   начала  и  до  конца  загрузки)  результат поиска для каждого
   модуля. 
   Процедура   -alloc-,  сопоставленная  -dft-  (см.  -newDFT-),
   используется  процедурой  -get-  для размещения кода, а также
   вызывается     для     размещения     глобальных     областей
   инициализируемых   модулей  после  того,  как  загружены  все
   кодофайлы.
   В   случае   неудачной   загрузки   -alloc-   вызывается  для
   "утилизации" использованной памяти. *)

PROCEDURE UnLoad(dft: DFT);
(* Разгружает   -dft-,  освобождая  занятую  кодом  и  глобалами
   память,  после  чего  удаляет  -dft-,  освобождая  занятую ею
   память. *)

PROCEDURE Call(dft: DFT);
(* Вызывает  модули  из  -dft-  в порядке обхода дерева импорта.
   Циклически  зависимые  модули инициализируются в произвольном
   порядке  относительно  друг  друга, но весь цикл происходит в
   правильном порядке относительно остального дерева. *)

TYPE
                 (*proc0*)
iterProc=PROCEDURE(PROC);

PROCEDURE Order(dft: DFT; ip: iterProc);
(* Может  быть  использована  для  генерации  кода  "встроенной" 
   инициализации. *)
END osLoader.