Джон Левкович Все о языке программирования и системе MUMPS Введение и справочное руководство по языку программирования MUMPS Перевод и примечания - Топалов И.К. 1992-94 Cтр. 2 Все о языке программирования и системе MUMPS / Введение и справочное руководство по языку программирования MUMPS/ Содержание От автора 7 Глава 1 Введение в MUMPS 8 1.1 Как использовать эту книгу 8 1.2 Введение в MUMPS 9 1.3 Кратко об истории 14 1.4 Начинаем работать с MUMPS 16 1.4.1 Регистрация в MUMPS 17 1.4.2 Ввод команд MUMPS с клавиатуры 18 1.4.3 Ввод и редактирование программ 19 1.4.4 Запись программ на диск и чтение их с диска 20 1.4.5 Исполнение программ 21 1.4.6 Выход и окончание работы 21 1.5 Основные положения 22 Глава 2 Основные составляющие MUMPS среды 2.1 Интерпретатор 23 2.2 Концепция Директориев и Разделов 24 2.2.1 Разделы 25 2.2.2 Директории 27 2.2.3 Именованные области /Локальные и глобальные/ 28 2.3 Задания /процессы/ 30 2.4 Основные положения 30 Глава 3 Операторы MUMPS 3.1 Назначающие операторы 3.1.1 Оператор SET 3.1.2 Оператор KILL 3.1.3 Оператор NEW 3.2 Операторы с условием выполнения 3.2.1 Оператор IF 3.2.2 Оператор ELSE 3.2.3 Оператор FOR 3.3 Операторы передачи управления 3.3.1 Оператор GOTO 3.3.2 Оператор DO 3.3.3 Оператор QUIT 3.3.4 Оператор HALT 3.3.5 Оператор XECUTE 3.4 Операторы ввода/вывода 3.4.1 Оператор OPEN 3.4.2 Оператор USE 3.4.3 Оператор READ 3.4.4 Оператор WRITE 3.4.5 Оператор CLOSE 3.4.6 Оператор PRINT Стр. 3 3.5 Системные и другие операторы 3.5.1 Оператор BREAK 3.5.2 Оператор HANG 3.5.3 Оператор JOB 3.5.4 Оператор LOCK 3.5.5 Оператор VIEW 3.5.6 Оператор ZLOAD 3.5.7 Оператор ZINSERT 3.5.8 Оператор ZREMOVE 3.5.9 Оператор ZPRINT 3.5.10 Оператор ZSAVE 3.5.12 Прочие 'Z'.. операторы 3.6 Обобщение Глава 4 Структуры данных 4.1 Строки 4.2 Числа 4.2.1 Экспотенциальное представление чисел 4.2.2 Числовая интерпретация строк 4.3 Логические величины (Истина/Ложь) 4.4 Переменные 4.4.1 Локальные переменные 4.4.2 Массивы 4.4.3 Глобали 4.4.4 Системные переменные 4.5 Общие положения Глава 5 Выражения 5.1 Числовые операции +, -, *, **, /, \, и # 5.1.1 Одноместные операции 5.1.2 Двуместные числовые операции 5.2 Строковые операции: Конкатенация, _ 5.3 Логические и операции отношения: >, <, [, ], ?, &, !, и ' 5.3.1 Операции отношения между числами 5.3.2 Строковые операции отношения 5.3.3 Логические операции 5.4 Виды комбинированных операций 4.5 Общие положения Глава 6 Синтаксис MUMPS 6.1 Операторы 6.1.1 Аргументы операторов 6.1.2 Неявно исполняемые операторы 6.1.3 Сокращения имен и сочетания операторов 6.1.4 Операторы с наложением условий на их исполнение 6.1.5 Операторы с временем ожидания 6.1.6 Комментарии 6.2 Строки 6.3 Подпрограммы /процедуры/ 6.4 Программы 6.5 Блочные структуры 6.6 Общие положения Стр. 4 Глава 7 Использование косвенности 176 7.1 Команда XECUTE 7.2 Оператор косвенности - '@' 7.2.1 Косвенное задание имени 7.2.2 Косвенное задание аргумента 7.2.3 Косвенное задание шаблона 7.2.4 Косвенное задание ссылки 7.3 Динамическая передача управления между программами 7.4 Косвенность: казусы и ловушки 7.5 Общие положения 186 Глава 8 Внутренние функции 187 8.1 Функции для работы со строками 188 8.1.1 Функция $EXTRACT 8.1.2 Функция $PIECE 8.1.3 Функция $LENGTH 8.1.4 Функция $FIND 8.1.5 Функция $TRANSLATE 8.1.6 Функция $JUSTIFY 8.1.7 Функция $FNUMBER 8.1.8 Функция $ASCII 8.1.9 Функция $CHAR 8.1.10 Функция $TEXT 8.2 Функции для работы с данными 8.1.1 Функция $DATA 8.1.2 Функция $ORDER 8.1.3 Функция $NEXT (OBSOLETTE) 8.1.4 Функция $QUERY 8.1.9 Функция $GET 8.2 Прочие функции 8.1.1 Функция $SELECT 8.1.2 Функция $RANDOM 8.1.3 Функция $VIEW 8.4 $Z ... функции 8.5 Общие положения Глава 9 Передача параметров и внешние функции 9.1 Передача параметров в операторе DO 9.1.1 Передача значений 9.1.2 Передача ссылки на массив 9.2 Внешние функции 9.3 Внешние переменные 9.4 Библиотечные программы и процедуры 9.4.1 Преобразование даты/времени/ из формата $H 9.4.2 Проверка и преобразование чисел 9.4.3 Преобразования чисел в разные системы счисления 9.4.4 Элементарная статистика 9.4.5 Внешние функции для переразмещения строк 9.5 Передача параметров в операторе JOB 9.6 Стиль и технология программирования 9.7 Общие положения Стр. 5 Глава 10 Технология работы с глобалями 10.1 Вступление к использованию глобалей 10.2 Индексация 10.2.1 Последовательность различения индексов в массиве 10.2.2 Функции $ORDER и $DATA 10.3 Использование неполного синтаксиса глобальной ссылки 10.4 Оператор LOCK 10.5 Дополнительные возможности при работе с глобалями 10.5.1 Распределенные базы данных 10.5.2 Дублирование 10.5.3 Журналирование 10.5.4 Администрирование доступа 10.6 Дополнительные примеры: Печать и копирование массивов 10.7 Планирование баз данных 10.7.1 Инвертированные /ключевые/ массивы 10.7.2 Справочные массивы 10.8 Поиск и сортировка 10.9 Общие положения Глава 11 Нюансы MUMPS 11.1 Организация глобальных массивов 11.1.1 Добавление данных в В-дерево 11.1.2 Варианты В-деревьев 11.1.3 Количество уровней индексации 11.1.4 "Сжатие" ключа 11.1.5 Удаление данных из В-дерева 11.1.6 Буферизация 11.1.7 Принципы эффективной записи данных 11.1.8 Ошибки баз данных 11.2 Исполнение MUMPS программ 11.3 Общие положения Глава 12 Ввод и вывод на переферийные устройства 12.1 Общее описание назначения Ввода/Вывода 12.2 Основное устройство 12.3 Системные переменные связанные с операциями ввода/вывода 12.4 Устройства 12.4.1 Видеотерминалы 12.4.2 Принтеры 12.4.3 Ввод/вывод на магнитную ленту 12.4.4 Последовательные файлы 12.4.5 Печать через спулинг /Устройства спулинга/ 12.4.6 Прочие устройства 12.5 Краткий обзор дополнительных возможностей управлением вводом/выводом 12.5.1 Утилиты управления экраном 12.5.2 Ввод с клавиатуры: чтение кодов нажатой клавиши 12.5.3 Запросы с выбором из нескольких вариантов 12.5.4 Редактирование введенной строки 12.5.5 Драйвер запросов 12.5.6 Обобщение обзора дополнительных возможностей управлением вводом/выводом Стр. 6 Глава 13 Обработка ошибок 13.1 Отслеживание ошибок и их обработка 13.2 Отладка программ /С использованием отладчика/ 13.3 Общие положения Глава 14 MUMPS: Развитие языка 14.1 Локальные сети обработки информации 14.1.1 Сети MUMPS приложений 14.1.2 Сети с разнородным программным обеспечением/не MUMPS/ 14.2 Дополнительное управление устройствами 14.3 Интерфейсы между MUMPS и другими языками программирования 14.4 Использование альтернативного набора символов ASCII 14.5 Стандартизация обработки ошибок 14.6 Дополнения и изменения в стандарте языка 14.7 Заключительные замечания П Р И Л О Ж Е Н И Я А Набор кодов ASCII B Дополнительная литература по MUMPS C Перечень операторов MUMPS D Перечень внутренних функций MUMPS E Перечень команд MUMPS F Перечень системных переменных MUMPS G Документирование процедур выбора H RS-232 устройства I Функции терминалов Стр. 7 От автора Я впервые столкнулся с MUMPS в 1971 году, после 10 лет работы программистом на таких языках как FORTRAN, BASIC и различных версиях ассемблеров. В это время Нью-Йоркский Государственный Ветеринарный институт столкнулся с серьезными проблемами в организации и управлении базами данных. Ввод огромного потока информации, связанной с учебным процессом, осуществление связи между удаленными устройствами, обработку большого количества разнообразных запросов, нельзя было осуществить работая в существующих, на тот момент, языках программирования. Использование MUMPS представило лучшую, и наиболее экономичную по затратам, возмож- ность решить эти проблемы. Но стоит отметить, что я не представлял тогда, что эта разработка станет одним из широко распространенных в мире программных продуктов работающих с базами данных. В последующие 15 лет происходило и повышение популярности и влия- ния MUMPS на потребности управления базами данных и рос мой профессио- нализм в его знании и использовании. Мои ранние трепетные попытки в создании и программировании баз данных сменились восторгом и уверен- ностью. В MUMPS удалось создать блестящие средства управления базами данных, обрабатывающие самые различные потоки информации, и связывать их между собой. MUMPS несет в себе идеальную смесь мощности, экономич- ности, легкости в использовании , что позволило широко распространять его в компьютерных приложениях и статистической обработке клинических исследований. MUMPS и до сих пор обычно именуют языком управления базами дан- ных, но это не совсем соответствует действительности. Основной его от- личительной особенностью от других языков и систем программирования, является поддержка уникальной системы организации данных в виде масси- вов с многоуровневой индексацией и иерархической структурой. Будучи разреженными структурами В-деревьев эти массивы позволяют создать вы- сокоэффективные /по времени доступа/ и компактные /по занимаемому объ- ему/ базы данных. Эти массивы, в многопользовательских задачах, могут быть доступны одновременно нескольким пользователям. В дополнение к этому, MUMPS поддерживает большое количество внутренних функций для работы со строковыми данными и достаточно гибкий и эффективный аппарат управления вводом/выводом с устройств. Я и сейчас продолжаю программировать на многих других языках, (Pascal, APL, C и т.д.), но почти всегда признаюсь себе, что многие из возникающих прикладных проблем гораздо проще было бы решить в MUMPS. И в силу этого обстоятельства такие отвлечения на работы связанные с другими языками программирования становятся все короче, поскольку большинство приложений действительно быстрее и проще разрабатывать в MUMPS. MUMPS является мощным и эффективным средством для решения задач многих направлений. Остается только сказать: "Попробуйте поработать с ним, и Вам понравится!" Стр. 8 Глава 1 Введение в MUMPS Эта книга создавалась для решения двух трудно совместимых задач: - дать первое представление об этом языке программирования и облег- чить начальные этапы работы с ним. - создать удобное и эффективное справочное руководство по возможнос- тям MUMPS для профессиональных программистов, которые уже знакомы с ним, и нуждаются только в описании нюансов и подробностей. В книге нет разделения текста для этих двух категорий читателей, поэтому, если Вы уже знакомы с MUMPS - пропустить ее первые главы. Кроме того, в приложении В дан список дополнительной литературы. /*1/ Разделы, посвященные разбору дополнительных возможностей, являют- ся хорошим иллюстративным и справочным материалом, но для начинающих программистов способны затруднить понимание общих концепций этого уни- кального языка. И кроме того, содержат слишком много технических под- робностей. 1.1 Как использовать эту книгу. В процессе чтения этой книги, желая наиболее полно овладеть всеми возможностями языка, мы с Вами должны соединить наши усилия. Все при- меры и положения, проиллюстрированные в книге, Вам желательно прове- рять на практике. Конечно, приведенные примеры не исчерпывают ВСЕХ возможностей языка, но вообще - то, скажу я Вам, что даже сейчас, пос- ле 15 лет практического программирования в MUMPS, при разработке прик- ладных пакетов я сталкиваюсь порой с неизвестными техническими нюанса- ми. Поэтому, Вы должны быть готовы к постоянному исследованию всех возможностей. В разработке поясняющих примеров была сделана попытка совместить ясность изложения в применении возможностей языка, так необходимую для начинающих, с демонстрацией нюансов и особенностей, характерной для справочного руководства. Все основные положения, обсуждаемые в книге, сопровождаются достаточным количеством примеров, причем начинающим программистам лучше всего совместить чтение книги с практической отра- боткой примеров на ЭВМ, что улучшит понимание и закрепление материала. В главе 1.4 поясняется, как осуществить ввод команд языка с клавиату- ры, а также как создавать, редактировать, записывать и вызывать прог- раммы с диска. Приложения к книге и индексный справочник терминов и понятий поз- волит более опытным программистам быстрее найти информацию, необходи- мую при разработке новых приложений. При чтении этой книги Вы также можете столкнуться с некоторой из- быточностью информации, одни и те же понятия, концепции и определения приводятся в различных ее местах. Это сделано исключительно для цель- ности восприятия материала. Поскольку ни что так не раздражает при ра- боте с книгой, как изобилие перекрестных ссылок. Поэтому, особенно для начинающих, полезно усилить полезный эффект даже ценой некоторого дуб- лирования информации. ________________________________________________________________ *1 (К сожалению практически вся на английском! - здесь и далее примечания переводчика) Cтр. 9 В книге используются несколько способов выделения специальной ин- формации. 1. Все примеры в MUMPS коде приводятся следующим образом: ┌──────────────────────────────────────────────────────────────┐ │о| > WRITE "Это пример" <- |o│ │ | | │ │о| Это пример |o│ └──────────────────────────────────────────────────────────────┘ (Подразумевается аналогия с листингом, получаемом на ЭВМ) 2. Символ <- указывает то место, где необходимо нажатие клавиши "Enter" ( "Return", "CR" или "ВК", "ВВОД" на различных термина- лах), при вводе команд с клавиатуры. 3. Все поясняемые основные положения и концепции языка выделяются следующим образом: ┌────────────────────────┐ │ │ │ Основные положения │ │ │ └────────────────────────┘ Каждая глава оканчивается сводкой основных положений, которые в ней были впервые приведены. Начинающий программист должен внимательно прочитать и изучить ма- териал 1 и 2 глав, затем ознакомиться с главой 3 ( особенно с описани- ем операторов SET, WRITE, DO). После прочтения глав 4,5,6 рекомендует- ся вновь вернуться к главе 3 для изучения подробностей в использовании операторов языка. Остальные главы могут изучаться в любой последова- тельности. 1.2 Введение в MUMPS Что такое MUMPS? Прежде всего это такой же язык программирования (алгоритмический язык, как это принято также называть), как и FORTRAN, COBOL, PL1 и т.д. Он был разработан в 1967 году компьютерным центром Массачусетского Центрального госпиталя для поддержки баз данных с про- извольным доступом и для обработки преимущественно строковой информа- ции. На тот момент, эти возможности отсутствовали в других языках и системах программирования, и должны были стать неотъемлемой частью разрабатываемых систем управления базами данных общего назначения для госпиталей. Название языка и сложилось, как аббревиатура компьютерного центра и его основного назначения: M Massachusetts General Hospital U Utility M Multi P Programming S System Стр. 10 Хотя MUMPS и разрабатывался для медицинских приложений, но его использование ими не ограничивается. В MUMPS были разработаны пакеты программ для обслуживания банков, библиотек, различные расчетные зада- чи, инструментальные, информационные и обучающие системы, а также мно- жество других не-медицинских задач. MUMPS сочетает в себе возможности по управлению базами данных с мощным аппаратом по обработке знако- во-ориентированной информации, а также высокоэффективную технологию чтения/записи данных. Эти возможности делают MUMPS привлекательным и экономичным средством для создания большинства прикладных баз данных. Как язык программирования MUMPS содержит много средств для обра- ботки строковой информации (операторов языка и внутренних функций), используя которые Вы можете эффективно обрабатывать любую строковую информацию и текстовые массивы. В качестве основного метода записи данных используется технология иерархических и разреженных массивов. Массивы могут быть: как временно создаваемыми и хранящимися только в оперативной памяти ЭВМ, так и постоянными и храниться на дисках. Мас- сивы записываемые на диск, в MUMPS называются ГЛОБАЛЬНЫМИ МАССИВАМИ, или просто ГЛОБАЛЯМИ. ( В отличии от временных массивов, которые име- нуются ЛОКАЛЬНЫМИ МАССИВАМИ, или просто ЛОКАЛЯМИ) Именно использование глобальных массивов, как основных структур хранения данных, составляет основное отличие MUMPS от других языков программирования. И одновременно обеспечивает его эффективность при построении различных прикладных пакетов. ┌─────────────────────────────────────────────────────────────────┐ │ MUMPS использует массивы, автоматически адресуемые для записи │ │ на диск, как основную форму записи данных. Эти массивы называ- │ │ ются ГЛОБАЛЯМИ │ └─────────────────────────────────────────────────────────────────┘ При создании массивов /локальных и глобальных/ Вы можете динами- чески наращивать количество индексов /глубину индексации/ в масси- ве./*1/ В отличии от других языков, поддерживающих матричную форму ор- ганизации и хранения массивов данных, MUMPS использует древовидную структуру массивов, в которой каждый индекс может быть началом новой ветви массива. Все проходы вниз, к нижним узлам древовидной структуры /*2/, должны включать в себе перечень промежуточных узлов. Т.е. в до- полнение к тому, что это древовидная структура хранения информации, это и иерархическая структура. ┌────────────────────────────────────────────────┐ │ Глобали являются иерархическими структурами. │ └────────────────────────────────────────────────┘ ______________________________________________________________________ *1 - Вообще то ограничения на количество индексов существуют, и зави- сят от реализации и версии MUMPS. Например, для ISM 4.85 общая длина индексов не должна превышать 123 символов *2 - Из общей теории таких структур, узлы дерева, которые не имеют "потомков"-называются листьями. Узлы находящиеся "выше" (ближе к "кор- ню" дерева), называются "предками", ниже - "потомками". Узлы находящи- еся на одном уровне, и имеющие одного "предка" являются "братьями" Стр. 11 В дополнение, к уже перечисленным особенностям в организации MUMPS массивов, необходимо отметить, что индексами в этих массивах мо- гут быть не только числа, но и любые символьные строки. Например воз- можно создание узла массива SET ARRAY("Имя")="Петр Петров", где индекс "Имя" однозначно определяет в массиве ARRAY строку данных "Петр Пет- ров" ┌─────────────────────────────────────────────────────────────────┐ │ Индексы в массивах могут быть числовыми, символьными, а также │ │ сочетанием из чисел с любыми символами. │ └─────────────────────────────────────────────────────────────────┘ ГЛОБАЛИ являются разреженными массивами и занимаемое ими прост- ранство на диске определяется только размером данных, записываемых при индексах. Это обстоятельство повышает эффективность записи данных, особенно, когда те носят нерегулярный характер. Предположим, Вы желае- те записать информацию о визитах пациента в клинику. Причем, визит мо- жет быть совершен в произвольное время, с ним может быть связано про- извольное количество назначений, диагнозов и т.д. В этом случае Вам нет необходимости резервировать место для всех возможных визитов и назначений. Достаточно связывать каждый из них со своим индексом, при- чем нет необходимости производить перезапись массива при добавлении индекса между уже существующими. MUMPS система будет сама динамически увеличивать или уменьшать размер ГЛОБАЛИ при добавлении/удалении узла из структуры. ┌─────────────────────────────────────────────────────────────────┐ │ ГЛОБАЛИ являются разреженными массивами, и только размер данных│ │ при индексах массива определяет занимаемый объем диска. │ └─────────────────────────────────────────────────────────────────┘ Многие из MUMPS приложений допускают многопользовательский режим работы. В таких многопользовательских /и многозадачных/ системах ГЛО- БАЛИ могут быть доступны всем пользователям одновременно. Другими сло- вами различные пользователи могут иметь доступ к одним и тем же данным одновременно. ┌─────────────────────────────────────────────────────────────────┐ │ ГЛОБАЛИ могут быть распределены одновременно между многими │ │ пользователями. │ └─────────────────────────────────────────────────────────────────┘ MUMPS имеет в себе также много атрибутов, присущих операционным системам. Так некоторые версии, запускаемые на мини-компьютерах (СМ ЭВМ и им подобные), включают в себя тесты аппаратного обеспечения, осуществляют управление вводом/выводом на устройства, исполнением за- даний, а также содержат в себе и другие черты, которые обычно присущи только операционным системам. Имеются также реализации MUMPS, которые запускаются как задания в другой операционной системе, (например на ПЭВМ под MS DOS), но даже и в этом случае MUMPS управляет функциями присущими операционной системе - такими как управление заданиями и распределение дискового пространства при записи массивов. Стр. 12 MUMPS является интерпретирующим языком. Первоначально все его версии были полными интерпретаторами. При исполнении любой программы производилась полная интерпретация MUMPS кода в машинные команды. В настоящее время появились версии, которые производят частичную компи- ляцию исходного кода при записи программы на диск. Правда, в отличии от "полных" компиляторов, (таких как в Си или FORTANе) производимый после компиляции код не может запускаться непосредственно, но интерп- ретация при использовании компилированных программ идет гораздо быст- рее. /*1/ ┌────────────────────────────────────────────┐ │ MUMPS - это язык интерпретирующего типа. │ └────────────────────────────────────────────┘ Программист может запустить на исполнение как команду прямо с клавиатуры, так и вызвать для исполнения программу с диска. Исполнение команды/программы/, может быть в любой момент прервано, для того, что- бы просмотреть /или изменить/ содержимое переменных и массивов. Затем исполнение может быть продолжено. ┌────────────────────────────────────────────────────────────────┐ │ MUMPS команды могут запускаться на исполнение прямо с клавиату-│ │ ры или записываться в виде программ на диск. │ └────────────────────────────────────────────────────────────────┘ И наконец, в отличии от других языков программирования, в MUMPS нет необходимости объявлять тип переменных или резервировать место для создаваемых локальных массивов. Большинство языков обычно требуют пе- ред использованием любой переменной идентифицировать ее, указав ее тип ( целая, строковая, логическая и т.д..), а для массивов дополнительно указывать тип данных, размещаемых в нем. В MUMPS, если при исполнении программы встречается оператор создания переменной (SET A=22, напри- мер), то интерпретатор анализирует, существует ли такая переменная, или нет. Если ее нет, она создается, и в нее записывается указанное значение, если же она определена, то в нее записывается новое значе- ние. При этом нет необходимости указывать тип данных, заносимых в эту переменную. ┌───────────────────────────────────────────────────────────────┐ │ В MUMPS нет необходимости производить объявление типа исполь- │ │ зуемых переменных, или перечислять их. │ └───────────────────────────────────────────────────────────────┘ Любое значение может быть строкой или числом /целым или с плаваю- щей точкой/. MUMPS, при необходимости, автоматически и неявно преобра- зует эти формы из одной в другую. /из числовой в строковую и наобо- рот/. Управление преобразованием и вычисление результата производится при анализа контекста производимой операции. ___________________________________________________________________ *1 - такая компиляция производит в результате, так называемый "псев- до-код", который используется MUMPS системой, в отличии от генерируе- мого "полным" компилятором "исполняемого" кода, который может быть за- пущен под управлением операционной системы /например MS DOS/ и при этом не требует запуска дополнительного программного обеспечения Стр. 13 Например, мы можем выполнив следующие MUMPS команды, создать две переменные, A и В: ┌────────────────────────────────────────────────────────────────┐ │ о| >SET A="500 км. "<- |o │ │ | >SET B="25 литров "<- | │ │ | > | │ └────────────────────────────────────────────────────────────────┘ Рис. 1.1 Создание двух переменных В этом примере, и в дальнейшем, символ '>' в начале каждой строки обозначает стандартную подсказку MUMPS системы, означающую, что она ждет ввода с клавиатуры. Как уже оговаривалось ранее, символ '<-' оз- начает, что в этом месте в процессе ввода необходимо нажать клавишу "Enter". Когда эти команды будут исполняться, MUMPS зарезервирует пространство для переменных А и В и свяжет с ними строки "500 км. " и "25 литров". Посмотрите, что произойдет, когда мы произведем операцию вычисления с использованием значений этих переменных: ┌────────────────────────────────────────────────────────────────┐ │ о| >WRITE А/В<- |o │ │ | 20 | │ │ | > | │ └────────────────────────────────────────────────────────────────┘ Рис.1.2 Неявное пробразование строковых значений в числовые В этом примере, MUMPS должен вывести результат деления значения переменной А на значение переменной В. Несмотря на то, что оба значе- ния являются строками, MUMPS оценивает их как числа /слева направо, до тех пор, пока не встречается символ не являющийся числом, или десятич- ной точкой - после этого оценивание прекращается/, так как требуемая операция - арифметическая. В нашем следующем примере, мы используем те же значения переменных, но хотим получить результат операции конкате- нации над содержимым переменных /операция конкатенации, или сцепления строк представляется символом подчеркивания '_' / Оператор конкатена- ции указывает MUMPS прибавить символы строки, стоящей с ПРАВОЙ стороны оператора, к символам строки стоящей СЛЕВА от оператора. ┌────────────────────────────────────────────────────────────────┐ │ о| >WRITE A_B<- |o │ │ | 500 км. 25 литров | │ │ | > | │ └────────────────────────────────────────────────────────────────┘ Рис. 1.3 Результат слияния /конкатенации/ строк Эти примеры иллюстрируют то, что MUMPS всегда интерпретирует зна- чения в контексте совершаемых над ними операций. Арифметические опера- ции приводят к числовой интерпретации, и наоборот, строковые операции совершают строковую интерпретацию над одинаковыми элементами данных. ┌──────────────────────────────────────────────────────────────┐ │ MUMPS интерпретирует типы данных в контексте совершаемых │ │ над ними операций. │ └──────────────────────────────────────────────────────────────┘ Стр. 14 1.3 Краткая история MUMPS Как уже упоминалось, MUMPS был разработан в 1967 году компьютер- ным центром Массачусетского Общего госпиталя. Основной целью его соз- дания была потребность в новой компьютерной системе, которая обладала бы возможностью использования распределенных файлов и включала в себя большое количество функций, и других элементов, ориентированных на об- работку строковой текстовой информации, столь характерной для меди- цинских приложений. Кроме того, для снижения общих затрат, необходимо было осуществить переход на работу с мини-компьютерами, вместо доро- гостоящих больших ЭВМ. Такая специфическая особенность медицинской ин- формации, как ее нерегулярность /например пациент может иметь произ- вольное количество визитов, анализов, назначений и т.д./, заставила разрабатывать систему с наиболее эффективным способом записи и доступа к разреженным информационным структурам. Все эти условия обусловили основные черты первых реализаций MUMPS. В последствии, в связи с успешным распространением MUMPS приложе- ний, стало расти число версий MUMPS, адаптированных к широкому диапа- зону ЭВМ, от персональных ЭВМ до больших машин. Уже в 1972 году было зарегистрировано свыше 14 версий и их число продолжает нарастать с каждым годом. 1.3.1 Комитет развития MUMPS (MDC - MUMPS Development Committee) Комитет по развитию MUMPS был учрежден в 1972 году, для подготов- ки стандарта языка MUMPS и представлению его в Американский Националь- ный Институт Стандартизации /ANSI - American National Standarts Insti- tute/. Первоначальные работы по подготовке стандарта финансировались Национальным Бюро Стандартов и Национальным Исследовательским Центром Охраны Здоровья /США/. Работы по формальной стандартизации MUMPS были закончены в 1975 году, и были представлены в ANSI. Официальное свиде- тельство с положительным решением было получено в 1977 году. С тех пор, Комитет развития MUMPS остается активной организацией, ведущей деятельность по оценке новых предложений по развитию языка, с целью включения их в новые редакции стандарта языка. Все поступающие предложения проходят многоступенчатую проверку, прежде чем поднимается вопрос о их включении в стандарт. Вкратце это можно представить следу- ющим образом. Все предложения делятся на три группы: 1.3.1.1 Предложения группы С Комитет по развитию MUMPS встречается дважды в год с целью обсуж- дения новых предложений. Вновь поступающие предложения подвергаются открытому обсуждению, и, если они получают большинство голосов, им присваивается статус С. Присвоение этого статуса означает только то, что предложение признано заслуживающим внимания и его стоит рассматри- вать в дальнейшем. 1.3.1.2 Предложения группы В Следующим шагом является переход предложений из группы С в группу В. Это происходит только тогда, когда большинство членов комитета сог- лашаются с тем, что данное предложение может улучшить функциональные характеристики языка и заслуживает проверки в экспериментальном поряд- ке. Стр. 15 1.3.1.3 Предложения группы А После прохождения тестирования в нескольких реализациях, предло- жения переводятся в группу А. Эта группа включает в себя все желатель- ные изменения и дополнения к СТАНДАРТУ ЯЗЫКА которые будут внесены в него в ближайшие 4-5 лет. Если в процессе тестирования предложения возникали некоторые затруднения, то это предложение будет оставаться в группе А до тех пор, пока проблемы, связанные с его реализацией не бу- дут решены. При этом, даже может быть понижен статус предложения, вплоть до перевода в группу С, или оно вообще будет исключено из расс- мотрения. Почти все разработчики версий MUMPS, включают в свои реализации предложения из группы А, и "де-факто" стандарт языка становится теку- щим стандартом плюс все предложения группы А. В данной книге, вся ин- формация, связанная с встречающимися в реализациях предложениями груп- пы А обудет отмечена особо, так как некоторые из версий в точности со- ответствуют стандарту и не включают их. /*1/ 1.3.1.4 Стандарт MUMPS Последним этапом движения предложений по изменению стандарта, яв- ляется представление их в ANSI, от лица сообщества пользователей MUMPS и других заинтересованных организаций. Заключительное решение о вклю- чении предложения из группы А в новую редакцию стандарта принимается с внимательным рассмотрением аргументов как за, так и против. И если ко- митет по развитию языка не находит убедительных аргументов за включе- ние предложения, в ответ на любой из возникающих вопросов, то предло- жение НЕ включается в новый стандарт. Стандарт языка MUMPS представляет собой минимальный набор требо- ваний, которым должны придерживаться все разработчики новых версий. Он содержит описание всех элементов языка, но не касается конкретных ас- пектов реализации, таких, как например, ограничение размера программ или пространства для локальных переменных, а также аспектов совмести- мости между различными версиями MUMPS. Все эти требования отражаются в другом документе подготавливаемом комитетом по развитию MUMPS - Стандарте Переносимости /Совместимости/. Этот стандарт содержит в себе как минимальные требования для разработ- чиков, так и максимальные для совместимых приложений. Все прикладные пакеты, написанные в соответствии со стандартом, должны работать во всех реализациях MUMPS соответствующих этому стандарту. Следует отме- тить, что не все разработчики следуют стандарту совместимости, руко- водствуясь только стандартом языка, поступая так в собственных интере- сах, но все текущие версии разрабатывались с использованием этого до- кумента. В процессе изложения материала этой книги я попытаюсь пока- зать разницу между ограничениями, накладываемыми стандартом языка и требованиями стандарта совместимости. _____________________________________________________________________ *1 Необходимо учесть, что книга написана в 1987 году. Стр. 16 1.3.2 Объединение пользователей MUMPS (MUG - MUMPS User's Group) Лучший источник информации о языке MUMPS - это Объединение поль- зователей MUMPS (MUG). MUG - международная некоммерческая организация, ставящая своей целью развитие и распространение языка MUMPS различными средствами: в периодических публикациях, проводя ежегодные встречи, организуя обучающие семинары и консультации, распространяя разнообраз- ные библиотеки утилит и прикладные пакеты. ┌─────────────────────────────────┐ │ │ │ MUMPS User's Group │ │ 4321 Hartwick Road, Suite 510 │ │ College Park, Maryland 20740 │ │ (301) 779-6555 │ │ │ └─────────────────────────────────┘ MUG обеспечивает хранение и накопление всей информации о текущих реализациях MUMPS, включая информацию об аппаратном обеспечении в ко- тором эксплуатируются версии и об операционных системах поддерживающих MUMPS. Общество занимается также распространением основной документа- ции по MUMPS, для членов MUG по сниженным ценам. Все публикации, упо- мянутые в приложении В, доступны через MUG. Главный комитет MUG координирует усилия международных групп под- держки MUMPS, в том числе обществ Европы, Японии и Бразилии. 1.3.3 Область применения MUMPS /в аппаратном обеспечении/ Как уже упоминалось, созданы версии MUMPS для широкого спектра ЭВМ. В настоящее время широко используются версии работающие под MS-DOS на ПЭВМ, почти на всем семействе машин PDP-11 /фирмы Degital Equipment Corparation -DEC, "девичья" фамилия СМ-ЭВМ/, на компьютерах VAX и больших системах подобных семейству IBM 370. Более подробную ин- формацию об аспектах работы MUMPS на различном аппаратном обеспечении можно получить через Объединение пользователей MUMPS /MUG/. 1.4 Начало работы с MUMPS Этот раздел содержит информацию необходимую для регистрации в MUMPS, ввода и исполнения команд в непосредственном режиме /с клавиа- туры/, а также по созданию и редактированию программ. Он предназначен в основном для тех, кто впервые сталкивается с MUMPS. Ранее я упоминал о том, что желательно, в целях лучшего усвоения предлагаемых концеп- ций, сочетать чтение книги с исполнением примеров включенном в MUMPS систему терминале. Итак, представим Вам минимально необходимые пояснения о том, как зарегистрироваться в системе, как исполнять команды в непосредственном режиме, как вводить программы, как запускать на исполнение программы введенные ранее. Стр. 17 1.4.1 Регистрация в MUMPS Вход в систему может исполняться по разному, это зависит от мно- гих различий в аппаратном обеспечении, на которых эксплуатируются MUMPS системы, а также от особенностей, связанных с конкретными реали- зациями MUMPS. Все возможные реализации, с которыми Вы можете столк- нуться, в общем случае можно разделить на два больших класса: 1 - Монопольные системы, где MUMPS является единственным заданием и захватывает все ресурсы машины. /*1/ 2 - Не-монопольные системы, которые запускаются, как один из прикладных пакетов, в среде другой операционной системы /*2/ Когда Вы регистрируетесь на монопольном MUMPS компьютере, то не- обходимо выполнить следующие операции: 1. После включения терминала обратиться к системе. Это совершается обычно нажатием на клавиатуре клавиши , или (эта клавиша может быть также помечена как , а также или на русифицированных терминалах). Операционная система MUMPS ответит выводом своего идентификационного сообщения и приглашением к регистрации. /содержащего сведения о версии, и, очень часто, в нем указывается внутренний номер терминала с которого Вы регистри- руетесь. *3/ 2. После этого необходимо указать пароль или код доступа в систему. Это обеспечивается специальной программой администрирования доступа или общей внутрисистемной процедурой регистрации, которая входит в большинство подобных систем. В большинстве случаев, Вам необходимо знать для регистрации и правильно ввести UCI и PAC /User Class Identifier-UCI, Programmer Access Code -PAC или пароль *4/ Перед выполнением этого шага узнайте доступный КИП /UCI/ и код доступа у системного программиста. Введите КИП и код доступа после по- явления идентификационного сообщения. Если ввод был осуществлен непра- вильно, Вы увидите реплику системы по этому поводу и приглашение к ре- гистрации. Иногда, вместе с КИП Вам необходимо будет набрать и имя то- ма данных, к которому осуществляется доступ. Узнайте это у системного программиста. /*5/ _____________________________________________________________________ 1* Например СМ ЭВМ, на которой запускается DSM-11 /Диамс 3.0 / 2* Нап- ример IBMPC, на которой под управлением MS DOS запускается одна из ре- ализаций MUMPS. 3* При регистрации на СМ ЭВМ работающей под управлением DSM-11, для того, чтобы обратиться к системе необходимо нажать либо +, либо . Идентификационное сообщение будет выглядеть следующим образом: ДИАМС 3.0 ЛХ ТЕРМИНАЛ #72 КИП,НАБ:КОД> 4* В установившейся практике эти термины переводятся как Код Идентифи- кации Пользователя или КИП, и Код Доступа Программиста - КДП. 5* Для регистрации в DSM-11, в общем случае надо набрать: КИП,ИМЯ_ _НАБОРА_ТОМОВ:КОД ДОСТУПА Например: ДИАМС 3.0 ЛХ ТЕРМИНАЛ #72 КИП,НАБ:КОД> DOP,DOP:SYS Cтр. 18 Если все сделано правильно и код доступа корректен, система MUMPS выведет специальный символ, обозначающий, что система ожидает ввода команд. Этим символом обычно служит '>', но в различных реализациях могут встретиться и другие символы, или в дополнение к этому символу будет выведены, например имя КИП и т.д. и т.п.. /Слэнговое обозначение - "подсказка системы"/. Если Вы увидели этот символ, Вы можете перехо- дить к разделу 1.4.2., если нет, попросите кого-нибудь помочь. Технология запуска и регистрации в MUMPS системе на компьютерах общего назначения, в общих чертах, подобна регистрации на машине, на которой запущена монопольная MUMPS система. За исключением части опе- раций, выполняемых перед запуском MUMPS в среде операционной системы, под управлением которой функционирует эта машина. Как правило, Вам не- обходимо перейти в каталог, где размещается MUMPS, и инициализировать его запуск. Для того, чтобы упростить этот процесс, посоветуйтесь с системным программистом. /*1/ После инициализации MUMPS, процедура ре- гистрации должна быть подобна указанной выше. 1.4.2 Ввод команд MUMPS с клавиатуры MUMPS позволяет пользователю вводить команды с клавиатуры, и пос- ле завершения ввода немедленно интерпретирует и исполняет их. Команды, вводимые подобным образом, часто называются командами непосредственно- го режима, а режим работы с выполнением таких команд именуется непос- редственным режимом работы MUMPS системы. Когда на экране появилась "подсказка системы" Вы можете ввести любую строку состоящую из MUMPS команд для исполнения. Все то, что Вы вводите не оценивается и не интерпретируется до тех пор, пока Вы не нажмете клавишу . До того, как Вы нажмете эту клавишу Вы можете исправить допущенные ошибки, используя клавишу /Забой/ и повторить ввод. Нажатие указывает системе, что ввод окончен, и она начинает разбирать и оценивать ее приступая к исполнению. Макси- мально возможная длина строки /согласно требованиям Стандарта Совмес- тимости/ равна 255 символов. На практике, командная строка непосредс- твенного режима, обычно короче. В случае ошибки необходимо повторить ввод всей строки. ┌───────────────────────────────────────────────────────────────┐ │ Командная строка в MUMPS может быть длиной до 255 символов. │ └───────────────────────────────────────────────────────────────┘ Когда MUMPS обнаруживает ошибку в командной строке /например син- таксическую ошибку/, он прерывает оценивание и исполнение строки, вы- водит сообщение об ошибке и вновь выводит подсказку системы. Сообщения об ошибках для каждой из версии MUMPS индивидуальны. Используйте руко- водство пользователя для понимания сущности ошибки. _____________________________________________________________________ 1* Например для запуска DataTreeMUMPS под управлением MS DOS на IBM PC необходимо: -перейти в каталог где размещены модули системы -набрать на подсказке DOS команду: >MUMPS Стр. 19 1.4.3 Ввод и редактирование программ В дополнение к возможности ввода и редактирования командной стро- ки непосредственно с клавиатуры, многие из версий поддерживают возмож- ность ввода с клавиатуры командных строк в виде программ для последую- щей записи на диск или исполнения. Такие строки не исполняются немед- ленно по завершению ввода. К сожалению, несмотря на то, что в MUMPS создан богатый и хорошо развитый язык программирования, в нем не представлено стандартных средств по вводу, редактированию, загрузке или записи программ. Но с другой стороны, по неформальному соглашению, поддерживаемом большинс- твом разработчиков версий MUMPS, в каждой из версий создается и под- держивается одна или несколько версий редакторов, для ввода и редакти- рования программ. Особенности работы этих редакторов должны быть отра- жены в руководстве пользователя. Стандарт языка MUMPS допускает создание дополнительных команд оп- ределяемых разработчиком. Все эти команды должны начинаться с буквы 'Z', которая отличает их от основных команд. Среди таких команд в раз- личных реализациях поддерживаются команды для загрузки программы во временную рабочую область /раздел/, вставке в нее новых строк, удале- нию из программы части строк и сохранению /записи/ программы на диск. В соответствии с исполняемыми функциями, эти команды и называются со- ответственно: ZLOAD, ZINSERT, ZREMOVE и ZSAVE. Более подробно эти ко- манды обсуждаются в главе 3. Но в связи с тем, что от версии к версии существуют различные ню- ансы по использованию этих 'Z' команд и соответственно по загрузке, редактированию и сохранению, с их помощью, программ, то мне хочется посоветовать пользователю не пожалеть времени на освоение "штатного" редактора программ, поддерживаемого в данной реализации. Как правило, работа в таких редакторах гораздо проще, чем непосредственное исполь- зование этих 'Z' команд. В этой книге примеры программ /и их фрагменты/ будут представ- ляться в форме, немного отличающейся от формы представления команд, введенных в непосредственном режиме с клавиатуры. /См. Рис. 1.1/ Прог- раммы, и все сообщения выводимые ими, будут представляться следующим образом: ┌─────────────────────────────────────────────────────────────────┐ │ о|Start Write !," Пример MUMPS программы" |o │ │ | Write !," Дважды три = ",2*3 | │ │ о| |o │ │ о|__________________________________________________________|o │ │ |>DO Start<- | │ │ | Пример MUMPS программы | │ │ | Дважды три = 6 | │ │ о|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 1.5. Пример формата программ В этом примере выше горизонтальной сплошной линии представлен текст программы, ниже нее запуск этой программы в непосредственном ре- жиме и выводимые ее сообщения. Стр. 20 Мы постараемся ограничить наши первые примеры относительно прос- тыми и понятными командами языка. Команда WRITE во всех предыдущих примерах указывает компьютеру на необходимость вывести на экран строку символов /Например - "Пример MUMPS программы"/, или результат вычисле- ния /Например - результат умножения 2*3=6 /. Восклицательный знак свя- занный с командой WRITE указывает на необходимость осуществления выво- да в начало следующей строки /Write ! - т.е. осуществляется операция - перевод строки/. В отличии от команд непосредственного режима вводимых с клавиату- ры строка программы должна начинаться либо с метки отделенной символом табуляции от самой строки, либо с символа табуляции /Или соответствую- щего табуляции числа пробелов - при работе в некоторых редакторах программ/. Метка может содержать от одного до 8 символов /цифр и/или букв/. MUMPS различает большие и маленькие буквы, поэтому START и Start это различные метки. Оператор DO указывает на запуск на исполнение программы и требует аргумента, которым должно быть имя метки с которой начинается програм- ма. Соответственно, первая строка программы должна содержать эту мет- ку, так, как это показано на примере. Для того, чтобы сохранить созданную программу на диске, ей необ- ходимо назначить имя. Подобно имени метки, имя программы может содер- жать от одной до 8 букв и/или цифр /причем первой должна быть обяза- тельно буква/. Также как и для меток, в имени программ MUMPS различает большие и маленькие буквы, и поэтому имена MYPROG и Myprog представля- ют собой разные имена программ. 1.4.4 Загрузка программ с диска Программы сохраненные на диск после редактирования могут быть загружены обратно в рабочую область /раздел/ командой ZLOAD. Эта ко- манда должна иметь в качестве аргумента имя программы, которую Вы хо- тите загрузить. Перед загрузкой программы MUMPS автоматически очищает рабочую область от ранее существующих в ней программных строк. /Без их сохранения на диск/ ┌─────────────────────────────────────────────────────────────────┐ │ о|>ZLOAD MYPROG<- |o │ │ |>DO Start | │ │ | Пример MUMPS программы | │ │ | Дважды три = 6 | │ │ о|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 1.6 Загрузка программы с диска Стр. 21 1.4.5 Исполнение программы Как показано в примере 1.6 Вы можете запустить на исполнение программу, предварительно загруженную командой ZLOAD, исполнив команду DO (DO Start). Но в тоже время, Вы можете запустить программу на ис- полнение прямо с диска, указав в команде DO имя программы вместо мет- ки. ┌─────────────────────────────────────────────────────────────────┐ │ |>DO ^MYPROG<- | │ │ | Пример MUMPS программы | │ │ | Дважды три = 6 | │ │ о|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 1.7 Исполнение программы записанной на диск Обратим Ваше внимание на то, что имени программы предшествует символ '^'. Этот символ сообщает MUMPS системе, что указанная в аргу- менте команды DO программа находится на диске. Эта программа загружа- ется в рабочую область и начинает исполняться с первой строки. 1.4.6 Завершение работы в системе. Для того, чтобы закончить Вашу работу в MUMPS /и освободить раз- дел и терминал/, введите команду 'HALT', которую, впрочем можно сокра- тить и до одной буквы - 'H', в непосредственном режиме ввода команд на подсказке системы. ┌──────────────────────────────────────────────────────────────┐ │o|>HALT<- |o│ │ | | │ │ |EXIT - End of MUMPS session | │ └──────────────────────────────────────────────────────────────┘ Пример 1.8 Окончание работы в MUMPS Если Вы работаете на компьютерах общего назначения, выход из MUMPS системы может быть несколько иным, справьтесь у системного прог- раммиста, или в руководстве по системе. /*1/ ┌──────────────────────────────────────────┐ │ Команда HALT завершает работу с MUMPS. │ └──────────────────────────────────────────┘ __________________________________________________________________ *1 Для выхода из не-монопольной MUMPS системы, запускаемиой на компь- тере общего назначения, перед выходом необходимо остановить все зада- ния, и только после этого осуществить выход из системы. В системе ISM /NTSM/ выход осуществляется по вводу команд: ZQ - в ISM 4.81-4.85 EXIT - в NTSM 5.1 и старше Для выхода, из системы DataTreeMUMPS v4.2+ необходимо запустить утили- ту %mhalt, - тем самым остановить все задания, с рассылкой сообщений всем пользователям системы, и затем ввести команду HALT Стр. 22 1.5 Общие положения - MUMPS использует динамически распределяемые на диске массивы, как основную форму хранения информации. Эти массивы именуются ГЛОБАЛЯМИ. /1.2/ - ГЛОБАЛИ являются иерархическими структурами /1.2/ - Индексами в массивах могут быть цифры, буквы, знаки и их соче- тания /1.2/ - ГЛОБАЛИ являются разреженными структурами, записываются только те узлы, которые ассоциируются с данными /1.2/ - ГЛОБАЛИ могут быть доступны одновременно нескольким пользовате- лям /1.2/ - MUMPS это язык интерпретирующего типа /1.2/ - Команды могут исполняться сразу после ввода, или записываться в виде программ, для последующего исполнения - Строки в MUMPS программах могут быть длиной до 255 символов /1.4.2/ - Команда 'HALT' завершает работу в MUMPS системе /1.4.6/ Cтр. 23 Глава 2 Общее описание среды MUMPS Эта глава описывает общие особенности MUMPS систем, включая опи- сание его интерпретатора, концепцию дисковых областей /директорий/, рабочих разделов, именованных областей и заданий. Программисты, имею- щие представление об этих понятиях могут опустить эту главу при чте- нии. MUMPS это больше, чем просто один из языков программирования, он также охватывает многие области более присущие обычно операционным системам общего назначения, которые обеспечивают функционирование и разработку прикладных программ. Кроме просто интерпретатора языка, ему присущи функции управления базами данных, и кроме того обеспечение хо- рошего интерфейса с устройствами ввода/вывода. В дополнение к этому, большинство из версий MUMPS допускают работу в многопользовательских и многозадачных режимах с использованием принципов разделения времени между заданиями. В этом случае MUMPS создает очереди на обработку за- пущенных заданий, /в некоторых версиях назначение приоритета заданий отсутствует, и каждое из заданий обрабатывается в течении одинакового кванта времени последовательно друг за другом/, обеспечивает связь между ними и управление конкурирующими процессами. Некоторые версии MUMPS запускаются под управлением других опера- ционных систем, таких как VM, VMS или MS-DOS, но программист работаю- щий в MUMPS, в большинстве случаев, может игнорировать этот факт, пос- кольку система MUMPS обеспечивает пользователю управление над всеми MUMPS процессами и ресурсами компьютера на котором они запускаются. 2.1 Интерпретатор. MUMPS во всех своих версиях содержит в себе интерпретатор, и он всегда оценивает элементы командной строки перед ее исполнением. Этим он отличается от большинства других языков программирования, которые трансформируют исходный текст программ в инструкции машинного кода. Этот шаг /обычно именуемый трансляцией или компиляцией исходного кода/ обязателен перед запуском программ на исполнение. В этом плане интерп- ретирующие языки, к которым относится и MUMPS, имеют некоторые преиму- щества. В связи с тем, что команды декодируются в язык машинных инс- трукций непосредственно перед исполнением, кроме возможности запуска существующих программ, Вам предоставляется возможность задавать коман- ды в непосредственном режиме. Непосредственный режим ввода команд не требует, как в случае работы с компилирующим языком, необходимости на- писания программы, ее компиляции и только после этого, запуска на ис- полнение. В непосредственном режиме Вы легко можете проверить любую из языковых конструкций, с помощью отладчика провести отладку программ и их процедур, просмотреть и изменить в любой момент содержание локаль- ных и глобальных переменных. Нельзя не отметить в тоже время и недостатки самого принципа ин- терпретирующих языков, главный из которых состоит в снижении скорости исполнения, в связи с необходимостью оценивания команд разбора команд- ной строки. Cтр. 24 Для уменьшения снижения скорости исполнения в некоторых версиях MUMPS предпринимается частичная компиляция исходного текста MUMPS ко- да. При этом не производится замена исходного кода в ассемблерные инс- трукции. Генерируемый код является промежуточным звеном между машинны- ми инструкциями и исходным текстом программ, и потому при исполнении все равно необходима интерпретация этого "скомпилированного" кода MUMPS программ. Но ! При этом уже не требуются многие операции необхо- димые при полной разборке и оценивании строки /проверка синтаксиса, определение адресов и имен перехода управления и т.д../, поэтому ис- полнение таких "скомпилированных" программ идет много быстрее. В неко- торых версиях такая компиляция производится не зависимо от программис- та при сохранении программ на диск, некоторые допускают эту возмож- ность при необходимости ускорить исполнение уже отлаженных программ. Но в любом случае в непосредственном режиме Вы можете ввести команду на исполнение, которая будет немедленно, по завершению ввода, оцени- ваться и исполняться. 2.2. Концепция директориев и разделов Этот раздел описывает часть основных характеристик MUMPS, имеющих особое значение в системах с управлением несколькими одновременными процессами. Мы попытаемся исследовать понятия разделов для заданий, именованных областей, директориев и построить общую логическую модель среды MUMPS. В различных версиях MUMPS могут быть различные ограниче- ния на основные характеристики, потому при рассмотрении лимитирующих ограничений мы будем ссылаться на наименьшие из величин. Общая модель MUMPS среды представлена на Рис.2.1. ┌──────────────────────────────────────────────────────────────┐ │ Операционная система общего назначения /ОС/ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Задание 1 под управлением ОС │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ Задание 2 под управлением ОС │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ Задание 3 - Многопользовательский MUMPS │ │ │ │ │ │ │ │ ┌───────────────────────────────┐ │ │ │ │ │ MUMPS процесс 1 │ │ │ │ │ ├───────────────────────────────┤ │ │ │ │ │ MUMPS процесс 2 │ │ │ │ │ ├───────────────────────────────┤ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────┘ │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ Задание 4 под управлением ОС │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ │ │ │ ├─────────────────────────────────────────────────────────┤ │ │ │ Задание nn под управлением ОС │ │ │ └─────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ Рис. 2.1 Модель MUMPS системы Cтр. 25 Операционная система представляет собой контролирующий и управля- ющий ресурсами машины комплекс программ, подобный VM, VMS или MS-DOS, и обеспечивающий независимый запуск прикладных программ, связанных обычно с различными пользователями компьютера. Операционная система должна распределять время отводимое на исполнение каждого из заданий, а также представляет различным заданиям возможность обращаться к общим ресурсам системы /терминалам, дискам, магнитной ленте и т.д./, разре- шая конфликтные ситуации между ними. Задания обращаются к устройствам не напрямую, а через операционную систему. На этой модели MUMPS представлен как одно из заданий запущенных под управлением ОС на машине. Он вынужден конкурировать с другими за- даниями за ресурсы машины /время исполнения, устройства и т.д./. Но с другой стороны, MUMPS сам управляет, полностью независимо от внешней операционной системы своим заданиями /процессами/. Каждое из заданий /процессов/ является полным аналогом задания запущенного под управле- нием внешней операционной системы, но они функционируют прежде всего под управлением MUMPS, чем внешней ОС. Обычно каждое из заданий есть независимый процесс связанный с каким-либо из конечных пользователей. В некоторых реализациях MUMPS запускается без участия какой-либо из операционных систем. В этом случае на рассматриваемой модели отбра- сываются все остальные уровни управляемые операционной системой, поми- мо MUMPS. MUMPS в таком случае обеспечивает все операции, обычно отво- димые операционной системе. 2.2.1 Разделы Каждому процессу в MUMPS назначается раздел, или рабочая область для функционирования /См. рис. 2.2 / Этот раздел содержит все необхо- димые данные для функционирования отдельного MUMPS процесса /задания/, включая пространство для размещения программы, для локальных перемен- ных и пространство используемое системой. Размер раздела в MUMPS, как правило, величина порядка 10 000 байт. Раздел делится на область фик- сированного раздела используемую системой для записи информации свя- занной с заданием и две других области - для хранения программы и для записи локальных переменных. Размер каждой из этих областей зависит от размера другой. Если Ваша программа большая по размеру, меньше места в разделе для хранения локальных переменных, и наоборот. Когда процессом создаются новые локальные переменные, они добав- ляются в область хранения локальных переменных, именуемую обычно как ТАБЛИЦА ЛОКАЛЬНЫХ СИМВОЛОВ, или просто таблица символов. Когда Вы вы- зываете программу, или производите ее загрузку с диска, оно переписы- вается в раздел для исполнения. /На самом деле сам интерпретатор MUMPS находится вне раздела и обслуживает все задания./ Представление модели раздела, как она представлена на рис 2.2, характерно для большинства реализаций, но может послужить и источником некоторой путаницы. Стандарт Совместимости предложенный комитетом раз- вития MUMPS определяет пространства для переменных и для программ нес- колько иных терминах. Подобные термины пока отнесены к группе предло- жений В. Почти все современные реализации излишне придерживаются опре- делений текущего стандарта совместимости. Стр. 26 Для ограничений размера пространств для локальных переменных и программ существуют разные величины, порядка, примерно 5 000 байт. /1 байт эквивалентен 1 символу/. ┌────────────────────────────────────────┐ │ Пространство для локальных переменных │ │ /Таблица локальных символов/ │ │ | │ │ | │ │ V │ │ ----------------------------------- │ │ ^ │ │ | │ │ | │ │ Пространство для MUMPS программ │ └────────────────────────────────────────┘ Рис. 2.2 Модель MUMPS раздела Т.е. программа может содержать порядка 5 000 байт исходного кода и может быть использовано до 5 000 байт под локальные переменные во всех реализациях MUMPS. /В каждой из реализаций есть свои ограничения на эти величины, и как правило они позволяют использовать больше пространства, но если Вы будете следовать указанным ограничениям ни- когда не ошибетесь/ По определению, пространство резервируемое для пе- ременных и область программ занимают различные области памяти и не мо- гут смешиваться. ┌─────────────────────────────────────────────────────────────────┐ │ Отдельная MUMPS программа может быть размером до 5 000 байт. │ └─────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────┐ │ Под создание локальных переменных отводится около 5 000 байт. │ └─────────────────────────────────────────────────────────────────┘ Конечно, ограничение в 5 К исходного текста отдельной программы, устанавливаемое Стандартом Совместимости, не означает что Вы не сможе- те создать прикладную задачу большего объема. Любой прикладной пакет в MUMPS состоит из большого количества программ, вызываемых другими программами. При вызове из одной программы другой на исполнение, в оп- ределенной части раздела сохраняется информация о вызывающей програм- ме, и той точке в ней, откуда был произведен вызов. Вызываемая прог- рамма загружается с диска в раздел и начинает исполняться. После того, как она завершится, с диска будет переписана та программа, которая осуществила ее вызов, и ее исполнение продолжится со следующего опера- тора за тем, которым был осуществлен вызов программы. Это конечно, очень приблизительное описание процесса, но дающее тем не менее предс- тавление о том, как осуществляется перевызов программ. Таким образом, прикладные пакеты, могут содержать, вполне возможно и тысячи программ, но размер каждой из них не может превышать предела в 5 К. Стр. 27 Во многих других языках программирования, существует необходи- мость связывать между собой все программные модули перед их запуском. Когда, в таком случае, вызывается такой пакет, то он должен полностью загружаться в память. Конечно, загрузка в память всех связанных между собой процедур прикладного пакета позволяет ему исполняться очень быстро, так нет необходимости загружать подпрограммы с диска в память, но требует большого объема оперативной памяти. Напротив, MUMPS с его принципом ПЕРЕЗАПИСИ программ ПОВЕРХ друг друга не требует много памя- ти для работы отдельной задачи, но при этом затрачивается дополнитель- ное время на операции чтения с диска как вызываемых программ, так и тех, которые осуществляли их вызов, после того, как вызываемая прог- рамм заканчивает свою работу. Для снижения времени требуемого на заг- рузку часто вызываемых программ, в большинстве реализаций используют принцип буферизации обращений к диску. В этом случае, информация про- читанная с диска засылается также и в некую буферную область. В после- дующих запросах на чтение с диска первоначально производится поиск нужной информации в буферной области, и если она находится в буфере, она вновь загружается в раздел, без дополнительных обращений к диску. Обращаю Ваше внимание на то, что обсуждая аспекты перевызова программ, мы не упоминали об области сохранения локальных переменных /таблице символов/. Когда новая программа загружается в раздел "по- верх" прежней, а область локальных переменных не подвергается измене- ниям. В связи с этим вызываемая программа получает для работы все ок- ружение локальных переменных созданное вызывающей программой. Все соз- данные, и измененные программой переменные останутся в разделе и при возврате управления обратно к вызывающей программе. В этом аспекте MUMPS тоже сильно отличается от других языков программирования. Есть, конечно, возможности назначения вызываемой программе индивидуальной таблицы символов, /См. главу 9, описывающую передачу параметров и опе- ратор NEW/, но в общем случае, все программы, работающие в одном раз- деле используют общую область локальных переменных. ┌─────────────────────────────────────────────────────────────────┐ │ Область для хранения локальных переменных /таблица символов/, │ │ общая для всех программ вызываемых в одном разделе /задании/ │ └─────────────────────────────────────────────────────────────────┘ 2.2.2 Директории В MUMPS любой процесс или задание /связанный со своим разделом/, запускается, и работает независимо от других заданий /процессов/. Ко- нечно, существуют методы осуществления связи между заданиями, но если они не применены, то каждое из них работает так, как если было бы одно на компьютере /*1/. В дополнение к вышесказанному, большинство MUMPS систем позволяет создавать директории, для содержания дисковых файлов /программ и баз данных/. Директория используемая данным процессом, обычно определяется ей одновременно с выделением раздела. ____________________________________________________________________ *1 - Скорость работы конечно будет падать, чем больше заданий одновре- менно работают в системе. Стр. 28 Используя различные дисковые директории, в одной из них програм- мист может модифицировать копии программ, или базу данных, и в тоже время, в другой директории, прикладной пакет будет нормально работать, используя другую копию программ /или базы данных/. Директории могут также именоваться как КИПы, рабочие области и наборы данных /USIs, na- mespaces, datasets /, но вне зависимости от обозначающего термина, об- щий их смысл тот же. /*1/ ┌─────────────────────────────────────────────────────────────────┐ │ Каждая директория в MUMPS является уникальной, различные дире- │ │ ктории могут содержать различные версии программ и наборов да- │ │ нных с одинаковыми именами. │ └─────────────────────────────────────────────────────────────────┘ Обычно существует также и библиотечная директория /часто именуе- мая системной/, которая содержит библиотечные программы и наборы дан- ных системы MUMPS. Имена этих библиотечных программ и наборов данных, начинается со специального символа /знака '%'/, и все обращения из других директорий к таким именам адресуются системой в эту директорию. Программы и массивы с именами начинающимися с '%' обычно разрешено размещать только в библиотечной /системной/ директории, но при этом они доступны из всех прочих директориев. 2.2.3 Именованные области: локальные и глобальные Как мы уже упоминали, MUMPS использует временные или локальные переменные, и когда встречается в программе имя локальной переменной, то в таблице символов раздела производится поиск этой переменной. Ло- кальные переменные доступны только процессу запущенному в этом разде- ле, и как только процесс завершается и покидает раздел, все значения локальных переменных связанные с этим процессом теряются. ┌───────────────────────────────────────────────────────────────┐ │ Локальные переменные являются временными. Как только процесс │ │ заканчивается, они удаляются. │ └───────────────────────────────────────────────────────────────┘ Второй вид переменных в MUMPS не является временным, и они сос- тавляют основу баз данных MUMPS. Эти переменные именуются ГЛОБАЛЯМИ, и они адресуются на диск, вместо таблицы символов раздела, записываясь в область диска, назначенную этому процессу. Имена локальных переменных начинаются с буквы, или с символа '%'. Имена глобальных переменных на- чинаются с символа '^'. Глобальные переменные записываются на диск и становятся доступными для ВСЕХ процессов, связанных с этой областью диска. ________________________________________________________________ *1 Проиллюстрируем терминологическую путаницу в этом вопросе: DSM 11 Наборы томов Кипы ISM/NTSM Логический диск Директория DataTreeMUMPS Рабочая область Набор данных Именно в силу подобных причин, автор пришел к использованию термина, который наиболее близко можно перевести как "именованная область дис- ка" /дисковой памяти/ Стр. 29 Эти переменные НЕ удаляются при завершении процесса и остаются на дис- ке до тех пор, пока не будут удалены. Использование глобальных масси- вов /т.е. массивы индексированных переменных, чьи имена начинаются с символа '^' / - основной элемент баз данных. ┌─────────────────────────────────────────────────────────────────┐ │ Глобальные переменные адресуются на диск, где и остаются до │ │ пор, пока не будут специально удалены. Глобальные переменные │ │ доступны ВСЕМ процессам работающим в одной директории. │ └─────────────────────────────────────────────────────────────────┘ Область записи глобальных переменных может рассматриваться как более устойчивая и долговременная форма таблицы локальных переменных. В MUMPS нет необходимости при работе с глобальными массивами открывать и закрывать их, подобно использовании команд OPEN и CLOSE при работе с дисковыми файлами в других языках и системах программирования. А также нет ограничения на количество глобальных массивов, к которым должен быть осуществлен доступ в прикладном пакете. /В отличии от ограничения на количество одновременно открываемых файлов/ ┌─────────────────────────────────────────────────────────────────┐ │ НЕТ ограничения на количество глобальных массивов, к которым │ │ может быть осуществлен доступ. Глобальные переменные могут рас-│ │ сматриваться как расширение понятия локальных переменных. │ └─────────────────────────────────────────────────────────────────┘ Ясно, что у такого рода структур есть много положительных сторон, так и отрицательных черт. Так как глобальные данные могут быть доступ- ны в любое время любому процессу, то необходимо обеспечивать механизм, посредством которого можно было бы ограничить доступ к массиву на кри- тические моменты. Эта возможность реализуется через использование опе- ратора LOCK, которое обсуждается в разделах 3.5.4 и 10.4 MUMPS не накладывает никаких формальных ограничения на размер глобального массива, или как его иногда называют, глобального файла. Однако в некоторых версиях, особенно запускаемых как задания под уп- равлением других операционных систем, такие ограничения введены. Но они, как правило, объяснимы ограничениями связанными со свойствами операционной системы, под управлением которой функционирует MUMPS. Дополнительную информацию о массивах, глобалях и о соглашениях по именам переменных ВЫ можете получить в главе 4. Стр. 30 2.3 Задания /процессы/ Задание или процесс в MUMPS, это операции производимые компьюте- ром, в отдельном разделе и связанные с именованной областью диска. Это может быть и исполнение прикладной программы и программист работающий в непосредственном режиме. Каждый процесс изолирован от других процес- сов, все используемые заданием локальные переменные уникальны, задание не может проверять или изменять другие задания. По большей части, про- цессы работают независимо друг от друга, хотя и можно организовать их взаимодействие через глобальные массивы, или другим образом. Приклад- ные программы могут инициировать новые процессы с выделением им нового раздела, используя оператор JOB ( Раздел 3.5.3 ). Этим запускаемым "фоновым" процессам назначаются собственные разделы. Связь между по- рождающим и порождаемым процессом обычно осуществляется через глобаль- ные переменные. 2.4 Общие положения Отдельная программа может иметь размер до 5 Кбайт /2.2.1/ Под локальные переменные выделяется пространство порядка 5 000 байт /2.2.1/ Пространство для временных /локальных/ переменных, /таблица ло- кальных символов/ является общей для всех программ запускаемых в этом разделе. /2.2.1/ Каждая директория /именованная область диска/ является уникаль- ной. Различные директории могут содержать различные версии прог- рамм и баз данных с одинаковыми именами /2.2.2/ Локальные переменные являются временными. По завершению процесса они удаляются /2.2.3/ Глобальные переменные назначаются на диск, где они и достаются до тех пор, пока не будут специально удалены. Глобальные переменные доступны всем процессам работающим в той же области диска /дирек- тории/ /2.2.3/ Нет ограничения на число глобальных массивов, к которым может быть осуществлен одновременный доступ. Глобальные переменные яв- ляются расширением понятия локальных переменных /2.2.3/ Стр. 31 Глава 3 Операторы MUMPS Эта глава посвящена обсуждению назначения и синтаксиса использо- вания операторов языка. Те, кто уже знаком с языком, может извлечь пользу от общего обзора различных форм использования операторов, осо- бенно тех, что недавно включены в стандарт. К ним относится оператор NEW, использование передачи параметров в вызываемую процедуру /по DO/, и QUIT с параметром, используемый для завершения внешних процедур с непосредственным возвратом параметров. Краткая сводка операторов, с примерами их использования, приводится в приложении С. Начинающим программистам рекомендуется ознакомиться со всем спис- ком операторов, обращая особое внимание на вступительные разделы к различным классам операторов /Разделы 3.1-3.5/. Особенное внимание следует обратить на описание операторов SET, WRITE и DO, которые будут интенсивно использоваться в примерах последующих глав. При изучении других глав Вы можете возвращаться к описанию подробностей использова- ния встречающихся в них операторов. Не расстраивайтесь, встречая пока непонятные для Вас сложные конструкции, например КОСВЕННОСТЬ, или ПОС- ТУСЛОВИЯ, эти концепции будут подробно рассмотрены в главах 6 и 7. В описании всех операторов MUMPS мы будем использовать единый формат определения основного синтаксиса и дополнительных разрешенных синтаксических форм. /Подобно косвенности, постусловий, и т.п./ Опре- деление основного формата оператора будет приводиться в следующем фор- мате: /*1/ Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ │VARIABLE │ Set A="Test" │(VARIABLE,VARIABLE,...) │=EXPRESSION Set(A(1),2)=2 SET │$PIECE │ Set $P(X,"*",2)=3 │ │ ┌──────┐ Сокращение имени оператора │ S │ S a="Test" Использование постусловий: │ │ при операторе │ ДА │ S:a=1 b=2 при аргументе │ НЕТ │ Использование косвенности │ ДА │ S @"a=22" Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ S A="B",C=1,@A=C └──────┘ Рис. 3.1 Форма представления описания операторов языка __________________________________________________________________ *1 При определении оператора будут использоваться английские сокраще- ния и термины в их буквальном значении, а не переводе, во-первых, имя оператора переводить нельзя, и получится порою SET ПЕР=ВЫР, а во-вто- рых всем программистам в большей, или меньшей мере знакомы эти слова. На всякий случай приведем сводку используемых английских терминов. /Продолжение на следующей странице./ Стр. 32 Описание всех операторов состоит из двух частей: 1 - представле- ние синтаксиса использования, с примерами, и 2 - сводка сведений о до- пустимых синтаксических конструкциях этого оператора. На Рис.3.1 представлены обе части описания оператора SET. В первой части, в разделенных вертикальными линиями блоках, представлены элементы оператора. В левом поле представлено имя опера- тора /На этом примере представлен оператор SET/, в следующем поле ука- заны элементы, которые используются с этим оператором /В данном случае это может быть либо переменная, либо список переменных, либо функция $PIECE/, причем каждый из возможных элементов представлен на отдельной строке. Если элемент является необязательным, т.е. может использовать- ся, а может и отсутствовать, то он будет представлен заключенным в квадратные скобки, вот так: [...]. /Например поле для параметров в описании оператора DO представленного в разделе 3.3.2/. Элементы, представленные вне квадратных скобок, и при этом нет оговорки, что они могут отсутствовать, являются ОБЯЗАТЕЛЬНЫМИ элементами в этом операто- ре. Попытка использовать оператор без них приведет к ошибке. Если просуммировать все четыре поля в описании оператора, то Вы получаете список возможных вариантов использования этого оператора, с примером использования. Так например, возможны следующие варианты ис- пользования оператора SET, как показано в представленном примере: SET VARIABLE=EXPRESSION Set A="Test" SET (VARIABLE,VARIABLE,...)=EXPRESSION Set(A(1),2)=2 SET $PIECE...=EXPRESSION Set $P(X,"*",2)=3 Во второй части описания представляются сведения о элементах син- таксических конструкций языка, которые могут, или не могут быть ис- пользованы в этом операторе. Так же как и в первой части, справа представляются поясняющие примеры. Начинающие программисты могут пока игнорировать разборку второй, третьей и четвертой строк в этой части описания, как уже упоминалось, описание косвенности и постусловий бу- дет дано позднее. ___________________________________________________________________ Продолжение примечания с предыдущей страницы VARIABLE (var) - переменная /без указания вида/ EXPRESSION (expr) - выражение ARGUMENT (arg) - аргумент LOCAL (loc) - локальный /ая/ LOCAL VARIABLE (locvar) - локальная переменная OFFSET (offset) - смещение ROUTINE (rout) - программа ROUTINE NAME (rname) - имя программы LABEL (lab) - метка ROUTINE LABEL (rlab) - метка программы VALUE (val) - значение Стр. 33 Из существующих вариантов порядка представления сведений об опе- раторах /алфавитный, в порядке повышения сложности и т.д./, мы выбрали представление операторов в соответствии с их включением в ту или иную группу операторов языка. Традиционно в языках программирования различаются следующие груп- пы операторов: 1. Операторы объявления. Используются для объявления переменных, их типа, или объема отводимого им пространства. Как уже упоминалось, MUMPS не требует объявлять имя и тип используемых переменных, он динамически позволяет создавать и использовать переменные в ходе работы программы. ┌─────────────────────────────────────────────────────────────────┐ │ MUMPS не требует использования и не поддерживает операторы │ │ объявления. │ └─────────────────────────────────────────────────────────────────┘ 2. Назначающие операторы. Используются для изменения значений пере- менных, или связывания значение данных с именами переменных, удале- ния переменных, или временного переназначения состава переменных во время исполнения вызванной процедуры. 3. Условные операторы. Эти операторы позволяют управлять исполнение последующих операторов, т.е. если условия выполняется последующие операторы исполняются, или наоборот. 4. Операторы передачи управления. Эти операторы используются для изменения порядка последовательного исполнения строк программы, пу- тем передачи управления на другую строку, или в другую программу. Эти операторы применяются для вызова процедур и логического "ветв- ления" программы. 5. Операторы ввода/вывода. Операторы этой группы управляют перифе- рийными устройствами, например принтерами, терминалами, и устройс- твами ввода вывода с магнитной ленту для чтения с них, или вывода информации. 6. Системные и прочие операторы. К этой группе относятся все прочие операторы, которые не могут быть легко классифицированы. Для MUMPS операторы могут быть разделены по представленным группам следующим образом: Операторы Управление Системные Назначающие Условные передачи вводом/вывода и ________________________управления__________________прочие_______ 3.1.1 SET 3.2.1 IF 3.3.1 GOTO 3.4.1 OPEN 3.5.1 BREAK 3.1.2 KILL 3.2.2 ELSE 3.3.2 DO 3.4.2 USE 3.5.2 HANG 3.1.3 NEW 3.2.3 FOR 3.3.3 QUIT 3.4.3 READ 3.5.3 JOB 3.3.4 HALT 3.4.4 WRITE 3.5.4 LOCK 3.3.5 XECUTE 3.4.5 CLOSE 3.5.5 VIEW Стр. 34 В некоторых случаях, операторы могут иметь признаки позволяющие относить их к более чем одной группе. Возьмем для примера оператор RE- AD. Этот оператор предназначен для чтения информации с периферийного устройства, например с клавиатуры, или с магнитной ленты и записи этой информации в переменную. Таким образом ее тоже можно было бы отнести и к группе назначающим операторам. В таких случаях при решении вопроса об отнесении оператора к той или иной группе мы руководствовались ло- гическими принципами /операция READ относится к вводу/выводу информа- ции/ Кроме того, MUMPS обеспечивает дополнительные синтаксические конструкции, применимые с большинством операторов, которые позволяют управлять их исполнением, в зависимости от дополнительно задаваемых условий /аппарат постусловий при операторах подробно рассмотрен в раз- деле 6.1.4/ 3.1 Назначающие операторы Использование назначающих операторов изменяет состав переменных, так они либо осуществляют связывание значений данных с именами пере- менных, или изменение связанных с именами переменных значений. А также позволяют произвести удаление переменных из среды исполнения програм- мы, или произвести на время исполнения вызываемой процедуры изменение состава переменных. Все операторы, описываемые в данном разделе, используются для связывания значений данных с именами переменных, или производят удале- ние /временное или постоянное/ переменных из окружения исполняемой программы. Понятие Переменных /VARIABLES/ более подробно рассматрива- ется в главе 4. Для использования в данной главе достаточно сказать, что переменная представляет собой символическое имя, которое использу- ется для ссылки на конкретное значение данного. В MUMPS имя переменной может быть любой длины, но только первые 8 символов являются значащи- ми, /в некоторых реализациях до 15, например DataTree MUMPS/ причем первым символом ОБЯЗАТЕЛЬНО должны быть либо буква, либо знак '%', ос- тальные не регламентируются. Как уже говорилось, большие и маленькие буквы различаются, поэтому MILES и miles - это разные переменные В этом разделе рассматриваются следующие операторы: SET - Связывает имя переменной с значением данного KILL - Производит удаление переменных, и связанных с ними дан- ных из состава переменных. NEW - Временно позволяет переопределить значения и состав пе- ременных во время выполнения вызванной подпрограммы, или процедуры. Стр. 35 3.1.1 Оператор SET Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ │VARIABLE │ Set A="Test" │(VARIABLE,VARIABLE,...) │=EXPRESSION Set(A(1),2)=2 SET │$PIECE │ Set $P(X,"*",2)=3 │ │ ┌──────┐ Сокращение имени оператора │ S │ S a="Test" Использование постусловий: │ │ при операторе │ ДА │ S:a=1 b=2 при аргументе │ НЕТ │ Косвенность в аргументе │ ДА │ S @"a=22" S @("d")=33 Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ S A="B",C=1,@A=C Использование времени │ НЕТ │ ожидания │ │ └──────┘ Оператор SET является одним из двух операторов языка, которые приравнивают имя переменной и значение данного. /Второй - оператор RE- AD/. Переменные, которые назначаются оператором SET /до знака = /, не обязательно должны быть определены, перед исполнением оператора, но все переменные входящие в выражение EXPRESSION, /с права от знака =/, должны быть определены. Оператор KILL является логической инверсией оператора SET и предназначен для удаления локальных или глобальных пе- ременных вмести с их значениями. Понятие выражения /EXPRESSION/ будет подробно рассмотрено в главе 4. 3.1.1.1 VARIABLE=EXPRESSION Это наиболее общий случай использования оператора. VARIABLE может иметь любой из разрешенных форматов и быть локальной, или глобальной, простой, или индексированной. Если перемен- ная не определена до использования оператора, то MUMPS создает новую переменную и назначает ей значение определяемое выражением. Если пере- менная уже была определена, то значение задаваемое выражением заменит прежде связанное с этим именем переменной данное. Посмотрите на предс- тавленный пример. ┌─────────────────────────────────────────────────────────────────┐ │ о|>SET A=12,B=2,C=A*B WRITE A," Умножить на ",B,"=",C<- |o │ │ |12 Умножить на 2=24 | │ │ о|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.1 SET VARIABLE=EXPRESSION В этом примере, мы используем оператор SET для создания перемен- ных А со значением 12, и В со значением 2, а также переменной С, кото- рой назначается значение произведения А*B. Стр. 36 Оператор WRITE использованный вслед за оператором SET использован для показа на экране значений всех трех переменных, аргументы операто- ра WRITE заключенные в кавычки / " Умножить на " / выводят на экран строки заключенных в них символов. 3.1.1.2 (VARIABLE,VARIABLE,...)=EXPRESSION Эта форма является рас- ширением предшествующего примера /VARIABLE=EXPRESSION/, которая позво- ляет произвести одновременное назначение нескольким переменным одного значения. Каждому имени из списка заключенного в круглые скобки назна- чается значение задаваемое выражением EXPRESSION. ┌─────────────────────────────────────────────────────────────────┐ │ о|>S (Count,Sum,Average)=0 W " Count = ",Count,!<- |o │ │ |Count = 0 | │ │ |>Write "Average = ",Average," Sum = ",Sum<- | │ │ |Average = 0 Sum = 0 | │ │ | | │ │ о| |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.2 Set (VARIABLE,VARIABLE,...)=EXPRESSION Специально отметить внешне видимую похожесть этой формы оператора SET и исключающую форму KILL / в виде : KILL (VARIABLE,VARIABLE,...) /, которая описывается среди операторов следующей группы. Так как опе- раторы SET и KILL являются взаимодополняющими, то функциональная раз- ница между ними приводит к полному различию между этими формами. KILL (VARIABLE,VARIABLE,...) удаляет ВСЕ локальные переменные из текущего раздела, КРОМЕ указанных в списке заключенном в круглые скобки, в то время, как обсуждаемая форма SET назначает значение задаваемое EXPRES- SION ТОЛЬКО перечисленным в списке переменным. 3.1.1.3 $PIECE...=EXPRESSION $PIECE - это внутренняя функция языка манипулирующая подстроками размещенными в строке. Функции, и частности функция $PIECE описаны подробнее в разделе 8.1.2.4, но мы вынуждены упомянуть о ней сейчас, в связи с ее использованием в одной из форм оператора SET. Функция $PIECE, входящая как часть выражения возвращает одну или более подстрок, выделенных из исходной строки которая разделена на части неким символом-разделителем. Поясним это следующим примером: ┌─────────────────────────────────────────────────────────────────┐ │ о|>WRITE $piece("abc;def;ghi;jkl",";",2)<- |o │ │ |def | │ │ | | │ │ |>WRITE $piece("abc;def;ghi;jkl",";",2,3)<- | │ │ о|def;ghi |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.3 Функция $PIECE Стр. 37 В обоих случаях рассмотренном в этом примере исходная строка одна и та же: "abc;def;ghi;jkl", состоящая из подстрок разделенных точкой с запятой ';'. В первом случае MUMPS исполняет инструкцию вида "вывести на экран второе вхождение подстроки из исходной строки, которая разде- лена символом ';'". Во втором случае используется функция $PIECE с че- тырьмя аргументами и исполняется инструкция "вывести на экран вхожде- ние подстрок с второй по третье, из исходной строки, которая разделена символом ';'". Обратим Ваше внимание на то, что во втором случае возв- ращается и значение символа-разделителя разделяющего подстроки. Когда функция $PIECE используется как часть выражения справа от знака '=' то она извлекает подстроки из исходной строки. Когда же она используется слева от знака ';', в данной форме оператора SET, то ис- пользуется для выполнения обратной операции, результат выражения ВСТАВЛЯЕТСЯ в указанную строку. Рассмотрим это на примере: ┌─────────────────────────────────────────────────────────────────┐ │ о|>S a="",$piece(a,"/",3)=4*2 W a<- |o │ │ |//8 | │ │ | | │ │ o|>s $p(a,"/",1)="testing" W a<- |o │ │ |testing//8 | │ │ | | │ │ o|>s b="",$piece(b,"*",5)="" w b |o │ │ |**** | │ └─────────────────────────────────────────────────────────────────┘ Рис.3.4 Set $PIECE Если описать словами действия предписываемые первым примером, то это будет выглядеть следующим образом: "приравнять третью подстроку в строке, задаваемой переменной а, и использующей символ '/' в качестве разделителя, результату умножения 4*2", причем, одновременно при этом, несмотря на то, что переменная 'а' не содержит символов '/', они вно- сятся в результат, так, чтобы значение заносилось в указанную подстро- ку. Когда функция $PIECE используется в левой части оператора SET, то ее первый аргумент должен оцениваться как допустимое имя переменной /переменная или элемент массива, локальная или глобальная/. Отмечая это, обратите внимание на то, что во втором примере в результирующей строке только 4 звездочки, так как указано 5-е вхождение подстроки. Во всех случаях, результирующая строка изменяется вставкой ре- зультата EXPRESSION в предназначенное место, указываемое разделителем и указателем позиции, который задается третьим аргументом функции. Ес- ли строка, не содержит указанное число разделителей, то строка допол- няется требуемым количеством перед вставкой значения выражения. Если строка, в которую назначается вставка значения выражения не определе- на, то она инициализируется и заполняется требуемым количеством разде- лителей. Стр. 38 Следующий пример демонстрирует использование функции $PIECE в ле- вой части оператора, с четырьмя аргументами: ┌─────────────────────────────────────────────────────────────────┐ │ о|>S a=";;;;;",$piece(a,";",2,4)="replace" W a<- |o │ │ | | │ │ o|>s $p(a,";",2)="REPLACE" W a<- |o │ │ |;replace;; | │ │ | | │ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Рис.3.5 Set $PIECE с 4 аргументами Комментируя первую часть этого примера, заметим, что указано, вставить в подстроки со 2 по 4 значение "replace", которое не содержит символов разделителей. Результирующая строка оказывается на два разде- лителя меньше исходной! 3.1.2 Оператор KILL Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ │без аргументов │ KILL S a=1 KILL │VARIABLE │ KILL A,^a(4) │(VARIABLE,VARIABLE,...) │ KILL (a,b,c) │ │ │ │ ┌──────┐ Сокращение имени оператора │ K │ K A,B Использование постусловий: │ │ при операторе │ ДА │ K:Truth A,B,C при аргументе │ НЕТ │ Использование косвенности │ ДА │ s a="b" k @a Обязательность аргументов │ НЕТ │ k s a=1 Разрешенность использования │ │ списка аргументов │ ДА │ k a,b,c,^xyz,d Установка времени ожидания │ НЕТ │ └──────┘ Оператор KILL, являясь логическим дополнением оператора SET пред- назначен для удаления переменных из таблицы символов /локальных пере- менных/, и с диска /глобальных переменных/. Однако имеются тонкие раз- личия с работой оператором SET, касающихся работы оператора KILL в массивах. Когда мы удаляем элемент из массива, то вместе с ним удаля- ются и все его потомки. Если имя массива, без индексов, используется в качестве аргумента, то удаляется весь массив /локальный или глобаль- ный/. Кроме того, оператор KILL со скобками, работает принципиально по другому, чем оператор SET со списком переменных. Стр. 39 Необходимо указать на то, что некоторые операторы исполняют неяв- ный KILL с различными оговорками. Это оператор NEW, и вызов через опе- ратор DO процедуры с передачей в нее параметров. Обе эти возможности производят временный неявный KILL. /Оператор NEW описан в этом разде- ле, а передача параметров обсуждается в главе 9/ 3.1.2.1 Безаргументный KILL. Использование оператора KILL без аргу- ментов удаляет все переменные из раздела. Общие синтаксические правила построения командных строк пояснены подробней в главе 6. Сейчас необходимо отметить, что безаргументный KILL должен отделяться от следующего оператора в строке двумя пробела- ми. /Один заменяет пропущенный аргумент, второй служит разделителем между операторами в строке/. Если оператор KILL без аргументов стоит последним оператором в строке, то после него не требуются пробелами. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set a=22,b=3 kill Write a*b<- |o │ │ | ERROR | │ │ о|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.6 Безаргументный KILL В этом примере безаргументный KILL удаляет две переменные, соз- данные до этого оператором SET. Когда произведение a*b оценивается для оператора Write, мы сталкиваемся с тем, что эти переменные не опреде- лены, так как они только что удалены оператором KILL. В результате вы- водится соответствующее сообщение об ошибке. Заметьте, что безаргу- ментный KILL отделен от команды WRITE двумя пробелами. Если бы был один, то произошла бы ошибка вида . 3.1.2.2 KILL со списком. В этом случае аргументом оператора являет- ся список вида VARIABLE,VARIABLE,..., который задает список удаляемых имен переменных. Если в списке указывается имя несуществующей перемен- ной, то ошибки не происходит. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set a=22,b=3,c=a*b Write c<- |o │ │ |66 | │ │ о|>kill a,b write c<- |o │ │ |66 | │ │ |>write c<- | │ │ о| ERROR |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.7 Kill со списком значений 3.1.2.3 Исключающий KILL. В этом случае аргументом оператора стано- вится список имен переменных, заключенный в круглые скобки. KILL (VA- RIABLE,VARIABLR,...). В подобном случае удаляются все локальные пере- менные, КРОМЕ указанных в списке. Стр. 40 Заметим, что и в этом случае, так и во всех других формах исполь- зования оператора KILL производятся в текущем составе локальных пере- менных, исключая те, что имеют отношение к оператору NEW. Глобальные переменные не затрагиваются исключающей формой оператора KILL. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set a=22,b=3,c=a*b Write c<- |o │ │ |66 | │ │ о|>kill (a,b) write c<- |o │ │ | ERROR | │ │ о|>write a," ",b<- |o │ │ |22 3 | │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.8 Исключающий Kill В подобной конструкции запрещено использование индексированных имен локальных переменных. Однако неиндексированные имена локальных массивов могут быть включены в список. 3.1.2.4 Дополнительные пояснения. В связи с использованием оператора KILL необходимо разобрать два вопроса: - работа оператора внутри массивов - некоторые эффекты при удалении переменных передающих параметры из процедур Удаление массивов и их ветвей. MUMPS массивы /локальные и гло- бальные являются иерархическими структурами, очень часто их именуют древовидными структурами и соответственно представляют. ary ┌────────────┐ │ │ └─┬────┬───┬─┘ ary(1)=10 ┌────────────┘ │ └──────────────┐ ary(1,1)=40 │ (1) │ (2) │ (3) ary(1,2)=50 ┌────┴─┐ ┌──┴───┐ ┌─┴────┐ ary(2)=20 │ 10 │ │ 20 │ │ 30 │ ary(3)=30 └──┬─┬─┘ └──────┘ └─┬────┘ ary(3,1)=60 │ │ │ ┌──┘ └─────┐ │ │ │ │ │ (1) │ (2) │ (1) ┌───┴───┐ ┌─┴──────┐ ┌─┴────┐ │ 40 │ │ 50 │ │ 60 │ └───────┘ └────────┘ └──────┘ Представление массива иерархической структуры массива На данном рисунке изображена иерархическая структура MUMPS масси- ва, в прямоугольных областях отображены данные при индексах, а в круг- лых скобках рядом с ними значения индексов массива. Стр. 41 Используя оператор KILL может удалить либо весь массив, либо ка- кую-то из его ветвей. ┌─────────────────────────────────────────────────────────────────┐ │ о|>WRITE ary(1,2)<- |o │ │ |50 | │ │ |>kill ary | │ │ o|>write ary(1,2)<- |o │ │ | ERROR | │ │ |>write ary(1)<- | │ │ | ERROR | │ │ о|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.9 Удаление всего массива ┌─────────────────────────────────────────────────────────────────┐ │ о|>WRITE ary(1,2),",",ary(3,1),",",ary(1) |o │ │ |50,60,10 | │ │ |>kill ary(1,2),ary(3)<- | │ │ o|>write ary(1)<- |o │ │ |10 | │ │ |>write ary(1,2)<- | │ │ | ERROR | │ │ |>write ary(3,1)<- | │ │ | ERROR | │ │ о|>write ary(1,1)<- |o │ │ |40 | │ │ | | │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.9 Удаление ветви массива Когда оператором KILL удаляется ветвь массива, то удаляются и не- посредственно указанный узел массива вместе с данным, и все непосредс- твенно следующие за указанным индексом узлы массива вместе с их данны- ми, при этом удаляются также и узлы, которые не имеют при себе данных, а служат для указания прохода к следующим уровням индексации. Еще раз отметим, что нельзя с помощью исключающей формы оператора KILL удалить часть ветвей массива. ┌─────────────────────────────────────────────────────────────────┐ │ о|>kill (ary)<- |o │ │ |>write ary | │ │ |10 | │ │ о|>kill (ary(1)) |o │ │ | ERROR | │ │ о| |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.11 Исключающий KILL на массиве Стр. 42 Удаление параметров внутри процедуры. Существует несколько инте- ресных моментов связанных с использованием KILL внутри процедур. Обыч- но, процедура не удаляет переменные в списке ФОРМАЛЬНЫХ параметров /подробнее в разделе 3.3.2.2 и главе 9 о передаче параметров/, испол- няя неявное NEW на все имена переменных, указанных в этом списке, при этом все значения переменных будут восстановлены после того, как она завершится по QUIT. Передача параметров в процедуру возможна в двух вариантах: ЗНАЧЕ- НИЕМ и ССЫЛКОЙ /подробнее в разделах 9.1.1 и 9.1.2/. При передаче ЗНА- ЧЕНИЯ, в вызываемой процедуре будет создана переменная с именем, ука- занном в списке формальных параметров, с назначением ей передаваемого значения. Передача ССЫЛКИ с другой стороны, не что иное как установле- ние связи между именем переменной из списка формальных параметров и соответствующей ей переменной из списка действительных параметров. Удаление имени переменной внутри процедуры, которая была создана в результате передачи ЗНАЧЕНИЯ в процедуру, не оказывает влияния на вызывающую программу. Напротив, удаление же переменной, которая указа- на в списке формальных параметров, как передающую ССЫЛКУ, будет произ- ведено, как удаление переменной с таким именем из списка формальных параметров, и соответствующей ей переменной из списка действительных параметров. Переменная передающая ссылку, в списке действительных па- раметров идентифицируется точкой перед именем переменной. ┌─────────────────────────────────────────────────────────────────┐ │ о|Mult(x,y) ;Показать результат x*y |o │ │ | Write !," Результат =",x*y | │ │ | Kill x,y | │ │ o| quit |o │ │ | | │ │ |----------------------------------------------------------| │ │ o| |o │ │ |>set a=4,b=10 Do Mult(a,.b)<- | │ │ |Результат =40 | │ │ o| |o │ │ |>write a<- | │ │ |4 | │ │ o|>write b |o │ │ | ERROR | │ │ | | │ │ о| |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.12 KILL в процедуре с передачей параметров Подчеркиваю, что нет необходимости удалять при завершении вызыва- емой процедуры, переменные, указанные в списке формальных параметров. Исполнение неявного NEW на все имена переменных включенных в список формальных параметров, исключает эту необходимость. Стр. 43 3.1.3 Оператор NEW Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ │ без аргументов │ New s a=1 NEW │ LOCVAR │ new a │ (LOCVAR,LOCVAR,...) │ new (a,b,c) │ │ │ │ ┌──────┐ Сокращение имени оператора │ N │ N a Использование постусловий: │ │ при операторе │ ДА │ N:Truth a,b,c при аргументе │ НЕТ │ Использование косвенности │ ДА │ S a="b" N @a Обязательность аргументов │ НЕТ │ N s a=1 Разрешенность использования │ │ списка аргументов │ ДА │ N a,b,c Установка времени ожидания │ НЕТ │ └──────┘ Для того, чтобы полностью разобраться в работе оператора NEW не- обходимо предварительно изучить вызов программы и процедуры в MUMPS. Начинающему программисту предлагается предварительно изучить функции оператора DO по передаче параметров /раздел 3.3.3.2/, перед тем, как приступать к изучению оператора NEW. Оператор NEW позволяет внутри вызываемой процедуры, программы или в строке исполняемой по XECUTE, повторно использовать имена переменных уже существующих в вызывающей программе. При этом все значения локаль- ных переменных указанных в списке оператора NEW /включая и локальные массивы целиком/ сохраняются в специальной области и переменные стано- вятся неопределенными. Теперь, переменные с именами, сохраненными опе- ратором NEW, могут быть использованы в вызываемой процедуре. Когда вы- зываемая процедура завершается по QUIT, то все переменные с именами перечисленными в NEW удаляются, и им восстанавливаются прежде сохра- ненные значения. При передаче параметров с использованием DO, или обращении к внешней функции, исполняется неявный NEW на все имена переменных, ука- занных в списке ФОРМАЛЬНЫХ параметров, вызываемой процедуры или внеш- ней функции. Однако, назначенные в процедуре переменные не удаляются по завершению процедуры, напротив, им назначаются значения. Если спи- сок ДЕЙСТВИТЕЛЬНЫХ ПАРАМЕТРОВ короче, чем список ФОРМАЛЬНЫХ парамет- ров, переменные, которым нет соответствия в списке действительных па- раметров будут неопределенными, при старте исполняемой процедуры. Стр. 44 Возможно использование больше, чем одного, оператора NEW на одном уровне исполнения процедуры. Окончание ее по QUIT отменит все дейс- твия, связанные с использованием NEW. Обычно, все переменные из таблицы символов раздела доступны вызы- ваемой подпрограмме. Все имена переменных, которые не были упомянуты в NEW, могут быть ею использованы, или изменены, все произведенные изме- нения станут доступны вызывающей программе, и соответственно всем программам, вызываемым в этом разделе. При создании процедуры, которая нечувствительна к изменениям в среде локальных переменных, в ней долж- ны сохраняться все переменные используя явное, или неявное NEW. Запрещено использовать индексированные имена в операторе NEW, но в тоже время возможно упоминание массива целиком. /Т.е. имя локального массива без индексов/ 3.1.3.1 Безаргументный NEW. В этом случае сохраняются значения ВСЕХ переменных из раздела. Эту форму следует применять осторожно, особенно при передаче параметров. Ибо в этом случае, уже будет исполнено неяв- ное NEW на переменные из списка формальных параметров, и второе NEW сделает их недоступными для вызывающей программы. Безаргументный NEW, также, ка и безаргументный KILL должен отде- ляться от следующих операторов в этой строке двумя пробелами. /Подроб- ности о синтаксисе командной строки в главе 6/ ┌─────────────────────────────────────────────────────────────────┐ │ o| Sub New set a=33,b=22 |o │ │ | write !,"a=",a," b=",b | │ │ | quit | │ │ o| |o │ │ |----------------------------------------------------------| │ │ |>set a=50 do Sub write !,"a=",a<- | │ │ o| |o │ │ |a=33, b=22 | │ │ |a=50 | │ │ o|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.13 Общий NEW 3.1.3.2 Argument=locvar Эта форма может быть также и формой исполь- зования NEW со списком переменных. В этом списке допускается использо- вание имен переменных, не определенных в разделе. Та переменная, кото- рая была не определенной в списке оператора NEW, также будет неопреде- ленной при возврате управления назад в вызывающую программу. Стр. 45 ┌─────────────────────────────────────────────────────────────────┐ │ | Sub New a,b set a=33,b=22 | │ │ | write !,"a=",a," b=",b | │ │ o| quit |o │ │ | | │ │ |----------------------------------------------------------| │ │ o|>kill set a=50 do Sub write !,"a=",a<- |o │ │ |a=33, b=22 | │ │ |a=50 | │ │ o|>write !," b=",b<- |o │ │ | ERROR | │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.14 NEW со списком 3.1.3.3 Argument=(locvar,locvar,...) Эта форма оператора, известна также как и ИСКЛЮЧАЮЩАЯ форма оператора NEW, для всех имен переменных не включенных в список. Эта форма в чем то подобна безаргументному NEW. Это сходство состоит в том, что сохраняется все окружение локаль- ных переменных, за ИСКЛЮЧЕНИЕМ переменных указанных в списке операто- ра. ┌─────────────────────────────────────────────────────────────────┐ │ | Sub New (x) set a=33,b=22 | │ │ | write !,"a+b*x"=,a+b*x | │ │ o| quit |o │ │ | | │ │ |----------------------------------------------------------| │ │ o|>set x=2 Do ^Sub<- |o │ │ |a+b*x=110 | │ │ |>write x<- | │ │ o|2 |o │ │ |>write a<- | │ │ | ERROR | │ │ o|>write b<- |o │ │ | ERROR | │ │ |> | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.15 Исключающее NEW Эту форма оператора может показаться желательной для использова- ния в процедурах с передачей параметров, но на самом деле достаточно использовать явное NEW только на те имена переменных, которые будут изменяться в процедуре. 3.1.3.4 Дополнительные пояснения. Необходимо пояснить, что происхо- дит с переменными, указанными в операторе NEW. При этом всегда следует обращать внимание на уровень исполнения процедуры /программы/. Стр. 46 Подпрограммы вызываются в следствии исполнения оператора DO, или использовании в выражении внешней функции. Вызываемая подпрограмма в свою очередь может вызвать другую подпрограмму, и так далее. Это иног- да называется вложенностью подпрограмм, в некотором соответствии с вложенностью циклов FOR. Вызов подпрограммы, или процедуры мы можем сравнить с повышением уровня исполнения на одну ступень. В то же время исполнение явного, или неявного оператора QUIT при завершении процедуры, понижает уровень исполнения на одну ступень. Оператор NEW используемый в процедуре, за- писывает указанные переменные в стек и делает их НЕОПРЕДЕЛЕННЫМИ /UN- DEFINED/. Переменным могут быть назначены новые значения данных, и они могут свободно использоваться. При завершении подпрограммы, происходит понижение уровня исполнения, и при этом восстановление из стека всех значений переменных /или их статуса неопределенных переменных/ сохра- ненное оператором NEW, непосредственно перед возвратом обратно, в вы- зывающую программу. Количество переменных записываемых и извлекаемых из стека зависит от уровня исполнения. Что произойдет при повторном использовании NEW на том же уровне исполнения? Каждый раз при использовании NEW в стек будет записываться статус переменной, /неопределена, или ее значение/. и переменная дела- ется неопределенной. При завершении этого уровня по QUIT, из стека бу- дут извлечены и восстановлены те значения переменных, которые совсем не обязательно было сохранены последним использованием оператора NEW. Продемонстрируем это на следующем примере: ┌─────────────────────────────────────────────────────────────────┐ │ о| Sub New i |o │ │ | For i=1:1:10 New a Set a=1 Write " ",a quit:a=5 | │ │ | write !,"a=",a | │ │ o| quit |o │ │ | | │ │ |----------------------------------------------------------| │ │ o|>set a=100 Do Sub<- |o │ │ |1 2 3 4 5 | │ │ |a=5 | │ │ o|>write a<- |o │ │ |100 | │ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.16 Использование NEW на одном уровне Заметим, что оператор QUIT завершающий цикл организованный по QU- IT, не оказывает влияния на значение переменной А, которая будет изме- нена только после завершения процедуры по QUIT и восстановления значе- ния переменной a из стека. Также обратим Ваше внимание на то, что по завершению процедуры происходит правильное восстановление значение пе- ременной a, равное 100, несмотря на то, что во время исполнения пос- леднего оператора NEW a=4. Стр. 47 3.2 Условные операторы Условные операторы используются для изменения нормального порядка команд, в зависимости от условий складывающихся во время работы, и обычно зависящих от значения многих переменных. Как уже упоминалось ранее, MUMPS позволяет наложить условия на исполнение практически лю- бого оператора. Этот механизм часто именуется как "пост-условия при операторах" и "пост-условия при аргументах", и обсуждены подробнее в главе 6. В этом разделе будут рассматриваться только те операторы, которые специально созданы для того, чтобы оценивать складывающиеся во время работы условия и менять исполнение программы, в зависимости от них. Для правильного понимания сущности этих операторов, следует помнить, что MUMPS является строчно-ориентированным языком, и каждая строка программы содержит, в большинстве случаев несколько операторов, запи- санных последовательно, друг за другом. Обычно, MUMPS исполняет строки программы, последовательно, начиная с самой первой, и в каждой строке последовательно исполняются операторы, слева - направо и до конца строки. После чего начинается исполняться следующая строка. Если в строках программы отсутствуют операторы IF, ELSE и FOR, то практически нет разницы между программами, в которых каждый оператор размещен в новой строке и теми, где они записаны строках последовательно, друг за другом. Операторы IF, ELSE, FOR, однако определяют как будет исполняться остаток строки, справа от этих операторов и до конца строки. Оценива- ние аргумента, или диапазона значений аргументов определяют или факт исполнения остатка строки, или количество циклов исполнения этого ос- татка. Уточним это определение для каждого из операторов: IF Этот оператор используется для накладывания условия исполнения на строку команд в зависимости от оценива- ния результата вычисления логического выражения упот- ребленного в качестве аргумента оператора. Если ре- зультат оценки выражения ИСТИНА - /логическая 1/, то строка, следующая за операторoм IF исполняется, иначе начинает исполняться строка расположенная ниже строки с оператором IF. Этот же оператор применяется для проверки результата исполнения операторов c установ- кой времени ожидания. /Например READ или LOCK/, опе- ратор IF работает также, как в случае, когда резуль- тат оценки выражения являющегося его аргументом исти- нен. ELSE Логическая инверсия оператора IF. Он проверяет слу- чай, когда результат оценки ранее оцениваемого выра- жения, или команды - ЛОЖЬ /логический -0/. Стр. 48 FOR Этот оператор задает повторяющееся исполнения строки расположенной слева от него. Оператор с аргументами может определять количество повторений. Оператор IF при оценке выражения использованного в качестве его аргумента устанавливает значение системной переменной с именем $TEST /Подробнее в разделе 4.4.4.5/. Эта же системная переменная использует- ся в качестве НЕЯВНОГО аргумента оператора ELSE, который применяется без аргументов. Также важно отметить, что результат исполнения некото- рых операторов оказывает влияние на значение переменной $TEST, напри- мер операторы READ и LOCK с установкой времени ожидания. 3.2.1 Оператор IF Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ │без аргументов │ IF │EXPRESSION │ IF a>b ... │ │ IF ... ┌──────┐ Сокращение имени оператора │ I │ I a["abc" Использование постусловий: │ │ при операторе │ НЕТ │ при аргументе │ НЕТ │ Использование косвенности │ ДА │ If @ok Обязательность аргументов │ НЕТ │ If ... Разрешенность использования │ │ списка аргументов │ ДА │ i a>b,c="xyz" ... Установка времени ожидания │ НЕТ │ └──────┘ Когда оператор IF оценивает результат логического выражения, и этот результат - ИСТИНА /'=0/, то продолжается исполнение операторов расположенных правее оператора IF. Если результат оценки - ЛОЖЬ /=0/, остаток строки будет игнорирован. Безаргументная форма оператора по умолчанию проверяет значение системной переменной $TEST, в которой мо- гут содержаться результаты последней оценки логического выражения при IF, или результат исполнения операторов READ, LOCK, JOB, OPEN с уста- новленным временем ожидания завершения операции. 3.2.1.1 Argument=EXPRESSION Как уже упоминалось, в этой форме опе- ратора, результат оценки логического выражения управляет исполнением остатка строки, правее оператора IF. Если Вы не знакомы с операциями отношений между операндами: СОДЕРЖИТ, БОЛЬШЕ ЧЕМ, которые представля- ются соответственно символами '[' и '>', использованными в этом приме- ре, обратитесь к разделам 5.3.1.1 и 5.3.2.2. Стр. 49 ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set a=1234,b=123<- |o │ │ |>If b[a write " b содержит а"<- | │ │ | | │ │ o|>If a>b,a[b write "a больше b и a содержит b."<- |o │ │ |a больше b и a содержит b. | │ │ |> | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.17 Аргумент оператора = логическое выражение 3.2.1.2 Безаргументный IF - Эта форма не что иное, как сокращенная запись конструкции вида: IF $TEST ... И предназначена, естественно, для проверки значения системной переменной $TEST, в которой могут со- держаться результаты последней оценки логического выражения при пос- леднем выполненном операторе IF, или результат исполнения операторов READ, LOCK, JOB, OPEN с установленным временем ожидания завершения операции. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set low=22,Test=14<- |o │ │ |>If TestIf Write "Новое значение Low =",Low,! | │ │ o|Новое значение Low =14 |o │ │ | | │ │ |> | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.18 Использование IF без аргументов Следует отметить, что проверка постусловий при операторах и пос- тусловий при аргументах, НЕ оказывает влияние на значение системной переменной $TEST. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set a=1,b=2<- |o │ │ |>If aSet:a=b b=b*b<- |o │ │ |>If write "$TEST истинен /=1/"<- | │ │ |a меньше b | │ │ o|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.19 Влияние постусловий на $TEST Проверка постусловия при операторе SET (SET:a=b b=d*b) НЕ измени- ло значения системной переменной 4TEST, установленной предшествующим оператором IF. Стр. 50 Родственный оператору IF оператор ELSE тоже проверяет значение системной переменной $TEST, НО он проверяет ее НУЛЕВОЕ - ЛОЖНОЕ значе- ние и в этом смысле идентичен конструкции вида: IF '$TEST. Отличием оператора ELSE от оператора IF, является то обстоятельство, что он не изменяет значение этой системной переменной. Помните, что как и все безаргументные команды, безаргументная форма оператора IF должна отделяться от следующего оператора в строке двумя пробелами. 3.2.1 Оператор ELSE Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ ELSE │без аргументов │ ELSE ... │ │ ┌──────┐ Сокращение имени оператора │ E │ E ... Использование постусловий: │ │ при операторе │ НЕТ │ при аргументе │ НЕТ │ Использование косвенности │ НЕТ │ Обязательность аргументов │ НЕТ │ Разрешенность использования │ │ списка аргументов │ НЕТ │ используется без аргументов Установка времени ожидания │ НЕТ │ └──────┘ Оператор ELSE проверяет значение системной переменной $TEST, и, если оно ЛОЖНО, /т.е. =0/ и передает управление, на следующий справа от него оператор в строке. Если значение в $TEST - ИСТИНА /т.е. $TEST=1/, то остальные операторы в данной строке игнорируются, и уп- равление передается на следующую строку. Переменная $TEST устанавлива- ется в зависимости от последнего исполнения оператора IF с аргументом, или в зависимости от результата завершения операции в операторах READ, JOB, OPEN, LOCK с установленным временем ожидания завершения операции. Оператор ELSE эквивалентен конструкции IF '$TEST, за исключением того, что он НЕ изменяет значение $TEST /if '$TEST инвертирует это значение/ ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set a=500,b=250<- |o │ │ |>If aelse write "a больше b"<- | │ │ |a больше b | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.20 Оператор ELSE Стр. 51 Заметим, что в этом примере есть ЛОГИЧЕСКАЯ ошибка. Если a=b, то ошибочно будет выведено сообщение "а больше b". Кроме того, что ошибочным будет использование в одной строке, следом за оператором IF оператора ELSE, так он никогда не будет испол- няться. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set a="bcd",b="abc"<- |o │ │ |>If a]b write "a следует за b" Else w "b следует за а"<- | │ │ |>write "Ошибка, ELSE исполняться никогда не будет !"<- | │ │ |Ошибка, ELSE исполняться никогда не будет | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.21 Оператор ELSE в строке следом за IF 3.2.3 Оператор FOR Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ │ │START:INCREMENT:END For i=1:1:10 │ │START:INCREMENT For i=1:10 FOR │LOCVAR=│EXPR,EXPR,..... For i="a","b",3 │ без аргументов For Set x=$o │ ┌──────┐ Сокращение имени оператора │ F │ F i=1:1:10 Использование постусловий: │ │ при операторе │ НЕТ │ при аргументе │ НЕТ │ Использование косвенности │ НЕТ │ Обязательность аргументов │ НЕТ │ F .... Разрешенность использования │ │ списка аргументов │ НЕТ │ Установка времени ожидания │ НЕТ │ └──────┘ Основное назначение оператора FOR - управлять циклическим испол- нением остатка командной строки, в зависимости от значений задаваемых его аргументами. Словами алгоритм его работы можно описать в виде сле- дующего выражения: - "Просмотреть диапазон значений аргументов опера- тора и исполнять остаток строки до тех пор, пока этот диапазон не бу- дет исчерпан, либо пока не встретится исполнения оператора QUIT или GOTO. Стр. 52 3.2.3.1 Argument=START:INCREMENT:END Форма оператора с аргументами в виде START:INCREMENT:END /НАЧАЛО:ШАГ:КОНЕЦ/, представлена на этом примере: ┌─────────────────────────────────────────────────────────────────┐ │ о|>For i=1:1:5 Set sqr=i*i write i," в квадрате = ",sqr,!<- |o │ │ |1 в квадрате 1 | │ │ |2 в квадрате 4 | │ │ o|3 в квадрате 9 |o │ │ |4 в квадрате 16 | │ │ |5 в квадрате 25 | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.22 FOR START:INCREMENT:END Рассмотрим, по шагам, исполнение цикла, управляемого оператором FOR: 1. Оценить значения выражений определяющие START, INCREMENT и END /НАЧАЛО, ШАГ, КОНЕЦ/. 2. Создать переменную, являющуюся счетчиком цикла /i/ и установить ее значение равным значению START. 3. Сравнить значение счетчика цикла с величиной END, если i>END - прекратить исполнение остатка строки /все сделано/. Если нет, то перейти к шагу 4. 4. Исполнить последовательно операторы в остатке командной строки правее оператора IF. 5. Изменить значение счетчика цикла /переменной i/ на величину INCREMENT /ШАГ/ и вернуться к исполнению шага 3. В обеих формах оператора FOR с использованием величин START и INCREMENT /НАЧАЛО и ШАГ/, оценка их значений производится ТОЛЬКО ОДИН раз в начале цикла. Если переменные, определяющие эти выражения будут меняться в цикле, то это НЕ произведет НИ какого влияния на процесс выполнения цикла. Эти значения могут быть целыми числами, и действи- тельными, положительными и отрицательными. Если INCREMENT /ШАГ/ ,будет иметь отрицательное значение, то в вышеописанном алгоритме работы опе- ратора третий шаг будет выглядеть следующим образом: 3. Сравнить значение счетчика цикла с величиной END, если iFor i=5:-1:2 write !,i<- |o │ │ |5 | │ │ |4 | │ │ o|3 |o │ │ |2 | │ │ |>write i<- | │ │ o|2 |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.23 FOR с отрицательным шагом После завершения цикла в счетчике оказывается значение с которым было совершено последнее исполнение остатка строки, а не ТО, которое оказалось больше значения END и привело к завершению цикла. /В данном случае 2, а не 1/. Кроме того, если при инициализации цикла, значение START меньше END, то цикл не будет исполняться ни разу, и значение INCREMENT /ШАГ/ останется равным значению START. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set Start=20,Increment=1,End=15<- |o │ │ |>For I=Start:Increment:End Write !,I<- | │ │ | | │ │ o|>Write "Счетчик цикла = ",I<- |o │ │ |Счетчик цикла =20 | │ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Рис.3.24 Значение счетчика при завершении цикла Еще раз обращаю Ваше внимание на то, что значения START, INCRE- MENT и END вычисляются только один раз, перед началом исполнения цик- ла. Изменение значений переменных, использованных в выражениях, задаю- щих эти значения, внутри цикла, НЕ влияет на процесс исполнения цикла. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Set x="abc" For i=1:1:$l(x) Write $e(x,i) Set x=x_i<- |o │ │ |abc | │ │ | | │ │ o|>Write x<- |o │ │ |abc123 | │ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Рис.3.25 Изменение значения END внутри цикла Обычно, цикл FOR продолжается до тех пор, пока счетчик цикла не превысит значение END, но есть два способа прервать цикл, до достиже- ния этого момента. 1 - исполнение оператора QUIT, 2 - исполнение GOTO. После прерывания цикла по QUIT, счетчик цикла остается равным послед- нему назначенному ему значению. Исполнение будет передано на следующий оператор, расположенный вне действия этого оператора FOR, на следующую строку, или же на этой же, если это "вложенный" цикл. Стр. 54 Исполнение оператора GOTO передает управление на некую метку LA- BEL, определяемую где-то в программе. Посмотрите это на этом примере, отображающем "вложенный" FOR. ┌─────────────────────────────────────────────────────────────────┐ │ о|>F L1=1:5:20 w ! F L2=2:2:8 w L1*L2,"," QUIT:L1*L2>50<- |o │ │ |2,4,6,8, | │ │ |12,24,36,48, | │ │ o|22,44,66, |o │ │ |32,64, | │ │ |> | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.26 "Вложенный" FOR На этом примере, оператор QUIT с постусловием прерывает выполне- ние "внутреннего" цикла, возвращая управление во "внешний" цикл. За- метьте, что выводимые значения превышают 50, так как оператор QUIT с постусловием находится за оператором WRITE. Вы можете также изменить внутри цикла значение счетика цикла, но это опасно. В обоих нижеприве- денных примерах демонстрируется изменение счетчика цикла в "теле" цик- ла, и как результат прерывание цикла, в этом плане этот способ может также предлагается в качестве альтернативного метода прерывания цикла. ┌─────────────────────────────────────────────────────────────────┐ │ о|>For ID=1:1:10 Set A=ID*2,ID=1<- |o │ │ | | │ │ | | │ │ o|>For ID=1:1:30 For ID=1:1:5 Write !,ID<- |o │ │ | | │ └─────────────────────────────────────────────────────────────────┘ Рис.3.27 Изменение счетчика цикла внутри цикла 3.2.3.2 Argument=START:INCREMENT Эта форма использования оператора сходна с вышеописанной, за исключением того, что значение END не зада- ется, и прерывание цикла достигается исполнением либо оператора QUIT, либо GOTO, расположенных внутри /в "теле"/ цикла. Форма использования оператора, представленная на примере, с применением функции $ORDER /которая описана в главе 8/, часто используется для поиска значений среди глобального файла, когда конечное значение элемента неизвестно /количество индексов на одном уровне индексации может меняться в самых широких пределах/. Читатель уже сталкивался с предлагаемой формой ис- пользования функции. ┌─────────────────────────────────────────────────────────────────┐ │ о|>For i=1:1:5 Set sqr=i*i write i," в квадрате = ",sqr,!<- |o │ │ |1 в квадрате 1 | │ │ |2 в квадрате 4 | │ │ o|3 в квадрате 9 |o │ │ |4 в квадрате 16 | │ │ |5 в квадрате 25 | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.28 FOR START:INCREMENT Стр. 55 3.2.3.3 Безаргументный оператор FOR Это дальнейшее сокращения формы задания диапазона изменения аргументов оператора FOR. В этом случае счетчик цикла вообще не определяется, и цикл требуется прервать по ис- полнению внутреннего оператора /GOTO или QUIT/ из "тела" цикла. Этот пример демонстрирует еще одну форм поиска в глобальном файле базы дан- ных. ┌──────────────────────────────────────────────────────────────────┐ │ о|>Set sub="",^TEST(10)=1,^TEST(20)=1,^TEST(30)=1<- |o │ │ |>For Set sub=$Order(^TEST(sub)) Quit:sub="" Write !,sub<-| │ │ |10 | │ │ o|20 |o │ │ |30 | │ │ |> | │ └──────────────────────────────────────────────────────────────────┘ Рис.3.29 FOR без аргументов 3.2.3.3 FOR=EXPRESSION,EXPRESSION,... Оператор FOR не допускает ис- пользование списка аргументов. Поэтому строка вида : FOR I=1:1:10,J=2:2:6 write I*J<- является ОШИБКОЙ. Но, с другой стороны допускается использовать пере- чень значений используемых как переменные в цикле. Каждый из таких элементов может быть любым выражением /EXPRESSION/, включая строки символов, как Вы можете видеть на следующих примерах. ┌──────────────────────────────────────────────────────────────────┐ │ о|>Set sum=0,N=0 For i=230,20,-50,100 Set sum=sum+1,N=N+1<- |o │ │ |>write "sum=",sum," среднее арифметическое=",sum/N,"."<- | │ │ |sum=300 среднее арифметическое=75 | │ │ o|> |o │ └──────────────────────────────────────────────────────────────────┘ Рис.3.30 FOR EXPRESSION,EXPRESSION ┌──────────────────────────────────────────────────────────────────┐ │ о|>Set N=2 For X="Start","By","End" Set @X=N,N=N*N<- |o │ │ |>write Start,", ",By,", ",End<- | │ │ |2, 4, 16 | │ │ o|> |o │ └──────────────────────────────────────────────────────────────────┘ Рис.3.31 Строковые значения, как аргументы FOR 3.2.3.5 Совместное использование различного вида аргументов. Как уже упоминалось, использование списка аргументов в операторе FOR зап- рещено / Например, FOR I=1:1:10,J=2:2:6 write I*J<- является ошибкой /, но каждое значение в списке может быть любой из вышеперечисленных форм задания диапазона значений аргумента FOR. Пример такого смешения типов приведен ниже: Стр. 56 ┌─────────────────────────────────────────────────────────────────┐ │ о|>FOR i=1,5,10:1:13,"abc",2:2:6 write !,i<- |o │ │ |1 | │ │ |5 | │ │ o|10 |o │ │ |11 | │ │ |12 | │ │ o|13 |o │ │ |abc | │ │ |2 | │ │ o|4 |o │ │ |6 | │ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Рис.3.32 Различные виды выражений в списке аргументов FOR 3.3 Операторы передачи управления Операторы передачи управления, как говорится в этой книге, ис- пользуется для передачи управления за пределы исполняемой строки. Та- кая передача управления может осуществляться либо на время, как для операторов GOTO, или XECUTE, в которых, после их исполнения, управле- ния возвращается на следующий за ними оператор, либо полностью, как в случае использования GOTO. Отметим, что проведение отчетливой границы, разделяющей УСЛОВНЫЕ операторы от операторов ПЕРЕДАЧИ УПРАВЛЕНИЯ до- вольно условно. И те, и другие управляют передачей управления в прог- рамме, и только рассмотрение механизма их действия позволяет отнести их к разным группам операторов. В этом разделе обсуждается следующие операторы: GOTO - Передает в программе управление на другую строку, идентифи- цированную меткой, или в другую программу. Управление НЕ возвращается на операторы, следующие за GOTO. DO - Производит вызов программы /процедуры/, аналогично использо- ванию оператора CALL в других языках,и, временно передает в нее управление. По завершению вызванной процедуры, управле- ние возвращается на оператор следующий за оператор DO. QUIT - используется для завершения исполнения последнего оператора FOR, или DO. Управление возвращается либо на следующий за DO оператор, либо на оператор стоящий "вне" цикла завершен- ного FOR. HALT - Инициализирует последовательность действий, связанных с "закрытием" текущего процесса. Все устройства "захваченные" пользователем, закрываются и возвращаются в систему, теку- щий процесс останавливается, раздел освобождается, и возв- ращается в систему. Стр. 57 XECUTE - Действует подобно оператору DO, но при этом операторы MUMPS содержащиеся в строке, или записанные в переменную исполняется, как строка программы. Также, как и для DO после завершения оператора, управление передается на сле- дующий за ним оператор. 3.3.1 Оператор GOTO Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ │ label[+offset] │ GOTO label2 GOTO │ label[+offset]^rname │ GOTO get^PAT │ ^routine name │ GOTO ^PRPAT │ │ │ без аргументов │ GOTO /*1/ ┌──────┐ Сокращение имени оператора │ G │ G ^BUILD Использование постусловий: │ │ при операторе │ ДА │ G:Truth="ok" Calc при аргументе │ ДА │ G Calc:Truth="ok" Использование косвенности │ ДА │ S a="END" G @a Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ G Calc:Ok=1,Setup:Ok=0,Done Установка времени ожидания │ НЕТ │ └──────┘ Оператор GOTO передает управление на определенную метку или прог- рамму. Это абсолютная передача управления, управление НЕ будет возвра- щаться к последующим за GOTO операторам. ┌─────────────────────────────────────────────────────────────────┐ │ о| Start Read !,"Do you really want to HALT (Y/N) : ",ans |o │ │ | If ans'="Y"&(ans'="N") Write " ??" Goto Start | │ │ | If ans="N" Goto Cont | │ │ | Halt | │ │ o| Cont Write !," Continuing ..." |o │ │ | . | │ │ | . | │ │ |----------------------------------------------------------| │ │ o|>Do Start<- |o │ │ |Do you really want to HALT (Y/N) : Maybe<- ?? | │ │ |Do you really want to HALT (Y/N) : N<- | │ │ | Continuing ... | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.33 Оператор GOTO ______________________________________________________________ *1 - Безаргументная форма оператора GOTO стандартом НЕ поддерживается. Вместо нее в большинстве реализаций используется оператор ZGO. Стр. 58 При исполнении оператора GOTO ВНУТРИ процедуры вызванной по опе- ратору DO, на метку расположенную ВНЕ этой процедуры, то происходит эффект "распространения" переданного в эту процедуру уровня исполне- ния. При исполнении первого встреченного QUIT, вне зависимости от ко- личества исполненных к тому моменту GOTO, управление будет возвращено на оператор, следующий за DO, который вызвал эту процедуру. Обратитесь к разделу 6.6 для подробного ознакомления с концепцией УРОВНЯ ИСПОЛНЕ- НИЯ, и рассмотрения ситуаций, когда оператор GOTO может, а когда не может применяться. ┌─────────────────────────────────────────────────────────────────┐ │ о|Start Write !," Вводите положительные значения " |o │ │ | Kill Array Set n=0 | │ │ | For Do Geta Quit:a=" If a>0 Set n=n+1,Array(n)=a| │ │ | Goto Calc | │ │ o|Geta Read !,": ",a If a<0 Goto Error |o │ │ | Quit | │ │ |Error Write "?? только положительные значения" set a=-1 | │ │ | Quit | │ │ |----------------------------------------------------------| │ │ o|>Do Start<- |o │ │ | Вводите положительные значения | │ │ |5 | │ │ |12 | │ │ o| -3 ?? только положительные значения |o │ │ |3 | │ │ | . | │ │ | . | │ │ o| . |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.34 Оператор GOTO из процедуры, вызванной по DO. Когда в этом примере вводится значение -3, управление передается на метку Error. При этом уровень исполнения остается тем же, что был при вызове процедуры Geta. И поэтому QUIT на строке Error+1 имеет тот же эффект, что и QUIT на строке Geta+1. Когда GOTO исполняется в "теле" цикла FOR, то при это прекращают- ся не только текущий FOR, но и ВСЕ циклы расположенные ЛЕВЕЕ него на командной строке, с последующей передачей управления на указанную точ- ку. Стр. 59 ┌─────────────────────────────────────────────────────────────────┐ │ о|Start W ! F i=1:1:10 F j=1:1:10 W " ",i*j G:i*j>8 Done |o │ │ | W " Это строка НЕ будет исполняться" | │ │ |Done W " Выполнено" | │ │ | Quit | │ │ |----------------------------------------------------------| │ │ o|>Do Start<- |o │ │ | | │ │ | 1 2 3 4 5 6 7 8 9 Выполнено | │ │ o|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.35 GOTO для завершения циклов FOR Этот пример представлен в формате слегка отличающемся от ранее использованного. MUMPS команды приведены в допустимых сокращениях, и кроме того, сочетание в операторах больших и малых букв не имеет зна- чения для их распознавания. Подробности о допустимом синтаксисе опера- торов даны в разделах главы 3. Обратите внимание на то, что на экране отображается значение вы- ражения i*j>8, так как оператор WRITE предшествует GOTO. И еще, Вы должны увидеть, что GOTO прерывает исполнение обоих циклов. 3.3.1.1 Argument=LABEL В этой форме оператора, управление передает- ся на строку определенную меткой LABEL. Строка с меткой LABEL должна быть среди строк текущей программы находящихся в разделе, если такая метка не будет найдена, будет сгенерировано сообщение об ошибке. При работе в непосредственном режиме программа может быть загружена в раз- дел /как на примере ZLOAD TEST/ и запустить ее на исполнение с указан- ной метки. Заимствуя текст программы из предыдущего примера, мы можем запустить ее на исполнение следующим образом: ┌─────────────────────────────────────────────────────────────────┐ │ о|>ZLOAD TEST Goto Start<- |o │ │ | | │ │ | 1 2 3 4 5 6 7 8 9 Выполнено | │ │ o|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.36 GOTO на метку LABEL 3.3.1.2 Argument=LABEL^RNAME В этой форме оператора вы можете за- пустить на исполнение программу, не загружая ее предварительно с диска в раздел. Указанная программа сама будет загружена в раздел и запущена /или продолжена, если Вы работаете в прикладном режиме/ с метки LABEL. ┌─────────────────────────────────────────────────────────────────┐ │ о|>Goto Start^TEST<- |o │ │ | | │ │ | 1 2 3 4 5 6 7 8 9 Выполнено | │ │ o|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.36 GOTO на метку LABEL в программе ROUTINE Стр. 60 3.3.1.3 Argument=^RNAME Эта форма является сокращением вышеприве- денной формы использования оператора GOTO. При ее использовании управ- ление будет передаваться на первую строку указанной программы. Также как и в предыдущем примере, программа будет автоматически загружена в раздел. 3.3.1.4 Использование без аргументов. Безаргументный GOTO не входит в стандарт языка, но часто используется в различных реализациях MUMPS. В большинстве случаев он используется вместе с оператором BREAK, или в процедурах обработки ошибок. Оператор BREAK используется главным обра- зом при отладке программ, для временного прерывания их работы, в целях изучения состояния локальных переменных, изменения их значений и т.п. При таком временном прерывании работы программист имеет возможность в непосредственном режиме произвести любые действия, а затем, использо- вав GOTO без аргументов продолжить исполнение программы. Этот момент не является согласованным методом продолжения приостановленной прог- раммы, но большинство версий приняло именно его. В этом аспекте здесь больше подходит определить этот оператор, как GO, вместо GOTO. ┌─────────────────────────────────────────────────────────────────┐ │ о|Test ;Демонстрация использования BREAK и GOTO |o │ │ | ; | │ │ | Set a=22,b=3 w !," Начало работы " Break | │ │ | Write !,"a/b= ",a/b | │ │ o| Quit |o │ │ |----------------------------------------------------------| │ │ |>Do ^TEST<- | │ │ |Начало работы | │ │ o| |o │ │ |<> | │ │ |>Write a<- | │ │ |22 | │ │ o|>Set a=6<- |o │ │ |>GOTO<- | │ │ |a/b= 2 | │ │ |> | │ │ o| |o │ └─────────────────────────────────────────────────────────────────┘ Рис.3.38 Безаргументный GOTO Заметим, что оператор BREAK выводит также сообщение, сообщающее о том, что компьютер исполняет режим временного прерывания исполнения программы. И непосредственный режим, в котором Вам предлагается рабо- тать, отличается от обычного, наличие подсказки отладчика, перед обыч- ным приглашением системы к работе в непосредственном режиме./ Выглядит обычно как 'BREAK >', или 'Debug >', вместо '>' / После ввода операто- ра GOTO без аргументов / сокращено до 'G' /, исполнение программы про- должается. В реализациях поддерживающих эту возможность, без аргумент- ный GOTO разрешен только в непосредственном режиме, и только для про- должения приостановленной оператором BREAK программы. Стр. 61 Впрочем, не все реализации используют этот метод продолжения при- остановленной программы. В них может применяться и оператор ZGO и дру- гие методы, используя конкретную реализацию, и столкнувшись с тем, что предложенная возможность или приводит к ошибке, или не исполняется, обратитесь к технической документации. В разделе 13.1, где обсуждается возможность обработки ошибок MUMPS программ, и, в связи с этим аспекты использования GOTO без аргу- ментов, для продолжения программы, после обработки ситуации ошибки. 3.3.1.5 Оператор GOTO передающий управление на метку со смещением. Во всех формах оператора GOTO с аргументами /GOTO LABEL, GOTO LA- BEL^RNAME, GOTO^RNAME/, Вы можете использовать параметр Offset /смеще- ние/, для указания строки на которую необходимо передать управление. Смещение интерпретируется как целое, неотрицательное число /0 допус- тим/, представляющий номер строки, отсчитываемой после метки LABEL /или отсчитывая с начала программы в форме GOTO ^RNAME/, на которую передается управление. ┌─────────────────────────────────────────────────────────────────┐ │ о|Test ;Демонстрация GOTO со смещением |o │ │ | Write !,"0 -выход, 1-режим1, 2-режим2, 3-режим3" | │ │ |Loop Read !,"Введите режим (0-3): ",opt | │ │ o| If opt<0!(opt>3) Write " ??" Goto Test |o │ │ | Goto Branch+opt | │ │ |Branch Write !,"Конец работы" Quit | │ │ o| Write !,"Это 1-й режим" Goto Loop |o │ │ | Write !,"Это 2-й режим" Goto Loop | │ │ | Write !,"Это 3-й режим" Goto Loop | │ │ o| |o │ │ | | │ │ |----------------------------------------------------------| │ │ o|>Do ^TEST<- |o │ │ | | │ │ |0 -выход, 1-режим1, 2-режим2, 3-режим3 | │ │ o|Введите режим (0-3): 2<- Это 2-й режим |o │ │ |Введите режим (0-3): 0<- Конец работы | │ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Рис.3.39 GOTO на метку со смещением Эту форму следует применять очень внимательно, так как не всегда ясно, куда происходит ветвление передачи исполнения при использовании сходных строк после LABEL. Кроме того, очень легко совершить ошибку при редактировании программ, например, при разбиении строки на две, при внесении дополнений, в сочетании с использованием GOTO со смещени- ем может привести к непредсказуемым последствиям в ходе исполнения программы. Стр. 62 3.3.2 Оператор DO Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ │ label[+offset] │ DO label2 DO │ label[+offset]^rname │[(parametr,..)] DO get^PAT │ ^routine name │ DO ^PRPAT │ │ │ без аргументов │ DO Set a=1.. ┌──────┐ Сокращение имени оператора │ D │ D ^BUILD Использование постусловий: │ │ при операторе │ ДА │ D:Truth="ok" Calc при аргументе │ ДА │ D Calc:Truth="ok" Использование косвенности │ ДА │ S a="END" D @a Обязательность аргументов │ НЕТ │ Разрешенность использования │ │ списка аргументов │ ДА │ D Calc:Ok=1,Setup:Ok=0,Done Установка времени ожидания │ НЕТ │ └──────┘ Оператор DO в MUMPS представляет основную форму вызова подпрог- рамм. Точка входа в процедуру определяется аргументами оператора и идентифицируется по метке в теле процедуры LABEL. Вызываемая процедура завершается по оператору QUIT, после чего управление возвращается на следующий, после DO, оператор в строке программы. Вызываемая подпрограмма может быть программой /или ее частью/, размещенной на диске, или процедурой из текущей программы. В любом случае вызываемая подпрограмма будет работать с той таблицей символов, которая существовала до ее вызова по оператору DO. Все переменные, созданные в подпрограмме, и не удаленные оператором KILL по заверше- нию, будут доступны вызывающей программе. Это обстоятельство является значительным отклонением от общепринятого управления вызовом подпрог- рамм. В большинстве других языков вызываемые процедуры начинают рабо- тать в новом, пустом, окружении переменных, и им доступны только зна- чения, передаваемые в качестве параметров. 3.3.2.1 Оператор DO без передачи параметров. В этом разделе мы рассмотрим процесс работы оператора DO в сравнительно простых услови- ях, без передачи параметров в вызываемую процедуру. Предположим, что рассматриваемая в примере программа DEMO уже создана и записана на диске. В приводимом листинге программы, слева от него отмечены номера строк, для того, чтобы облегчить ссылки из текста на конкретную строку программы. Стр. 63 ┌─────────────────────────────────────────────────────────────┐ 1 │ о|Demo ;Дж.Левкович 12/12/86 |o │ 2 │ | ;Демонстрация использования DO | │ 3 │ | Kill For i=1:1:10 Set array(i)=i*2 | │ 4 │ | Do Average Write " Среднее = ",AVG | │ 5 │ o| Kill array,AVG,i |o │ 6 │ | Quit | │ 7 │ |Average Set sum=0 | │ 8 │ | For i=1:1:10 Set sum=sum+array(i) | │ 9 │ o| Set AVG=sum/i Kill sum,i |o │ 10│ | Quit | │ | | │ └─────────────────────────────────────────────────────────────┘ Рис.3.40 DO без передачи параметров Это пример очень простой программы. Строки 1-6 составляют основ- ное ТЕЛО программы, строки 7-10 - это маленькая процедура, вызываемая для вычисления среднего значения из 10 чисел записанных в локальном массиве Array. Рассмотрим как работает эта программа, строка за стро- кой. Тело программы. 1. Первая строка программы, начинается, по соглашению, с метки, ко- торая совпадает с именем программы. Далее, за знаком комментария обычно указывают инициалы автора программы, и дату /время/ ее созда- ния /редактирования/. 2. Вторая строка, опять же по соглашению, обычно содержит краткое описание назначения программы. 3. Третья строка начинается с оператора KILL, который удаляет ВСЕ ЛОКАЛЬНЫЕ переменные, а затем создается локальный массив array с де- вятью значениями. 4. В этой строке производится вызов процедуры, и, затем выводится на экран значение, вычисленное в этой процедуре. Заметим, что результат возвращается в переменной AVG, которая создается в вызываемой проце- дуре. Программист должен хорошо знать какие переменные используются в программе и в вызываемой процедуре. Если процедура будет пытаться использовать неопределенные ранее переменные, или в ней будут уда- ляться переменные, позднее используемые в основной программе, то при работе программы произойдет ошибка. 5. Удаляются из таблицы переменные, которые в дальнейшем не будут использоваться. Это не является обязательной и существенной частью программы, но представляет собой пример хорошего стиля в программи- ровании. /Если Вы уверены, что переменные больше не понадобятся - удаляйте их, освобождая тем самым место в таблице локальных перемен- ных раздела./ Стр. 64 6. Оператор QUIT в этой строке завершает исполнение тела программы DEMO. Если Вы запускали программу из непосредственного режима, то Вы вернетесь обратно к подсказке системы в непосредственном режиме. Ес- ли же нет, то управление будет возвращено в программу, из которой был произведен вызов этой программы. /DEMO/ Процедура Average 7. Строка начинается меткой с именем процедуры и устанавливает на- чальное значение переменной Sum равное 0. /Программист должен знать, что вызываемая процедура Average использует, назначает, две перемен- ные: Sum и AVG и их значения, если они были бы использованы в теле программы, будут изменены при возврате из процедуры./ 8. Производится цикл по всем значениям массива ary, с вычислением их суммы. /Значения всех элементов суммируются с значением счетчика Sum/ 9. В этой строке создается переменная AVG, в которую заносится зна- чение среднего из элементов массива ary. Далее KILL все переменные, которые были созданы и использованы в процедуре, кроме той, которая необходима для возврата значения из процедуры. 10. Оператор QUIT в этой строке завершает процедуру и управление пе- редается в тело основной программы, на оператор WRITE, который сле- дует за оператором DO, вызвавшем процедуру Average. Запуск программы на исполнение, производимый из непосредственного режима будет выглядеть следующим образом: ┌───────────────────────────────────────────────────────────────┐ │о|>Do ^DEMO<- |o │ │ |Среднее = 11 | │ │о|> |o │ └───────────────────────────────────────────────────────────────┘ Рис. 3.41 Пример запуска программы на исполнение Команда Do ^DEMO, указывает системе MUMPS на необходимость заг- рузки с диска в раздел программы с именем DEMO, и, затем начать ее ис- полнение с первой строки. При этом исполняются все описанные выше ша- ги, на экран выводится результат вычисления среднего значения, и уп- равление возвращается в непосредственный режим. В этой команде стрелка вверх '^' - /глобалик/ указывает на то, что программа расположена на диске и должна быть скопирована в раздел для запуска на исполнение. Если же программа уже была в разделе /То есть предварительно была ис- полнена команда ZLOAD DEMO/, то для ее запуска необходима команда 'Do DEMO'. В этом случае исполнение началось бы с метки DEMO. /В данном случае это дает один и тот же результат, так как, по соглашению, пер- вая строка начинается с метки, которая совпадает с именем программы./ Стр. 65 Очень важно отметить, что в данном формате использования операто- ра, вызывающая программа и вызываемая ее процедура должны согласованно использовать локальные переменные с которыми они работают, ибо они ра- ботают с ОДНОЙ таблицей локальных переменных. Все изменения производи- мые процедурой в составе локальных переменных оказывают влияние на вы- зывающую программу. Необходимо особо остановиться на возврате управления после QUIT в процедуре в тело основной программы. Вызов процедуры может быть "вло- женным", вызываемая процедура в свою очередь может также производить вызов следующей процедуры и т.д.. Цепочка последовательных вызовов процедур именуется "вложенностью" или стеком исполнения. Но как каждая вложенная процедура определяет момент своего завершения? Мы уже упоми- нали, что "Процедура возвращает управление в вызывающую программу, при исполнении оператора QUIT". Теперь мы можем дополнить и расширить это определение следующей формулировкой: "исполнении оператора QUIT на том же уровне исполнения вызванной процедуры." На примере продемонстрируем это положение: ┌─────────────────────────────────────────────────────────────┐ 1 │ о|Demo ;Демонстрация "вложенного" DO |o │ 2 │ | ; | │ 3 │ | Kill For i=1:1:10 Set ary(i)=i*2 | │ 4 │ | Do Average Write " Среднее = ",AVG | │ 5 │ o| Kill ary,AVG,i |o │ 6 │ | Quit | │ 7 │ |Average Set sum=0 | │ 8 │ o| For i=1:1:10 Set X=ary(i) Do Value |o │ 9 │ | Set AVG=sum/i Kill sum,i | │ 10│ | Quit |o │ 11│ |Value Write !," Значение данного ",i," = ",X | │ 12│ | Set sum=sum+X | │ 13│ | Quit ; | │ │ | | │ └─────────────────────────────────────────────────────────────┘ Рис.3.42 Завершение подпрограмм В этом примере в процедуре Average производится вызов еще одной процедуры Value, которая производит вывод данных перед их суммировани- ем в счетчике. Quit в строке 13 возвращает управление на строку 9, вслед за которой исполняется строка 10. Оператор QUIT исполняемый в строке 10 возвращает управление в строку 4. Следует подчеркнуть, что не все операторы QUIT встречающиеся в программе приводят к завершению работы процедур и возвращению управления в точку вызова. QUIT в цикле FOR приводит к прекращению выполнения цикла, но не к прекращению ис- полнения процедуры. Стр. 66 ┌─────────────────────────────────────────────────────────────┐ 1 │ о|Demo ;Демонстрация "вложенного" DO |o │ 2 │ | ; | │ 3 │ | Kill For i=1:1:10 Set ary(i)=i*2 | │ 4 │ | Do Average Write " Среднее = ",AVG | │ 5 │ o| Kill ary,AVG,i |o │ 6 │ | Quit | │ 7 │ |Average Set sum=0 | │ 8 │ o| For i=1:1:10 Set X=ary(i) Quit:X>10 Do Value |o │ 9 │ | Set AVG=sum/i Kill sum,i,X,N | │ 10│ | Quit |o │ 11│ |Value Write !," Значение данного ",i," = ",X | │ 12│ | Set sum=sum+X | │ 13│ | Q ; | │ │ | | │ └─────────────────────────────────────────────────────────────┘ Рис.3.43 QUIT завершает цикл FOR и подпрограмму В этой программе процедура Average слегка модифицирована, так, что вызов процедуры Value будет производиться только до тех пор, пока значения не превышают 10. Как только встречается значение большее 10 цикл FOR прерывается по QUIT. Этот QUIT оказывает влияние только на исполнение цикла, но не на исполнение процедуры Average. UIT в строке 6 прерывает исполнение собственно тела основной программы /DEMO/. Если эта программа вызвана на исполнение не из не- посредственного режима, и в вызывающей ее программе будут использованы какие либо из следующих переменных: AVG, i или ary, то произойдет ошибка вида . QUIT в строке 10 завершает исполнение процедуры Average, если он будет пропущен, то процедура Value будет ошибочно исполняться всякий раз, как вызывается процедура Average. /*1/ QUIT с наложенным на него постусловием в строке 8 приводит к преждевременному, /до исчерпывания аргументов FOR/ прекращению испол- нения цикла. QUIT в строке 13 завершает исполнение процедуры Value. Здесь он мог бы быть опущен, так как в последней строке программы исполняется неявный QUIT. Но это ухудшает "читаемость" программы и не является признаком "хорошего стиля". Дополнительные пояснения по к использованию оператора QUIT даны в разделе 3.3.3. __________________________________________________________________ *1 - Для того, чтобы лучше различать процедуры, в тексте программ сле- дует оставлять между ними пустую строку. Или, что еще лучше, так как приведет к возникновению ошибки на отладке, вставлять между процедура- ми строку с каким то символом, например дефисом./тире/ Тогда процедуры у Вас никогда не будут проваливаться из одной в другую. Стр. 67 3.3.2.2 Оператор DO с передачей параметров в вызываемую процедуру. В этой форме использования оператора Do MUMPS предоставляет Вам воз- можность избежать проблем связанных с "пересечением" имен в вызывающей программе и вызываемой ею процедуре. В этом случае обеспечивается воз- можность передачи параметров в вызываемую процедуру и также использо- вание оператора NEW. Эти возможности, связанные также и с вызовом внешних функций подробно рассматриваются в главе 9. Оператор NEW тесно связан с использованием операторов DO и QUIT. Программисту необходимо для его эффективного и обоснованного использо- вания, хорошо уяснить концепцию вложенности вызовов и уровней исполне- ния DO. При передаче параметров оператор NEW исполняется неявно, сей- час мы вкратце опишем механизм его работы. Читателей, желающих полу- чить более подробную информацию, отсылаем к разделу 3.1.3. Этот оператор имеет синтаксис сходный с синтаксисом использования KILL, и некоторое внешнее сходство с ним же. Этот оператор позволяет программисту выборочно переназначать часть переменных в вызываемой процедуре, так что при ее завершении их прежние значения будут восста- новлены. Наиболее часто NEW ставится в начале процедуры, для того, чтобы позволить переопределить в ней "пересекающиеся" переменные. Все значения переменных указанных как аргументы NEW /в том числе и локаль- ные массивы/ сохраняются в отдельной области оперативной памяти, и за- тем удаляются из раздела. Теперь процедура может использовать перемен- ные /и массивы/ с этими именами без опасения за пересечения с основной программой. Когда на этом же уровне исполнения встретится оператор QU- IT, /то есть при нормальном возврате управления в вызывающую програм- му/, все созданные в процедуре переменные с именами упомянутыми в ар- гументе NEW, будут удалены. И им будут назначены сохраненные значения. Если переменная перед сохранением была неопределена, то она и останет- ся неопределенной при возврате в вызывающую программу. Это позволяет избежать узких мест при конструировании независимых процедур. Когда используется оператор NEW можно быть уверенным, что ни одна из создаваемых и используемых переменных не скажется на работе вызывающей программы. Однако необходимо предусмотреть передачу в эту процедуру извне параметров определяющих ее работу. Одним из путей яв- ляется передача значений в эту процедуру как параметров при ее вызове, с одновременным изменением назначаемых этим параметрам имен. Когда используется вызов процедуры с передачей параметров, то список этих параметров заключается в круглые скобки непосредственно за именем вызываемой процедуры. /LABEL, LABEL^ROUTINE, ^ROUTINE/ в опера- торе DO. Кроме того, в подпрограмме LABEL должен быть также указан со- ответствующий список параметров, также заключаемый в круглые скобки. Стр. 68 ┌─────────────────────────────────────────────────────────────┐ │ о|Square(Value) ;Вычисление квадрата числа |o │ │ | NEW SQR | │ │ | Set SQR=Value*Value | │ │ o| Write !,Value," в квадрате = ",SQR |o │ │ | Quit | │ │ | | │ │ o|------------------------------------------------------|o │ │ |>For i=1:1:3 Do Square(i)<- | │ │ | | │ │ o|1 в квадрате = 1 |o │ │ |2 в квадрате = 4 | │ │ |3 в квадрате = 9 | │ │ o|> |o │ └─────────────────────────────────────────────────────────────┘ Рис.3.44 Оператор DO с передачей параметров Параметры, упомянутые в списке при операторе DO называются ДЕЙС- ТВИТЕЛЬНЫМИ параметрами, а те, что следуют в тексте вызываемой проце- дуры за меткой, - ФОРМАЛЬНЫМИ параметрами. В списке действительных па- раметров возможно использование других имен, чем использованные в списке формальных параметров. В списке действительных параметров можно указывать не только имена переменных, но и любые допустимые MUMPS вы- ражения. В списке формальных параметров допустимо использование любых имен переменных или неиндексированных массивов. В предлагаемом примере i - [Square(i)] является именем действительного параметра, а Value - [Square(Value)] - именем формального параметра. Когда начинает исполняться вызываемая процедура, все переменные с именами перечисленными в списке формальных параметров неявно сохраня- ются по NEW. Так что если при вызове процедуры в разделе есть перемен- ные с именами упомянутыми в списке формальных параметров этой процеду- ры, они будут сохранены. Переменным с именами из списка формальных па- раметров будут назначены соответствующие значения из списка действи- тельных параметров. /Переменной с первым именем из списка формальных параметров будет назначено первое значение из списка действительных параметров, второму имени переменной - второе значение, и так далее./ Кроме того, в указанном примере используется сохранение по NEW переменной с именем SQR, для того, чтобы избежать возможного пересече- ния пересечения этой переменной создаваемой в теле процедуры и из ос- новного тела программы. Параметры могут передаваться в процедуру либо в виде значения, либо в виде ссылки. Параметр, передающий ЗНАЧЕНИЕ может представляться любым допустимым выражением, передача ССЫЛКИ допустима только в виде имени переменной. При передаче ссылки может быть указано как имя пере- менной, так и имя локального массива, но нет возможности в этом случае указать индексированную переменную или указать ссылку на глобальную переменную /как индексированную, так и неиндексированную/. Стр. 69 Передача параметров в виде ссылки указывается в списке действи- тельных параметров добавлением точки '.' перед значением параметра. В этом случае переменная с именем указанном в списке действительных па- раметров связывается с именем переменной из списка формальных парамет- ров. Все изменения такой переменной производимые во время исполнения процедуры, возвращаются в вызывающую программу. При возврате управле- ния обратно, все действия произведенные с переменной с именем из спис- ка формальных параметров отразятся на соответствующей переменной из списка действительных параметров. Глава 9 предоставит читателю допол- нительные подробности об этом процессе. Но если Вы хотите запустить на исполнение процедуру, в которой указан список формальных параметров, без передачи в нее параметров, то все равно придется за именем этой процедуры ввести круглые скобки, без указания действительных параметров, заключив в них значение, равное пустой строке (""). ┌─────────────────────────────────────────────────────────────┐ │ о|Square(Value) ;Вычисление квадрата числа |o │ │ | Set:'$d(Value) Value=0 | │ │ | Set SQR=Value*Value | │ │ o| Write !,Value," в квадрате = ",SQR |o │ │ | Quit | │ │ | | │ │ o|------------------------------------------------------|o │ │ |>Do Square(4)<- | │ │ |4 в квадрате = 16 | │ │ o|>Do Square()<- |o │ │ |0 в квадрате = 0 | │ │ |>Do Square<- | │ │ o| ERROR |o │ │ |> | │ └─────────────────────────────────────────────────────────────┘ Рис.3.45 Списки формальных и действительных параметров Этот пример указывает на необходимость внутри процедуры произво- дить проверку на наличие передаваемых параметров. Потому, что если па- раметр в процедуру не передается, то соответствующая переменная из списка формальных параметров будет неопределена. В этом примере функ- ция $DATA /раздел 8.2.1/ используется для того, чтобы установить зна- чение принимаемое по умолчанию, если переменная не определена. В этих же целях можно использовать и функцию $GET. 3.3.2.3 Оператор DO с использованием смещения. Так же как и в опе- раторе GOTO, Вы можете после указания имени и метки /в формах LABEL, LABEL^ROUTINE или ^ROUTINE/, указать необязательный параметр смещение - Offset. В этом случае Вы не сможете использовать передачу парамет- ров, так в этой форме использования оператора DO, они не передаются. Смещение интерпретируется как целое, неотрицательное число /Включая ноль/, которое указывает на физический номер строки после метки с ко- торой будет начинать исполняться вызываемая подпрограмма /процедура/. Стр. 70 Здесь стоит отметить, что как и в случае использования такой фор- мы в операторе GOTO, с ней следует быть очень осторожным и вниматель- ным. Обратитесь к разделу 3.3.1.5 где подробно описаны узкие места ис- пользования смещения при передаче управления. 3.3.2.4 Оператор DO без аргументов. Безаргументная форма оператора DO используется для начала исполнения ВЛОЖЕННОГО блока строк програм- мы. Для рассмотрения этой формы оператора DO требуется знакомство с допустимым синтаксисом командной строки и концепцией уровня исполне- ния, которые рассматриваются в дальнейшем. Поэтому, описание использо- вание этой формы оператора DO и, так называемой, блочной структуры, дано в разделе 6.6. 3.3.3 Оператор QUIT Оператор │ Аргументы Примеры _________│______________________________________ ____________ │ │ │ без аргументов │ QUIT ; QUIT │ EXPRESSION │ QUIT ans*4 │ │ ┌──────┐ Сокращение имени оператора │ Q │ Q ; Использование постусловий: │ │ при операторе │ ДА │ Q:Truth ; при аргументе │ НЕТ │ Использование косвенности │ ДА │ Quit @ans Обязательность аргументов │ НЕТ │ Разрешенность использования │ │ списка аргументов │ НЕТ │ Установка времени ожидания │ НЕТ │ └──────┘ Оператор QUIT используется для завершения исполнения процессов, инициируемых операторами DO, XECUTE или FOR, а также для завершения внешних функций. Когда QUIT исполняется внутри цикла охватываемого FOR он определяет его завершение и передачу управления на следующий опера- тор стоящий ВНЕ этого цикла. QUIT исполняемый вне цикла FOR приводит к завершению последнего DO, или к завершению исполнения аргумента XECU- TE, или же к завершению последней вызванной внешней функции. Оператор QUIT всегда будет неясно исполняться в последней строке текущей прог- раммы, но рекомендуется всегда, для завершения программы использовать явно указываемый QUIT, для того, чтобы четко отметить место окончания программы. 3.3.3.1 Оператор QUIT без аргументов. Такая форма используется для завершения исполнения операторов DO, FOR, XECUTE. Когда QUIT встреча- ется внутри цикла, то прекращается исполнение самого внутреннего цик- ла, и управление передается на первый оператор стоящий вне прерванного цикла. Стр. 71 ┌─────────────────────────────────────────────────────────────────┐ │ о|Test For Read !," Число = ",num Quit:num="" Do Square|o │ │ | Quit | │ │ |Square Write !," квадрат = ",num*num | │ │ o| Quit |o │ │ |----------------------------------------------------------| │ │ |>Do ^Test<- | │ │ o|Число = 2 квадрат = 4 |o │ │ |Число = 3 квадрат = 9 | │ │ |Число = <- | │ │ o|> |o │ └─────────────────────────────────────────────────────────────────┘ Рис. 3.46 Безаргументный QUIT завершающий подпрограмму Пример 3.46 демонстрирует применение QUIT использованный и для завершения работы цикла FOR и окончания исполнения процедуры. Цикл FOR в первой строке будет исполняться до тех пор, пока условие при опера- торе QUIT /постусловие/ в результате оценивания даст ИСТИНУ /логичес- кую 1/, что определит исполнение оператора QUIT и прерывание цикла. /В настоящем примере постусловие будет истинно, если пользователь нажмет в ответ на запрос. Если постусловие в результате оценивания дает ЛОЖЬ, то оператор QUIT игнорируется и исполняется следующий за ним оператор в строке - Do Square/ Заметим, что оператор QUIT, и следующий за ним оператор DO разде- ляют ДВА пробела. Это обусловлено тем, что безаргументные операторы /а в данном случае <:num=""> является постусловием, а не аргументом опе- ратора/, требуется отделять двумя пробелами от следующего оператора в строке. Один пробел символизирует опущенные аргументы оператора, вто- рой - это разделитель между операторами строки. /MUMPS допускает ис- пользовать между операторами, перед ними, любое количество пробелов и знаков табуляции - так что Вы можете писать программы с любым коли- чеством отступов, в виде подобном исходным текстам Cи, Паскаль../ Син- таксис командной строки и синтаксис обобщенного оператора языка предс- тавлен в главе 6. На следующем примере демонстрируется эффект прерывания "вложенно- го" цикла с использованием QUIT. ┌──────────────────────────────────────────────────────────────┐ │ |>For i=1:1 Quit:i>5 For j=1:1 Quit:j>2 Write " ",i*j<- | │ │o| 1 2 2 4 3 6 4 8 5 10 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.47 QUIT прерывающий цикл FOR Еще раз обратим Ваше внимание на то, что оба QUIT в этом примере, отделяются от последующих операторов двумя пробелами. Внешний цикл FOR /FOR i=1:1 .../, являясь бесконечным циклом, благодаря использованию QUIT с постусловием будет исполняться до тех пор, пока значение счет- чика цикла - переменной i не превысит 5. /Т.е. i=1,2,3,4,5/ Стр. 72 Внутренний цикл /FOR j=1:1/, в связи с использованием QUIT с пос- тусловием, будет исполняться два раза на каждом проходе внешнего цик- ла. Исполняемы й в нем оператор QUIT / Quit:j>2 / прерывает только внутренний цикл, не оказывая влияния на внешний. Когда QUIT встречается вне непосредственно цикла FOR /То есть НЕ содержится в остатке строки, следующей за оператором FOR/ то он дейс- твует применительно к последнему использованию операторов DO или XECU- TE, процессы начатые ими завершаются, а управление возвращается на оператор следующий за ними в текущей строке, или на следующую строку. /если это были последние операторы в строке/ Если эти DO или XECUTE были использованы из непосредственного режима, управление будет возв- ращено в непосредственный режим. ┌──────────────────────────────────────────────────────────────┐ │ |Test ;QUIT завершающий процедуру и цикл FOR | │ │ | Write " Вычисление 'n' в степени 'p'",! | │ │o| For Read !,"n=",n Quit:n="" Read " p=",p Do Pow |o│ │ | Quit | │ │ |Pow If p<1 Write" ??" Quit | │ │o| Set x=n For i=1:1 Quit:i=p Set x=x*n |o│ │ | Write " Результат = ",x | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Do ^Test<- | │ │ | Вычисление 'n' в степени 'p' | │ │o|n=3<- p=2 Результат=9 |o│ │ |n=3<- p=3 Результат=27 | │ │ |n=3<- p=0 ?? | │ │o|n=<- |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.48 QUIT прерывающий цикл FOR не завершает программу. Необходим еще один оператор QUIT. Этот пример достаточно ясен и не требует дополнительных разъясне- ний, но требуется отметить, что QUIT во второй строке процедуры Pow оканчивает исполнение цикла FOR, но не саму процедуру. Оператор QUIT в строке Test+3 возвращает управление назад в непосредственный режим. 3.3.3.2 Argument=EPRESSION. Вторая форма оператора QUIT использует в качестве своего аргумента выражение EXPRESSION. Эта форма использу- ется для возвращения значения из внешней функции. Внешняя функция представляет собой некую разновидность вызова процедуры по DO с пере- дачей параметров. /См. раздел 3.3.3.2/, основное отличие вызова внеш- ней функции состоит в том, что она возвращает значение непосредствен- но, а не через переменные. Вызов внешней функции представляется в сле- дующем формате: $$EXTRFUNC(PARAMETR,PARAMETR,...) Стр. 73 где EXTRFUNC - имя этой функции в любом из допустимых форматов (LABEL, LABEL^ROUTINE или ^ROUTINE), а PARAMETR,PARAMETR - список па- раметров передаваемых в функцию. Также как и при использовании вызова процедуры с передачей параметров по DO, в тексте внешней функции дол- жен быть список параметров, соответствующий списку передаваемых в функцию параметров. Список параметров в тексте функции именуется спис- ком ФОРМАЛЬНЫХ параметров, список параметров при ее вызове - списком ДЕЙСТВИТЕЛЬНЫХ параметров. При вызове функции на имена переменных ука- занные в списке формальных параметров. Как уже упоминалось, основное отличие использования внешней функ- ции от вызова процедур с передачей параметров по DO, состоит в способе возврата значений. Внешняя функция возвращает результат своей работы непосредственно в виде значения, а не через переменные, которые будут их содержать. Представим в качестве иллюстрации: ┌──────────────────────────────────────────────────────────────┐ │ |Start For i=1:1:4 Write !,$$Cube(i) | │ │ | Quit | │ │o|Cube(Data) ;Возвращает "куб" данного |o│ │ | Set Data=Data*Data*Data | │ │ | Quit Data | │ │o| |o│ │ |----------------------------------------------------------| │ │ |>Do Start<- | │ │o| |o│ │ |1 | │ │ |8 | │ │o|27 |o│ │ |64 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.49 QUIT с аргументом возвращает значение из внешней В этом примере, при вызове функции $$Cube(i), управление переда- ется на метку Cube с передачей параметра i. Эта функция вычисляет зна- чение куба числа /i/, и возвращает его назад непосредственно. /Опера- тор QUIT завершающий исполнение внешней функции используется с пара- метром/. При исполнении оператора WRITE производится вывод на экран значения, ВОЗВРАЩАЕМОГО внешней функцией $$Cube(i). Можно привести бо- лее простой пример функции возвращающий куб данного: ┌──────────────────────────────────────────────────────────────┐ │o|Cube(Data) Quit Data*Data*Data |o│ └──────────────────────────────────────────────────────────────┘ Рис.3.49 Самая простая внешняя функция возвращающая куб значения Более подробно использование внешних функций обсуждается в главе 9. Стр. 74 3.3.4 Оператор HALT Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ HALT │ без аргументов │ Halt │ │ ┌──────┐ Сокращение имени оператора │ H │ H Использование постусловий: │ │ при операторе │ ДА │ H:Done ... при аргументе │ НЕТ │ Использование косвенности │ НЕТ │ Обязательность аргументов │ НЕТ │ /*1/ Разрешенность использования │ │ списка аргументов │ НЕТ │ Установка времени ожидания │ НЕТ │ └──────┘ Оператор HALT производит завершение процесса /"закрытие" зада- ния/, освобождаются все таблицы блокированных переменных /см описание оператора LOCK/, очищается раздел, в котором производилась работа и он возвращается в систему MUMPS. На этот оператор можно накладывать пос- тусловие, определяющее его исполнение. Также, как и все безаргументные операторы, после оператора HALT /или после его постусловия/, необходи- мо для его отделения от следующих операторов ставить ДВА пробела. При исполнении оператора HALT одновременно исполняется неявно ис- полняется оператор CLOSE освобождающий все устройства захваченные зак- рываемым процессом, включая основное устройство задания. ┌──────────────────────────────────────────────────────────────┐ │ |ChkDon Read !," Действительно СТОП? (Y или N): ",ans | │ │ | If ans="Y" Halt | │ │o| Quit |o│ │ |----------------------------------------------------------| │ │ |>Do ChkDon<- | │ │o| |o│ │ | Действительно СТОП? (Y или N): N<- | │ │ |>Do ChkDon<- | │ │o| |o│ │ | Действительно СТОП? (Y или N): Y<- | │ │ |<> | │ │ | | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.51 Оператор HALT __________________________________________________________________ *1 Отсутствие аргументов является основным отличием между операторами HALT и HANG. И тот и другой могут сокращаться до одной буквы - 'H'. Но оператор HALT ВСЕГДА используется БЕЗ аргументов !!! Стр. 75 3.3.5 Оператор XECUTE Оператор│ Аргументы Примеры ________│______________ ________________________________ │ │ │ │ XECUTE │ EXPRESSION │ XECUTE "Set a=22,b=7 Write !,a/b" │ │ ┌───┐ Сокращение имени оператора │ X │ X Code Использование постусловий: │ │ при операторе │ДА │ Xecute:TR "Set TR=0 W !!" при аргументе │ДА │ Xecute Code:Test Использование косвенности │ДА │ S a="Set P=22",b="a" X @a Обязательность аргументов │ДА │ Разрешенность использования │ │ списка аргументов │ДА │ X "Set a=0","W !,""Начало""" Установка времени ожидания │НЕТ│ └───┘ Этот оператор исполняет строку, задаваемую EXPRESSION как команд- ную строку MUMPS. При этом эта строка исполняется так, как это была бы строка MUMPS инструкций введенных в непосредственном режиме. EXPRESSI- ON может быть переменной, /локальной или глобальной, простой, или эле- ментом массива/ или строковым литералом, вплоть до максимально допус- каемой системой MUMPS длины и может содержать любые допустимые MUMPS операторы и функции. Оператор XECUTE обсуждается также и в разделе 7.1 Исполнение оператора XECUTE в чем-то аналогично исполнению опера- тора DO. Так же как и в DO, для него в конце исполняемой строки выпол- няется неявно оператор QUIT, а встреченный в тексте этой строки QUIT - вызывает немедленное прерывание исполнения /исключая те случаи, когда QUIT используется для прерывания исполнения цикла FOR/. После прерыва- ния, или завершения исполнения аргумента XECUTE, управление передается на непосредственно следующий за ним оператор, или начинается исполне- ние следующего аргумента оператора. Если в аргументе оператора XECUTE встречается функция $TEXT /См. раздел 8.1.10/, то ее действие относит- ся к строкам программы, в которой было начато исполнение текущего опе- ратора XECUTE. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Z="set PID=ans quit:ans'?1N.N Write ans"<- | │ │ |>Set ans=1234 Write !,Z,!," Идентификатор =" Xecute Z<- | │ │o| |o│ │ |set PID=ans quit:ans'?1N.N Write ans | │ │ |Идентификатор =1234 | │ │o| |o│ │ |>set ans="Доброе утро" Write !," Ответ =" Xecute Z<- | │ │ | | │ │o|Ответ = |o│ │ | | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.52 Оператор XECUTE Стр. 76 Заметим, что при втором исполнении оператора XECUTE значение 'ans' не выводится на экран, так как оператор QUIT с постусловием пре- рывает исполнение аргумента, раньше исполнения оператора WRITE. Обычно оператор XECUTE используется в системных программах, или программах обрабатывающих ввод/вывод с переферийных устройств. Она мо- жет быть также использована для инициализации при запуске прикладных пакетов, а также в любом месте программы для обеспечения системной не- зависимости, унификации работы с устройствами, или независимости от уровня исполнения процедуры. Представим пример с необходимостью уста- новки курсора в указанное место экрана и выводом сообщения. Большинс- тво терминалов /но не все/ позволяют путем использования специальных управляющих последовательностей кодов перемещать курсор в произвольную точку экрана /этот процесс иногда называется произвольной адресацией курсора/. В большинстве случаев эти управляющие последовательности на- чинаются кодом Esc /ASCII=27/, и потому называются Esc-последователь- ностями. Подробнее об этой возможности мы поговорим в разделе 3.4.4 при обсуждении оператора WRITE и в главе 12. В дополнении к выводу уп- равляющей последовательности определяющей перемещение курсора на ука- занное место, необходимо позаботиться о модификации значений системных переменных $X и $Y, которые обычно отражают текущее положение курсора устройства. Их значения не модифицируются системой при переводе курсо- ра при помощи Escпоследовательности. На нижеприведенном примере мы де- монстрируем, как с помощью оператора XECUTE произвести адресацию кур- сора на терминале VT-100. Если изменить управляющую Escпоследователь- ность /обычно подобные вещи устанавливаются при запуске прикладного пакета/, то подобная комбинация может использоваться для любых других терминалов. ┌──────────────────────────────────────────────────────────────┐ │ |>Set XY="Write *27,*91,DY+1,DDX+1,*102 Set $X=DX,$Y=DY"<- | │ │ |>Set DX=15,DY=7 Xecute XY W "Курсор =(",$X,",",$Y,")"<- | │ │o| |o│ │ | | │ │ | | │ │o| |o│ │ | Курсор=(23,7) | │ │ | | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 3.53 Позиционирование курсора с помощью XECUTE В этом примере мы предполагаем, что экран только что полностью очищен, и координаты его верхнего левого угла $X=0 и $Y=0. Но! Выве- денное значение системной переменной не равно 15, так как при выводе строки 'Курсор =(' система модифицирует занесенную в эту переменную координату позиции курсора. А в аргументе XECUTE мы устанавливаем ко- ординаты курсора равными адресуемой нами позиции, так как система НЕ может отследить перемещение курсора при выводе управляющей последова- тельности. Стр. 77 В качестве аргумента оператора XECUTE можно использовать пустую строку '""', в этом случае не будет предприниматься никаких действий, и процесс будет продолжаться так, как если бы этого аргумента не было. Оператор XECUTE, как уже упоминалось подобен оператору DO, за исключением того, что оператор DO передает управление на указанную строку программы, а оператор XECUTE временно передает в указанную пе- ременную или строковый литерал. Оператор DO начинает исполнение группы программных строк /пока в них не встретится оператор QUIT/, а оператор XECUTE способен исполнить только одну строку /содержащуюся в перемен- ной или литерале/. В обоих упомянутых операторах исполняется неявный QUIT при достижении последней строки /для DO/, или при завершении ис- полняемой строки /для XECUTE/. Так же как и при использовании оператора DO внутри исполняемого аргумента XECUTE допустимо использование оператора NEW. Его использо- вание в аргументе оператора XECUTE полностью совпадает с использовани- ем в процедурах вызываемых по DO. /Обращайтесь к разделу описывающему использование NEW в процедурах вызываемых по DO с передачей параметров и общей концепции использования NEW в главе 9/. В командной строке, используемой в качестве аргумента оператора XECUTE, Вы можете также использовать дополнительные операторы XECUTE. Но также, как и в случае использования косвенного задания аргумента, необходимо очень внимательно и осторожно использовать вложенный XECUTE в программах. Так как и в простом листинге программы порой довольно трудно разобраться откуда и куда передается управление, то использова- ние вложенного XECUTE способно принести больше неприятностей, чем пре- имуществ. Хочется порекомендовать не жалеть тратить время на тщатель- ное документирование используемых аргументов оператора XECUTE. ┌──────────────────────────────────────────────────────────────┐ │ |>S XY="Write *27,*91,DY+1,*59,DX+1,*102 S $X=DX,$Y=DY"<- | │ │ |>Set ErMsg="Set DX=0,DY=8 Xecute XY Write ""Error"""<- | │ │o|>Xecute ErMsg<- |o│ │ | | │ │ | | │ │o| |o│ │ | | │ │ |Error | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис.3.54 "Вложенный" XECUTE Представьте себе, что Вы встречаете команду вида: 'Xecute ErMsg' в сложной и запутанной программе, и не знаете, где были созданы пере- менные XY и ErMsg, и что они содержат. Только исполнив эту команду в непосредственном режиме можно получить представление, что именно она делает. Стр. 78 Все MUMPS команды могут встречаться в исполняемой как аргумент XECUTE строке, включая дополнительные XECUTE, DO и так далее. Исполня- емая строка может рассматриваться как короткая подпрограмма, и встре- чающийся в ней оператор NEW будет работать аналогично NEW в вызываемых подпрограммах. Необходимо обратить Ваше внимание на еще один аспект связанный с использованием оператора XECUTE. В тех версиях MUMPS, который исполня- ют "компиляцию" программ, аргумент XECUTE будет исполняться значитель- но медленнее, чем любая, сопоставимая с ним, часть программы. Это про- исходит в связи тем, что исходный текст содержащийся в аргументе XECU- TE не компилируется. Для того, чтобы преодолеть это затруднение в не- которых реализациях MUMPS компиляция аргумента производится при первом исполнении оператора XECUTE, так что при последующих вызовах оператора с этим аргументом он будет уже "скомпилированным". В других реализаци- ях включены дополнительные 'Z' операторы, используя которые, можно скомпилировать аргумент оператора XECUTE. /*1/ 3.4 Операторы Ввода/Вывода Операторы управления вводом и выводом управляют различными пере- ферийными устройствами в среде MUMPS и обслуживают обмен информацией между MUMPS процессом и устройством. В эту категорию можно отнести шесть операторов: OPEN - Этот оператор резервирует устройства для его дальнейшего ис- пользования в MUMPS процессе. Процессом может резервиро- ваться одновременно несколько устройств, но в большинстве реализаций, в конкретный момент времени может использовать- ся только одно из зарезервированных устройств. USE - Процессом может резервироваться несколько устройств, но только одно из них может быть активным в текущий момент времени. Все операторы READ и WRITE работают с устройством, которое объявлено активным. Оператор USE производит объяв- ление одного из зарезервированных устройств активным. READ - Этот оператор используется для чтения информации с активно- го устройства в переменную, для дальнейшего использования. WRITE - Этот оператор используется для передачи информации на уст- ройство, указанное активным. _______________________________________________________________ *1 - Например в реализации DataTree MUMPS для того чтобы скомпилиро- вать аргумент XECUTE, необходимо использовать функцию $ZXECUTE. Напри- мер: Set a=$zxecute(a) Xecute a Стр. 79 CLOSE - Этот оператор предназначен для прекращения операций вво- да-вывода на устройства и его освобождения для использова- ния другими процессами. PRINT - Этот оператор не является частью стандарта MUMPS, но реали- зован в большинстве версий. Предназначен для вывода текущей программы, или группы ее строк на активное устройство. Подробное описание операций ввода/вывода содержится в главе 12, сейчас же мы представим краткое описание основных концепций, предназ- наченное для начинающих программистов. В системе MUMPS поддерживается список идентификаторов все поддер- живаемых устройств /наиболее часто в виде номеров устройств/. Сюда включаются терминалы /видеотерминалы и последовательные принтеры/, па- раллельные принтеры, устройства записи на магнитную ленту, последова- тельные дисковые файлы, и так далее. Список идентификаторов устройств индивидуален для каждой реализации MUMPS, /*1/ который Вы можете найти в руководстве пользователя по данной версии. В большинстве реализаций любое устройство в текущий момент времени может использоваться только одним заданием. И, как только устройство резервируется заданием для использования /захватывается заданием, как иногда говорится/, другие задания уже не могут его использовать. Устройства резервируются /зах- ватываются/ при использовании оператора OPEN, и освобождаются операто- ром CLOSE. В тоже время, одним заданием может резервироваться сразу несколько устройств. После того, как устройство зарезервировано заданием для использо- вания /захвачено/, можно управлять операциями ввода/вывода на него. Ввод с устройства производится при использовании оператора READ, а вы- вод - оператора WRITE. Но так как одним процессом /заданием/ может быть захвачено несколько устройств, операции ввода/вывода производятся только с тем устройством, которое объявлено активным на данный момент /текущим/. Объявление одного из зарезервированных оператором OPEN уст- ройств, производится с помощью оператора USE. ____________________________________________________________ *1 Несколько примеров таблиц номеров /идентификаторов/ устройств в различных реализациях MUMPS Устройства DSM-11 ISM /NTSM/ DSM-PC Консоль 1 1 1 У-во спулинга 2 - - Параллельный принтер 3 2 30 Последовательные дисковые файлы - 5-8 10-19 У-ва магн.ленты 47-49 - и так далее... Стр. 80 Устройство, объявленное с помощью оператора USE текущим, остается та- ковым до следующего использования USE, или до тех пор, пока это уст- ройство не освобождается от использования в данном задании /процессе/ оператором CLOSE. В непосредственном режиме исполнения команд устройс- тво перестает быть текущим, кроме указанных случаев, после того, как система отрабатывает введенную строку команд и выведя новую системную подсказку, ожидает следующего ввода. /То есть устройство назначенное текущим, в непосредственном режиме остается таковым только в пределах исполняемой строки/ В дополнение к вышеперечисленным операторам следует упомянуть о еще двух, не включенных в стандарт языка, операторах, которые исполь- зуются в операциях ввода/вывода. Это операторы ZSAVE и ZLOAD , пред- назначенные, в основном, для загрузки и сохранения программ с диска, но которые могут использоваться также и с другими устройствами. Под- робное обсуждение использования этих операторов будет приведено в раз- делах 3.5.6 и 3.5.10 3.4.1 Оператор OPEN Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ OPEN │DEVICE│[:DEVICE PARAMETERS] [:TIMEOUT] OPEN 10 │ │ │ │ ┌──────┐ Сокращение имени оператора │ O │ O 10 Использование постусловий: │ │ при операторе │ ДА │ O:Truth 10,Mtape при аргументе │ НЕТ │ Использование косвенности │ ДА │ S Prn=30,Dev="Prn" O @Dev Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ O 5,Mtape,@Dev Установка времени ожидания │ ДА │ Open Mtape::10 └──────┘ Оператор READ используется для установления "собственности" про- цесса /задания/ на устройства для осуществления с него последователь- ных операций ввода/вывода. Используя оператор OPEN процесс /задание/ может зарезервировать для использования несколько устройств, и выби- рать из них устройство для работы оператором USE. Если процесс, используя оператор OPEN пытается зарезервировать для использования устройство, уже зарезервированное другим заданием, то оператор OPEN "подвиснет", /то есть исполнение временно приостано- вится/, до тех пор, пока резервируемое устройство не будет освобождено /"закрыто" использованием оператора CLOSE/ другим заданием. Дополни- тельный параметр [:TIMEOUT] позволяет программисту устанавливать конт- роль над процессом резервирования устройств и избегать приостановки исполнения на неопределенное время. Стр. 81 Параметр TIMEOUT устанавливает промежуток времени, в течение которого система MUMPS пытается зарезервировать указанное устройство, перед тем, как продолжить исполнение текущего задания. Если Вы указали пара- метр TIMEOUT, то после исполнения оператора OPEN, в системную перемен- ную $TEST будет занесена 1 /ИСТИНА/, если устройство удалось зарезер- вировать до истечения указанного промежутка времени, или 0 /ЛОЖЬ/, ес- ли процесс окончился неуспешно. Если параметр TIMEOUT не указывается, то исполнение оператора OPEN НЕ изменяет состояния системной перемен- ной $TEST. Синтаксис использования оператора OPEN довольно сильно отличается в различных реализациях MUMPS. Каждая из реализаций поддерживает свои списки идентификаторов устройств доступных в системе MUMPS и описания параметров этих устройств /классов устройств/, указываемых как DEVICE PARAMETERS. Синтаксис и функциональное назначение DEVICE PARAMETERS следует изучить по конкретному описанию Вашей реализации. Только одно устройство всегда доступно процессу /заданию/. По оп- ределению, устройство номер 0, которое всегда связывается с основным устройством текущего задания всегда в нем доступно. Под основным уст- ройством процесса /задания/ обычно понимается тот терминал, с которого была произведена регистрация в системе. Это устройство обычно имеет также и внутри системный идентификатор, но Вы всегда можете использо- вать адресацию на DEVICE 0, чтобы обратиться к основному устройству Вашего процесса /задания/, используя 0 как аргумент оператора USE. Когда Вы регистрируетесь в системе MUMPS, основное устройство автома- тически открывается и резервируется системой /исполняется так сказать неявный OPEN с параметрами определяемыми по умолчанию для этого уст- ройства./. При завершении процесса, /освобождении раздела при исполне- нии оператора HALT/, MUMPS автоматически освобождает основное устройс- тво задания /исполняется неявный CLOSE/, после чего это устройство мо- жет быть доступно другим заданиям для использования. Оператор OPEN НЕ направляет вывод или ввод на указанное устройс- тво, он только устанавливает, что это устройство может использоваться в текущем процессе /задании/. 3.4.1.1. ARGUMENT=DEVICE Наиболее простая и часто используемая фор- ма резервирования устройств для их последующего использования. Если указанное в DEVICE устройство не захвачено уже другим заданием, оно захватывается текущим заданием и исполнение продолжается. Но если ука- занное устройство зарезервировано для использования каким-нибудь дру- гим заданием, то исполнение текущего процесса /задания/ "подвиснет" /будет приостановлено/ на неопределенное время, пока резервируемое устройство не будет освобождено. В один момент времени сразу несколько процессов могут пытаться захватить одно и тоже устройство. Форма обс- луживания запросов зависит от конкретной реализации MUMPS и не всегда выражается формулой " первым запросил, первым обслужен" /то есть не всегда в MUMPS допускается обработка "очереди" последовательных запро- сов/ Стр. 82 Так как устройство может использоваться в задании с различными установками своих параметров /DEVICE PARAMETERS/, то возможно более разумным окажется резервирование устройства без указания конкретных параметров, указывая параметры устройства в дальнейшем, при аргументе оператора USE. А предварительно используя OPEN без параметров устройс- тв зарезервировать сразу ВСЕ необходимые устройства. Проверьте соот- ветствующие разделы документации на используемую Вами версию MUMPS для проверки допустимости этого положения. ┌──────────────────────────────────────────────────────────────┐ │ |>Open 67,68<- | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.55. Оператор OPEN 3.4.1.2. ARGUMENT=DEVICE:DEVICE PARAMETERS Эта форма использования оператора является расширением выше описанной, за счет включения конк- ретных параметров резервируемого устройства. ТО есть происходит резер- вирование указанного устройства /устройств/ с одновременным назначени- ем необходимых параметров. Это может быть, например, указание: правой границы поля вывода, плотности записи /для магнитной ленты/, включе- ния/выключения "эха" на вводе, и так далее. Поскольку и синтаксис назначения параметров, и список допустимых параметров устройств зависят от конкретной реализации MUMPS, то необ- ходимо при назначении параметров устройств в операторе OPEN / а также USE /, строго следовать рекомендуемым формам назначения параметров, приводимых в документации по конкретной реализации. Соответственно, по этой же причине, попытки переноса программ, которые манипулируют с назначением параметров, из одной реализации MUMPS в другую, могут окончится самыми непредвиденными осложнениями. Так форма назначения параметров в разных версиях может быть АБСОЛЮТНО несхожей. /*1/ ┌──────────────────────────────────────────────────────────────┐ │ |>Open 10:(file="c:\test.txt":mode="WRITE")<- | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.56. Оператор OPEN с параметрами _________________________________________________________________ *1 Действительно, это один из самых трудных аспектов переноса ПО между версиями MUMPS, сравните, например, формы открытия терминала, со сня- тием "эха" и назначением границы вывода: DSM-11: Open DEV:(78::::1) ISM : Open DEV:("MR":78:"EC":1) DTM-PC: Open DEV:(width:80:echom:0) Стр. 83 В примере 3.56 представлена форма, характерная для некоторых вер- сий MUMPS для IBM PC, открытия последовательного файла, с указанием диска и имени файла, для записи /mode="WRITE"/ /*1/ 3.4.1.3. ARGUMENT=DEVICE::TIMEOUT Указание необязательного парамет- ра TIMEOUT, определяет промежуток времени /*2/, в течении которого система MUMPS пытается зарезервировать устройство для использования в текущем процессе /задании/. TIMEOUT рассматривается, /в большинстве случаев, см прим.2 к этой странице/ как целое, неотрицательное число. Если оно указано меньшим 0, то интерпретируется, в этом случае, как равное 0. Если Вы указали параметр TIMEOUT, то после исполнения оператора OPEN, в системную переменную $TEST будет занесена 1 /ИСТИНА/, если устройство удалось зарезервировать до истечения указанного промежутка времени, или 0 /ЛОЖЬ/, если процесс окончился неуспешно. Если параметр TIMEOUT не указывается, то исполнение оператора OPEN НЕ изменяет сос- тояния системной переменной $TEST. Заметьте, что для того, чтобы указать время ожидания резервирова- ния устройства, в операторе OPEN без указания параметров устройства, необходимо поставить два двоеточия, между номером устройства и време- нем ожидания. Это необходимо, для того, чтобы система MUMPS отличила форму использования OPEN с назначением параметров, от формы с указани- ем времени ожидания. ┌──────────────────────────────────────────────────────────────┐ │ |>Open 51:("AV":0:1024):10<- | │ │ |>Open 40::30<- | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.57. Оператор OPEN с указанием времени ожидания В первой строке примера 3.57 производится попытка зарезервировать для использования устройство 51, с назначением параметров /DEVICE PA- RAMETERS - ("AV":0:1024) - форма характерная для DSM-11 и MSM, указы- вающая на формат использования магнитной ленты и размер блока записи/, в течении указанного времени - 10 сек. ________________________________________________________________ *1 Для наиболее популярных, на территории "бывшего СССР" версий MUMPS под MS-DOS: MSM : ISM (NTSM) : Open Dev:("FN":"c:\test\test.dat":"FA":2) DTM-PC : Open Dev:(file:"c:\test\test.dat":mode:"W") *2 В ряде версий Вы можете указывать время ожидания с точностью до де- сятых долей секунды. Это допускается, например, в NTSM /ISM/ 5.0 и старше, и в DataTree MUMPS версии 4.1 и старше Стр. 84 Вторая строка этого примера демонстрирует попытку зарезервировать устройство 40, в течении 30 секунд, без указания каких-либо парамет- ров. /Двоеточия внутри списка параметров устройства не являются разде- лителями аргументов оператора OPEN/ ┌──────────────────────────────────────────────────────────────┐ │ |Print ; Печать списка на принтер | │ │ | Open 3::0 Else Write " Принтер недоступен" Quit | │ │ | Use 3 | │ │ | . | │ │ | . | │ │ | . | │ │ | | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.58. Оператор OPEN с указанием времени ожидания без указания параметров Оператор ELSE во второй строке примера 3.58 проверяет состояние системной переменной $TEST. Значение $TEST отражает результат исполне- ния оператора OPEN с указанием времени ожидания /TIMEOUT /. Если $TEST = 0, /то есть не удалось "захватить" устройство/, то будет выведено соответствующее сообщение, и произведен выход из данной программы. В противном случае будет продолжено исполнение последующих строк прог- раммы. 3.4.2 Оператор USE Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ USE │DEVICE│[:DEVICE PARAMETERS] USE 1 │ │ ┌──────┐ Сокращение имени оператора │ U │ U Printer Использование постусловий: │ │ при операторе │ ДА │ U:Truth Magtape при аргументе │ НЕТ │ Использование косвенности │ ДА │ S prn=63,Dev="prn" Use @Dev Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ U Magtape,@Dev Установка времени ожидания │ НЕТ │ └──────┘ Используя оператор USE можно одно из ранее зарезервированных для использования устройств /см. оператор OPEN/, сделать текущим /или ак- тивным/ устройством. Краткое описание процесса манипулирования с уст- ройствами и комплексного использования операторов OPEN, USE и CLOSE дано в начале этого раздела /описания операторов обслуживания вво- да-вывода/, более подробное описание концепции операций ввода/вывода приведено в главе 12. Стр. 85 Оператор USE назначает все последующий операции ввода/вывода, /с ис- пользованием READ и WRITE/, на указанное в его аргументе устройство. Указанное устройство остается текущим /активным/ до тех пор, пока не будет исполнен следующий оператор USE, или пока оно не будет закрыто при использовании оператора CLOSE. Форма и синтаксис определения спис- ка параметров в необязательном аргументе оператора USE - DEVICE PARA- METERS зависят от конкретной версии MUMPS, и требуют обращения к доку- ментации. Если указанное устройство зарезервировано для задания /через OPEN/, то это устройство становится текущим /активным/ устройством этого задания, с одновременным изменением состояния системных перемен- ных $IO, $X и $Y - отражающих статус указанного устройства. Системная переменная $IO всегда содержит номер ТЕКУЩЕГО /активного/ устройства Вашего процесса /задания/. Переменные $X и $Y содержат номер строки и номер колонки текущего положения курсора /печатающей головки/ на теку- щем устройстве и модифицируются при использовании операторов READ и WRITE. Если указанное устройство не зарезервировано, то использование оператора USE для него приведет к возникновению ошибки. 3.4.2.1 Argument=DEVICE Эта форма использования оператора USE наз- начает текущее /активное/ устройства для процесса, из списка зарезер- вированных. Заметим, что несмотря на то, что Вы может указать в этой форме список номеров устройств, только ПОСЛЕДНЕЕ устройство, из ука- занных в списке станет текущим /активным/ устройством процесса /зада- ния/. Это объясняется тем, что в текущий момент времени, активным мо- жет быть только ОДНО устройство. 3.4.2.2 Argument=DEVICE:DEVICE PARAMETERS Так же как и в операторе OPEN, при использовании оператора USE Вы можете указать необязательный параметр указывающий определенные параметры устройства, которое указы- вается как текущее. Так можно изменять функциональные характеристики устройства, например, назначать границу поля вывода, указывать после- довательность кодов прерывающих операцию READ /см. описание оператора READ - раздел 3.4.3/, и так далее. На всякий случай, проверьте соот- ветствующий раздел в документации. ┌──────────────────────────────────────────────────────────────┐ │ |>Use 70:(speed="9600":parity="E")<- | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.59. Оператор USE с параметрами Такой формой использования оператора USE можно, в некоторых вер- сиях MUMPS, изменять скорость обмена по терминальной линии и вид конт- роля за передаваемыми данными. В этих случаях использование тех же па- раметров, как аргумент оператора OPEN должно произвести такие же изме- нения. Стр. 86 3.4.3 Оператор READ Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ │ STRING LITERAL │ Read "Ответ:",Ans READ │ FORMAT │ Read !,Ans │ LOCVAR [#READCOUNT] [:TIMEOUT] │ Read Ans#5:60 │ *LOCVAR [:TIMEOUT] │ Read *Char:15 │ │ ┌──────┐ Сокращение имени оператора │ R │ R a Использование постусловий: │ │ при операторе │ ДА │ R:Truth a при аргументе │ НЕТ │ Использование косвенности │ ДА │ S a="b" R @a Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ R !," Пар1=",p1,"Пар2=",p2 Установка времени ожидания │ ДА │ R Ans:60 └──────┘ Оператор READ предназначен для чтения символьных последователь- ностей с устройства, прочитанные данные используются для создания или модифицирования указанной локальной переменной. READ не используется для чтения элементов базы данных с диска, чтение, создание и модифика- ция элементов баз данных /локальных переменных/ производится с помощью оператора SET. READ используется для ввода строк символов с символь- но-ориентированных устройств таких как терминалы, магнитные ленты или последовательные дисковые файлы. Для удобства и простоты использования в операторе READ могут ис- пользоваться некоторые возможности присущие оператору WRITE, позволяю- щие программисту одновременно с назначением чтения выводить текстовые строки или коды управления форматом устройства ввода. При использовании оператора READ чтение информации производится с назначенного оператором USE текущего устройства задания /процесса/, идентифицируемого системной переменной $IO. Более подробное рассмотре- ние концепций использования устройств в MUMPS будет произведено в гла- ве 12. Начинающим программистам, до знакомства с системными подробностя- ми, следует опробовать все приводимые примеры в контексте состояния системы MUMPS, устанавливаемого после первой регистрации в ней. В этом случае текущим /активным / устройством будет терминал с которого про- изводилась регистрация, и все команды READ и WRITE будут направляться на него. Стр. 87 3.4.3.1 Argument=STRING LITRERAL Эта форма оператора /а также об- суждаемая следом Argument=FORMAT/ позволяет использовать в операторе READ некоторые возможности присущие оператору WRITE. Эти возможности, применяемые в диалоговых программах, облегчают их построение. Посмот- рите на следующий пример: ┌──────────────────────────────────────────────────────────────┐ │ |>Read !!," Введите возраст пациента : ",Age<- | │ │ | | │ │o| |o│ │ | Введите возраст пациента : 45<- | │ │ |> | │ │o| |o│ └──────────────────────────────────────────────────────────────┘ Рис.3.60. Оператор READ со строковым литералом В этом же примере демонстрируется и использование символов управ- ления форматов устройства ввода информации. Первоначально в операторе READ инициируются два перевода строки / задаваемые '!!' используемыми как коды управления форматом в форме Argument=FORMAT/, затем выводит текстовую строку, которая является подсказкой и приглашением ко вводу =' Введите возраст пациента : ' и только после этого осуществляется чтение в локальную переменную 'Age'. Заметим, что форма использования строковых литералов в операторе READ должна быть только такой - стро- ка, заключенная в кавычки. НЕЛЬЗЯ включать в этот строковый литерал значения переменных, и следующая форма использования строковых литера- лов в READ является недопустимой: ┌──────────────────────────────────────────────────────────────┐ │ |>S X="возраст" Read !!," Введите "_X_" пациента : ",Age<- | │ │ | Error | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис.3.61. Запрещенная форма использования строкового литерала в операторе READ Несмотря на это в некоторых версиях MUMPS эта форма использования оператора READ может работать, но она НЕ соответствует стандарту языка и НЕ рекомендуется. 3.4.3.2 Argument=FORMAT Эта форма аргумента READ идентична описан- ному в разделе, посвященному использованию символов управления форма- том в операторе WRITE. /Раздел 3.4.4.2/ За подробностями обращайтесь туда. Вкратце можно сказать следующее: - в операторе READ допустимо использование ТРЕХ символов управления устройством, это: - символ '#' - вызывающий 'перевод страницы' на устройстве - символ '!' - используемый для 'перевода строки' - символ '?' в сочетании с целым числом, в виде '?nn' - для гори- зонтальной табуляции в колонку 'nn' устройства /от левой грани- цы и только в том случае, если текущее положение курсора 'ле- вее' указанной колонки/ Стр. 88 ┌──────────────────────────────────────────────────────────────┐ │ |>Read !!,"Имя: ",N,?20,"Пол :",S,?30," Возраст :",Age<- | │ │ | | │ │o| |o│ │ |Имя: Иван<- Пол :M<- Возраст :35<- | │ │ |> | │ │o| |o│ └──────────────────────────────────────────────────────────────┘ Рис.3.62. Оператор READ с управлением форматом с использованием специальных управляющих символов /FORMAT/ 3.4.3.3 Argument=LOCAL VARIABLE [#READ COUNT][:TIMEOUT] Это основ- ная форма аргумента, производящее чтение в локальную переменную, с двумя возможными расширениями - [#READ COUNT], определяющее максималь- ное число читаемых символов, и - [:TIMEOUT], /*1/ задающий количество времени, в течении которого READ производит ожидание ввода, а затем исполнение передается на следующий оператор. При обычном чтении символов, вводимых с клавиатуры, они последо- вательно читаются и помещаются в указанную локальную переменную /LOCAL VARIABLE/. Но в любой реализации по своему определяется как завершает- ся обычная последовательность ввода, и будет ли помещаться код симво- ла, которым она завершилась, в переменную. Но на большинстве использу- емых терминалов все версии MUMPS обеспечивают завершение ввода по стандартному знаку окончанию ввода - символу /Cariage return/, получа- емому при нажатии клавиши / может также именоваться или , а также <ВК> на русских терминалах/. И все символы прочитан- ные до получения этого символа помещаются в указанную в аргументе READ локальную переменную. В некоторых реализациях программист имеет возможность определять свои коды /последовательности кодов - так как некоторые клавиши гене- рируют при нажатии несколько кодов/ завершающие ввод последовательнос- ти символов с клавиатуры в операторе READ. Эта возможность обычно раз- решается через использование специальных параметров, включаемых в ар- гумент операторов USE или OPEN. Посмотрите соответствующий раздел до- кументации на используемую Вами реализацию MUMPS, а также, если она предусмотрена, проконсультироваться, как инициализирован Ваш терминал. Определение кодов завершающих операцию READ становится актуальным чте- нии кодов, посылаемых при нажатии функциональных клавиш у некоторых терминалов. Так например, при нажатии функциональной клавиши на терми- нале VT-100 /или совместимом с ним/ генерирует последовательность ко- дов, начинающихся с кода 'Escape' /ASCII=27/ /здесь и далее - коды AS- CII всегда будут приводится в десятичном представлении/, а следом от одного до трех кодов. К сожалению, код CR /ASCII =13/ НЕ посылается в конце таких последовательностей, и, следовательно, оператор READ НЕ завершается. Предложения по чтению подобных последовательностей приве- дены в главе 12. ______________________________________________________________ *1 TIMEOUT обычно исчисляется в единицах секунд. В некоторых системах можно использовать и десятые доли. Стр. 89 Некоторое количество кодов при вводе или не отображаются, или выглядят иначе, чем обычные алфавитно-цифровые коды. /*1/ Они действу- ют как управляющие коды на операцию READ, и не всегда возвращаются в значении переменной по завершению чтения. В таблице 3.1 представлена часть из наиболее часто используемых управляющих кодов с описанием их воздействия на операцию READ. Заметим, что данные коды НЕ включены в стандарт, и поэтому, могут по разному обрабатываться в различных реа- лизациях /и даже по разному на различных устройствах в одной реализа- ции/ Таблица 3.1 Управляющие коды для операций ввода/вывода ________________________________________________________________ Обозначение Клавиши Код Исполняемые ASCII функции ________________________________________________________________ ETX + 3 Иногда генерируется также и при нажатии <Сtrl+Break>, - прерывает процесс. В некоторых реализациях - может вызывать отладчик. См главу 13 FF + 12 Используется иногда как код завершения операции READ. /При этом влияет, в основном, только на операции чтения с м.ленты/ CR 13 Нормально завершает операцию чтения SI + 15 Этот код, посланный с терминала, отменяет вывод на него. Но процесс /задание/ не приостанавливает- ся, и, следовательно, все символы, посылаемые на терминал, будут "потеряны". DC1 + 17 Именуется иногда как 'XON'. Он восстанав- ливает вывод на терминал, приостановленный посылкой XOFF /Ctrl+S/ DC3 + 19 Именуется иногда как 'XOFF', и приостана- вливает вывод на устройство до получения кода 'XON'. В некоторых реализациях приостановка производится до получения любого кода после XOFF. NAK + 21 Удаляет все символы, введенные в текущей операции READ. В некоторых реализациях для этого используется DEL 127 Удаляет последний введенный с клавиатуры символ. BS 8 Удаляет последний введенный с клавиатуры символ. ______________________________________________________________ *1 - Эти коды генерируются при одновременном нажатии клавиши и одной из алфавитных клавиш. При этом, в большинстве случаев, генериру- ется код с десятичным значением равным номеру соответствующей буквы в латинском алфавите. Это правило действует ТОЛЬКО на алфавитные клави- ши. Стр. 90 При вводе символов с устройства, системные переменные $X и $Y, отражающие положение курсора /или печатающей головки/, автоматически модифицируются системой, так же, как и в операторе WRITE. Необязательный параметр [#READ COUNT] изменяет условия завершаю- щие операцию чтения. READ COUNT оценивается как целое число, интерпре- тируемое как максимальное количество символов, которое может быть вве- дено в текущей операции READ, до ее завершения. Но даже если этот па- раметр указан, оператор READ может быть завершен когда получен код символа завершающего операцию чтения /обычно /. ┌──────────────────────────────────────────────────────────────┐ │ |>Read "Введите 4 символа: ",Ans#4 Write " Ans=",Ans<- | │ │ |Введите 4 символа: 12<- Ans=12 | │ │o| |o│ │ |>Read "Введите 4 символа: ",Ans#4 Write " Ans=",Ans<- | │ │ |Введите 4 символа: 1234 Ans=1234 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 3.63 READ с ограничением длины вводимой строки Обратите внимание, что во второй операции READ нет необходимости нажимать для завершения чтения. Как только будет введено 4 символа, операция READ завершится, и исполнение продолжится. В стандарте READ COUNT определяется как положительное число, от- рицательное значение, или ноль, приводит к ошибке. Стандарт не опреде- ляет, что случается с символами, введенными после завершения READ. Когда завершается обычная операция READ, /например READ Ans/, все вве- денные следом символы могут быть потеряны, или потом использоваться в следующем READ. Более подробное обсуждение символов попадающих в буфер ввода будет произведено далее. Действия системы по обработке этих сим- волов зависит от реализации MUMPS. Чтение последовательности символов фиксированной длины наиболее часто используется при чтении данных с переферийных устройств. Предпо- ложим, что читаемые с магнитной ленты данные состоят из последователь- ности записей, каждая состоящая из пяти полей фиксированной длины, в следующем формате: ┌───────┬───────┬───────┬───────┬───────┬──────────┐ Запись │ Поле1 │ Поле2 │ Поле3 │ Поле4 │ Поле5 │ ..... │ └───────┴───────┴───────┴───────┴───────┴──────────┘ Длина поля 8 13 6 8 15 Пусть устройство 47 является используемым в системе драйвером магнитной ленты. Во фрагменте программы, представленном на рис.3.64 производится чтение одной записи и разделение ее в пять отдельных пе- ременных (F1,F2,...F5): Стр. 91 ┌──────────────────────────────────────────────────────────────┐ │ |GetRec ;Чтение записи с м.ленты и разделение на 5 полей | │ │ | Use 47 Read Rec | │ │o| Set F1=$E(Rec,1,8),F2=$E(Rec,9,21) |o│ │ | Set F3=$E(Rec,22,27),F4=$E(Rec,28,35) | │ │ | Set F5=$E(Rec,36,50) | │ │o| Quit |o│ └──────────────────────────────────────────────────────────────┘ Рис. 3.64 Чтение данных и разделение ее на поля Функция $E ($EXTRACT раздел 8.1.1), используется для выделения из исходной строки подстроки лежащей в указанных пределах. Функция $E(Rec,1,8) выделяет в подстроку символы начиная с первого по восьмой включительно из исходной строки, содержащейся в переменной Rec. Другой подход к решению этой проблемы, заключающийся в использовании операто- ров READ с ограничением на длину читаемой последовательности символов, демонстрируется ниже: ┌──────────────────────────────────────────────────────────────┐ │ |GetRec ;Чтение записи с м.ленты и разделение на 5 полей | │ │ | Use 47 | │ │o| Read F1#8,F2#13,F3#6,F4#8,F5#15 |o│ │ | Quit | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.65 Разделение записи на поля с использованием READ с фиксированной длиной. Необязательный параметр [:TIMEOUT] также изменяет условия завер- шения оператора READ. Значение TIMEOUT расценивается целым /*1/ неот- рицательным числом, определяющим время в течении которого система ожи- дает ввода, затем прекращает чтение и передает управление дальше. По завершению READ с ограниченным временем ожидания происходит установка значения системной переменной $TEST в зависимости от результата завер- шения операции чтения. Если операция READ завершилась ДО истечения времени ожидания, $TEST устанавливается в 1 /ИСТИНА/, если же $TEST =0 /ЛОЖЬ/ - это означает, что время установленное TIMEOUT истекло, рань- ше, чем произошло нормальное завершение операции чтения. Обратитесь к разделу 6.1.5 для добавочных пояснений по командам с установленным времен завершения операции. ┌──────────────────────────────────────────────────────────────┐ │ |Read "Ответ: ",Ans:10 Write:'$TEST "Нет ответа",Ans<- | │ │ |Ответ: 123 Нет ответа 123 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 3.66 READ с временем ожидания ввода Обратите внимание на то, что в примере 3.66, несмотря на то, что время ожидания истекло, все введенные символы возвращаются в перемен- ной, являющейся аргументом READ. _________________________________________________________________ *1 - Еще раз напомним, что в некоторых системах допускается с точ- ностью до десятых долей секунды. Стр. 92 Оператор READ с установленным временем ожидания, может использо- ваться также вместо оператора HANG /раздел 3.5.2/. Оператор HANG при- останавливает исполнение процесса на указанное количество секунд, и обычно используется после высвечивания сообщений, перед очисткой экра- на, для того чтобы дать пользователю возможность прочитать и осмыслить это сообщение. К сожалению, пользователь не имеет возможность прервать исполнение оператора HANG и вынужден ждать весь указанный интервал времени, пока исполнение не продолжится. Если вместо HANG использовать READ с временем ожидания, то после того, как Вы прочитали сообщение, может нажать и исполнение продолжится. /*1/ ┌──────────────────────────────────────────────────────────────┐ │ | Do DispMsg,Pause ... | │ │ | ... | │ │ | | │ │ |Pause New a | │ │ | Read !,"Нажмите для продолжения",a:30 | │ │o| Quit |o│ └──────────────────────────────────────────────────────────────┘ Рис. 3.67 Использование READ с временем ожидания вместо оператора HANG В этом примере процедура Pause выводит пользователю сообщение о том, что он должен сделать для того, чтобы исполнение продолжилось, /нажать / и затем ждет в течении до 30 секунд. Если пользова- тель выполняет предписанное действие /нажимает /, то исполнение немедленно продолжается. Если нет, то эта процедура приостанавливает исполнение на 30 секунд, после чего исполнение продолжается. Заметим, что у этой процедуры есть один недостаток - она "портит" /то есть ус- танавливает в зависимости от результата завершения READ/ системную пе- ременную $TEST, которая может использоваться в других процедурах. Для того, чтобы избежать этого, следует выполнить процедуру Pause в виде внешней функции /см. в главе 8 как это сделать/, поскольку внешние функции сохраняют значение системной переменной $TEST перед вызовом, и восстанавливают его при возврате управления в точку вызова. 3.4.3.4 Argument = *LOCAL VARIABLE /Односимвольное чтение/ Эта фор- ма оператора READ возвращает значение одиночного прочитанного кода AS- CII в виде целого числа. _________________________________________________________________ *1 - Хочется отметить, что в данном случае исполнение продолжится, если пользователь нажмет ЛЮБУЮ клавишу. При этом могут возникнуть две проблемы: - на экране отобразится символ, соответствующий нажатой клавиши. - может произойти сдвиг и искажение экрана Для того, чтобы этого избежать, следует либо применять односим- вольное чтение, описанное в следующем разделе, либо используя управля- ющие коды "погасить 'эхо' " на терминале, и изменить границу вывода на терминал /установить ее =0/. Подробнее об этих и других приемах в гла- ве посвященной работе с видеотерминальными устройствами. Стр. 93 ┌──────────────────────────────────────────────────────────────┐ │ |>Read ": ",*Char Write "Прочитан символ - ",Char<- | │ │ |: Z Прочитан символ - 90 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 3.68 Чтение одиночного символа В этом примере с клавиатуры читается один символ - 'Z', чей код ASCII =90 /См. приложение А, где приведены символы ASCII и их коды/. Стандартом языка не предписывается должен ли высвечиваться вводимый таким образом символ на экране или нет, - все зависит от конкретной реализации. Если в Вашей реализации, символы, вводимые в такой форме операто- ра READ, не высвечиваются на экране, то Вы возможно захотите использо- вать вместе с такой формой READ оператор WRITE, как показано в следую- щем примере. ┌──────────────────────────────────────────────────────────────┐ │ |>Read *Char Write *Char," Десятичное значение = ",Char<- | │ │ |A<- Десятичное значение = 65 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 3.69 Вывод на экран прочитанного символа и соответствующего ему кода Во время обычного чтения /READ LOCVAR/, Вы можете использовать некоторые специальные клавиши, например "Delete" или "Backspace". Их действие обычно интерпретируется как "если введены символы, удалить последний введенный из них и передвинуть курсор на одну позицию вле- во". При этом код, генерируемый этими клавишами НЕ попадает в перемен- ную. Но этот процесс ОБЫЧНО происходит по другому, при односимвольном чтении кода. В этом случае управляющие коды /"Delete"-обычно 127, "Backspase" - 8/ возвращаются в значении локальной переменной. /В виде кода этого символа, но не самого символа/. И в тоже время, в некоторых реализациях, и в случае использования односимвольного чтения, введение специальных кодов /например , и /, оказывает прямое воздействие на процесс вывода информации на терминал, и они НЕ возвращаются в переменной. Необходимо на практике убедиться, как реа- гирует используемая Вами реализация на введение специальных кодов в односимвольном чтении. Необходимо также обсудить проблему возможной "потери" символов при чтении, и в связи с этим, использование буфера вводимых с клавиа- туры символов. Исполнение оператора READ интерпретируется как чтение ВСЕХ символов, вводимых с клавиатуры, во ВРЕМЯ действия операции чте- ния. Но к сожалению, в связи с инерционностью процесса обработки, ин- терпретатор MUMPS может потерять первый введенный символ, особенно при использовании односимвольного чтения. В связи с этим, в большинстве реализаций, поддерживается специальный буфер ввода, куда предваритель- но /до начала обработки их системой/ помещаются все введенные с клави- атуры символы. Этот буфер клавиатуры часто именуют "буфером опережаю- щего ввода". Стр. 94 Когда используется подобный буфер, MUMPS помещает в него введен- ные символы, и операция READ читает их именно из буфера, а не непос- редственно с клавиатуры. Использование этой технологии позволяет избе- жать потенциальную возможность "потери" введенных символов. Но иногда, в некоторых заданиях, возникает необходимость экстренного прерывания некого процесса, и запуска нового. В этом случае желательно иметь воз- можность игнорировать буфер ввода по получению некого сигнала, и уда- лить все остающиеся в нем символы перед запуском нового процесса. Мно- гие версии, поддерживающие буфер клавиатуры, допускают возможность его игнорирования. Но для того, чтобы определиться, обратитесь к Вашей до- кументации. В версиях, имеющих возможности управления буфером ввода, обычно в аргументах операторов USE и OPEN допускается использование специальных параметров, позволяющих программисту программно управлять использованием буфера опережающего ввода. /*1/ И в заключении, необходимо упомянуть об использовании односим- вольного READ с указанием временем ожидания ввода. Он исполняется, в общем также как и READ LOCVAR:TIMEOUT, но есть одно исключение. Если в односимвольном READ с указанным временем ожидания оно истекает до вво- да символа, то результирующей переменной назначается значение = -1. В тоже время, в обычном READ, при истечении времени ожидания, если не было введено НИ одного символа, результирующая переменная возвращается = "" /пустой строке/. Но в обоих случаях состояние системной перемен- ной $TEST будет отражать результат завершения операции READ. /Она бу- дет = 0, если до истечения времени ожидания ввод не был закончен. ┌──────────────────────────────────────────────────────────────┐ │ |>Read *a:0<- | │ │ | | │ │o|>Write a<- |o│ │ |-1 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.70 Односимвольный READ с временем ожидания ввода В обоих формах использования READ, как обычной, так и односим- вольной, с указанием необязательного параметра - времени ожидания /TI- MEOUT/ =0, /Обычный READ в таком случае будет выглядеть как Read Ans:0/, возвращаемый результат будет зависеть от состояния буфера кла- виа- туры, а также от того, используется ли он в реализации. Если бу- фер недоступен, то, наиболее вероятным результатом будет неуспешное завершение чтения с установкой $TEST =0 /ЛОЖЬ/. Если буфер исполь- зу- ется, чтение МОЖЕТ завершиться успешно. 3.4.4 Оператор WRITE Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ │ EXPRESSION │ Write "Текст" WRITE │ FORMAT │ W #,!,?5 │ *INTEGER EXPRESSION │ W *7 Стр. 95 ┌──────┐ Сокращение имени оператора │ W │ W "Test" Использование постусловий: │ │ при операторе │ ДА │ W:Truth "Значение=ИСТИНА" при аргументе │ НЕТ │ Использование косвенности │ ДА │ S a=12,b=33 W @"a=b" Обязательность аргументов │ ДА │ /см. примечание/ Разрешенность использования │ │ списка аргументов │ ДА │ W !,"Сумма =",SUM Установка времени ожидания │ НЕТ │ └──────┘ Оператор WRITE используется для вывода информации на текущее уст- ройство. Текущее устройство устанавливается совместным использованием операторов OPEN и USE, и идентифицируется системной переменной $IO. Системная переменная $IO была впервые упомянута в разделе 3.4.2, более подробное ее обсуждение будет приведено в главе 12. Формат выводимых на устройство данных, в зависимости от вида используемого аргумента /EXPRESSION, FORMAT, *INTEGER EXPRESSION/, оказывает воздействие на состояние некоторых системеных переменных /$X и $Y, а также некоторые другие, в зависимости от реализации/ Все сказанное при обсуждении в разделе, посвященном оператору RE- AD, о влиянии ввода управляющих кодов с терминала, на работу термина- ла, одинаково относится и к выводу через оператор WRITE этих управляю- щих кодов. См. таблицу 3.1, где содержится краткая сводка сведений по управляющим символам, вводимых с клавиатуры. 3.4.4.1 Argument=EXPRESSION Результат оценки выражения EXPRESSION выводится на устройство, назначенное текущим /активным/ устройством задания. Символы выводятся одновременно с ценкой выражения, слева нап- раво. При выводе каждого символа происходит модификация значения сис- темной переменной $X, ее значение увеличивается на 1 при выводе каждо- го символа. $X уменьшается на 1 при "забое" одного символа /получении кода "Backspase"=8/, и остается неизменным при получении остальных уп- равляющих кодов /со значениями от 1 до 7, с 9 по 31, а также 127/. Но заметим, что в некоторых реализациях поддерживается установка и отсле- живание правой границы поля вывода /обычно устанавливается через аргу- менты в OPEN или USE /, в этих случаях при достижении правой границы, система автоматически исполняет перевод курсора в первую позицию новой строки /то есть отрабатывает "перевод строки" -LF и "возврат каретки" - CR/, после чего модифицирует значения $X и $Y /то есть увеличивает $Y на 1, а $X приравнивает 0/ /*1/ _________________________________________________________________ В некоторых версиях поддерживается использование оператора WRITE без аргументов для того, чтобы просмотреть находящиеся в разделе локальные переменные. При этом выводится весь перечень локальных переменных, а также и локальных массивов, с присвоенными им значениями. /Поддержива- ется, например, в DSM-11 и DataTree MUMPS/ *1 В некоторых других реа- лизациях, когда не отслеживается правая граница поля вывода, значение $X и $Y могут возрастать до 255, дальнейшая попытка их увеличения при- равнивает их 0 /Обычно это не сопровождается перемещением курсора/ Стр. 96 3.4.4.2 Argument = FORMAT CONTROL Как Вы уже знаете, есть три спе- циальных аргумента для операторов READ и WRITE , управляющих форматом устройства: '!' - приводит к переводу вывода/ввода на новую строку '#' - обновляет страницу ввода/вывода, очищая /или прогоняя/ предыдущую '?nn' - переводит курсор /печатающую головку/ на позицию nn от левой границы. /Если текущее положение курсора, /печатающей головки/ НЕ превышает эту позицию/ При указании в аргументе знака '!' генерируется последователь- ность кодов, переводящая вывод на новую строку, на устройство посыла- ются коды "перевод каретки" /ASCII=13/ и "перевод строки" /ASCII=10/, значение системной переменной $Y увеличивается на 1, а $X устанавлива- ется в 0. В MUMPS допускается использовать несколько символов '!' под- ряд, без разделения их запятыми. Это демонстрируется на следующем при- мере. ┌──────────────────────────────────────────────────────────────┐ │ |>Write "Строка1",!!!!,?5,"Строка2"<- | │ │ |Строка1 | │ │o| |o│ │ | | │ │ | | │ │o| |o│ │ | Строка2 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.71 Оператор WRITE с управление форматом вывода Последовательность кодов, приводящая к "переводу страницы" на устройстве генерируется указанием '#', в аргументе WRITE. При этом на устройство посылается код "прогон листа" /'form feed' - FF ASCII=12/, и значения системных переменных $X и $Y приравниваются 0. Заметим, что посылка кода 'FF' не на всех устройствах приводит к прогону страницы, или очистке экрана. В главе 12 мы вернемся к обсуждению концепции вво- да и вывода. Аргумент '#' может также комбинироваться с произвольным количеством символов '!' без разделения их между собой запятыми. Нап- ример, ' W #!!!,"текст" ', будет означать перевод страницы, потом вниз на три строки и вывод строки "текст". Табуляция на указанную колонку /каждая колонка приравнивается ши- рине одного символа/, происходит при указании в этой форме выражения вида '?ЦЕЛОЕ_ЧИСЛО'. Курсор /печатающая головка/ передвигаются к пози- ции, задаваемой ЦЕЛЫМ ЧИСЛОМ, передвигаясь на число колонок, определя- емое разностью между значением $X для текущей строки, и указанным ЦЕ- ЛЫМ ЧИСЛОМ. Если текущее значение $X больше, чем это число, аргумент игнорируется. Если же меньше, то на устройство выводится последова- тельность пробелов, приводящая к тому, что $Х становится равным ЦЕЛОМУ ЧИСЛУ указанному в этом аргументе. Самая левая колонка /первая позиция $X в строке/ имеет значение равное 0. Стр. 97 В настоящее времы Комитетом по развитию MUMPS прилагаются усилия по включению в стандарт еще одной формы управления функциями уст- ройств, такими как например, позиционирование курсора на терминале, или перемотка магнитной ленты. На ранних стадиях обсуждения была уста- новлена приблизительно следующая форма синтаксиса подобного управле- ния: W \mne[parameters] Например, ' WRITE \CUP(2,20) ' передвигает курсор терминала на позицию $X=2, $Y=20, или 'Write \rewind ' перематывает магнитную ленту. /*1/ В этой форме знак '\' указывает на использование в аргументе опе- ратора WRITE специальной формы управления функциями устройства, a 'mne' - мнемоника исполняемой функции. Этот синтаксис, если будет включен в стандарт, позволит добится большей независимости от конкрет- ных устройств и переносимости программ между различными реализациями MUMPS. В некоторых реализациях уже поддерживается эта форма синтаксиса, но для каждой из них используется свой список ключевых мнемоник и их связи с именами конкретных устройств, обычно несовместимый между раз- личными версиями. Углубленное обсуждение использования устройств, и функциональной независимости, будет дан в главе 12. 3.4.4.3 Argument= *INTEGER EXPRESSION Эта форма выводит один сим- вол, с кодом задаваемым INTEGER EXPRESSION, на текущее устройство. Ин- терпретация этого символа зависит от конкретной реализации MUMPS и ви- да устройства. При посылке кодов, задаваемых положительными числами в диапазоне от 0 до 127, на терминалы, они ОБЫЧНО интерпретируются как соответствующие символы ASCII /см. приложение А, где приведены коды ASCII и их десятичные значения/. Продемонстрируем это следующим приме- ром. ┌──────────────────────────────────────────────────────────────┐ │ |>Write !,*7," Произошла ошибка "<- | │ │ | | │ │o|[Звуковой сигнал] Произошла ошибка |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.72 WRITE *var В этом примере, Вы видите вывод строкового сообщения и активацию звукового сигнала. /ASCII =7 вызывает звуковой сигнал с терминала/. Подобная реакция терминалов на непечатаемые символы будет происходить даже при включении их "вовнутрь" строковых литералов, где они не будут явно видны. ________________________________________________________________ *1 - Каждая из версий поддерживается свой перечень форм подобного синтаксиса, например для ISM и Data Tree MUMPS : позиционирование курсора W /CUP:row:col W /C(col,row) очистка экрана W /ED W /EF и т.д. и т.п. Стр. 98 В следующем примере демонстрируется прием часто используемый при управлении форматом вывода различных устройств. Выше мы уже обсуждали использование аргумента '#' для инициирования новой страницы. При ис- пользовании этого аргумента на устройство посылается код FF /form feed (ASCII=12)/, и системные переменные $X и $Y устанавливаются в 0. Но, к сожалению, не все типы видеотерминалов очищают экран при получении ко- да FF. В этом примере используется управляющая Escпоследовательность для очистки экранов терминалов совместимых с VT-100. ┌──────────────────────────────────────────────────────────────┐ │ |>Write *27,"[H",*27,"[2J",#," Новая страница"<- | │ │ | | │ │o|__________________________________________________________|o│ │ | Новая страница | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.73 Очистка экрана Для большинства терминалов предусмотрена возможность управления их функциями, такими, например, как очистка экрана, или произвольное позиционирование курсора, путем посылки на них некоторой Esc - после- довательности. Такие последовательности начинаются кодом Esc /AS- CII=27/, следом за которым посылаются один, или больше кодов. Терминал воспринимает коды, следующие за Esc как управляющие, и не отображает их на экране. В примере 3.73 последовательность '*27,"[H", указывает терминалу /совместимому с VT-100/, на необходимость перемещения курсо- ра в левый верхний угол экрана, а последовательность '*27,"[2J" вызы- вает удаление с экрана всех символов от текущей позиции курсора до конца экрана. Необходимость посылки также и аргумента '#' объясняется необходимостью установким $X и $Y в 0, для того, чтобы синхронизиро- вать положение курсора терминала и значения этих системных переменных. MUMPS "не понимает", что посланная Esc последовательность очистила эк- ран, и установила курсор в его левый верхний угол. Другая и более обобщенная возможность достичь тех же результатов состоит в создании определенного набора специальных переменных при за- пуске прикладного пакета, и использовании этих переменных, как косвен- ных аргументов оператора WRITE для управления терминалом и достежения независимости прикладного программного средства от типа конкретно при- мененного терминала. Посмотрите, как это реализовано в примере: ┌──────────────────────────────────────────────────────────────┐ │ |>If DevType="VT100" s ClrScrn="*27,""[H"",*27,""[2J"",#"<-| │ │ |>If DevType'="VT100" s ClrScrn="#"<- | │ │o|> |o│ │ |>Write @ClrScrn,"Новая страница"<- | │ │ | | │ │ |_________________________________________________________ | │ │ | Новая страница | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.74 Использование косвенности в аргументе WRITE Стр. 99 В этом примере мы помещаем в переменную ClrScrn необходимые аргу- менты для исполнения очистки экрана на текущем терминале. Теперь можно использовать эту переменную как косвенный аргумент оператора WRITE для очистки экрана в любом месте прикладного пакета программ. Заметим, что характер использования *INTEGER EXPRESSION одновре- менно зависит как от типа и вида конкретного устройства, так и от спе- цифики используемой версии MUMPS. В некоторых реализациях в этой форме использования оператора WRITE переменные $X и $Y модифицируются, в не- которых нет. В некоторых реализациях это значение не лимитируется диа- пазоном разрешенных ASCII кодов. В некоторых реализациях разрешено ис- пользовать отрицательное значение INTEGER EXPRESSION, как специальную форму управляющих аргументов для тех же устройств. /Так, например, WRITE *-5 может управлять перемоткой магнитной ленты./ Необходимо об- ратиться к справочному руководству по Вашей реализации, за разъяснени- ями о действиях системы в подобных случаях. ┌──────────────────────────────────────────────────────────────┐ │ |>Write !,?20,"Один",*13,?10,"Два",*13,"Три"<- | │ │ | | │ │o|Три Два Один |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.75 Модифицирование $X и $Y командой WRITE *var При исполнении команд в непосредственном режиме, MUMPS автомати- чески осуществляет перевод строки после нажатия клавиши . Одна- ко при исполнении программы все операторы WRITE опираются на текущее положение курсора или печатающей головки. Новая строка будет генериро- ваться только при явном использовании аргумента '!' /WRITE !/, или в некоторых реализациях, достижении правой границы поля вывода. То есть при исполнениии каждого нового оператора WRITE не исполняется автома- тически операция перевода строки. В примере 3.75, указана одна явная операция перевода строки, в начале оператора WRITE. Далее используется управление табуляцией для перемещения в двенадцатую колонку и вывода строки "один", аргумент *13 переводит курсор опять в нулевую колонку без посылки символа LF /AS- CII=10/. Следущая табуляция выводит строку "два" в колонку 10 и так далее. В этом же примере предполагается, что значения переменных $X и $Y модифицируются системой MUMPS при использовании аргумента вида 'Write *Argument'. Для того, чтобы быть абсолютно уверенными в поведе- нии системы при использовании подобного синтаксиса можно использовать следующий прием: __________________________________________________________________ *1 Заметим, что НЕ все реализации поддерживают присвоение значений системным переменным, например DSM-11 v3 /Диамс 3.0/ Стр. 100 ┌──────────────────────────────────────────────────────────────┐ │ |>Write !,?20,"Строка",*13 Set $X=0 Write ?10,"Проба"<- | │ │ | | │ │o| Проба Строка |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.76 Влияние значения $X на исполнение WRITE ?nn /*1/ Здесь оператор SET используется для явного присвоения системной переменной значения 0, после того, как использование Write *13 переме- щает курсор в первую колонку текущей строки. 3.4.5 Оператор CLOSE Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ CLOSE │ DEVICE │ Close 8 │ │ ┌──────┐ Сокращение имени оператора │ C │ C 10 Использование постусловий: │ │ при операторе │ ДА │ C:Truth MagTape при аргументе │ НЕТ │ Использование косвенности │ ДА │ S Prn=63,Dev="Prn" C @Dev Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ C MagTape,@Dev Установка времени ожидания │ НЕТ │ /*1/ └──────┘ Оператор CLOSE используется для "освобождения" одного или нес- кольких устройств зерезервированных заданием процессом, и возвращения их в систему, где они могут быть зарезервированы другим процессом. /заданием/ Устройства захватываются /резервируются/ оператором OPEN, а оператор USE делает захваченное устройство активным. Таково общее из- ложение концепции использования устройств в MUMPS более подробно расс- матривемой в разделе 3.4 , и описанной в главе 12, где рассматриваются операции ввода/вывода. После того, как устройство освобождено /"закрыто"/, попытка сде- лать его текущим, с помощью оператора USE /без предварительного ис- пользования OPEN на это устройство/, приведет к ошибке. ________________________________________________________________ *1 В некоторых реализациях допускается использования времени ожидания в операторе CLOSE. Во первых для того, чтобы можно было проанализиро- вать успешность операции, во-вторых указать системе время, в течении которого, оно, например должна сбросить все буфера, связанные с уст- ройством. Такая возможность реализована в DataTreeMUMPS ver4.2 в сле- дующей форме: Close DEVICE [:parameters] [:timeout] Стр. 101 Если закрываемое устройство было текущим /установленным с помощью USE/, то после его освобождения текущим устройством задания станет его основное устройство /с которого был запущен этот процесс при регистра- ции, или назначенное фоновому процессу при запуске /См. описание опе- ратора JOB//, как если бы была использована команда USE 0. При завершении процесса /задания/ исполняется неявный CLOSE на все зарезервированные устройства. Например, при исполнении оператора HALT все устройства зарезервированные процессом будут возвращены об- ратно в систему. 3.4.5.1 Argument = DEVICE Если устройство, связанное с именем DEVI- CE открыто /после использования OPEN/, то оно будет освобождено и возвращено обратно в систему. Все последующие попытки использовать оператор USE применительно к устройству DEVICE будут ошибочными. Если Вы закрываете /"освобождаете"/ текущее устройство процесса /задания/, то текущим устройством обычно становится основное устройс- тво Ввода/Вывода задания /процесса/ - но это положение не определено СТАНДАРТОМ языка. Основное устройство также может быть закрыто. Одна- ко, при этом следует учесть, что если в этом процессе, после закрытия его основного устройства / и без назначения другого текущего устройс- тва ввода/вывода/ потребуется вывод /в том числе и сообщения об ошиб- ке/, то исполнение этого процесса будет приостановлено на неопределен- ное время. / то есть до тех пор, пока это устройство не будет вновь открыто./ Неудачная попытка исполнения оператора CLOSE игнорируется. 3.4.6 Оператор PRINT /Этот оператор НЕ входит в стандарт языка- Прим. Автора/ Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ │ без аргументов │ Print PRINT │ LABEL[+Offset] │ Print Start+1 │ LABEL[+Offset]:LABEL[+Offset] │ Print Beg:End │ │ ┌──────┐ Сокращение имени оператора │ P │ P Start+5:End Использование постусловий: │ │ при операторе │ ? │ при аргументе │ ? │ Использование косвенности │ ? │ Обязательность аргументов │ НЕТ │ Разрешенность использования │ │ списка аргументов │ ? │ Установка времени ожидания │ НЕТ │ └──────┘ Стр. 102 Хотя этот оператор и не является частью стандарта языка он вклю- чен в большинство существующих реализаций MUMPS. Кроме того, в некото- рых из них, наряду с оператором PRINT поддерживается эквивалентный ему оператор ZPRINT. Оператор PRINT используется для вывода части строк программы, имеющейся в разделе /*1/ на текущее устройство /установленное операто- ром USE/. Часто при этом производится допустимое форматирование вывода /так вместо символа TAB метки отделяются от последующей командной строки пробелами, или же, если метки нет начинаются с пробелов/ Так как этот оператор не включен в стандарт, необходимо обратить- ся к документации по конкретной реализации в отношении его использова- ния и синтаксиса. 3.5 Системные и прочие операторы. В этом разделе пойдет речь о операторах, которых нельзя отнести ни к одной из ранее упомянутых категорий. Так оператор LOCK, является уникальным, и порожден необходимостью обеспечить механизм упорядочить доступ к данным, так как глобальные массивы, в общем случае доступны, в одно и тоже время, всем процессам. Оператор JOB используется для за- пуска /порождения/ нового MUMPS процесса /задания/, с помощью операто- ра HANG можно приостановить исполнение процесса, а VIEW позволяет просмотреть или модифицировать элементы операционной системы. Другая категория включает группу операторов начинающихся с буквы 'Z'. Операторы, начинающиеся с этой буквы, включают в себя новые, за- частую еще только тестируемые, элементы языка. Однако, по соглашению, в эту же группу, в большинстве реализаций, включен набор команд, обес- печивающий манипуляции с MUMPS программами. /загрузку, сохранение, пе- чать, удаление и так далее.../. В этом разделе мы постараемся коснуть- ся только тех 'Z' - операторов, которые включены в большинство реали- заций. В этом разделе пойдет речь о слудующих операторах: BREAK Используется как для обеспечения возможности прерывания ис- полнения программы с клавиатуры, так и для установки дина- мической точки прерывания, в целях обеспечения диалоговой отладки программ. HANG Используется для приостановки исполнения программ на определенный интервал времени. JOB Запуск /порождение/ нового, незавиcимого MUMPS процесса в другом разделе. LOCK Указание использования процессом, или освобождение от этого указания, одной, и более, ветвей глобального массива. /*2/ __________________________________________________________________ *1 Текущая программы не всегда загружается в раздел. В некоторых реа- лизациях определение текущей программы производится установкой соот- ветствующего указателя с ее именем. /DataTreeMUMPS, ISM / *2 Не только ветви, но и вся глобаль целиком Стр. 103 VIEW Этот оператор специфичен в каждой реализации MUMPS. В общих чертах он позволяет программисту просматривать и модифици- ровать области системной памяти. /Как оперативной, так и дисковой/. Для тех, кто хорошо знаком с BASIC-ом можно сказать, что этот оператор, вместе с функцией $VIEW, подо- бен операторам языка BASIC - PEEK и POKE , соответственно. ZLOAD Загрузка программ с диска в рабочий раздел, в некоторых реа- лизациях, используя этот оператор можно загрузить програм- му и с других устройств, например, с магнитной ленты. ZINSERT Вставка в текущую программ новых программных строк ZREMOVE Удаление программы из раздела, или с диска. В некоторых реализациях, этот оператор может только удалять строки программы из раздела. В этом случае, для удаления програм- мы с диска необходимо записать с именем удаляемой програм- мы на диск пустой раздел /то есть исполнить следующую пос- ледовательность команд: ZREMOVE ZSAVE ProgName/ /Поддержи- вается например, в DSM-11/ ZPRINT Вывод текущей программы, или части ее строк, на текущее устройство. Этот оператор соответствует /или полностью эк- вивалентен/ оператору PRINT, описанному в разделе 3.4.6. ZSAVE Сохранение текущей программы на диск В некоторых реализациях можно сохранять программу и на другом устройстве, напри- мер, на магнитной ленте. 3.5.1 Оператор BREAK Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ │ без аргументов │ Break ... BREAK │ в зависимости от версии │ Break 1 ... ┌──────┐ Сокращение имени оператора │ B │ B ... Использование постусловий: │ │ при операторе │ ДА │ B:Truth ... при аргументе │ ? │ Зависит от реализации Использование косвенности │ ? │ Зависит от реализации Обязательность аргументов │ НЕТ │ Разрешенность использования │ │ списка аргументов │ ? │ Зависит от реализации Установка времени ожидания │ НЕТ │ └──────┘ Стр. 104 Оператор BREAK не полностью определен в стандарте языка, в связи с этим, его функционирование определяется конкретной реализацией MUMPS. В общем случае, можно сказать, что этот оператор используется для отладки программ, с переходом в непосредственный режим, или подоб- ный ему Shell отладчика. В этом случае, программист имеет возможность проверить или изменить состояние локальных переменных в разделе, или глобальных массивов, и затем продолжить исполнение прерванной програм- мы. В стандарте языка не указан метод, для продолжения исполнения прерванной по BREAK программы. В большинстве реализаций, для этих це- лей используется оператор ZGO без аргументов, или допускается без-ар- гументная форма использования оператолра GO /См. раздел 3.3.1.4/, как нестандартизованные формы продолжения программ. Конкретные формы использования оператора BREAK определяются ис- пользуемой реализацией. В некоторых из них, все встречающиеся в прог- раммах BREAK будут игнорироваться до тех пор, пока они не будут запу- щены в непосредственном режиме. В таких случаях, если Вы регистрируе- тесь для работы в непосредственном режиме, и запускаете программы с использованием опертора DO, то все встречающиеся BREAK будут приеры- вать исполнение. При запуске программ в прикладном режиме BREAK будут игнорироваться. Проверьте содержащуюся в Вашей документации информацию о том, где и как будет работать оператор BREAK, а также, как продол- жать программу, после прерывания ее исполнения. ┌──────────────────────────────────────────────────────────────┐ │ |Test ;Демонстрация работы с BREAK | │ │ | Write !,"LC=",LC Break | │ │o| For i=1:1:LC Write !," Строка =",i |o│ │ | Quit | │ │ |__________________________________________________________| │ │o|>Set LC=100 Do ^Test<- |o│ │ | | │ │ |LC=100 | │ │o|<> |o│ │ |>Set LC=2<- | │ │ |>Write "LC=",LC<- | │ │o|LC=2 |o│ │ |>ZGO | │ │ |Строка 1 | │ │o|Строка 2 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.77 Демонстрация использования BREAK BREAK после первого оператора Write в этой программе, прерывает ее исполнение, и переводит терминал в непосредственный режим работы. После того, как мы изменяем значение переменной LC, используя оператор ZGO без аргументов, мы указываем на необходимость продолжения исполне- ния прерванной программы. Заметьте, что изменение значение переменной LC оказывает непосредственное влияние на ход исполнения программы. Цикл исполняется не 100 раз, а только 2. Использование ZGO - демонс- трация наиболее общего подхода, этот оператор работает не во всех сис- темах. Стр. 105 Наиболее часто встречающаяся цель использования BREAK с аргумен- тами - разрешение, или запрещение отдельных классов прерываний. Во многих реализациях исполнение программ может быть прерывано нажатием на клавиатуре + /То есть нажав, и удерживая клавишу /Control/, Вы одновременно нажимаете клавишу /. Используя BREAK с аргументом Вы сможете запретить возможность преждевременного прерыва- ния исполнения прикладной программы. В реализациях, поддерживающих этот механизм, этот аргумент оцени- вается как целое число, принимающее фиксированные значения. Но все же, обратитесь к документации по Вашей реализации MUMPS cистемы для получения исчерпывающих пояснений. /*1/ _________________________________________________________________ *1 Не желая отсылать пользователя к документации, приведем краткую сводку информации по использованию BREAK в трех реализациях MUMPS: DSM-11, ISM /NTSM/ и DataTree MUMPS DSM-11 /Диамс 2.0, и старше/ BREAK можно использовать либо без аргументов, либо с тремя аргументами 0, 1 и 2 B 0 - в диалоговом режиме отменяет все встречающиеся операторы BREAK в программах. Оператор BREAK не работает в прикладном режиме. В системе поддерживается встроенный диалоговый отладчик, который управляется оператором ZBREAK /ZB/ Оператор ZB используется со следующими аргументами: ON, OFF, IN, OV, OU /аргументы НЕ могут быть элементами выражений/ Если Вы использовали команду ZB ON для прерывания программы, то нажатию , или при вводе команды ZB IN, производится пошаговое исполнение операторов и их аргументов. ZB OFF - выход из отладчика ZB OV - исполнение вызова подпрограммы /процедуры/, аргумента XECUTE, за один шаг. /Без трассировки/ ZB OU - выход из трассируемой процедуры, подпрограммы к следующему оператору, исполняемому после QUIT /этой процедуры/ При отладке программ Вы можете выполнять любые операции, в том числе и вызов редактора и утилит. Необходимо только позаботиться при этом о том, чтобы не было повреждено окружение локальных переменных. Произшедщая ошибка в диалоговом режиме не отрахается на прерванной программе. Если указано подключение отладчика в конфигурацию ЭВМ, то любую исполняемую программу можно прервать по нажатию клавиш +. После чего Вы можете предпринимать действия аналогичные тому, как если бы в этом месте программы встретился оператор ZB ON. Стр. 106 ISM /NTSM/ Interface Standart MUMPS Оператор BREAK используется только без аргументов, и прерывает исполнение программы, передавая управление в диалоговый режим. Если в диалоговом режиме возникло состояние ошибки, Вы не сможете продолжить исполнение приостановленной программы. А если Вы не хотите продолжать исполнение приостановленной программы, нажмите + DataTree MUMPS version 4.2 + Оператор BREAK может использоваться как без аргументов, так и ни- ми. Безаргументый BREAK приостанавливает исполнение программы и вызы- вает диалоговый отладчик. /Его описание лучше изучать по технической документации/. Аргумент может принимать три значения 1 - Управление прерыванием по + 2 - Управление прерываниями с модема 4 - Управление прерываниями локальной сети BREAK с положительным значением аргумента разрешает обработку со- ответствующего прерывания /ий/, с отрицательным - запрещает. Для уп- равления одновременно несколькими прерываниями сложите соответствующие значения аргумента. 3.5.2 Оператор HANG Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ HANG │ SECONDS │ HANG 10 │ │ ┌──────┐ Сокращение имени оператора │ H │ H Pause Использование постусловий: │ │ при операторе │ ДА │ H:Truth Pause при аргументе │ НЕТ │ Использование косвенности │ ДА │ S a=10,Hang="a" H @Hang Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ H 10,20 Установка времени ожидания │ НЕТ │ └──────┘ Оператор HANG приостанвливает исполнение программы на определен- ное время, определяемое параметром SECONDS. SECONDS интерпретируется как целое число /*1/, и определяет число секунд до продолжения испол- нения. Если это значение равно 0, или отрицательное - то оператор не будет исполняться. ___________________________________________________________________ *1 В DataTreeMUMPS можно устанавливать с точностью до 0.1 секунды. Стр. 107 Пауза, на которую производится приостановка исполнения, не в точности соответствует значению системных часов /отражаемого в системной пере- менной $HOROLOG / и может быть и более, так и менее одной секунды. Например, если оператор HANG 1 исполняется непосредственно перед уве- личением значения системных часов на одну секунду, то действительная пауза будет долей секунды. Одновременно и оператор HANG, и оператор HALT могут быть сокраще- ны до одной буквы 'H'. MUMPS различает эти операторы между собой по наличию, или отсутствию аргумента. HANG всегда используется с аргумен- том, HALT всегда используется без аргументов. Оператор HANG в большинстве случаев используется для того, чтобы дать пользователю возможность прочитать выводимое на экран сообщение перед тем, как произвести очистку экрана и продолжить исполнение, как показано на примере ниже. ┌──────────────────────────────────────────────────────────────┐ │ | Do DispText Hang 30 Do ClrScrn | │ │ | . | │ │o| . |o│ │ | . | │ │ | . | │ └──────────────────────────────────────────────────────────────┘ Рис. 3.78. Оператор HANG Основное неудобство, заклячаемое в подобном подходе состоит в том, что оператор HANG исполняется в течениии фиксированного интерва- ла, и пользователь вынужден ждать указанное время. Другой подход к ре- шению этой проблемы состоит в том, чтобы использовать оператор READ с временем ожидания, как показано в примере 3.67, при рассмотрении опе- ратора READ. 3.5.3 Оператор JOB Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ │ LABEL │[(PARAM1,PARAM2..)] JOB Print JOB │ LABEL^ROUTINE │[:SPCL PAR][:TIMEOUT] JOB Pr^Pat │ ^ROUTINE │ JOB ^Upd(PID) │ │ JOB ^Util(Fun)::30 ┌──────┐ Сокращение имени оператора │ J │ J Print^BILLS Использование постусловий: │ │ при операторе │ ДА │ J:Truth ^PatPrint(PID) при аргументе │ ДА │ J LA120^Prt:P="LA120" Использование косвенности │ ДА │ S a="SavePat" J @a(PID) Обязательность аргументов │ ДА │ Разрешенность использования │ │ списка аргументов │ ДА │ J S^PAT(PID),Prt^PAT(PID) Установка времени ожидания │ ДА │ J ^Print::5 └──────┘ Оператор JOB производит запуск /порождение/ нового MUMPS процес- са, в другом выделенном разделе. Стр. 108 MUMPS обычно допускает режимы многопользовательской и многозадачной работы /с разделением времени/. В этом случае, в одно и тоже время, на компьютере может быть запущено несколько прикладных заданий./процес- сов/ Каждому заданию /процессу/ выделяется собственный раздел, и свой, уникальный в системе, идентификационный номер /иногда в реализациях допускается идентификация процессов и по имени/, который отражается в системной переменной $JOB.Задания запускаются и функционируют незави- симо друг от друга, но могут связываться между собой через глобальные массивы, или используя другие специальные приемы. Любой из процессов может запускать /порождать/ один или несколько других процессов, которые могут функционировать конкурируя с процес- сом, их породившим. Функционирование оператора JOB можно уподобить процессу регистрации на MUMPS терминале, и затем запуску с этого тер- минала программы, с использованием оператора DO. За исключением того обстоятельства, что запуская новый процесс по JOB можно НЕ назначать ему основного устройства Ввода/Вывода. Обычно, при регистрации с MUMPS терминала, он назначается основ- ным устройством для Вашего задания. То есть при регистрации MUMPS не- явно для Вас выполняет OPEN и USE на этот терминал, так что все после- дующие операции Ввода/Вывода, до тех пор пока Вы не используете опера- торы OPEN и USE, будут направляться на него. Когда же ВЫ запускаете новое задание, используя JOB, то этому заданию /процессу/ можно назна- чить основное устройство для ввода/вывода, а можно и не назначать. В стандарте MUMPS не оговаривается, что должно произойти, когда запущен- ное оп JOB задание будет пытаться производить операции ввода/вывода без явного использования OPEN и USE. В некоторых реализациях для за- пускаемых процессов неявно назначается, в качестве основного устройс- тва ввода/вывода, основное устройство родительского процесса. В других реализациях, если не будет явного использования OPEN и USE на устройс- тво, то возникнет ошибка. Обычно, в этом случае, допускается конкрет- ное указание идентификатора основного устройства для нового процесса, в виде необязательного параметра, включаемого в список специальных па- раметров /SPCL PAR/ В реализациях, допускающих назначение процессу основного устройс- тва, в случае назначения одного и того же устройства для "родительско- го" и "порожденного" процесса, может произойти "зависание" "порожден- ного" процесса, при выводе на это устройство, пока оно не будет осво- бождено "родительским" процессом. 3.5.3.1 Аргументы с включением списка передаваемых параметров в виде (PARAM1,PARAM2..) Этот формат оператора подобен, но не идентичен, ис- пользованию оператора DO с передачей параметров. /См. раздел 3.3.3.2./ Любой параметр, из заключенного в круглые скобки списка действительных параметров, может быть любым допустимым MUMPS выражением. Значения, определяемые этими выражениями, используются при создании в новом про- цессе переменных. /в соответствии с их именами, указанными в списке формальных параметров инициализируемого процесса./ Значения, связываемые с именами инициализируемых переменных пере- даются только в одном направлении - из родительского процесса в порож- даемый. Порождаемый процесс не имеет доступа к таблице символов роди- тельского процесса, и не возвращает результаты своей работы через ло- кальные переменные. Стр. 109 Связь между "родительским" и "порожденным" процессом может быть орга- низована, например, через глобальные переменные. При передаче парамет- ров, в списке действительных параметров, возможна передача только зна- чения, но не возможна передача ссылки. При необходимости обращайтесь к разделу 3.3.2.2. и главе 9, где подробно обсуждаются проблемы, связан- ные с передачей параметров. 3.5.3.2 Аргументы с включением списка специальных параметров Эти не- обязательные параметры /:SPCL PARMS/ используются для задания порожда- емому процессу определенных параметров работы. Значения, включаемые в список специальных параметров, могут определять размер раздела, прио- ритет, основное устройство ввода/вывода, и некоторые другие параметры инициируемого процесса /задания/. Список значений в этом параметре должен начинаться с двоеточия ':', но его внутренняя структура, и син- таксис задания отдельных параметров, зависят от конкретной реализации MUMPS. 3.5.3.3 Аргументы с указанием времени ожидания Указание необязатель- ного параметра TIMEOUT /время ожидания/ позволяют "родительскому" про- цессу получить информацию о успешности запуска "порождаемого" процес- са. В некоторs случаях система не может произвести запуск нового про- цесса. /Например, не хватает памяти для выделения раздела требуемого размера/. /*1/ Стандартом не оговариваются действия системы в подобных случаях, поэтому некоторые реализации "зависают" до тех пор, пока про- цесс не сможет быть запущен /то есть на неопределенное время/, другие - в этом случае игнорируют запрос на иницирование нового процесса. Па- раметр TIMEOUT, оцениваниемый как целое число секунд, определяет ин- тервал времени, в течении которого система пытается инициировать новый процесс /иногда "порождаемый" процесс именуют еще и "фоновым" процес- сом/, перед возвратом управления в процесс, где встретился оператор JOB. Если "фоновый" процесс успешно запущен, то системная переменная $TEST будет установлена в 1 /ИСТИНА/, при невозможности инициирования фонового процесса, в $TEST будет занесен 0 /ЛОЖЬ/. В приводимом примере демонстрирется запуск фонового процесса для печати отчета, в то время как основной процесс может исполнять другие действия, например выборку данных для следующего отчета. В этом приме- ре предполагается, что устройство номер 15 является по-строчно печата- ющим принтером, и вывод отчета будет производиться на него. Здесь так- же не представляется процедура производящая собственно печать отчета, а только ее запуск. Также предполагается, что в фоновом процессе для печати отчета на устройство 15 будут использованы операторы OPEN и USE, поэтому перед запуском процесса производится проверка доступности этого устройства. ___________________________________________________________________ *1 Новый процесс не может быть запущен и тогда, когда в системе исчер- пан лимит на количество запускаемых заданий. ISM /NTSM/ определяет число заданий равное числу пользователей, которое устанавливается при конфигурировании системы. DataTreeMUMPS v4.2+ определяет число заданий в зависимости от приобретенной Вами лицензии. Максимально возможное число заданий, в конкретной системе, возвращается функцией $ZJOB("TO- TAL") Стр. 110 ┌──────────────────────────────────────────────────────────────┐ │ |Sprt Set Status=0 O 15::0 | │ │ | If '$T W *7," Принтер НЕДОСТУПЕН" Quit | │ │o| Job PRT^REPORT(PID)::0 Close 15 |o│ │ | If W "Отчет запущен" Set Status=1 | │ │ | Else W " Отчет НЕ запущен" | │ │o| Quit |o│ │ |__________________________________________________________| │ │ |>Set PID=9913 Do Sprt<- | │ │o| Принтер НЕДОСТУПЕН |o│ │ |>D Sprt<- | │ │ | Отчет НЕ запущен | │ │o|>D Sprt<- |o│ │ | Отчет запущен | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.79 Использование оператора JOB Заметьте, что в операторах OPEN и JOB используется параметр TIME- OUT=0. В обоих случаях случаях управление будет немедленно возвращено в программу, с переменной $TEST установленной в 1 или 0, в зависимости от успешности выполнения требуемой операции. Обратите внимание на то, что в процедуре используется также присвоение значения переменной Sta- tus, /0 или 1/ в зависимости от успешности запуска фонового процесса печати отчета. И наконец, посмотрите, как проводится проверка доступ- ности устройства 15, и на то, что оператор CLOSE стоит непосредственно после оператора JOB. Если фоновый процесс будет успешно запущен, то он попытается открыть, используя оператор OPEN, устройство 15, и исполне- ние фонового процесса "зависнет", пока устройство не будет освобождено "родительским" процессом. В этом случае не будет промежутка времени между закрытием устройства в "родительском" процессе, и его "открыти- ем" в "фоновом", в течении которого это устройство может быть захваче- но другими заданиями, в этом случае фоновый процесс попросту "завис- нет", если Вы конечно не используете в нем оператор OPEN с параметром TIMEOUT. Если Вы указываете параметр TIMEOUT в операторе JOB без задания списка специальных параметров, то он должен быть отделен от имени за- пускаемой программы двумя двоеточиями - '::TIMEOUT'. Дополнительные примеры использования оператора JOB, включая пере- дачу параметров приведены в разделе 9.5 3.5.4 Оператор LOCK Оператор│ Аргументы Примеры ________│________________________________ ____________ │ │ │ без аргументов │ Lock ... LOCK │ VARIABLE │ Lock XYZ │ (VAR1,VAR2,VAR3,..) │ [:TIMEOUT] Lock (^A,^B(22)) │ +VARIABLE │ Lock +XYZ(33) │ -VARIABLE │ Lock -XYZ(33) Стр. 111 ┌──────┐ Сокращение имени оператора │ L │ L ^abc Использование постусловий: │ │ при операторе │ ДА │ L:Truth ^PAT(PID) при аргументе │ НЕТ │ Использование косвенности │ ДА │ S gbl="^ZIP(14850)" L @gbl Обязательность аргументов │ НЕТ │ Разрешенность использования │ │ списка аргументов │ ДА │ L ^a,^XYZ(12,34) /*1/ Установка времени ожидания │ ДА │ L(^BC(23),^Addr):0 └──────┘ В главе 3 мы уже обсуждали концепцию глобальных переменных. В от- личии от локальных переменных, которые доступны только из данного про- цесса, глобальные переменные записываются на диск, и могут быть дос- тупны для всех процессов, запущенных в этой области. /*2/ При этом различные процессы могут читать и изменять данные в одном узле глобали ОДНОВРЕМЕННО. Кроме хороших возможностей осуществления взаимосвязи между заданиями, этот подход имеет также и некоторые потенциальные не- достатки. Поясним их на небольшом примере. Предположим, что на компь- ютере, одновременно работают два независимых задания, которые читают значения данных из глобального массива, производят их изменение, и за- писывают обратно. При этом возможно возникновение следующей ситуации: после того, как первый процесс прочитал некоторые данные, второй про- изводит чтение тех же данных, изменяет их, и записывает их на диск, прежде, чем производит запись измененных данных первый процесс. В ито- ге, результат работы второго процесса оказывается потерянным, так как первый процесс записывает данные обработанные без учета изменений, произведенных вторым процессом. Становится очевидным, что необходимо каким-то образом избежать конфликтов подобного типа. Оператор LOCK обеспечивает механизм резервирования базы данных, или ее части, для отдельного процесса. При этом, когда одним процессом производится блокировка глобали, или ее ветви, ни один другой процесс не может произвести БЛОКИРОВКУ этой глобали /ветви/. Оператор LOCK производит установление признака использования процессом глобального массива /или его ветви/. Этот процесс именуется иногда "блокировкой по соглашению", поскольку после использования LOCK на глобаль, она по прежнему может быть прочитана или изменена любым процессом. __________________________________________________________________ *1 При использовании списка аргументов НЕ заключенного в круглые скоб- ки добавляет в таблицу блокировок только последнее имя. Остальные под- робности - в разделе 3.5.4.1 *2 Некоторые реализации позволяют осуществить доступ и к глобалям, на- ходящимся в других рабочих областях, через использование так называе- мого "расширенного синтаксиса" глобального имени. Так, например, в DSM-11 Вы можете обратиться к глобалям находящимся в другом КИПе и наборе томов /НАБ/: ^["КИП","НАБ"]имя_глобали Тот же самый аппарат используется в DataTreeMUMPS v4.2+ применительно к рабочей области и узлу локальной сети: ^["node","nspace"]global_name Стр. 112 Предотвращение неумышленного повреждения базы данных, в результа- те одновременного доступа разных процессов к одним узлам данных может быть достигнуто только при условии, что во всех процессах используется оператор LOCK и правильно анализируется результат его действия на гло- бали. Поэтому можно сказать, что оператор LOCK эффективен только тог- да, когда все работающие программисты приходят к соглашению по его ис- пользованию. И поэтому, термин "блокировка", используемый приложитель- но к действию оператора LOCK, не должен пониматься буквально, в смысле того, что использование этого оператора предотвращает доступ к упоми- наемым в его аргументах глобалям. ┌────────────────────────────────────────────────────────────┐ │ Использование оператора LOCK не запрещает доступ других │ │ процессов, и в том числе и собственного, к элементам │ │ глобальных массивов, которые объявляются "используемыми" │ │ в процессе. │ └────────────────────────────────────────────────────────────┘ Оператор LOCK производит только временную блокировку глобалей. Все "заблокированные" глобали автоматически освобождаются при заверше- нии процесса /при использовании HALT, например/. "Заблокированные" глобали могут быть также явно "разблокированы" при использовании соот- ветствующей формы оператора LOCK. Таблица "блокируемых" глобальных имен данного процесса доступна всем остальным процессам MUMPS cистемы, и потому, имя "заблокированное" одним процессом не может быть "блоки- ровано" другим процессом, пока оно не будет "разблокировано" первым процессом. В тоже врремя, в одном процессе может быть использована повторная "блокировка" одного имени. Результат повторных блокировок одного имени зависит от формы использования оператора LOCK. Если Вы используете "дополняющую" форму оператора LOCK, то в ней при этом по- полняется стек /счетчик/ операций "блокировки". В этом режиме каждый последующий LOCK на одно и тоже имя заносит его в стек. "Блокирован- ные" имена могут быть удалены из стека при использовании "удаляющей" формы LOCK. Кроме того, есть такая форма оператора LOCK, которая про- изводит очистку таблицы "блокировок" перед исполнением "блокировки" /"блокировок"/. Обратимся назад, к моменту обсуждения иерархической структуры глобального массива. Когда Вы производите "блокировку" элемента глоба- ли, то одновременно производится "блокирование" как ВСЕХ его "потом- ков", так и его ПРЯМЫХ "предков". Обсудим этот механизм на примере глобальной структуры представленной на рисунке. Если мы "блокируем" элемент ^A(33,57), то одноврменно происходит "блокировка" как ВСЕХ его "потомков" /в данном случае узлов ^A(33,57,17) и ^A(33,57,94)/, так и НЕПОСРЕДСТВЕННЫХ "предков" /в дан- ном случае ^A и ^A(33)/. Однако, несмотря на то, что при "блокировке", ^A(33,57) "блокируются" узлы ^A и ^A(33), являющиеся его НЕПОСРЕДС- ТВЕННЫМИ "предками", другим процессом может осуществляться блокировка любых других узлов, являющихся их потомками. Например ^A(1) или ^A(33,9). Другим интересным аспектом является блокирование локальных имен. Несмотря на то, что таблица локальных символов одного процесса недос- тупна другому процессу, Вы можете использовать блокировку имен локалей также, как и глобалей. Стр. 113 ^A ┌───────┐ └───────┘ ┌────────────┬──────────────┼────────────┬──────────┐ │ │ │ │ │ │(1) │(15) │(33) │(39) │(9912) ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │"Data1"│ │"Data2"│ │"Data3"│ │"Data4"│ │"Data5"│ └───────┘ └───────┘ └───┬───┘ └───────┘ └───────┘ │ ┌────┴───────┐ │(9) │(57) ┌───┴───┐ ┌──┴────┐ │"Data6"│ │"Data7"│ └───────┘ └───┬───┘ │ ┌───────┴──────┐ │(17) │(94) ┌──┴────┐ ┌───┴───┐ │"Data8"│ │"Data9"│ └───────┘ └───────┘ Рис. Пример глобальной структуры для пояснения работы LOCK При этом, другой процесс не сможет произвести "блокировку" уже "забло- кированного" имени. Процесс "блокировки" локальных имен может исполь- зоваться как форма осуществления связи между независимыми процессами. 3.5.4.1 Argument=VARIABLE Это простейшая форма использования опера- тора LOCK. VARIABLE может быть именем локальной переменной, глобальной переменной, или элементом массива /имя и индексы/. После исполнения оператора LOCK эта переменная, или узел массива, а также все его по- томки и непосредственные предки будет недоступны для "блокирования" в других процессах. Эта форма LOCK не является "ДОПОЛНЯЮЩЕЙ", перед по- пыткой исполнить "блокировку" первого из аргументов, происходит очист- ка таблицы "блокировок" процесса. Если один из аргументов уже "забло- кирован" другим процессом, то оператор LOCK "зависнет" /исполнение его будет приостановлено до тех пор, пока имя не будет "разблокировано"/. Для поддержки механизма управления блокированием необходимо использо- вание параметра TIMEOUT /описанного в следующем разделе/. Все имена "заблокированные" этой формой LOCK могут быть "разблокированы" без-ар- гументной формой оператора. Запомните, что при использовании этой формы оператора ВСЕ имена "заблокированные" ранее, будут "разблокированы" перед исполнением но- вой "блокировки". Кроме того, использование в такой форме списка аргу- ментов, является не больше чем сокращенной формой повторения этой фор- мы оператора LOCK с новым аргументом. Другими словами, команда: LOCK ^X,ABC,^TEST(3,4),ZZ<- эквивалентна использованию 4-х отдельных операторов LOCK: LOCK ^X LOCK ABC LOCK ^TEST(3,4) LOCK ZZ<- Стр. 114 Таким образом, только последнее имя оказывается занесенным в таблицу "блокировок" текущего процесса./Каждый из последующих операторов LOCK, в этой форме использования, неявно очищает таблицу "блокировок" про- цессов/. LOCK с аргументами заключенными в круглые скобки способен из- бежать эти ограничения. 3.5.4.2 Argument=(VARIABLE,VARIABLE,VARIABLE..) Так же, как и в случае использования прежде описанной формы, перед попыткой осущест- вления "блокировки" производится очистка таблицы "блокировок" процес- са. После этого производится одновременная попытка "заблокировать" все указанные имена, обращаясь последовательно к их списку, слева - напра- во. Однако, в отличии от предшевствующей формы, все указанные в списке /заключенном в круглые скобки/ имена будут занесены в таблицу "блоки- ровок" процесса. Если же одно из имен "заблокировано" другим процес- сом, то исполнение будет приостановлено до тех пор, пока это имя не будет "разблокировано". Использование параметра TIMEOUT позволяет программе управлять процессом "блокировки" По завершению исполнения LOCK (^A,XYZ,^B(123,456)) все три имени будут "заблокированы". Если при указании параметра TIMEOUT, эта форма оператора будет завершена неуспешно /в случае, если хотя бы одно из имен, уже "заблокировано" другим процессом/, то ни одно из имен, указанных в списке, не будет "заблокировано". 3.5.4.3 Оператор LOCK без аргументов . Эта форма использования опертора LOCK очищает таблицу "блокировок" процесса, "разблокируя" все имена, "заблокированные" текущим процессом. Неявно LOCK без аргументов исполняется при завершении процесса. /по HALT/, гарантируя то, что все "заблокированные" имена будут "освобождены", перед тем, как раздел бу- дет возвращен в MUMPS систему. 3.5.4.4 Использование параметра TIMEOUT /время ожидания/ Ранее мы уже упоминали, что оператор LOCK "зависнет" /то есть его исполнение будет приостановлено/, если хоты бы одно из "блокируемых" имен уже "заблокировано" другим процессом. Исполнение при этом будет продолжено только в том случае, когда "блокируемое" имя будет "освобождено" в другом процессе. Это означает, что процесс исполнения программы может быть приостановлен на неопределенное время. Для того, чтобы избежать этой ситуации, в любом случае использования оператора LOCK можно ис- пользовать указание необязательного параметра TIMEOUT /время ожида- ния/. TIMEOUT интерпретируется как целое число секунд, определяющих ин- тервал времени, в течении которого система MUMPS пытается "заблокиро- вать" имена из списка аргументов LOCK, перед возвращением управления в текущую программу. При указании этого параметра, он должен быть отде- лен от списка аргументов двоеточием ':'. По завершению исполнения опе- ратора LOCK с указанием времени ожидания /TIMEOUT/, управление возвра- щается в программу, а значение, заносимое в системную переменную $TEST характеризует результат исполнения оператора LOCK. $TEST = 1 - если все указанные в списке аргументов имена "заблокированы", 0 - если "блокирование" произвести не удалось /и не одно из имен не внесено в таблицу "блокировок"/. Использование параметра TIMEOUT продемонстриро- вано в следующем примере, при этом предполагается, что элемент гло- бального массива ^PAT(1234) уже "заблокирован": Стр. 115 ┌──────────────────────────────────────────────────────────────┐ │ |GetPat Read !,"Идентификатор пациента: ",PID Q:PID="" | │ │ | If PID'?1N.N Write *7," Неверно" Goto GetPat | │ │o| Lock ^PAT(PID):5 If $T Write "Работайте" Quit |o│ │ | W *7,!," Пациент ",PID," уже обрабатывается" | │ │ | W !?14," обратитесь позднее" | │ │o| Goto GetPat |o│ │ |__________________________________________________________| │ │ |>Do GetPat<- | │ │o|Идентификатор пациента: 1234<- |o│ │ | Пациент 1234 уже обрабатывается | │ │ | обратитесь позднее | │ │o|Идентификатор пациента: 4567<- Работайте |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.80 Оператор LOCK с временем ожидания В этом примере производится попытка в течении 5 секунд произвести "блокировку" узла ^PAT(PID). Если она производится, то управление не- медленно возвращается в программу, и в $TEST заносится 1. Если же этот узел уже "захвачен" другим процессом, то в течении 5 секунд система будет ждать его "освобождения", после чего управление вернется в прог- рамму, а в $TEST занесен 0. Если параметр TIMEOUT не определен, значе- ние системной переменной $TEST не будет изменяться. Во всех прикладных программах настойчиво рекомендуется использо- вание параметра TIMEOUT в операторе LOCK. В противном случае, програм- ма может "зависать" на неопределенное время, без каких либо сообщений для конечного пользователя. 3.5.4.5 Argument=+VAIABLE или +(VARIABLE,VARIABLE,VARIABLE..) Эта форма в чем то подобна уже описанным двум форма, но она является, по своей природе, "дополняющей". В отличии от описанных форм, в этом слу- чае не производится очистка таблицы "блокировок" процесса, перед осу- ществлением "блокировки" указанных в этой форме оператора LOCK имен. Каждое значение из списка аргументов будет при этом добавляться в таблицу "блокировок" процесса, при этом возможна повторная блокировка одного имени. Если одно имя будет "блокироваться" несколько раз, то в таблице "блокировок" оно будет также занесено в эту таблицу несколько раз. Когда используется эта форма оператора, со списком значений, зак- люченным в круглые скобки, то будут "заблокированы" или все имена из списка, или, в случае, если хотя бы одно из них уже "заблокировано" другим процессом - ни одного. LOCK без аргументов "разблокирует" все имена, вне зависимости от того, как эти имена были "заблокированы" - в "дополняющей" форме, или в не-"дополняющей" форме оператора LOCK. Кроме того, последовательная форма "разблокирования" имен / LOCK -VARIABLE /, может использоваться для последовательного удаления имен из таблицы "блокировок" процесса. Используйте только "дополняющие" формы "блокирования" и "разблокирова- ния" имен в процедурах общего назначения, когда текущее состояние таб- лицы "блокировок" неизвестно. Стр. 116 Если в таких процедурах будут использованы не-"дополняющие" формы, то все ранее "заблокированные" имена будут "разблокированы", что может привести к неблагоприятным эффектам. Использование "дополняющих" форм позволяет процедуре производить "блокировку" и "осовобождение" имен, без изменения статуса таблицы "блокировок" процесса. 3.5.4.5 Argument=-VAIABLE или -(VARIABLE,VARIABLE,VARIABLE..) Эта форма использования оператора LOCK позволяет избирательно "разблокиро- вать" некоторые имена, вне зависимости от того, как эти имена были "заблокированы". В связи с тем, что при использовании "дополняющей" формы LOCK в таблтицу "блокировок" одно и тоже имя может быть занесено несколько раз, то "дополянющая" форма "разблокировки" должна испол- няться столько же раз, сколько раз исполнялась "дополянющая" форма "блокировки" одного и того же имени. Обратите внимание на последова- тельность команд в следующем примере: ┌──────────────────────────────────────────────────────────────┐ │ |>Lock +^PAT(PID)<- | │ │ |>Lock +^PAT(PID)<- | │ │o|>Lock -^PAT(PID)<- |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис.3.81 "Дополняющая" и "исключающая" формы LOCK где в результате узел ^PAT(PID) так и останется "заблокировнным", пос- кольку он дважды был внесен в таблицу "блокировок", но удалено только одно его включение из таблицы. С другой стороны, без - аргументная форма оператора LOCK может быть использована для очистки таблицы "бло- кировок" процесса, и "разблокирования" всех имен. 3.5.5 Оператор VIEW Оператор│ Аргументы Примеры ________│______________________________________ ____________ │ │ NEW │ Зависят от реализации │ │ │ ┌──────┐ Сокращение имени оператора │ V │ Зависит от реализации Использование постусловий: │ │ при операторе │ ? │ Зависит от реализации при аргументе │ ? │ Зависит от реализации Использование косвенности │ ? │ Зависит от реализации Обязательность аргументов │ ? │ Зависит от реализации Разрешенность использования │ │ списка аргументов │ ? │ Зависит от реализации Установка времени ожидания │ НЕТ │ └──────┘ Стр. 117 Оператор VIEW специфичен для каждой реализации MUMPS, которые по-разному определяют как синтаксис оператора, так и функции, исполня- емые этим оператором. В связи с этим обстоятельством, программист не должен использо- вать эту команду при разработке прикладных пакетов, которые должны ра- ботать в разных реализациях MUMPS. Если этого, все же не избежать, то этот оператор должен быть вынесен в характерные места, и хорошо доку- ментированы все случаи его использования. В различных реализациях оператор VIEW используется в-основном для модифицирования дисковой и/или оперативной памяти. Оператор VIEW обыч- но используется вместе с соответствующей ему функцией $VIEW - которая возвращает значения из определенных областей памяти. Необходимо заметить, что хотя при работе с областями памяти рабо- та обычно ведется с указанием шестнадцатиричных или восьмиричных вели- чин, оператор VIEW, и функция $VIEW работают только с десятичными зна- чениями. В связи с тем, что некорректное использование VIEW может привести к разрушению системы MUMPS и баз данных, необходимо перед его исполь- зованием хорошо ознакомиться как с внутренней структурой системы, так и с используемыми структурами данных. Будьте очень осторожны при использовании этого оператора! Для того, чтобы составить представление об функциях этого опера- тора, приведем фрагмент его описания из руководства "Системного прог- раммиста" по работе c DataTreeMUMPS v4.2 ---фрагмент руководства по работе с DataTreeMUMPS v4.2------- Оператор VIEW использует значение value для модификации содержи- мого области памяти определяемой mode и offset, в следующем формате: VIEW mode:offset:value Доступ к памяти может осуществляться по абсолютному адресу, или по смещению от начала определенной таблицы, которая задается аргумен- том 'mode'. mode Определяет вид области памяти, к которой осуществляется дос- туп. mode - 0 идентифицирует режим абсолюьной адресации, в этом случае offset интерпретируется как байт смещения от начала памяти. Во всех остальных случаях идентифицирует системные таблицы DTM PC и другие структуры. offset байт смещения от начала области задаваемой mode Стр. 118 Аргумент Mode может принимать следующие значения: 0 - Absolute - Начало памяти 1 - SYSTAB - Системная таблица 2 - PARTAB - Таблица раздела 3 - MCB - Блок конфигурации MUMPS 4 - HEAP - Заголовок /?/ текущего процесса 5 - MPB - Таблица устройств DTM-PC 6 - SCAT - Таблица общих системных адресов 8 - BASEBUF - Основной буфер 9 - VIDBLK - Блок управления втидеоатрибутами 10 - COMMBLK - Блок управления связью 12 - PSP - Префикс программного сегмента MS DOS 14 - PAGE - Страница идентифицируемая ZGETPAGE 15 - IMMEDIATE - Адрес объекта заданный mode и offset 1024+D - DEVICE - Блок управления устройством /0 - основное/ Параметр value может быть строкой, или 32-х битовым целым. Если это строка, то оператор view копирует ее в указываемую mode и offset область памяти. Если значение value - целое, то производится установка или очистка /операция логического ИЛИ или И /, бит в 32-х битовой об- ласти. Если V не отрицательное целое, тогда: VIEW mode:offset:V испол- нит операцию логического ИЛИ с битами длинного слова по адресу, опре- деляемому значениями mode и offset,в соответствии с значением шаблона V, тогда как: VIEW mode:offset:-V очистит те же самые биты. Примеры: VIEW 0:ADDR:"WXYZ" Скопирует строку "WXYZ" в 4 байта, начиная с абсолютного адреса ADDR. Выражение $VIEW(0,ADDR,4) возвратит строку "WXYZ" VIEW 0:ADDR:32768 Установка бита: исполняется логическое ИЛИ на значение длинного слова по обсолютному адресу ADDR и значения 32768. Так если до этого, выражение $V(0,ADDR,-4) возв- ращало 3, то после исполнения этой команды, оно возвра- тит 32771 VIEW 0:ADDR:-32768 Очистка бита: исполняется логическое И значения -32767 /?-так в документации/ и 32-х битового целого, по адресу ADDR. В данном случае очищается 15-й бит. VIEW 0:$V(9,0,-3)+1:128 Вызывает эффект мерцания верхнего левого символа экрана. /логическое ИЛИ значения 128 и байта атрибутов вызывает мерцание соответствующего символа экрана/ Стр. 119 3.5.6 Оператор ZLOAD Оператор ZLOAD используется для загрузки исходного /*1/ текста программы в текущий раздел. Обычно его имя может быть сокращено до двух первых букв 'ZL', и используется в следующей форме: ZL ROUTINE Перед загрузкой указанной программы в раздел, MUMPS система про- изводит очистку области раздела, отводимую под программы /без сохране- ния, исполняя неявный ZREMOVE/. Так что не всегда будет возможно осу- ществить связку программ посредством оператора ZLOAD. После того, как программа загружена в раздел /*2/, часть ее строк может быть удалена /ZREMOVE/, или напротив, в нее может быть вставлено несколько новых строк /ZINSERT/. Вся программа, или ее часть, может быть распечатана /ZPRINT или PRINT/. И, наконец, программа, или ее процедура, или ее часть может быть запущена, при использовании локаль- ного DO /например, DO LABEL/ В некоторых реализациях поддерживается без-аргументная форма опе- ратора ZLOAD, используемая для загрузки программы не с диска, а с те- кущего устройства /подробнее о текущем устройстве см. в разделах пос- вященных операторам OPEN и USE / 3.5.7 Оператор ZINSERT Оператор ZINSERT позводяет вставлять программные строки в тело текста программы из раздела. Обычно его имя может быть сокращено до двух первых букв 'ZI', и используется в следующей форме: ZI"Строка программы":LABEL+Offset где "Строка программы" - это текст вставляемой строки, а LABEL и Off- set определяют точку программы, в которую будет производиться вставка. Некоторые из реализаций производят вставку строк ПЕРЕД указанной, дру- гие - ЗА ней. В большинстве случаев использование LABEL и/или Offset необязательно. Подробности Вы можете найти только в документации на Вашу реализацию. _________________________________________________________________ *1 Не следует понимать, что в "компилирующих" реализациях MUMPS этим оператором загружается только исходный текст программ. Объектный код при этом тоже загружается, но доступен для редактирования только ис- ходный. И, если исходный текст удален, то редактировать программу нельзя, а запускать можно. /DTM v4.2+/ *2 В некоторых реализациях, в частности DTM v4.2+ и ISM /NTSM/, ZLOAD не производит загрузку программы в раздел, а только объявляет указан- ное имя программы текущим. Стр. 120 3.5.8 Оператор ZREMOVE Оператор ZREMOVE позводяет удалять программные строки из текста программы загруженной /ZLOAD/ в раздел. Обычно его имя может быть сок- ращено до двух первых букв 'ZR', и используется в следующей форме: ZR LABEL+Offset или ZR LABEL1+Offset1:LABEL2+Offset2 или ZR где: LABEL и Offset определяют удаляемую строку программы. Указание Offset - необязательно. Если в аргументах оператора содержится указа- ния на две строки программы, разделяемые двоеточием (:), то произво- дится удаление этих строк, и всех строк лежащих между ними. Обычно поддерживается также и без-аргументная форма этого опера- тор, для удаления всех программных сстрок из раздела. В этом случае НЕ всегда происходит одновременное удаление текущей программы с диска. В этом случае, для удаления программы с диска необходимо записать, с именем удаляемой программы, на диск пустой раздел /то есть необходимо исполнить следующую последовательность команд: ZREMOVE ZSAVE ProgName - Поддерживается например, в DSM-11/ 3.5.9 Оператор ZPRINT Оператор ZPRINT позводяет выводить на текущее устройство /см. описание оператора USE относительно текущего устройтсва/ программные строки из текста программы загруженной /ZLOAD/ в раздел. Обычно его имя может быть сокращено до двух первых букв 'ZP', и используется в следующей форме: ZP LABEL+Offset или ZP LABEL1+Offset1:LABEL2+Offset2 или ZP где LABEL и необязательное смещение /Offset/ определяют печатаемую строку. При указании двух строк, печатаются эти строки, и все строки программы, лежащие между ними. Без-аргументная форма обычно печатает всю программу. Во многих реализациях поддерживается также и оператор PRINT, ис- полняющий те же функции, что и оператор ZPRINT. Оператор PRINT не яв- ляется частью стандарта ANSI MUMPS. 3.5.10 Оператор ZSAVE Этот оператор используется для сохранения текущей программы на диск /в некоторых реализациях и на другие устройства/. Его имя обычно может быть сокращено до двух первых букв 'ZS' и используется в следую- щем формате: ZS ROUTINE /*1/ ________________________________________________________________ *1 В реализациях MUMPS производящих компиляцию программы при ее сохра- нении, оператор ZSAVE используется с дополнительными аргументами, оп- ределяющими режим компиляции. /Подробнее в описаниях реализаций MUMPS - MSM /MicroneticsStandartMUMPS/ и DataTreeMUMPS Стр. 121 где ROUTINE - имя, под которым будет сохранена программа. Если текущий раздел пуст, то во многих реализациях это интерпретируется как требо- вание удалить программу с именем ROUTINE с диска. В дополнение к сказанному, во многих реализациях поддерживается без-аргументная форма ZSAVE, для сохранения текущей программы из раз- дела на диск. При этом, в большинстве случаев, используется последнее использованное имя программы. 3.5.11 Прочие 'Z' операторы Все операторы, чьи имена начинаются с буквы 'Z' определяются стандартом, как операторы определяемые по-разному в различных реализа- циях. Несмотря на это, некоторые из них доступны во многих реализаци- ях. В этом разделе мы упомянем несколько из наиболее часто поддержива- емых в разных реализациях операторов: ZD(elete) Routine : Удаление программы с указанным именем с диска. ZE(dit) Routine : В некоторых системах вызов системного редактора программ. При этом, если не указывать имя программы редактируется текущая программа. /ISM и DTM PC v4.2+/ ZO(ption) (routine directory:global directory) : Изменение теку- щей директории программ и массивов. При этом параметры routine directory и global directory должны содержать как имя соответсву- ющего файла, так и спецификацию диска и каталога, где они разме- щены. (Например: C:\MUMPS\APP) ZNSPACE "DIR" : В некоторых реализациях используется для измене- ния текущей рабочей области ( В этом примере производится переход в область "DIR") /DTM PC v4.2/ ZQ(uit) : Подобен использованию оператора QUIT , но без очистки стека исполнения. Используется, как правило, в процедурах обра- ботки ошибок. /См. главу 13, где обсуждается процесс обработки ошибок/ /В ISM /NTSM/ этот оператор используется для закрытия системы и выхода в MS DOS/ Вся вышеупомянутая информация должна быть проверена по Вашей до- кументации. НЕ используйте эти операторы, если Вы планируете использо- вать Ваши программы в различных реализациях MUMPS. 3.6. Заключение Операторы языка являются основой MUMPS программы и являются ди- рективами определяющими действия компьютера. Многие операторы могут иметь аргументы /указываемые явно или неявно/, которые дополнительно определяют и уточняют исполняемые действия. Аргумент(ы) отделяются от оператора одним пробелом, аргументы в списке отделяются друг от друга запятыми. Многие операторы, и их аргументы, могут иметь при себе специаль- ные синтаксические конструкции, определяющие их исполнение, в зависи- мости от значений, определяемых в ходе работы прикладных программ. /пост-условия/ Стр. 122 Некоторые операторы (LOCK, READ, JOB и OPEN) допускают использование параметра TIMEOUT, определяющего интервал времени, в течении которого система пытается их исполнить, перед тем, как продолжить исполнение программы. Если параметр TIMEOUT указан, то в системную переменную $TEST заносится результат исполнения соответствующего оператора. И пост-условия, и необязательный параметр TIMEOUT, идентифицируются дво- еточием (:), которое отделяет их от аргументов оператора, или от имени оператора. Эти специальные формы синтаксиса операторов подробно обсуж- дены в главе 6. Алфавитный перечень операторов, вместе с кратким описанием и при- мерами использования, приведен в приложении 'С' Стр. 123 Глава 4 Структуры данных MUMPS управляет своими данными, как строками символов. В отличии от большинства других языков программирования, программист в MUMPS не объявляет тип данных, с которым он работает /целое число, число с пла- вающей точкой, строка символов или логическая величина/, связывает имя переменной. Все содержимое переменных всегда интерпретируется в кон- тексте, оно подвергается числовой интерпретации в арифметических опе- рациях, или как строки символов в строковых операциях. Одна и также переменная может интерпретироваться в одном и том же процессе и как целое, и как число с плавающей точкой, и как строка символов и как ло- гическое значение. В MUMPS определены три вида операций: числовые, строковые и логические. Тип операции, /оператор/, определяет проводи- мое оценивание операндов /аргументов, с которыми производятся опера- ции/, но результат любой операции будет представляться как строка сим- волов. Существуют достаточно веские аргументы как за, так и против стро- гого определения типов данных и переменных. Сторонники формального объявления переменных и типов данных связываемых с каждой из них моти- вируют свои требования тем, что программист должен более конкретно представлять использование переменных перед написанием прикладных программ. Кроме того, бытует мнение, что подобный подход улучшает ана- лиз программ и позволяет избежать ошибок, связанных с операциями со смешением типов данных /например умножение целого числа на строковое значение/. Одним из наиболее ярких представлений этого подхода являет- ся компьютерный язык Паскаль - он требует формального объявления всех используемых переменных и строгого определения типов переменных, и запрещает операции операции со смешанными типами данных /подобных ум- ножению строки на целое число/. Противники формального объявления пе- ременных и типов данных утверждают, что подобные операции снижают эф- фективность работы. И кроме того, по их убеждению, подобные операции имеют значение только в отношении стиля программирования. И, при жела- нии, сторонники подобного подхода могут использовать, по соглашению, подобный подход даже в языках без поддержки методов формального опре- деления переменных и типов данных /подобных MUMPS/. В MUMPS переменные могут динамически создаваться и переопределяться в процессе работы программы, и не требуется формального объявления переменных или типов данных. 4.1 Строки В качестве основного набора символов для представления любых дан- ных в языке MUMPS используется семибитный код ASCII /American Standart Code for Information Interchange - Американский Стандартный Код для Обмена Информацией /. В этот код входит 128 неповторяющихся символов представленных в приложении А. В стандарте языка MUMPS определено, что отдельная строка может быть длиной до 255 символов, хотя некоторые ре- ализации поддерживают создание строк гораздо большей длины. /*1/ _____________________________________________________________________ *1 Обращаясь к наиболее доступным примерам: DSM-11 /v3/ - 255 символов ISM /NTSM/ - 255 символов MSM - 4 096 символов DTM PC /v4.2/ - 32 768 символов Стр. 124 ┌────────────────────────────────────────────────────────────┐ │ Строка данных может быть в MUMPS длиной до 255 символов. │ └────────────────────────────────────────────────────────────┘ Строковые константы называются также строковыми литералами и представляются заключенными в кавычки ("). Например: ┌──────────────────────────────────────────────────────────────┐ │ |>Write "Это пример"<- | │ │o|Это пример |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.1 Строковый литерал Аргументом представленного в примере оператора WRITE является строковый литерал - "Это пример". В MUMPS возможно также использовать и "пустой" строковый литерал, (часто называемый также и "пустой" стро- кой /*1/). Необходимо провести границу между понятием 'пустая' строка и символом NUL /пусто/ /ASCII=0/. Символ NUL это один из допустимых символов ASCII /и может быть получен при использовании внутренней функции $CHAR/. И строка состоящая из символа NUL НЕ является "пустой" строкой, - поскольку она содержит символ - символ NUL. В то время как "пустая" строка это строка НЕ содержащая НИ одного символа - ее длина равна 0. ┌─────────────────────────────────────────────────────────────────┐ │ "Пустая" строка это строка НЕ содержащая НИ одного символа, ее │ │ длина =0. Символ NUL /ASCII=0/ не является "пустой" строкой. │ │ Строка содержащая этот символ не "пустая". │ └─────────────────────────────────────────────────────────────────┘ Особое внимание следует обратить на особенности связанные с ис- пользованием кавычек (") внутри строковых литералов, то есть в тех случаях, когда MUMPS должен рассматривать их в качестве данных, а не ограничителей строковых литералов. Когда две кавычки подряд встречают- ся внутри строкового литерала, заключенного в кавычки, то они интерп- ретируются как одна кавычка включенная в строковый литерал. Ниже Вы увидите пример, иллюстрирующий использование различных видов строковых литералов. /*2/ Команды /Ввод/ Получаемый результат /Вывод/ WRITE ""<- [Ничего] /"" - "пустая" строка/ WRITE "Test"<- Test WRITE "Это ""1"" проба"<- Это "1" проба WRITE """"<- " Илл. 4.1 Строковый литерал и использование кавычек. ________________________________________________________________________ *1 В оригинале используется термин 'null string' - поэтому автор и бе- рется пояснять разницу между ним и символом NUL, в русскоязычной прог- раммисткой среде эти понятия разделены уже смысловым толкованием ис- пользуемых терминов. *2 Могут встречаться и более сложные случаи, особенно при использова- нии строковых литералов в аргументах XECUTE. Для того, чтобы справить- ся с ними, следует запомнить самое простое правило - "для того, чтобы вставить кавычку в литерал - удвойте число кавычек на его границе." Стр. 125 В соответствии со стандартом строковый литерал должен заключаться в кавычки и содержать в себе любую последовательность из печатаемых ASCII символов /со значениями от 32 до 126/. Это означает, что управ- ляющие коды /со значениями в диапазоне 0 - 31 и 127/, а также коды составляющие расширение таблицы ASCII символов не могут включаться в строковые литералы. На практике, большинство реализаций допускает включение этих символов в строковые литералы. Хотя большинство из управляющих и кодов ASCII и кодов из расшире- ния основного набора ASCII могут быть сгенерированы с клавиатуры /нап- ример символ BEL /ASCII=7/, вызывающий звуковой сигнал с клавиатуры, может быть получен при одновременном нажатии клавиши и /, и, как уже упоминалось, большинство из реализаций допускает включение этих символов в строковые литералы, но из соображений "читаемости" текста программ, и выводимых ими сообщений НЕ рекомендуется включение невыводимых на экран символов в строковые литералы. Хотя, например, звуковой сигнал может быть желателен при выводе сообщения об ошибке, но другому программисту из "листинга" программы вряд ли будет ясно по- чему этот сигнал выводится. Существует альтернативный метод управления выводом непечатаемых кодов - функция $CHARACTER будет обсуждена в раз- деле 8.1.9, а синтаксис использования оператора WRITE в форме WRITE * , уже осуждался в разделе 3.4.4.3 - при использовании одного из этих методов использование непечатаемых символов будет более очевидным. 4.2 Числа MUMPS не требует специальных отличий между целыми числами, и чис- лами с плавающей точкой, при исполнении над ними операций, и в отличии от некоторых других языков позволяет проводить операции со смешением их типов. Целое число - это положительное, или отрицательное число без дробной части, числа с плавающей точкой имеют дробную часть. Число мо- жет предшествовать положительный, или отрицательный знак. Если знак числа не указан, оно предполагается положительным. Числовые эквивален- ты строковых литералов именуются числовыми литералами /числовыми конс- тантами/. В следующем примере выражения 3.4 , 2 и 1 являются числовыми литералами. ┌──────────────────────────────────────────────────────────────┐ │ |>Write 3.4*2-1<- | │ │ |5.8 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 4.2 Числовые литералы При представлении некоторых чисел с повторяющейся, или бесконеч- ной дробной частью возникают некоторые сложности. При их использовании мы должны знать, сколько цифр из их записи являются значимыми. Соглас- но стандарта, все числа и результаты вычисления операций должны иметь не меньше 12 значимых цифр. Это означает, что в любой реализации долж- ны поддерживаться как минимум 12 значащих цифр в числовых операциях /считая цифры в записи числа слева направо/. ┌──────────────────────────────────────────────────────────────┐ │ |>Write 22/7<- | │ │ |3.142857142857142857 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 4.3 Значащие цифры Стр. 126 Цифры за 12-ой позицией не гарантируется. Поэтому, хотя результат в данном пример содержит 19 цифр, только первые 12 из них / 3.14285714285 /, являются значимыми. И хотя конкретная реализация мо- жет производить операции вычисления с большей точностью, чем это пре- дусмотрено в стандарте, но использование этих возможностей в програм- ме, которая будет применяться в различных реализациях недопустимо, в связи с возможными непредсказуемыми искажениями результатов. 4.2.1 Экспоненциальное представление MUMPS поддерживает также и экспоненциальную запись чисел. C по- мощью подобной формы записи могут быть записаны и очень большие, и очень малые числа без необходимости записывать длинные последователь- ности нулей. Экспоненциальная форма записи чисел в MUMPS представляет собой число, затем букву 'E' и, следом за ней показатель степени в ко- торую необходимо возвести 10, чтобы затем умножить на предшествующее число. Например '3.14E4' равно 31400. /3.14 умноженное на 10 в 4-ой степени/ Подобным образом малые числа могут быть представлены в виде числа умножаемого на 10 возведенное в отрицательную степень. Так число 0.0000456 может быть представлено в экспоненциальной форме как 4.56Е-5. Экспоненциальное представление может быть использовано при вводе любого значения, но система преобразовывает их перед исполнением опе- рации, и возвращает результат в неэкспоненциальной форме. Числовой ре- зультат никогда не возвращается в экспоненциальной форме представления вне зависимости от величины числа. ┌──────────────────────────────────────────────────────────────┐ │ |>Write 1.32E3*2.6E-2<- | │ │ |34.32 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 4.4 Экспоненциальное представление чисел Экспоненциальное представление не надо путать с возведением в степень. В большинстве других языков операция возведения в степень представляется символами ** или ^ (например 3**4=81). В некоторых MUMPS системах тоже есть оператор возведения в степень. Согласно стандарта MUMPS диапазон экспоненциального представления чисел должен охватывать числа в диапазоне от 1Е-25 до 1Е25. Но вне за- висимости от диапазона точность вычисления, должна составлять не менее 12 десятичных знаков. 4.2.2 Числовая интерпретация строк Мы уже упоминали тот факт, что в зависимости от вида выполняемых операций MUMPS может интерпретировать строки символов как числа. В большинстве случаев такая интерпретация достаточна очевидна. Стр. 127 ┌──────────────────────────────────────────────────────────────┐ │ |>Write !,"+0.22"*4<- | │ │ | | │ │o|.88 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.5 Числовая интерпретация строки. Однако в некоторых случаях этот процесс не столь очевиден, и, по- этому подробно рассмотрим порядок выполнения операций, которые произ- водит система при интерпретации строки в число. В процессе интерпрета- ции MUMPS просматривает строку слева - направо, одновременно формируя результирующее число. Если первым встреченным символом будут знаки '+', '-' или десятичная точка '.' то просмотр продолжается дальше, лю- бой другой встреченный символ прерывает просмотр строки, в этом случае результирующее число = 0. Просмотр строки продолжается дальше, пока следующим символом будет не число, а любой другой символ. На Рис 4.5.1 представлен, шаг за шагом процесс интерпретации строки. Необходимо отметить, что запятая (,) не рассматривается как раз- решенный символ при сканировании строки. Когда она встречается - прос- мотр и построение результирующего числа прекращается. Так например числовая интерпретация строки '10,000,000' = 10 Когда производится числовая интерпретация строки не содержащей допустимых символов, то результат такой интерпретации =0. Соответс- твенно выражение 3*"THREE" будет давать в результате 0. (3*0=0) Проверяемый символ Получаемое число +0.034ABC + + 0 +0 . +0. 0 +0.0 3 +0.03 4 +0.034 A После символа "А" просмотр строки прекращается, в результате числовая интерпретация исходной строки = .034 Рис. 4.5.1 Числовая интерпретация строки символов 4.3 Логические /Булевы/ величины - (Истина/Ложь) Некоторые виды MUMPS выражений оцениваются согласно правил мате- матической логики и могут быть либо истинными, либо ложными. Подробнее такие выражения рассматриваются в главе 5. Здесь же, в качестве приме- ра представим сравнение двух чисел: Стр. 128 ┌──────────────────────────────────────────────────────────────┐ │ |>Write 7>2<- | │ │ | | │ │o|1 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.6 Логическая операция и ее результат В выражении, следующем за оператором WRITE мы предлагаем MUMPS системе оценить является ли число 7 БОЛЬШИМ числом. чем число 2. Опе- рации отношения, как показано здесь возвращают в качестве результата 1 /ИСТИНА/ или 0 /ЛОЖЬ/. В общем случае, в логических выражения не рав- ное нулю число /вне зависимости от его знака/ понимается как ИСТИНА, а все значения оцениваемые как числовой 0 являются ЛОЖЬЮ. ┌────────────────────────────────────────────────────────┐ │ Логические операции возвращают только : 1 если ИСТИНА │ │ 0 если ЛОЖЬ │ │ │ └────────────────────────────────────────────────────────┘ 4.4 Переменные В этом разделе мы дадим более подробное определение уже обсуждае- мого ранее понятия "переменной". Переменные, это не что иное как сим- волические имена, связываемые со значениями данных. Первым символом имени переменной может быть любая буква /*1/, или символ '%', осталь- ными символами, могут быть как буквы, так и цифры. Длина имени пере- менной может быть любой, вплоть до максимально возможной в системе MUMPS длины строки /обычно 255 символов/, но системой имена переменных различаются только по первым 8 символам. Так MUMPS системой имена пе- ременных DayOfTheWeek и DayOfThe будут интерпретироваться как одна пе- ременная с именем DayOfThe. Пробелы и другие знаки НЕ могут включаться в имена переменных. ┌────────────────────────────────────────────────────────┐ │ Имена переменных могут начинаться с символа '%', или │ │ с любой буквы, после чего могут быть любые буквы или │ │ цифры. │ └────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────┐ │ Имена переменных могут быть длиннее чем 8 символов, │ │ но различаются они системой только по первым 8 │ │ символам. │ └────────────────────────────────────────────────────────┘ Система отличает использование больших и малых букв в именах пе- ременных, так имена Rate и RATE относятся к разным переменным. ___________________________________________________________________ 1* - В некоторых MUMPS системах допускается использование русских букв в именах переменных . Это адаптированные версии DSM-11, известные у нас как ДИАМС-2 и старше, а также ISM /NTSM/ версии 5 и старше. В пер- вом случае Вы можете использовать только большие русские буквы, во втором - как большие, так и малые. Стр. 129 Как мы уже говорили, первым символом имени переменной должна быть буква или знак '%'. Но обычно переменные, начинающиеся с символа '%' должны использоваться, создаваться и уничтожаться только библиотечными и системными программами, во избежания пересечения с переменными ис- пользуемыми в прикладных программах. Можно сказать что символ '%' за- резервирован для имен переменных используемых в библиотечных и систем- ных утилитах. /Вспомните, что мы говорили относительно области локаль- ных переменных в главах 1 и 2/. Знак '%' может быть только первым сим- волом имени переменной, в любом другом случае его использование приве- дет к созданию некорректного имени переменной. Все программы исполняемые в одном разделе оперируют с одной об- ластью локальных переменных. Без использования оператора NEW и вызова с передачей параметров, кроме соглашения о именах переменных, начинаю- щихся с '%' нет иного способа разделить переменные, используемые прик- ладной программой и библиотечной /системной/ утилитой. Такое соглаше- ние позволяет снизить риск конфликта между именами используемых пере- менных. Конечно, использование NEW и DO с передачей параметров позволяет игнорировать это соглашение, но никогда нет полной уверенности в том, что конкретная утилита переписана с использованием этих возможностей, которые появились только в последних редакциях стандарта MUMPS. ┌────────────────────────────────────────────────────────┐ │ Имена переменных могут начинаться с символа '%', но │ │ использование таких имен в прикладных программах │ │ нежелательно. │ └────────────────────────────────────────────────────────┘ И, наконец, нельзя не упомянуть о еще одном большом исключении, относительно правила по составлению имен переменных. Кроме знака '%' еще один символ может быть первым символом имени переменной - это сим- вол '^'. Добавление этого символа разделяет локальные /размещающиеся в оперативной памяти/ и глобальные /размещающиеся на диске/ переменные. ┌────────────────────────────────────────────────────────┐ │ Переменные, чьи имена начинаются с символа '^' , │ │ размещаются на диске и относятся к ГЛОБАЛЬНЫМ перемен-│ │ ным. │ └────────────────────────────────────────────────────────┘ 4.4.1 Локальные переменные Подводя итог вышесказанному, можно заключить, что локальные пере- менные размещаются в таблице локальных символов, связываемой с конк- ретным процессом и разделом. Напротив, глобальные переменные размеща- ются на диске. Локальные переменные являются временными, они могут быть доступны только текущему процессу и все значения, связанные с ни- ми, будут потеряны при завершении процесса. Все имена локальных пере- менных начинаются с буквы, или символа '%'. Локальные переменные могут быть одиночными переменными или являться элементами локальных масси- вов, при этом их отнесение к элементам массива идентифицируется по присутствии индексов, заключенных в круглые скобки после имени пере- менной. Стр. 130 4.4.2 Массивы Массивы могут состоять как из локальных, так и из глобальных пе- ременных, и, соответственно находиться в оперативной памяти или на диске. В большинстве других языков и систем программирования массив оп- ределяется типом /целый, с плавающей точкой, логический и тому подоб- ное/ и размером. Перед использованием массивы, обычно его необходимо либо объявить, либо проинициализировать. Индексами массива, в боль- шинстве случаев, могут быть только числа, часто только целые. Массивы представляются обычно в виде матриц, в которых количество индексов определяет размерность массива. Например X(3) - это элемент одномерного, а Z(2,8) - двумерного массива При объявлении массива тип массива определяет пространство, выде- ляемое под элемент массива /так например под числа с плавающей точкой требуется больше места, чем под целые числа/, размерность массива оп- ределяет количество резервируемых ячеек. Так например, объявив массив X(3,5) мы зарезервируем использование двумерного массива с 15 элемен- тами /См. рис./ Колонки 1 2 3 4 5 ┌─────┬─────┬─────┬─────┬─────┐ 1 │ 17 │ 35 │ 21 │ 11 │ 71 │ ├─────┼─────┼─────┼─────┼─────┤ Ряды 2 │ 94 │ 76 │ 14 │ 49 │ 53 │ ├─────┼─────┼─────┼─────┼─────┤ 3 │ 5 │ 31 │ 68 │ 19 │ 51 │ └─────┴─────┴─────┴─────┴─────┘ Рис. Двумерная матрица В массиве, показанном на этом примере, элемент массива X(2,4) связан со значением 49, а X(1,2)=35 и так далее. В MUMPS системах, в отличие от всего вышесказанного, массивы яв- ляются ИЕРАРХИЧЕСКИМИ и РАЗРЕЖЕННЫМИ. Индексы, указывающие на элементы массива, могут быть как числами /целыми, или с плавающей точкой/, так и строками ASCII символов. Перейдем к обсуждению этих положений, при- водя в качестве примера варианты записи и получения данных из масси- вов. 4.4.2.1 Иерархические массивы Иерархические массивы часто представляются в виде древовидных структур данных и часто отображаются подобно корню дерева с растущими из него ветвями, на которых размещаются индексы и данные. Каждый уро- вень ветвления структуры, при продвижении от корня отображает уровень, или глубину структуры. Каждая точка ветвления может иметь связанное с нею данное и одного "предка", но неограниченное количество "потомков". В свою очередь "потомок" может быть "предком" для ветвей более нижнего уровня. Точка ветвления структуры, может именоваться как "узлом", так и "предком". Находящиеся ниже нее "узлы" - ее "потомки". Но если у этих "узлов" нет больше "потомков", то такие узлы называются "листь- ями". Количество потомков у любого узла, как и глубина структуры ничем не ограничивается. Стр. 131 ^Calories ┌───────┐ └─┬─┬─┬─┘ ┌───────────────┘ │ └───────────────┐ Dairy │ Grains │ Meat │ ┌────┴──┐ ┌───┴───┐ ┌──┴────┐ └┬────┬─┘ └──┬─┬──┘ └┬──┬─┬─┘ │ └────────┐ ┌─────────┘ │ └─────┐ Eggs │ Milk │ │ │ │ │ Products│ Fish│ Poultry│ Red│ ┌─┴─┐ ┌──┴───┐ ┌──┴───┐ ┌────┴─┐ ┌─┴────┐ │215│ └─┬─┬─┬┘ └─┬─┬─┬┘ └─┬───┬┘ └┬─┬─┬─┘ └───┘ ┌────┘ │ └────┐ ┌┘ └──────┐ Chease│ Milk│ │Yogurt │Chicken │ Turkey ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ │355│ │120│ │100│ │275│ │215│ └───┘ └───┘ └───┘ └───┘ └───┘ Рис. Иерархический массив Указатели прохода к узлам структуры являются индексами иерархи- ческого массива, они уникальным образом определяют каждый узел масси- ва. В иерархическом массиве MUMPS системы, узлы структуры могут быть индексами со связанными с ними данными, или индексами без данных. При- чем индекс без данных может стать индексом с данными, но не наоборот. На рисунке приведена структура массива, содержащего информацию о питательной ценности различных видов пищи. На его основе мы обсудим основные аспекты использования иерархических структур для хранения ин- формации. На этом рисунке прямоугольники отображают элементы данных, и идентифицируются строками символов, которые являются индексами этих данных. Пустые прямоугольники соответствуют индексам без связанных с ними данных. Для краткости некоторые ветви массива опущены. Эти ветви предс- тавлены в виде узлов, ветви которых только намечены, без указания по- томков /Например ^Calories("Grains")/ Информация в массиве содержится как в индексах, так и в данных. Индексы определяют категории пищи, данные - величину их пищевой цен- ности. Для получения данного из конкретного узла, необходимо перечислить все индексы этого узла, начиная с самого верхнего, как показано на следующем примере: ┌──────────────────────────────────────────────────────────────┐ │ |>Write ^Calories("Dairy","Milk Products","Yogurt")<- | │ │ |100 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 4.7 Индексация иерархического массива Данный вид индексации, и соответственно организации массива, представляет собой также один из способов быстрого получения информа- ции по связанной группе данных. Так просмотр потомков узла ^Calori- es("Meat","Poultry") позволяет получить всю имеющуюся информацию по этой группе продуктов. Стр. 132 ^Calories ┌─────────────┐ └─┬─┬─┬─┬─┬─┬─┘ │ │ │ │ │ └─────────────────────────────┐ ┌────────────────────┘ │ │ │ └───────────────────┐ │ │ ┌─────────┘ │ └─────────┐ │ │ │ │ │ │ │ │ Chease│ Chicken│ Eggs│ Milk│ Turkey│ Yogurt│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │Dairy │ │Meat │ │Dairy │ │Dairy │ │Meat │ │Dairy │ │Milk Prod│ │Poultry │ │215 │ │Milk Prod│ │Poultry │ │Milk Prod│ │335 │ │275 │ │ │ │120 │ │215 │ │100 │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ Рис.4.7.1 Иное представление массива ^Calories Те же данные могут быть организованы и по-иному, например, как показано на Рис.4.7.1. Но обратите внимание, что по сравнению с преды- дущим решением, здесь, кроме величины пищевой ценности, в данное при- ходится записывать и некую дополнительную информацию. И кроме того, для того, чтобы получить информацию по группе продуктов /например "Me- at" - мясо/ необходимо просмотреть весь массив, в отличие от просмотра по одной только ветке, при иной организации массива. Каждое имя переменной связывается только с одним значением данно- го, вне зависимости от того, является ли эта переменная простой или индексированной /то есть является элементом массива/. Одно и то же имя может иметь и простая переменная и массив. ┌──────────────────────────────────────────────────────────────┐ │ |>Set A=1 Set A(1,2)="Test"<- | │ │ |>Write A<- | │ │o|1 |o│ │ |>Write A(1,2)<- | │ │ | "Test" | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 4.8 Простая и индексированная переменные В этом примере Вы видите назначение переменной А и элемента мас- сива А - А(1,2) 4.4.2.2 "Разреженные" массивы Рассмотрим иную иерархическую структуру - массив содержащий ин- формацию по посещениям стоматологической поликлиники. Здесь каждому пациенту, при первом посещении назначается некий уникальный идентифи- кационный номер. При этим в массив PatRec по этому идентификатору за- писывается информация о пациенте, и далее, по дате посещения записыва- ются все последующие обращения. То есть в этом массиве образуется два уровня индексации. Первый уровень индексации содержит идентификаторы в диапазоне 1 - 999999. Второй уровень индексации определяется произ- вольным моментом посещения, и индексы записываются в виде ГГММДД. Где ГГ - год, ММ - месяц, ДД - день посещения. То есть визит 1 февраля 1990 года кодируется в виде 900201. Стр. 133 ^PatRec ┌─────────────┐ └──┬───┬───┬──┘ │ │ ┌────────────┘ └──────┐ │ (123) │ (687) ┌────────┴────────┐ ┌──────┴───────┐ │"Lewkowicz,John" │ │"King,Robert" │ └───┬─────────┬───┘ └──────────────┘ │ │ │ └────────┐ │ (850915) │ (860704) ┌─────┴──────┐ ┌───────┴─────────┐ │"ToothAche" │ │"Annual Checkup" │ └────────────┘ └─────────────────┘ Рис. "Разреженный" массив В результате получается подобная структура, как показано на ри- сунке выше. И, таким образом, сколько бы не было посещений, информация о па- циенте записывается только один раз /Например, ^PatRec(123)="Lewco- wicz,John"/, а количество записей о визитах равно числу визитов. Кроме того, нет необходимости при записи информации о визите записывать в данном его дату, так как она кодируется в индексе. Так как идентификаторы пациентов назначаются последовательно, то индексы первого уровня, следуют один за другим. При записи визитов, мы можем иметь информацию за несколько лет, и если бы пришлось бы резер- вировать место для каждой возможной записи, то пространства не хватило бы, даже при использовании больших дисков. Но в MUMPS пространство за- ранее не резервируется и способ записи информации полностью отличается от традиционных матричных структур. На диск записываются только узлы с данными, причем легко можно вставить индекс между двумя уже существую- щими, без необходимости проводить переиндексацию. Если бы мы оперировали с матричным способом записи, то для записи 1000 пациентов с 10 визитами пришлось бы резервировать 10 000 ячеек, даже в том случае если бы кол-во визитов у каждого было бы 1-2. При этом мы столкнулись бы с проблемами если бы у кого-то из пациентов бы- ло бы больше 10 визитов, так и при добавлении 101 пациента. В послед- нем случае для него пришлось бы сразу резервировать пространство. 4.4.2.3 Индексы массива Индексом массива может быть любое корректное MUMPS выражение. Подробнее выражения будут рассматриваться в главе 5, сейчас мы обсудим только некоторые положения. Результатом исчисления MUMPS выражения яв- ляется одиночное значение, которое может быть число /целым или дейс- твительным/, строка ASCII символов /длиной от 0 до 255 символов/, или логическим значением /истина-ложь/. А это значит, что индексом массива может быть любое из этих значений, включая строки символов. Существует только две оговорки, касающихся использования строк символов в индексах: 1 - Индексом не может являться "пустая" строка ("") 2 - В индексах не допускается использование управляющих ASCII кодов ( ASCII 0-31 и 127) Стр. 134 "Пустая" строка в случае просмотра массивов, с использованием функции $ORDER, является значением, обозначающим начало и конец прос- мотра. /Подробнее см. раздел 8.2.2/ ┌────────────────────────────────────────────────────────────────┐ │ Индексом может быть любая не пустая строка символов, которая │ │ НЕ содержит управляющих кодов ASCII. │ │ │ └────────────────────────────────────────────────────────────────┘ При использовании нескольких индексов они отделяются друг от дру- га запятыми. В общем случае ссылка на узел массива содержит имя масси- ва и список индексов, разделенный запятыми и заключенный в круглые скобки. Узлы массива создаются при использовании оператора SET. В ка- честве примера - запишем в массив PatRec информацию о новом пациенте (Jane Doe,ID#=4451) /*1/ и о двух его обращениях ( первое 5 января 1975 года, второе - 15 февраля 1978): ┌──────────────────────────────────────────────────────────────┐ │ |>Set PatRec(4451)="Doe,Jane"<- | │ │ |>Set PatRec(4451,750501)="ToothAche"<- | │ │o|>Set PatRec(4451,780215)="Gum Disease"<- |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.9 Запись значений в массив Получить записанную информацию из массива можно следующим обра- зом: ┌──────────────────────────────────────────────────────────────┐ │ |>Write PatRec(4451)<- | │ │ |"Doe,Jane" | │ │o|>Write PatRec(4451,750501)<- |o│ │ |"ToothAche" | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.10 Получение данных из массива Каждый индекс задается выражением, в качестве выражения может ис- пользоваться константа /числовая или строковая/ или имя переменной. Это означает, что индексы массива могут определяться переменными. Ис- пользование переменных в индексах массивов демонстрируется в следующем примере: ┌──────────────────────────────────────────────────────────────┐ │ |>Set PID=4451<- | │ │ |>Set DATE=770501<- | │ │o|>Write PatRec(PID,DATE)<- |o│ │ |"ToothAche" | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.11 Переменные как индексы массива В следующем примере демонстрируется использование строковых конс- тант в индексах массивов. ----------------------------------------------------------------- *1 - Здесь, и в дальнейшем, сочетание "ID#" означает - - "идентифика- ционный номер" Стр. 135 ┌──────────────────────────────────────────────────────────────┐ │ |>Set Array("John Lewcowicz","Phone")="253-3606"<- | │ │o|>Write Array("John Lewcowicz","Phone")<- |o│ │ |253-3606 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.12 Строки как индексы массива Использование строковых констант в индексах массивов является уникальной особенностью языка MUMPS, и имеет много важных особеннос- тей. Как уже упоминалось ранее, в большинстве других языков программи- рования, в качестве индексов массивов могут использоваться только чис- ла, обычно только целые. Использование строк в индексах является ог- ромным отличием от традиционных методов назначения и использования значений индексов в массивах. Индексы в MUMPS, в большинстве случаев, необходимы не только для идентификации узлов массива. Но подробнее о индексах массивов мы поговорим в главе 10. Необходимо также различать использование запятой для разделения отдельных индексов в списке, и запятую в строковой константе. Обратите внимание на следующий пример: ┌──────────────────────────────────────────────────────────────┐ │ |>Kill Array<- | │ │ |>Set Array("Doe,John")="Test Data 1"<- | │ │o|>Set Array("Doe","John")="Test Data 2"<- |o│ │ |>Write Array("Doe,John")<- | │ │ |Test Data 1 | │ │o|>Write Array("Doe","John")<- |o│ │ |Test Data 2 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.13 Использование запятой в индексах Исполнение каждого из операторов SET определяет создание различ- ных узлов массива Array. Первый оператор SET создает одиночный узел, с индексом 'Doe,John', во втором случае создается два узла, первый - это индекс, без данного - 'Doe', и записываемый в качестве его потомка ин- декс 'John' с данным. Общий вид создаваемого массива можно представить следующим образом: Array ┌───────┐ └─┬───┬─┘ ┌────────────────┘ └───────────────────┐ │ ("Doe") │ ("Doe,John") ┌─┴─┐ ┌──────┴──────┐ └─┬─┘ │ Test Data 1 │ │ ("John") └─────────────┘ ┌───┴────────┐ │Test Data 2 │ └────────────┘ Рис. Структура массива, создаваемого в предыдущем примере. Стр. 136 Как говорилось ранее в массиве могут быть использованы как индек- сы с данными, так и без них. Реально индексы без данных на диск не за- писываются, и являются только логическими указателями для обращения к "ниже" расположенным узлам. Попытка "прочитать" индексы без данных приводит к ошибке. Например: ┌──────────────────────────────────────────────────────────────┐ │ |>Set ABC(1,2,55)="Data"<- | │ │ |>Write ABC(1,2,55)<- | │ │o|Data |o│ │ |>Write ABC(1)<- | │ │ | ERROR | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 4.14 Индексы без данных /указатели/ В этом примере узел ABC(1) не содержит данных и является только указателем для обращения к "нижним" уровням в массиве. Статус узла массива /с данными или без, имеет потомков или нет/ определяется функ- цией $DATA /См. раздел 8.2.1/ Узлу, который был только логическим ука- зателем для обращения к нижним уровням /индекс без данного/ может быть назначено данное, без оказания какого-либо влияния на него и его по- томков. В то же время, у узла, который является указателем и имеет данное,это данное не может быть удалено без удаления этого узла и всех его потомков. Можно, впрочем, назначить в качестве данного такому узлу "пустую" строку (""), что будет означать отсутствие данных при узле. Но не забудьте, что узел с данными в виде "пустой" строки это не тоже самое, что узел без данных. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Test(1,2,3)="Data"<- | │ │ |>Write Test(1,2,3)<- | │ │o|Data |o│ │ |>Write Test(1)<- | │ │ | ERROR | │ │ |>Set Test(1)="Inserted data"<- | │ │ |>Write Test(1,2,3)<- | │ │o|Data |o│ │ |>Write Test(1)<- | │ │ |Inserted data | │ │ |>Kill Test(1)<- | │ │ |>Write Test(1,2,3)<- | │ │o| ERROR |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 4.15 Запись и удаление данных в массиве Когда Вы удаляете узел массива /с помощью оператора KILL, см. раздел 3.1.2/, то этот узел, все его непосредственные потомки, и все его непосредственные предки, являющиеся индексами без данных /логичес- кими указателями/ будут также удалены из массива. Удаление элементов массива "вверх" от непосредственно удаляемого оператором KILL продол- жается до тех пор, пока не встречается узел с данными, или указатель, который необходим для прохода к другой ветви массива. Другой, немаловажной особенностью использования индексации явля- ется то обстоятельство, что вне зависимости от очередности записи, ин- дексы выстраиваются в массиве согласно "упорядочивающей" последова- тельности кодов ASCII. Стр. 137 Подробнее об этом мы поговорим позднее /в разделах 8.2.2. и 10.2.2/, сейчас, в общем, можно сказать, что индексы выстраиваются в алфавитном порядке. Но числовые и нечисловые строки разделяются. Индексы массива могут быть просмотрены при использовании функции $ORDER, которая возв- ращает их в той-же последовательности, в которой они записаны. /*1/ 4.4.3 Глобальные массивы Мы уже описывали разницу между локальными и глобальными массива- ми. Локальные переменные размещаются в таблице локальных символов раз- дела и удаляются при закрытии раздела /процесса, задания/. Глобальные переменные адресуются и записываются на диск, где они и будут постоян- но находиться, пока не будут явно удалены. В примере 4.15 все массивы являются локальными. Различие в именах локальных и глобальных массивов - символ '^' перед именем глобального массива. Изменив все обращения к PatRec на ^PatRec в предшествующих примерах, Вы будете создавать гло- бальный массив, записываемый на диск, а не в оперативную память. 4.4.4 Системные переменные Кроме вышеописанных типов переменных при работе в MUMPS можно об- ращаться к еще одному виду переменных. Системные переменные содержат важную информацию о данном процессе или задании. Все системные пере- менные имеют перед именем знак доллара - '$'. ┌─────────────────────────────────────────────┐ │ Все имена системных переменны начинаются │ │ cо знака '$' │ │ │ └─────────────────────────────────────────────┘ Согласно стандарта MUMPS, значения системных переменных не могут быть изменены оператором SET, но почти во всех реализациях допускается изменение значений системных переменных $X и $Y /определяющих положе- ние курсора/, а также некоторых других. Общий список системных пере- менных - в приложении F. 4.4.4.1 $HOROLOG - Дата и время Аббревиатура $H Переменная $H содержит текущую дату и время в формате: $H=DATE,TIME где DATE - число дней прошедших с 1-го января 1841 года /то есть DA- TE=1 для 1 января 1841 года/, а TIME число секунд после полуночи. ------------------------------------------------------------------- *1 - Для DSM-11 /Диамс 2.1 и старше/ существуют определенные нюансы. При записи массива Вы имеете возможность выбрать вид упорядочивающей последовательности для индексов, а функции просмотра /$ORDER и $NEXT/ алгоритм просмотра не меняют. Кроме того, есть различия при использо- вании функций просмотра для локальных и глобальных массивов. Стр. 138 Например - значение $H='53312,1181' соответствует 18 декабря 1986 года 3 часам 16 минутам. Выбор в качестве начальной точки отсчета дат 1 ян- варя 1841 года обоснован только тем, что MUMPS первоначально разраба- тывался для медицинских приложений в начале 60-х годов, и надо было выбрать такую дату, чтобы не встретился пациент с более ранней датой рождения. Поэтому, если Вам придется записывать даты, более ранние, чем 1.1.1841 записывать их придется в ином формате - например ГГММДД. Представление в формате $H удобно для вычисления интервалов между двумя датами. Ну например, для того, чтобы вычислить число дней между 12.02.1980 и 5.04.1980, необходимо проделать много дополнительных опе- раций, проверить високосный год или нет, учесть что в месяцах разное к-во дней, в тоже время если эти даты представлены числами 54345 и 54418 - то достаточно вычесть одно из другого. В большинство реализаций MUMPS включены утилиты преобразующие внутренний формат даты и времени в более "читаемые" /или внешние/ фор- мы представления. 4.4.4.2 $IO - Номер текущего устройства Аббревиатура $I $I содержит значение, которое однозначно идентифицирует в MUMPS системе текущее активное устройство. Обычно представляется целым чис- лом, но в некоторых реализациях одно и тоже устройство может идентифи- цироваться разными системными именами. Подробнее о устройствах - раз- дел 3.12 в главе 12 4.4.4.3 $JOB - Номер задания /процесса/ или раздела Аббревиатура $J $J - содержит целое число, уникально идентифицирующее процесс /задание/ в MUMPS системе. Очень часто системная переменная $J исполь- зуется как индекс первого уровня в глобальных массивах, для разделения данных между процессами. Очень важно заметить, что $J уникально идентифицирует процесс /задание/ на отдельном компьютере. При работе в локальных сетях, когда на разных компьютерах функционируют независимые MUMPS системы, больше, чем одно задание может иметь одинаковый номер. В этом случае должны использоваться иные методы идентификации процессов /заданий/. 4.4.4.4 $STORAGE - Свободное пространство раздела Аббревиатура $S $S содержит целое число, которое отображает свободное пространс- тво раздела /в байтах/. Разделы, и управление ими, описаны в разделе 2.2.1. Значение, возвращаемое этой системной переменной, специфично для некоторых реализаций - за подробностями обращайтесь к документации на используемую Вами реализацию. /*1/ ---------------------------------------------------------------------- *1 Так для ISM 4+ $S всегда /!/ возвращает 10000, это определяется тем, что раздел в этой системе виртуален, и принципиально ничем, кроме объема жесткого диска, не ограничен. Стр. 139 4.4.4.5 $TEST - Флаг проверки Аббревиатура $T $Т - содержит результат оценивания логического выражения - ( 1 или 0 - ИСТИНА или ЛОЖЬ ), в аргументе последнего IF, или результат исполнения последнего из операторов OPEN, LOCK, READ или JOB с уста- новленным временем ожидания исполнения. Более подробно о этих операто- рах, и о синтаксисе их использования с установкой времени ожидания ис- полнения - в главах 3 и 6. Ни одна из форм использования операторов с накладыванием пост-ус- ловий /подробнее см. раздел 5.4/ НЕ оказывает влияния на значение пе- ременной $TEST. 4.4.4.6 $X - Координата Х текущего устройства Аббревиатура $X MUMPS система отслеживает координаты курсора /или печатающей го- ловки/ в процессе работы. Координата Х - отсчитывается в позициях от левого края, Y - в строках. $X и $Y всегда содержат неотрицательные целые числа. Начальная позиция /верхний левый угол экрана или страни- цы/ имеет координаты $X=0, $Y=0. Переменные $X и $Y обычно используются только со странично-ориен- тированными устройствами, и практически никогда с последовательными устройствами подобными дисковым файлам или устройствами магнитной лен- ты. При посылке на устройство кода перевода страницы /form-feed sequ- ence - Write #/ или перевода строки /carriage-return line-feed sequen- ce - Write ! / переменная $X устанавливается = 0. При вводе, или выво- де символа на устройстве, значение переменной $X возрастает на 1 /То есть передвижение курсора, или печатающей головки, приводит к увеличе- нию $X на 1/. Подробнее см. описание операторов READ и WRITE в разде- лах 3.4.3 и 3.4.4, а также главу 12. Обратите внимание на то обстоятельство, что $X и $Y только "отс- леживают" положение курсора /печатающей головки/, но не могут исполь- зоваться для их позиционирования. С другой стороны, для большинства терминалов допускается возмож- ность произвести позиционирование курсора /печатающей головки/ многими способами. В случае использования прямой адресации MUMPS система уже не может отследить перемещение курсора, поэтому для того, чтобы синх- ронизировать содержимое переменных $X, $Y и курсора, в большинстве ре- ализаций допускается переназначение этих переменных, с помощью опера- тора SET. 4.4.4.7 $Y - Координата Y текущего устройства Аббревиатура $Y Переменная $Y подобна переменной $X, но отслеживает координату Y /текущую строку/ активного устройства. Каждый раз после посылки на устройство символа перевода страницы /form-feed sequence - Write # / она устанавливается =0, и увеличивается на 1 при каждом переводе стро- ки /carriage-return line-feed sequence - Write ! /. Подробнее см. опи- сание операторов READ и WRITE в разделах 3.4.3 и 3.4.4, а также главу 12. Стр. 140 4.4.4.8 Системные переменные, начинающиеся с $Z....; Все переменные, начинающиеся с $Z специфичны для каждой из реали- заций MUMPS. Информацию о таких переменных лучше всего почерпнуть из документации на систему. Будьте внимательны с использованием подобных переменных, при проектировании переносимых между MUMPS реализациями пакетов. Потому, что если даже в них и встречаются системные перемен- ные с совпадающими именами, но возвращаемые ими значения могут сильно отличаться. В связи с этим обстоятельством мы приведем только несколько по- добных переменных: $ZA В некоторых системах содержит информацию о "статусе" активного устройства. См., например, раздел 12.4.3 где обсуждается использование статусного регистра устройства магнитной ленты. $ZC(ount) Возвращает количество свободных блоков на диске, для записи глобальных массивов. Заметьте, что возвраща- ется число блоков, а не свободных байт. Размер логи- ческого блока различен в каждой реализации. $ZE(rror) Содержит код ошибки, и ее место /программа, метка, смещение/, где она произошла. Эта переменная может быть обработана специальным образом. Подробности в главе 13. $ZI(os) Возвращает значение, характеризующее процесс завер- шения последней операции READ. --------------------------------- Значение Пояснение --------------------------------- 0 Нормальное завершение 1 Достигнут предел длины 2 Истекло время ожидания 3 Конец файла /носителя/ 4 Прерывание ввода/вывода --------------------------------- $ZNAKED Содержит последнюю исполненную в системе полную гло- бальную ссылку /имя и индексы/ Это только очень малая часть из доступных в различных реализациях $Z - системных переменных /*1/. Но используя их, не забывайте о том, что Вы вряд ли сможете перенести создаваемое программное обеспечение. ------------------------------------------------------------------ *1 Так в DTM-PC 4.2 29 таких переменных, в ISM 4.85 - 16, и так далее. Стр. 141 Общие положения - Длина строки в MUMPS, согласно стандарта, может быть до 255 симво- лов, но сильно отличается от реализации к реализации. /4.1/ - "Нулевая" строка - тоже самое что и "пустая" строка, ее длина рав- на 0, она не содержит ничего /4.1/ - Символ NUL /ASCII=0/ не эквивалентен понятию "нулевая" строка, строка, содержащая его не является "пустой", ее длина не равна 0 - Имя переменной должно начинаться с буквы, после чего могут исполь- зоваться буквы и цифры. /4.4/ - Имя переменной может быть длиннее 8 символов, но переменные разли- чаются только по первым 8 символам имени /4.4/ - Система отличает большие и маленькие буквы в именах переменных /4.4/ - Имя переменной может также начинаться с символа '%' , но использо- вание таких переменных в прикладных программах нежелательно /4.4/ - Переменные, чьи имена начинаются с символа '^', записываются на диск и относятся к ГЛОБАЛЬНЫМ переменным. /4.4/ - Индексом массива может быть любая не "пустая" строка, не содержа- щая управляющих ASCII кодов /4.4.2.3/ - Все системные переменные начинаются со знака '$' /4.4.4/ Стр. 142 Глава 5 Выражения Оценивая выражения, MUMPS производит вычисления, оперируя числами или строковыми значениями. Выражаясь иным образом, выражение есть не что иное, как формула, связывающая несколько значений, в результат ре- шения которой является одиночным значением. Поясним эти положения сле- дующим образом: ┌──────────────────────────────────────────────────────────────┐ │ |>Write 4*12/6<- | │ │ |8 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 5.1 Выражение Этот пример эквивалентен исчислению высказывания " Вывести на эк- ран результат арифметического умножения 4 на 12, и деления результата операции на 6". /Символ '*' - означает операцию умножения, '/' - деле- ния/ В этом примере порядок выполнения операций MUMPS системой не име- ет значения, так как вне зависимости от порядка вычислений результат будет одинаков /(4*12)/6=8 и 4*(12/6)=8/. Но в других случаях вычисле- ния без знания порядка оценивания выражений могут давать неясный ре- зультат. ┌──────────────────────────────────────────────────────────────┐ │ |>Write 4*12-6<- | │ │ |42 | │ │ |>Write (4*12)-6<- | │ │ |42 | │ │ |>Write 4*(12-6)<- | │ │ |24 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 5.2 Порядок исчисления В этих примерах порядок исчисления выражения имеет влияние на ре- зультат. Если вычитание производится ПОСЛЕ умножения - результат 42, если ДО - результат 24. В отличии от большинства других языков прог- раммирования, в MUMPS нет приоритета выполнения арифметических и дру- гих операций. ┌─────────────────────────────────────────────────────────────────┐ │ MUMPS исчисляет выражения СЛЕВА НАПРАВО, без преимущественного │ │ приоритета исполнения каких либо операций. │ │ │ └─────────────────────────────────────────────────────────────────┘ Пример 5.2 также показывает, что порядок исчисления выражения /слева - направо/ может быть изменен при использовании круглых скобок. Часть выражения, заключенная в скобки всегда будет вычисляться раньше, чем остальная часть выражения. ┌──────────────────────────────────────────────────────────────┐ │ |>Write (4*8)/(34-30)<- | │ │ |3 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.3 Изменение порядка исчисления при помощи скобок Стр. 143 В данном примере вычисление проводится следующим образом: 1. (4+8)=12 2. (34-30)=4 3. 12/4=3 ┌─────────────────────────────────────────────────────────────┐ │ Использование круглых скобок позволяет изменить обычный │ │ порядок исчисления выражений в MUMPS /слева-направо/ │ │ │ └─────────────────────────────────────────────────────────────┘ Выражения могут включать в себя не только арифметические опера- ции. Кроме того, любые включаемые в выражение константы и имена пере- менных могут быть сами по себе выражениями. Мы приведем только нес- колько самых простых примеров, где выражения будут аргументами опера- тора SET: SET A=1 SET A="Test" SET A=SUM SET A=4*B При исчислении выражений MUMPS интерпретирует используемые опе- ранды либо как строки, либо как числа, в зависимости от вида операции. Пример строковой и числовой интерпретации впервые был приведен в главе 1. (При операции КОНКАТЕНАЦИИ /сцепления строк/ операнды интерпретиру- ются как строки и результат строковый: "500 Miles"_"25 Gallons"="500 Miles25 Gallons", при арифметической операции - результат число: "500 Miles"/"25 Gallons"=20) В MUMPS есть три вида операций: -арифметические -строковые -логические В приложении E приведен полный список возможных операций. 5.1 Числовые операции : +, -, *, **, /, \, # При использовании одной из числовых операций MUMPS интерпретирует значение /ия/ как числа, и вычисляет числовой результат. Числовые опе- рации могут быть разделены на одноместные, оперирующие с одним значе- нием, и как результат, приводящие к его числовой интерпретации, и дву- местные, которые оперируют с двумя значениями. 5.1.1 Одноместные операции: + и - При необходимости Вы можете обратиться к разделу 4.2.2, где пол- ностью описаны правила интерпретации строк в числа, для краткости мож- но представить этот процесс следующим образом: Строка начинает просматриваться слева направо, с одновременным построением результирующего числа из просмотренных символов, которые являются числами, если встречается символ, который НЕ является числом, процесс прекращается. Если строка не содержит чисел вообще, ее число- вая интерпретация равна 0. /Например +"АВС"=0/ Стр. 144 ┌──────────────────────────────────────────────────────────────┐ │ |>Write +7.000<- | │ │ |7 | │ │ |>Write -"-22.30"<- | │ │ |22.3 | │ │ |>Write +"7 course meal"<- | │ │ |7 | │ │ |>Write +"Testing"<- | │ │ |0 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 5.4 Одноместные операции 5.1.2 Двуместные числовые операции В результате исполнения двуместной операции, два значения, стоя- щие с обоих сторон от знака операции преобразуются в одно значение. В списке приведены все двуместные операции поддерживаемые в MUMPS: + операция СЛОЖЕНИЯ (4+36=40) - операция ВЫЧИТАНИЯ (5-3=2) * операция УМНОЖЕНИЯ (8*1.2=9.6) ** операция ВОЗВЕДЕНИЯ В СТЕПЕНЬ (3**4=81) /*1/ / операция ДЕЛЕНИЯ (10/4=2.5) \ операция ДЕЛЕНИЯ НАЦЕЛО, исполняется аналогично операции обычного деления, но дробный остаток отбрасывается /не округляется/, до целого значения результата (22\7=3) # операция ВЗЯТИЯ ПО МОДУЛЮ - возвращает целый остаток от деления. Например, 6#4=2 Операция вида 0#nn, где nn - любое число, всегда дает 0 Более формализованное определение операции взятия по модулю, при- годное и для отрицательных чисел, может быть дано следующим образом: ABC#XYZ=ABC-(XYZ*FLOOR(ABC/XYZ)) где FLOOR(NUMBER) определяется как наибольшее целое, которое МЕНЬШЕ или РАВНО NUMBER. В качестве примера, рассмотрим по шагам две операции взятия по модулю: 7#4 и 7#(-4) 7#4=7-(4*FLOOR(7/4)) 7#(-4)=7-(-4*FLOOR(7/-4)) =7-(4-FLOOR(1.75)) =7-(-4-FLOOR(-1.75)) =7-(4*(1)) =7-(-4*(-2)) =7-(4) =7-(+8) =3 =-1 ----------------------------------------------------------------------- *1 Из всех известных мне версий MUMPS /MSM 2.1 и 2.2, ISM/NTSM/ 4 и 5, DSM-11 /ver.2-3/, и DTM 4.2+/ эта операция поддерживается только в DTM, начиная с версии 4.3. Стр. 145 Как Вы заметили результат действия операции взятия по модулю не симметричен относительно 0, так как абсолютное значение (-1.75) не равно абсолютному значению FLOOR(1.75). Знак результата операции взя- тия по модулю определяется знаком выражения следующего за символом операции взятия по модулю -(#) ┌──────────────────────────────────────────────────────────────┐ │ |Odd Write !," Проверка чисел на четность" | │ │ |Getn Read !," Число : ",n If n="" Quit | │ │o| If n#2 Write " НЕЧЕТНОЕ" |o│ │ | Еlse Write " ЧЕТНОЕ" | │ │ | Goto Getn | │ │o|----------------------------------------------------------|o│ │ |Do Odd<- | │ │ | Проверка чисел на четность | │ │o| Число : 10<- ЧЕТНОЕ |o│ │ | Число : 3<- НЕЧЕТНОЕ | │ │ | Число : 2<- ЧЕТНОЕ | │ │o| Число : <- |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.5 Операция взятия по модулю В любой из операций, включающих операцию деления, запрещено де- лить на 0, что всегда вызывает ошибку. Это относится как к простому делению (/) и делению нацело (\), так и к операции взятию по модулю (#) ┌────────────────────────────────────────────────────┐ │ Деление на 0 запрещено и приводит к ошибке. │ │ │ └────────────────────────────────────────────────────┘ Одна из удобных возможностей использования операции взятия по мо- дулю представлена в следующем примере, где производится вычисление дня недели. Эти вычисления основываются на том факте, что принятый за 1 день, в MUMPS системах день 1 января 1841 года был понедельником. ┌──────────────────────────────────────────────────────────────┐ │ |Day ;Определение дня недели основываясь на $H | │ │ | Set day=$H#7 | │ │o| If day=1 Write "Понедельник" |o│ │ | If day=2 Write "Вторник" | │ │ | If day=3 Write "Среда" | │ │o| If day=4 Write "Четверг" |o│ │ | If day=5 Write "Пятница" | │ │ | If day=6 Write "Суббота" | │ │o| If day=7 Write "Воскресенье" |o│ │ |----------------------------------------------------------| │ │ |Do Day Write " - ",$H<- | │ │ |Понедельник - 53601,24735 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.6 Использование взятия по модулю для вычисления дня недели Стр. 146 Очень часто операцию взятия по модулю использовании функции $DATA (Раздел 10.2.2) и при манипуляциях с битовыми строками (См. примеры 12.17 и 12.18) 5.2 Строковые операции: конкатенация _ При использовании строковой операции MUMPS всегда будет интерпре- тировать участвующие в операции значения как строки. К строковым опе- рациям относится только операция конкатенации: _ операция КОНКАТЕНАЦИИ /"слияния" строк/ - отображается символом "подчеркивания", который соединяет начало одной строки с концом другой. (например "Хороший "_" день "="Хороший день " ) ┌──────────────────────────────────────────────────────────────┐ │ |>Set x=22,y=3 Write x*y<- | │ │ |66 | │ │o|>Write x_y<- |o│ │ |223 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.7 Операция КОНКАТЕНАЦИИ 5.3 Логические операции и операции отношения: >, <, =, [, ], ?, &, ! Логические операции и операции отношения могут оценивать выраже- ния как в строковой, так и в числовой интерпретации, но их результатом всегда будет логическая величина - ИСТИНА или ЛОЖЬ (1 или 0, соответс- твенно). Операции отношения используются для сравнения значений, логи- ческие для проверки комбинаций из получаемых логических значений. Как уже неоднократно упоминалось ранее, MUMPS интерпретирует значения в контексте выполняемых операций. Если значения "2" и "2.0" сравниваются как числа, то они равны, если же как строки, то нет. Операции отноше- ния в связи с этим обстоятельством можно разделить на операции сравне- ния числовых и строковых значений. 5.3.1 Операции сравнения чисел В этих операциях MUMPS интерпретирует оба значения, участвующих в операции как числа, проводя их числовую интерпретацию перед исполнени- ем сравнения. К числовым операциям сравнения относятся операции БОЛЬШЕ ЧЕМ и МЕНЬШЕ ЧЕМ. 5.3.1.1 Оператор БОЛЬШЕ ЧЕМ: > В операции БОЛЬШЕ ЧЕМ производится сравнение числовых интерпрета- ций двух выражений. Результатом этой операции будет ИСТИНА /1/, если первое выражение больше чем второе, в противном случае результат - ЛОЖЬ /0/ ┌──────────────────────────────────────────────────────────────┐ │ |>Write "Двойка>Единицы = ",2>1<- | │ │ |Двойка>Единицы = 1 | │ │o|>Write "Четыре>Пяти = ",4>5<- |o│ │ |Четыре>Пяти = 0 | │ │ |>Write "Пять>Пять = ",5>5<- | │ │o|Пять>Пять = 0 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.8 Операция БОЛЬШЕ ЧЕМ Стр. 147 5.3.1.2 Операция МЕНЬШЕ ЧЕМ: < В операции МЕНЬШЕ ЧЕМ производится сравнение числовых интерпрета- ций двух выражений. Результатом этой операции будет ИСТИНА /1/, если первое выражение меньше чем второе, в противном случае результат - ЛОЖЬ /0/ ┌──────────────────────────────────────────────────────────────┐ │ |>Write "6<22 = ",6<22<- | │ │ |6<22 = 1 | │ │o|>Write "22<6 = ",22<6<- |o│ │ |22<6 = 0 | │ │ |>Write "22<22 = ",22<22<- | │ │o|22<22 = 0 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.9 Операция МЕНЬШЕ ЧЕМ 5.3.2 Операции сравнения строк В этих операциях MUMPS интерпретирует оба значения, участвующих в операции как строки, проводя их строковую интерпретацию перед исполне- нием сравнения. К строковым операциям сравнения относятся операции ЭК- ВИВАЛЕНТНО, СОДЕРЖИТСЯ, СЛЕДУЕТ и СРАВНЕНИЕ С ШАБЛОНОМ. 5.3.2.1 Операция ЭКВИВАЛЕНТНО: = Операция ЭКВИВАЛЕНТНО производит проверку ЭКВИВАЛЕНТНОСТИ /РА- ВЕНСТВА/ двух строк /для строк "AbC"="AbC" результат ИСТИНА, но "2.20"="2.2" - ЛОЖЬ/. Для выполнения числового сравнения необходимо использовать одноместную операцию для числовой интерпретации операндов перед сравнением. /Для примера +"2"=+"2.0" - ИСТИНА/. Отметим, что знак равенства, используемый в операции эквивалентности, исполняет другие функции, чем, скажем, в операторе SET. Основная форма оператора SET может быть пояснена примером: SET A=22 который есть не что иное как символическая запись следующего высказы- вания: "Записать в область памяти, связанную с именем локальной пере- менной А, значение равное 22". Но с другой стороны, вполне допустимо записать следующее выражение: SET A=22=3 Это выражение можно перевести следующим образом: "Записать в область памяти, связанную с именем локальной переменной А, результат сравнения значений 22 и 3 /ЛОЖЬ или 0/". ┌──────────────────────────────────────────────────────────────┐ │ |>Write "abc"="abc"<- | │ │ |1 | │ │o|>Write 234="+234"<- |o│ │ |0 | │ │ |>Write 234=+"+234"<- | │ │o|1 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.10 ЭКВИВАЛЕНТНОСТЬ как операция отношения Стр. 148 Разница между вторым и третьим сравнением, объясняется использо- ванием одноместной операции. Знак + перед строкой "+234" вызывает ее числовую интерпретацию перед сравнением. Число получаемое в результате интерпретации равно числу слева от знака операции. 5.3.2.2 Операция СОДЕРЖИТ : [ Операция СОДЕРЖИТ используется для сравнения двух строк символов. Если строка, находящаяся справа от знака операции содержится в строке слева /символ за символом, включая пробелы и другие знаки, и в том же порядке/, то результат операции - ИСТИНА /1/, в противном случае, ре- зультат - ЛОЖЬ /0/ ┌──────────────────────────────────────────────────────────────┐ │ |>Write "Хороший день"["ий де"<- | │ │ |1 | │ │o|>Write "Хороший день"["Хр"<- |o│ │ |0 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.11 Операция СОДЕРЖИТ 5.3.2.3 Операция СЛЕДУЕТ : ] Если строка, находящаяся слева от знака операции, СЛЕДУЕТ, за строкой справа, в соответствии с упорядочивающей последовательностью ASCII кодов, то результат операции - ИСТИНА /1/. Например, строка "Z" следует за строкой "ABC", но строка "AB" не СЛЕДУЕТ за строкой "ABC". Упорядочивающая последовательность ASCII кодов определяется относи- тельной позицией символов, друг относительно друга в кодовой таблице. /См. приложение А/. Символы, чье числовое представление /скажем, деся- тичное значение/ больше, СЛЕДУЮТ за символами, чье числовое представ- ление меньше. Так например "b" /ASCII=98/, СЛЕДУЕТ за символом A /AS- CII=65/. И строка "Bz" /десятичные значения 66,122/ СЛЕДУЕТ за строкой "B1", /десятичное представление 66,49/. /*1/ ┌──────────────────────────────────────────────────────────────┐ │ |>Write "ABC"]"A"<- | │ │ |1 | │ │o|>Write "ABC"]"a"<- |o│ │ |0 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.12 Операция СЛЕДУЕТ Особенно следует отметить то обстоятельство, что сравниваемые вы- ражения оцениваются как строки символов, но не как их числовые эквива- ленты. Для сравнения чисел используются операции БОЛЬШЕ ЧЕМ и МЕНЬШЕ ЧЕМ, но нельзя использовать операцию СЛЕДУЕТ, что продемонстрировано на следующем примере: -------------------------------------------------------------------- *1 Следует отметить, что эта операция одна из самых "капризных", так например, в ISM 4.85 строка "zz" /десятичное представление 122,122/, почему-то следует за всеми строками, в том числе и за строкой "яя" /десятичное представление 222,222/ Стр. 149 ┌──────────────────────────────────────────────────────────────┐ │ |>Write 123]4<- | │ │ |0 | │ │o|>Write 123>4<- |o│ │ |1 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.13 Операция СЛЕДУЕТ это нечисловая операция В MUMPS обычно поддерживаются две упорядочивающих последователь- ности: упорядочивающая последовательность ASCII кодов используемая при сравнивании строк, и упорядочивающая последовательность используемая при записи индексов массивов. Последняя в основном совпадает с упоря- дочивающей последовательностью ASCII кодов, за исключением организации записи числовых индексов. Подробнее см. в главе посвященной организа- ции глобальных массивов. /Глава 10/ Другая интересная возможность относится к использованию операции СЛЕДУЕТ для проверки строк на их равенство "пустой" строке. Так как ЛЮБАЯ строка следует за "пустой" строкой то проверка на "непустые" строки может выглядеть следующим образом: If ans]"" ;остаток строки исполняется если 'ans' НЕ пустая строка Эта строка эквивалентна строке If ans'="" (логический оператор НЕ - ' описывается в разделе 5.3.3.3). Мне лично больше нравится вторая форма, но и первая достаточно часто встречается в программах. 5.3.2.4 Операция СРАВНЕНИЯ С ШАБЛОНОМ: ? Операция СРАВНЕНИЯ С ШАБЛОНОМ в чем-то подобна операции СОДЕРЖИТ, только она проверяет строку на соответствие конкретной последователь- ности определенных символов. Эти символы представляются следующими ко- дами: Таблица 5.1 Кода шаблонов для сравнения ---------------------------------------------------------------------- A 52 буквы латинского алфавита /большие и малые/, например, A, b, C, x, Y /ASCII = 65 - 122/ C 33 управляющих кода ASCII /ASCII 0-31 и 127/, эти символы обычно не выводятся на печать. Е Любой из ASCII кодов /ASCII 0 - 255/ L 26 маленьких латинских букв /от a до z, ASCII 97-122/ N 10 цифровых символов /от 0 до 9, ASCII 48-57/ P 33 символа пунктуации /ASCII 32-47, 64, 91-96,123-126/ U 26 больших латинских букв /от A до Z, ASCII 65-90/ ----------------------------------------------------------------------- Стр. 150 В добавление к этим категориям Вы может также вставлять любые СТРОКОВЫЕ ЛИТЕРАЛЫ /строки, заключенные в кавычки/, и использовать их как шаблоны. /*1/ Вы можете также определять количество встречающихся символов каждого вида, или строковых литералов, а также проверять по- рядок вхождения этих символов в строку. Выражение СРАВНИВАЮЩЕЕ пере- менную с шаблоном выглядит в общем виде, следующим образом: Variable?nnPATTERN где Variable содержит проверяемую строку, а PATTERN - содержит специ- фикацию шаблона проверки. Символами nn заменено целое число обозначаю- щее количество вхождений PATTERN в проверяемую строку. Например, шаб- лон ?2N составлен для строки содержащей строго две цифры. Значение nn может также содержать и знак периода (.), который в данном случае изменяет смысл включения кол-ва повторений nn шаблона, как : "любое количество повторений шаблона от 0 до nn /включительно/". Этот же знак может использоваться и для задания диапазона значений, например : "mm?nn"- что будет означать "число повторений шаблона от mm до nn /включительно/". При использовании в шаблонах знака периода, максимальное число вхождений может опускаться, что будет пониматься как "любое число вхождений". Для примера шаблон вида '1N.A' составлен для строк содержащих 1 цифру /первый символ строки/, и затем любое количество больших или ма- лых латинских букв. Строки включающие любые иные символы /пунктуации, управляющие коды, или цифры не в первой позиции строки/ не будут соот- ветствовать этому шаблону. Шаблон вида '?1.4N' составлен для проверки строк, состоящих из от 1 до 4-х цифр. Коды шаблонов могут использоваться совместно, так например Вы мо- жете указать шаблон вида 'AN', что будет означать "либо буквы, либо цифры, или и то и другое вместе". Возможные способы задания шаблонов можно продемонстрировать на примерах: ZIP?5N Проверка идентификаторов пациентов, которые должны состоять из 5 цифр /14835 - правильно, 14853-2603 - неправильно/ DATE?2N1"/"2N1"/"2N Проверка введенной в формате ДД/ММ/ГГ даты, где ДД - день /две цифры/, ММ - месяц /две цифры/, ГГ - год /две цифры/ PATID?1N.N или PATID?1.N Проверка идентификатора пациента, состоящего из одной, или более цифр. /4 или 34879 - правильно, 23а или 23.4 - нет/ ----------------------------------------------------------------- *1 В реализациях, эксплуатируемых у нас встает вопрос о использовании русских букв в проверке на СООТВЕТСТВИЕ ШАБЛОНУ. В DSM-11 эта проблема решается за счет того, что используются только большие русские буквы, которые вставлены на место малых латинских. В ISM добавлены специаль- ные кода шаблонов для русских букв : R - все русские буквы, B - боль- шие русские буквы, M - малые русские буквы. DTM 4.2+ и MSM были дора- ботаны подобным образом. Стр. 151 ENTRY?.E1C.E Проверка переменной ENTRY на наличие хотя бы одного управляющего ASCII кода DATE?1.2N1"/"1.2N1"/"2N Более расширенная проверка даты, где день и месяц могут вводится 1-2 цифрами. 5.3.3 Логические операторы Логические операторы применяются для связывания операторов отно- шения, и вычисления результата такой комбинации /ИСТИНА или ЛОЖЬ/. Су- ществует три логических оператора И, ИЛИ и НЕ. 5.3.3.1 Оператор логического И: & Оператор логического И используется для исполнения операции с двумя /и более/ аргументами согласно приведенной ниже таблице истин- ности операции /Таблица 5.2/ Табл.5.2. Таблица истинности операции И ------------------------------ Операнды Результат ------------------------------ Истина&Истина Истина Истина&Ложь Ложь Ложь&Ложь Ложь ------------------------------ ┌──────────────────────────────────────────────────────────────┐ │ |>Write (1=+"1.0)&("abcd"["bc")<- | │ │ |1 | │ │o|>Write 4]1023&1<- |o│ │ |1 | │ │ |>Write 122>121&("aBc"["b")<- | │ │o|0 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.14 Операция И Не забывайте о том, что нет неявного приоритета исполнения опера- ций при оценивании выражения. 5.3.3.2 Оператор логического ИЛИ: ! Оператор логического ИЛИ используется для исполнения операции с двумя /и более/ аргументами согласно приведенной ниже таблице истин- ности операции /Таблица 5.3/ Табл.5.3 Таблица истинности операции ИЛИ ------------------------------ Операнды Результат ------------------------------ Истина&Истина Истина Истина&Ложь Истина Ложь&Ложь Ложь ------------------------------ Стр. 152 ┌──────────────────────────────────────────────────────────────┐ │ |>Write ("Nice Day"["Nc")!(10<20)<- | │ │ |1 | │ │o|>Write ("ABC"]"abc")!(10>20)<- |o│ │ |0 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.15 Операция ИЛИ 5.3.3.3 Оператор НЕ: ' Оператор НЕ /логическое отрицание, отображается знаком апострофа - (') / используется для реверса результата логической операции. НЕ ИСТИНА=ЛОЖЬ и НЕ ЛОЖЬ=ИСТИНА, например '(1="1.0") - ИСТИНА. Оператор логического отрицания размещается непосредственно перед знаком логи- ческой операции для реверса ее результата. Например выражение вида '(1.2=1.3) может быть записано в виде 1.2'=1.3 /результат оценивания обоих выражений - ИСТИНА/ ┌──────────────────────────────────────────────────────────────┐ │ |>Write +"2.0"'=2<- | │ │ |0 | │ │o|>Write "Nice Day"'["Nc"<- |o│ │ |1 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.16 Операция логического отрицания /оператор НЕ/ Используя оператор НЕ совместно с операторами БОЛЬШЕ ЧЕМ и МЕНЬШЕ ЧЕМ, можно создать логическую конструкцию которая будет исполнять функции БОЛЬШЕ ЧЕМ ИЛИ РАВНО и МЕНЬШЕ ЧЕМ ИЛИ РАВНО, как показано ни- же: --------------------------------------------------------------- Операция Пояснения --------------------------------------------------------------- '< переводится как НЕ МЕНЬШЕ ЧЕМ, что эквивалентно БОЛЬШЕ ЧЕМ ИЛИ РАВНО '> переводится как НЕ БОЛЬШЕ ЧЕМ, что экивалентно МЕНЬШЕ ЧЕМ ИЛИ РАВНО 5.4 Комбинация операций разных типов В одном выражении MUMPS допускает одновременное использование операций разных типов /числовых, строковых, логических и операций от- ношения/. Вид результата будет определяться видом используемых опера- ций и порядком их исчисления в выражении. Продемонстрируем это положе- ние следующим примером: ┌──────────────────────────────────────────────────────────────┐ │ |>Write 2*4=8_" TEXT"<- | │ │ | | │ │o|1 TEXT |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.17 Комбинирование операций разных типов Стр. 153 Рассмотрим по шагам исчисление этого выражения, не забывая о том, что MUMPS обычно исчисляет выражение слева направо, шаг за шагом. В скобках указан результат операции, выполняемой за каждый из шагов: Шаг 1 2*4=8_" TEXT" ;вычисление 2*4 (8) Шаг 2 8=8_" TEXT" ;вычисление 8=8 (1 - ИСТИНА) Шаг 3 1_" TEXT" ;конкатенация 1_" TEXT" (1_" TEXT") Результат "1 TEXT" ┌─────────────────────────────────────────────────────────────────┐ │ В выражении могут комбинироваться операции любых типов. Вид │ │ результата определяется последней исполненной операцией. │ │ │ └─────────────────────────────────────────────────────────────────┘ Как Вы помните, результат логической операции представляется од- ной из двух цифр: 0 или 1 /ложь или истина/. Комбинирование результата логической операции с результатами не-логических операций позволяет достичь определенного выигрыша. Разберем это на примере, где демонс- трируется проверка содержимого переменной. ┌──────────────────────────────────────────────────────────────┐ │ | If "YyNn"[ans=$Length(ans) ... | │ └──────────────────────────────────────────────────────────────┘ Рис. 5.18 Проверка ответа с помощью комбинирования операций В этой строке программы проверяется значение, занесенное в пере- менную ans. Переменная ans должна содержать только один символ, из следующего перечня : "y", "Y", "n", "N". Любой другой символ, кроме перечисленных, а также строки из более чем одного символа не должны рассматриваться. Первая часть выражения ("YyNn"[ans) проверяет, содержится ли строка, занесенная в ans в строке "YyNn". Эта часть выражения будет истинна, если в ans будет записана одна из следующих строк: "y", "Y", "N", "n", "Yy", "YyN", "YyNn", "yN", "yNn", "Nn" и "" /"пустая" стро- ка/. Любая другая строка приведет к тому, что результат оценивания этой части выражения будет ЛОЖЬ. Логический результат этой операции в дальнейшем сравнивается с длиной строки, содержащейся в переменной ans /функция $LENGTH (раздел 8.1.3) возвращает количество символов в строке, которая указана ее ар- гументом/. Если ans содержит один из перечисленных выше символов / "y", "Y", "n" или "N"/, то ее длина =1 и общий результат выражения, являющегося аргументом оператора If - ИСТИНА. В том случае, если в ans занесена одна из многосимвольных строк, удовлетворяющих первой части выражения, или любая другая строка - результат выражения - ЛОЖЬ. См. таблицу 5.4 Комбинирование операций разных типов в одном выражении повышает гибкость программирования, но иногда способно затруднить понимание текста программы. Вот какой фрагмент был обнаружен мной в одной из утилит преобразования дат: If Date<11E6*Date=Date*Date<1 Quit Стр. 154 Табл. 5.4 Исчисление вариантов комбинирования операций в выражении "YyNn"[ans=$Length(ans) --------------------------------------------------------------------- ans "YyNn"[ans $Length(ans) "YyNn"[ans=$Length(ans) Результат --------------------------------------------------------------------- "Y" 1 (Истина) 1 1=1 1 (Истина) "n" 1 (Истина) 1 1=1 1 (Истина) "X" 0 (Ложь) 1 0=1 0 (Ложь) "" 1 (Истина) 0 1=0 0 (Ложь) "Yy" 1 (Истина) 2 1=2 0 (Ложь) --------------------------------------------------------------------- В этой строке проверяется правильность значения даты занесенного в переменную Date /формат аналогичный системной переменной $H - неот- рицательное целое число, длиной не более 7 цифр/. Если результат оце- нивания выражения ИСТИНА, то исполняется оператор QUIT. Теперь вопрос, какая именно проверка исполняется в этой строке? После нескольких экс- периментов было установлено, что выражение ИСТИННО в двух случаях: ес- ли Date<1 и для Date>999999 В подобном случае это можно было бы запи- сать как: If Date<1E6*Date<1 Quit или в еще более наглядной форме как: If Date>999999!(Date<1) Quit Быть может автор желал произвести в исходной строке дополнитель- ную проверку на неотрицательные значения, и затем некорректно ее реа- лизовал? Сейчас трудно уже понять первоначальные намерения. Подобные "изыски" могут затруднить сопровождение даже собственных программ. Общие положения - MUMPS исчисляет выражения слева-направо без приоритета каких-либо числовых или других операций. /5/ - Используя круглые скобки Вы можете изменить порядок исчисления вы- ражений /5/ - Деление на 0 запрещено и вызывает ошибку /5.1.2/ - В выражениях Вы можете использовать операции разных типов, вид ре- зультата определяется типом последней исполняемой операции /5.4/ Стр. 155 Глава 6 Синтаксис MUMPS В этой главе мы будем обсуждать структуру и синтаксис использова- ния операторов MUMPS в командных строках и программах. Примеры, сопро- вождающие текст, поясняют нюансы использования операторов, описанных ранее. Если Вы встретитесь с трудностями с пониманием синтаксиса или назначения операторов, возвратитесь назад к соответствующему разделу главы 3. В связи с тем, что синтаксическим структурам в MUMPS присуща не- которая иерархия, мы начнем обсуждение с отдельных операторов и их ис- пользования в программах. Наименьшим исполняемым элементом в MUMPS яв- ляется оператор и его аргументы. Отдельные операторы могут быть объ- единены в одну строку, группы строк могут объединяться в отдельные процедуры /подпрограммы/, которые, в свою очередь, могут быть органи- зованы и записаны на диск в виде программы. В свою очередь программы могут связаны между собой и являются частью некоей задачи. /*1/ Для наших целей, мы будем различать программу /отдельный программный мо- дуль/ и задачу, задача, как мы будем ее понимать, будет состоять из набора всех модулей, необходимых для исполнения конкретного процесса /функции/ Прежде чем начать разбирать основы синтаксиса операторов, следует еще раз вернуться к некоторым базовым концепциям. MUMPS является строчно-ориентированным языком, каждая строка может содержать много операторов /количество ограничено только пределом длины строки - обыч- но 255 символов/. Каждый оператор и его аргументы отделяются в строке от других операторов одним, или больше пробелом. Действие некоторых операторов /таких как IF, ELSE и FOR/ будет определять характер испол- нения находящегося справа от них остатка строки. При отсутствии опера- торов передачи управления /подобных DO и GOTO/ MUMPS программа будет исполняться построчно, сверху - вниз, начиная с самой первой и до са- мой последней строки, и в каждой строке последовательно все операторы слева - направо. 6.1 Операторы Оператор является наименьшим исполняемым элементом языка MUMPS и базовой формой на основе которой строятся все остальные элементы язы- ка. Оператор состоит из директивы интерпретатора /глагола/ и списка аргументов, используемых как объекты директивы /существительные/. ------------------------------------------------------------------ *1 Автор проводит различие между терминами 'routine' и 'program'. Обычно, на практике, это синонимы, для большей ясности, я решил отказаться от термина "модуль", которым здесь можно было заменить термин 'routine'. Список используемых английских терминов и их толкование: program задача routine программа, программный модуль command оператор line командная или программная строка командная строка - это строка операторов, исполняемая в непосредственном режиме subroutine /part/ подпрограмма, процедура Стр. 156 6.1.1 Аргументы операторов Говоря в общем, для операторов в MUMPS возможны только две основ- ные синтаксические формы использования операторов - с аргументами и без них. Поясним это положение, рассматривая операторы IF и ELSE. Об- щая форма использования оператора IF: IF EXPRESSION ;исполняемый затем остаток строки Когда результат оценивания EXPRESSION - ИСТИНА, то остаток строки пра- вее оператора IF исполняется, когда результат оценивания EXPRESSION - ЛОЖЬ, то остаток строки правее оператора IF игнорируется. Оператор EL- SE родствен оператору IF, но проверяет противоположное значение ре- зультата оценивания - ЛОЖЬ и используется следующим образом: ELSE ;исполняемый затем остаток строки /ДВА пробела отделяют ELSE от следующего оператора в строке/ Вкратце мы уже обсуждали системную переменную $TEST раньше и вер- немся к ней позднее. В $TEST заносится 1 или 0 /ИСТИНА или ЛОЖЬ/ как результат последней исполняемой проверки /например IF с аргументами/. Оператор ELSE проверяет содержимое этой переменной и может быть экви- валентно заменен следующим выражением: IF '$TEST ;исполняемый затем остаток строки То есть, если в $TEST занесен 0 /ЛОЖЬ/, то будет исполняться ос- таток строки за оператором ELSE, если же $TEST=1, то остаток строки правее оператора ELSE игнорируется. Представим небольшой пример с сов- местным использованием операторов IF и ELSE: ┌──────────────────────────────────────────────────────────────┐ │ | Write " Start :: " Set A=1 Set B=2 | │ │ | If A=B Write "A равно В" | │ │o| Else Write "A не равно В" |o│ │ | Quit | │ │ |----------------------------------------------------------| │ │o| Start :: A не равно В |o│ │ | | │ └──────────────────────────────────────────────────────────────┘ Рис. 6.1 Операторы с аргументами и без аргументов Оператор IF в данном примере имеет аргумент /выражение А=В/, а оператор ELSE не имеет аргументов /его действие основано на неявной проверке значения системной переменной $TEST, значение которой, в дан- ном случае, устанавливается оцениванием аргумента оператора IF/ Операторы (Write, Set, If и так далее), отделяются от своих аргу- "Set A=1". Знак пробела используется для отделения аргументов от └┘ оператора и разделения операторов в строке. ----------------------------------------------------------------- *1 Здесь и далее, для явного отображения символа пробела /"space" - ASCII -32/ будет использоваться символ - └┘. Стр. 157 ┌─────────────────────────────────────────────────────────────────┐ │ Если оператор имеет аргументы, то они должны быть отделены от │ │ него ВСЕГДА только ОДНИМ пробелом. │ │ │ └─────────────────────────────────────────────────────────────────┘ Если в одной строке размещается больше чем один оператор, то они отделяются друг от друга одним или больше пробелами. (Set A=1 Write A) └┘ ┌─────────────────────────────────────────────────────────────────┐ │ Если оператор имеет аргументы, то следующий оператор в строке │ │ должен быть отделен от последнего аргумента по крайней мере │ │ одним пробелом. │ │ │ └─────────────────────────────────────────────────────────────────┘ Для поддержания синтаксического единства, безаргументные операто- ры, такие как ELSE, должны быть отделены от следующих элементов строки двумя, или более пробелами. (Else Write ...) └┘└┘ Если это обстоятельство будет забыто, то при исполнении произойдет ошибка вида SYNTAX. Безаргументные операторы, (подобные обсуждаемому ранее QUIT) располагаемые в конце логической строки НЕ требуют вставки пробелов правее себя. ┌─────────────────────────────────────────────────────────────────┐ │ Безаргументные операторы должны отделяться от следующих опе- │ │ раторов в строке двумя и больше пробелами. │ │ │ └─────────────────────────────────────────────────────────────────┘ Некоторые операторы /включая IF/ имеют обе допустимые формы ис- пользования, как с аргументами, так и без них. ┌──────────────────────────────────────────────────────────────┐ 1 │ | Write " Start --> " Set A=1 Set B=2 Set C=A | │ 2 │ | If A'=B Set C=B | │ 3 │o| If Write "A не равно В, С приравнено В" |o│ 4 │ | Quit | │ │ |----------------------------------------------------------| │ │o|Start --> A не равно В, С приравнено В |o│ │ | | │ └──────────────────────────────────────────────────────────────┘ Рис. 6.2 Использование If c аргументами и без аргументов В этом примере, второй If не имеет аргументов и потому отделяется от следующего оператора в строке двумя пробелами. Подобно оператору ELSE, безаргументный If проверяет значение системной переменной $TEST /которое устанавливается последней операцией проверки, здесь - опера- тором If в строке 2/. Если в $TEST занесена 1 /ИСТИНА/, то будет ис- полняться остаток строки правее безаргументного If, в противном случае он будет игнорироваться. Безаргументный If эквивалентен выражению: IF $TEST ;исполняемый затем остаток строки Стр. 158 6.1.2 Неявно исполняемые операции В предыдущем разделе мы упоминали три оператора с аргументами (IF, SET и WRITE). В некоторых случаях эти операторы были использованы по несколько раз подряд только с разными аргументами /например, Set A=1 Set B=2 Set C=A/. MUMPS позволяет достичь того же эффекта более коротким путем. В одном операторе могут назначаться сразу несколько аргументов, которые должны быть разделены запятыми. Например: ┌──────────────────────────────────────────────────────────────┐ │ | Write " Start --> " Set A=1,B=2,C=A | │ │ | If A>0,A 0 и < B" Set C=B | │ │ | Quit | │ │ |----------------------------------------------------------| │ │o|Start --> A > 0 и < B |o│ │ | | │ └──────────────────────────────────────────────────────────────┘ Рис. 6.3 Неявно исполняемые операции В примере 6.3 оператор SET в первой строке имеет три разных аргу- мента, отделенных друг от друга запятыми /А=1,В=2 и С=А/, а оператор IF в следующей строке два аргумента. В последнем случае эти аргументы могли быть записаны тремя разными способами: If A>0,A0 If A0&(A " S A=1,B=2,C=A | │ │ | I A>0,A 0 и < B" S C=B | │ │ | Q | │ │ | | │ └──────────────────────────────────────────────────────────────┘ Рис. 6.3 Сокращения имен операторов Достигаемое подобным образом уменьшение длины строк в программах, влечет за собой также уменьшение разборчивости /"читаемости"/ прог- рамм. Большинство из современных версий MUMPS производит компиляцию исходных текстов программ, для уменьшения затрат времени на его скани- рование при исполнении. При этом, независимо от того, записаны ли име- на операторов полностью, или даны аббревиатурой, они преобразуются в некое внутреннее представление. Так почему же большинство MUMPS - программистов отдают предпочтение аббревиатурам, а не полным именам операторов? Отчасти видимо по-привычке, а также, в связи с тем, что MUMPS является строчно-ориентированным языком. Действие некоторых MUMPS операторов, таких как IF, ELSE и FOR, распространяется только на расположенный за ними "остаток" строки. Длина строки программы в MUMPS ограничена требованиями стандарта Сов- местимости длиной в 255 символов /хотя в некоторых реализациях она мо- жет быть много больше/. Но все же не стоит пытаться всегда располагать подпрограмму в одной строке. Подобный подход, конечно, повышает ско- рость создания программ, но понижает возможность их дальнейшего сопро- вождения, так как они могут стать практически "нечитаемыми". Операторы могут записываться как полным именем, так и разрешенной аббревиатурой /обычно первой буквой имени/. Прочие формы частичного сокращения имени оператора не допускаются. Не имеет значения какими буквами Вы записываете имя оператора, при этом можно использовать лю- бые сочетания больших и малых /латинских/ букв. В этом случае подобное игнорирование контрастирует с чувствительностью к использованию боль- ших и малых букв в именах переменных, программ и меток. SET A=1 правильно Set A=1 правильно set A=1 правильно S A=1 правильно s A=1 правильно Se A=1 неправильно ┌─────────────────────────────────────────────────────────────────┐ │ Оператор может быть записан либо полным именем, либо допусти- │ │ мой аббревиатурой /обычно первой буквой -см. документацию/. │ │ Не допускается использование неполных имен операторов. │ │ Размер букв при записи имени оператора не имеет никакого │ │ значения. │ └─────────────────────────────────────────────────────────────────┘ Возможность использования больших и малых букв относится ко мно- гим элементам языка, в том числе к кодам шаблонов и именам внутренних функций. Стр. 160 6.1.4 Накладывание условий на исполнение операторов Как Вы могли убедиться ранее операторы IF и ELSE могут определять будут ли исполняться, либо игнорироваться следующие за ними в строке операторы. Кроме того, в MUMPS Вы можете использовать и другой метод управления исполнением отдельных операторов. Это накладывание на опе- ратор, или на его аргументы так называемых "постусловий". 6.1.4.1 Постусловия операторов Общий формат оператора с постусловием выглядит следующим образом: Command:expression [argument,argument,...] Выражение expression оценивается перед исполнением оператора, ес- ли результат его оценки - ИСТИНА, оператор будет исполняться, в про- тивном случае весь оператор будет игнорироваться. Если оператор содер- жит более одного аргумента /например, Set:expression A=1,B=2,C=3 /, то будут проигнорированы ВСЕ аргументы оператора, если результат оценки expression - ЛОЖЬ. Посмотрите, как используется этот механизм в следу- ющем примере, где производится просмотр массива из 10 значений и выде- ление из них наибольшего и наименьшего значения. ┌──────────────────────────────────────────────────────────────────┐ │ | For j=1:1:10 Set Ary(j)=j | │ │ | Set Hi=0,Lo=999999 | │ │o| For j=1:1:10 s:Ary(j)Hi Hi=Ary(j) |o│ │ | Write !," Наибольшее = ",Hi,!," Меньшее = ",Lo | │ │ |--------------------------------------------------------------| │ │o|Наибольшее = 10 |o│ │ |Меньшее = 1 | │ │ |> | │ └──────────────────────────────────────────────────────────────────┘ Рис. 6.5 Операторы с постусловием Оператор For используется для организации цикла, в примере 6.5 действие этого оператора можно интерпретировать как "начиная с j=1, с шагом =1, до тех пор пока j не станет равно 10 исполнять остаток стро- ки". Следует различать использование двоеточия (:) для задания списка аргументов оператора For и для накладывания постусловия на исполнение операторов Set. Второй цикл с использованием For используется для пос- ледовательного просмотра элементов массива с одновременным переопреде- лением значений переменных Lo и Hi. Если в этом цикле попытаться вмес- то Set с постусловием использовать оператор If, то можно столкнуться с непредсказуемой проблемой. ┌──────────────────────────────────────────────────────────────────┐ │o|>For j=1:1:10 i Ary(j)Hi s Hi=Ary(j) |o│ │ |>Write !," Наибольшее = ",Hi,!," Меньшее = ",Lo | │ │o|Наибольшее = 0 |o│ │ |Меньшее =0 | │ │ |> | │ └──────────────────────────────────────────────────────────────────┘ Рис. 6.6 Ошибочное использование оператора If Стр. 161 В данном примере мы получим истинное минимальное значение элемен- та массива, так как первый /левый в строке/ из операторов IF будет ис- полняться на каждом шаге цикла, но практически никогда не сможет найти максимальное значение /только в том случае, если оно будет первым зна- чением в массиве/. Это произойдет в связи с тем, что если результат оценки выражения - ЛОЖЬ, то остаток строки игнорируется и начинается новый шаг цикла. Для конкретного примера первый оператор IF оценивает- ся как ИСТИНА только для первого элемента массива, так как он содержит наименьшее значение. Во всех последующих шагах значения элементов мас- сива будут сравниваться с Low, и так как они будут больше, чем значе- ние этой переменной, то остаток строки будет игнорироваться, поэтому второй оператор IF в строке НИКОГДА не будет исполняться, и значение переменной High, будет содержать ошибочную величину, равную 0. Для решения этой проблемы необходимо использовать оператор SET с наложенным на него постусловием. Двоеточие (:) следующее за оператором SET, отделяет выражение, являющееся постусловием от самого оператора. Когда MUMPS система сталкивается с подобной синтаксической конструкци- ей, выполнение строки программы приостанавливается, и перед исполнени- ем оператора производится оценка выражения в постусловии. Если резуль- тат оценки ИСТИНА /не равно нулю/, оператор исполняется, если же ЛОЖЬ /равно нулю/ то этот оператор игнорируется и исполнение передается на следующий оператор в строке. Так в примере 6.5 производится сравнение элементов массива с значениями переменных High и Low в каждом шаге цикла, но изменение значений этих переменных происходит только в слу- чае выполнения условия проверки. ┌─────────────────────────────────────────────────────────────────┐ │ Постусловием является любое корректное выражение, которое в │ │ результате оценивание дает ИСТИНУ или ЛОЖЬ. Это выражение от- │ │ деляется от оператора двоеточием (:) │ └─────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────┐ │ Постусловие может добавляться к любому оператору MUMPS, │ │ за исключением операторов : IF, ELSE и FOR. │ └────────────────────────────────────────────────────────────┘ Вернемся к предыдущему примеру, но построим его так, чтобы проис- ходил поиск только значения переменной High, и одновременно, прекра- щался поиск в массиве, если встречается элемент массива, равный 5. Строка программы, исполняющая такие действия может выглядеть следующим образом: ┌──────────────────────────────────────────────────────────────┐ │ |>For i=1:1:10 Quit:Ary(i)=5 Set:Ary(i)>Hi Hi=Ary(i) | │ │o| |o│ └──────────────────────────────────────────────────────────────┘ Рис. 6.7 Прерывание цикла FOR использованием Quit с постусловием В этом примере, исполнение цикла прерывается оператором QUIT с наложенным на него постусловием, как только встречается элемент масси- ва равный 5. Этот способ перерывания цикла встречается очень часто, и Вы встретите его, в различных модификациях в последующих примерах. Стр. 162 Обратите внимание, что используемый здесь оператор QUIT НЕ имеет аргументов, и его отделяют два пробела от следующего оператора в стро- ке. ┌────────────────────────────────────────────────────┐ │ Постусловие НЕ является аргументом оператора. │ └────────────────────────────────────────────────────┘ Еще одна дополнительная возможность, приведенная в примере 6.5 это использование оператора WRITE со специальными аргументами, управ- ляющими форматом вывода. Один из них. использованный в примере, - это восклицательный знак. (!) Когда восклицательный знак встречается среди аргументов операторов READ или WRITE, MUMPS система вставляет в выход- ной поток данных символ CR /перевод каретки/, и вывод начинается с но- вой строки. В примере 6.5 строка : ┌──────────────────────────────────────────────────────────────────┐ │ |>Write !," Наибольшее = ",Hi,!," Меньшее = ",Lo | │ │ | | │ └──────────────────────────────────────────────────────────────────┘ эквивалентна : ┌──────────────────────────────────────────────────────────────────┐ │ |>Write ! Write " Наибольшее = " Write Hi | │ │ |>Write ! Write " Меньшее = " Write Lo | │ │ | | │ └──────────────────────────────────────────────────────────────────┘ Рис. 6.8 Эквивалент списка аргументов оператора Write Как Вы видите использование списка аргументов оператора позволяет уменьшить размер программы, необходимый для выполнения требуемых дейс- твий. 6.1.4.2 Постусловия накладываемые на аргументы операторов. Существует еще один метод использования постусловий - это постус- ловия при аргументах операторов, но этот метод можно использовать только для аргументов трех операторов - GOTO, который будет рассматри- ваться в данном разделе, DO и XECUTE, где они могут быть использованы аналогичным образом. ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Только три оператора могут иметь постусловия при аргументах: │ │ DO, GOTO и XECUTE. │ │ │ └─────────────────────────────────────────────────────────────────┘ Оператор GOTO используется для передачи управления на другую строку программы адресуясь относительно некой метки LABEL. ОСновная форма использования оператора GOTO : GOTO LABEL или G LABEL Рассмотрим пример, в котором необходимо организовать передачу уп- равления в различные части программы, в зависимости от значения пере- менной. В этом примере, если значение переменной Var меньше 1, или больше 3, то продолжается нормальное исполнение программы. Стр. 163 Если же значение этой переменной принимает значение равное 1-3, то управление будет передаваться в три различные части программы, от- меченные метками ONE, TWO, THREE, в зависимости от конкретного значе- ния переменной Var. ┌──────────────────────────────────────────────────────────────────┐ │ | If Var<1!(Var>3) Goto CONT | │ │ | If Var=1 Goto ONE | │ │o| If Var=2 Goto TWO |o│ │ | If Var=3 Goto THREE | │ │ |CONT Write !,"Переменная = ",Var | │ │o| Quit |o│ │ | | │ │ |ONE Set Var=97 Goto CONT | │ │o|TWO Set Var=98 Goto CONT |o│ │ |THREE Set Var=99 Goto CONT | │ │ | | │ └──────────────────────────────────────────────────────────────────┘ Рис. 6.9 Явное ветвление программы с использованием GOTO Пример, приведенный на рис. 6.9 может быть переписан с использо- ванием операторов с наложенным постусловием на исполнение : ┌──────────────────────────────────────────────────────────────────┐ │o| Goto:Var<1!(Var>3) CONT |o│ │ | Goto:Var=1 ONE Goto:Var=2 TWO Goto:Var=3 THREE | │ │ |CONT Write !,"Переменная = ",Var | │ │o| Quit |o│ │ | | │ │ |ONE Set Var=97 Goto CONT | │ │o|TWO Set Var=98 Goto CONT |o│ │ |THREE Set Var=99 Goto CONT | │ └──────────────────────────────────────────────────────────────────┘ Рис. 6.10 Использование GOTO с постусловием В этом случае каждый из операторов Goto используется для выполне- ния условного ветвления. Но можно использовать одновременное наложение постусловий как на сам оператор, так и на каждый из его аргументов, добиваясь более эффективного /или эффектного/ вида программы: ┌──────────────────────────────────────────────────────────────────┐ │ | Goto:Var>0&(Var<4) ONE:Var=1,TWO:Var=2,THREE:Var=3 | │ │ |CONT Write !,"Переменная = ",Var | │ │o| Quit |o│ │ | | │ │ |ONE Set Var=97 Goto CONT | │ │o|TWO Set Var=98 Goto CONT |o│ │ |THREE Set Var=99 Goto CONT | │ └──────────────────────────────────────────────────────────────────┘ Рис. 6.11 GOTO с постусловием при операторе и аргументах В примере 6.11 на весь оператор GOTO наложено постусловие, задан- ное выражением Var>0&(Var<4), оно может быть интерпретировано следую- щим образом - если значение Var, целое число, больше нуля и меньше 4, то передать управление на одну из меток указанных в списке аргументов /в зависимости от того, какое из постусловий при аргументах окажется истинным/. Стр. 164 В противном случае (Var<1 или Var>3), управление передается на следующую строку и весь оператор GOTO, вместе с неявными GOTO, опреде- ляемыми его аргументами, игнорируется. Если же постусловие при опера- торе оценивается как ИСТИНА, то начинается последовательное, сле- ва-направо оценивание постусловий при аргументах оператора, и произво- дится передача управления на метку, определяемую первым встреченным постусловием, которое дало в результате оценивания - ИСТИНУ. /не равно 0/ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Постусловие при операторе оценивается раньше, чем постусловия │ │ при его аргументах. │ │ │ └─────────────────────────────────────────────────────────────────┘ Обратите внимание, что командная строка: Goto:Var>0&(Var<4) ONE:Var=1,TWO,THREE:Var=3 не эквивалентна по своему действию приведенной в примере. Если Var=1, то передача управления будет производится на метку ONE. Во всех ос- тальных случаях будет производиться передача управления на метку TWO, без оценивания постусловия при третьем аргументе, так второй аргумент производит безусловную передачу управления /при нем нет постусловия/. 6.1.5 Время ожидания исполнения оператора Для части операторов допускается использование еще одной специ- альной формы синтаксиса - указания времени ожидания завершения испол- нения оператора. В число таких операторов входят те, которые могут "подвесить" процесс на неопределенное время, а также те, для которых актуально определения успешности /или неуспешности исполнения/. Так, например, процесс будет приостановлен на неопределенное время, при ожидании завершения ввода с клавиатуры ответа пользователя, или при попытке зарезервировать уже используемое другим процессом устройство, а при попытке запустить новый процесс необходимо определить - был ли он запущен или нет ? /Например из нехватки свободной памяти или по другой причине/. Рассмотрим эту форму синтаксиса на примере оператора READ. Основная форма использования этого оператора выглядит следующим образом : Read Variable Эта команда относится к чтению с устройства, объявленного теку- щим. /Предположим, что речь пойдет о видеотерминале/. Обычно процесс чтения символов, вводимых с клавиатуры, завершается по нажатию клавиши , и результат чтения возвращается в переменной Variable. /Кроме кода соответствующего клавише / Во время чтения, MUMPS - систе- ма приостанавливает исполнение программы до нажатия клавиши . Если эта клавиша не нажата, то программа и терминал будут "висеть" до тех пор, пока кто-нибудь не нажмет все-таки . Для того, чтобы предотвратить такую ситуацию и допускается указание предельного време- ни ожидания завершения исполнения оператора, при этом устанавливается специальный флаг указывающий на то, что операция завершена успешно, или нет. Стр. 165 ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Время ожидания окончания исполнения может быть указано для 4-х │ │ операторов : JOB, LOCK, OPEN и READ │ │ │ └─────────────────────────────────────────────────────────────────┘ Основной формат задания времени ожидания в этих операторах подо- бен указанию постусловия, хотя результат будет совершенно иным. Поле, задающее время ожидания, может быть числовым выражением /секунды ожи- дания /*1// и добавляется через двоеточие (:) непосредственно к концу аргумента оператора, например /*2/ : Read A:20 В этом примере, если чтение завершено раньше, чем истекли 20 се- кунд, действие оператора не отличается от случая, когда время ожидания не указано. В противном случае, /клавиша не нажата в течении 20 секунд после начала операции чтения/, производятся следующие дейс- твия : - Все символы, введенные до истечения указанного времени ожидания /20 секунд/, помещаются в переменную А. - В специальную системную переменную - $TEST заносится 0 /ЛОЖЬ/. Если же READ успешно завершается до истечения времени ожидания в $TEST заносится 1 /ИСТИНА/ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Время ожидания окончания исполнения оператора оказывает влияние│ │ на значение системной переменной $TEST │ │ │ └─────────────────────────────────────────────────────────────────┘ Разберем в качестве примера пример программы, в котором произво- дится чтение идентификатора пациента (ID) : ┌──────────────────────────────────────────────────────────────┐ │ |GETID Write !,"Введите идентификатор : " | │ │o| Read PATID:600 Goto:$Test GETNAM |o│ │ | Write " Ответьте на запрос ... " Goto GETID | │ │ |GETNAM Write !,"Введите ФИО пациента : " | │ │o| . |o│ └──────────────────────────────────────────────────────────────┘ Рис. 6.12 READ с временем ожидания ------------------------------------------------------------------ *1 В некоторых реализациях допустимо задание с точностью до 0.1 сек *2 В зависимости от MUMPS - реализации время ожидания указывается не после первого двоеточия, а после второго, и т.д., так например, для OPEN и JOB в DataTree MUMPS : Open 10:(file="example.txt":mode="w"):2 Открыть для записи файл , время ожидания - 2 сек. Job ^report:nspace="med":lvmem=30k:1 Запустить в области "med" программу ^report, с выделением 30k памяти под локальные переменные 30 kБайт, время ожидания - 1 сек. Стр. 166 В этом фрагменте программа выводит запрос, и после этого произво- дит чтение вводимого с клавиатуры идентификационного номера. Если пользователь завершает его ввод в течении 10 минут /600 секунд/, тогда $TEST возвращает 1 и управление передается на метку GETNAM. В против- ном случае программа выводит сообщение и повторяет запрос. Этот пример легко изменить для того, чтобы повторение запроса было только ограни- ченное число раз, а затем программа шла на завершение, и т.д. и т.п. Так как значение $TEST проверяется также оператором Else и безар- гументным If, то пример 6.12 можно переписать следующим образом: ┌──────────────────────────────────────────────────────────────┐ │ |GETID Read !,"Введите идентификатор : ",PATID:600 | │ │ | Else Write " Ответьте на запрос ... " G GETID | │ │o| . |o│ └──────────────────────────────────────────────────────────────┘ Рис. 6.13 READ с временем ожидания /второй вариант/ Заметьте, что в последнем примере, вместо полного имени оператора использовано сокращенное (G GETID вместо Goto GETID). Вообще-то, в тексте мы стараемся использовать полные имена операторов, и только в том случае, если в строке не хватает места будем использовать аббреви- атуры. Время ожидания может быть любым значением, разрешенным системными часами, отрицательные значения будут интерпретироваться как 0. Использование времени ожидания равного нулю (0), имеет некоторые дополнительные особенности, особенно в операторе READ. Нулевое время ожидание может быть интерпретировано в виде инструкции следующего вида : "попытаться исполнить операцию, но немедленно вернуть $TEST=0 /ЛОЖЬ/, если операция не может быть выполнена". Эта инструкция приме- нима к операторам JOB, LOCK и OPEN, но для оператора READ имеет смысл ее использовать только в случае использования буфера клавиатуры /*1/. Когда буфер клавиатуры не используется, т.е. режим "опережающего вво- да" отключен, большинство MUMPS реализаций будут игнорировать все сим- волы введенные вне действия оператора READ. В этом случае READ с вре- менем ожидания = 0, не будет функционировать. Если же "опережающий ввод" разрешен /обычно этот режим управляется параметром при операто- рах USE и OPEN - см. примечание 1/, то MUMPS система не очищает буфер клавиатуры, и поэтому все символы, введенные после окончания действия последнего из операторов READ, будут "прочитаны" текущим READ. Особен- но часто READ с временем ожидания = 0, используется в циклах для чте- ния последнего символа введенного с клавиатуры - Read *a:0. Более подробно об операторе READ с временем ожидания завершения чтения, а также об операторах OPEN, JOB и LOCK с временем ожидания и без него, - в главе 3, в разделах посвященным соответствующим операто- рам. ----------------------------------------------------------------------- *1 Для DataTree MUMPS режим опережающего ввода управляется параметр TA в операторах USE и OPEN. Например : USE $i:TA=0 Отменить режим "опережающего ввода" Стр. 167 6.1.6 Комментарии в программах Комментарии обычно вставляются в программы с целью их документи- рования. В MUMPS программах комментарием считаются все символы в ко- мандной строке, встреченные после точки с запятой - (;). Они никоим образом не интерпретируется и игнорируются при исполнении и компилиро- вании /в компилирующих реализациях MUMPS систем/. Точка запятой должна устанавливаться в командной строке по тем же правилам, что и оператор, в противном случае возникнет синтаксическая ошибка. Например, данная строка является правильной : If Answer="YES" Do Save ;Сохранение результатов после подтверждения а эта содержит синтаксическую ошибку : If Answer="YES" Do Save;Сохранение результатов после подтверждения Эта строка генерирует ошибку SYNTAX в связи с тем, что после ар- гумента оператора Do необходим один пробел. ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Комментарии могут быть вставлены в любую строку после символа │ │ (;). Остаток строки после этого символа игнорируется MUMPS- │ │ системой и не-интерпретируется как командная строка. │ │ │ └─────────────────────────────────────────────────────────────────┘ 6.2 Строки Как уже говорилось во вступлении к этой главе MUMPS является строчно ориентированным языком. Отдельные операторы и функции языка могут быть сгруппированы вместе в одной физической строке программы. Есть много доводов в пользу использования подобной возможности, первы- ми среди них можно упомянуть логическую группировку связанных команд, и уменьшение физических строк программы. ┌──────────────────────────────────────────────────────────────┐ │o|Sqrt ;Вычисление квадратного корня от IN, результат в OUT |o│ │ | Set OUT=0 | │ │ | Quit:IN'>0 | │ │o| Set OUT=IN+1/2 |o│ │ |Loop Set x=OUT | │ │ | Set OUT=IN/x+x/2 | │ │o| Goto Loop:OUTSet IN=16 |o│ │ |>Do Sqrt | │ │ |>Write OUT | │ │o|4 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Рис. 6.14 Операторы MUMPS, по одному в строке Если Вы используете больше одного оператора в строке, то каждый из операторов, включая свои аргументы, должен отделяться от следующего оператора в строке по меньшей мере одним пробелом. Стр. 168 Если аргументы оператора опущены, то он должен отделяться от следующе- го оператора в строке по меньшей мере двумя пробелами (один пробел для отделения оператора от несуществующего аргумента, второй для отделения от следующего оператора в строке). Имея ввиду эти положения Вы может включать дополнительные пробелы между операторами (разделители опера- торов), для повышения "читаемости" программ. Каждой строке MUMPS программы может предшествовать метка. Метки используются для адресации обращения к строкам при передачи управления при использованием операторов GOTO и DO. Метки необходимы только для тех строк, на которые производятся ссылки, поэтому большинство строк в программах не имеют меток. /Хотя если Вам угодно, то можно пометить каждую строку программы на манер Basic'а/ Метка может начинаться с : - любой латинской буквы /большой или маленькой/ - цифры /от 0 до 9/ - символа процента (%) Если первым символом метки является цифра, то и все остальные символы в ней ДОЛЖНЫ быть цифрами. Большие и маленькие буквы в метках различаются, поэтому метки Start и START - это различные метки. Метки могут быть любой допустимой длины, но MUMPS система будет их идентифи- цировать только по первым 8 символам. /*1/. Поэтому для системы метки LETSGONOW и LETSGONO - идентичны. В метке НЕ может быть пробелов. Метка отделяется от самой строки программы неким начальным симво- лом строки. Стандарт не оговаривает этот символ, и в разных реализаци- ях могут встречаться разные начальные символы строк. Наиболее часто встречающиеся начальные символы строк программы это пробел - SPACE /ASCII =32/ и табуляция - TAB /ASCII=9/. В части MUMPS реализаций начальный символ строки используется для указания отличия строк программы, от непосредственно исполняемых ко- манд. В командах, вводимых в непосредственном режиме символ если он является первым символом строки, указывает на то, что все команды, вводимые вслед за ним надо сохранить в выделенном рабочем пространстве пользователя - разделе. Если же этого символа нет, то вводимая команда немедленно интерпретируется и исполняется. При работе с редакторами текста программ это различие обычно игнорируется, и начальными симво- лами строки могут быть как пробелы, так и символ табуляции - . Метка в строке программы должна отделяться от самой строки по меньшей мере одним начальным символом строки. Дополнительные начальные символы строки могут быть использованы для визуального отображения УРОВНЯ строки /подробнее см. раздел 6.6 посвященный блочным структу- рам/. Кроме начальных символов строки, в нее может быть добавлено про- извольное количество пробелов. Если строка программы не имеет связан- ной с ней метки, то она должна начинаться с по меньшей мере одного на- чального символа строки. При передаче параметров в процедуру /программу/, или во внутрен- нюю функцию, то список параметров, заключенный в круглые скобки (так называемый список формальных параметров - см. главу 9), является частью метки. В этом случае метка состоит из имени метки, непосредс- твенно за ним - открывающаяся круглая скобка, список формальных пара- метров, затем закрывающая круглая скобка. После чего, может быть про- извольное количество начальных символов строки, затем остаток строки. --------------------------------------------------------------------- *1 Некоторые из MUMPS реализаций различают в метках большее число сим- волов, так в DataTree MUMPS version 4.2+ - 15 символов. Стр. 169 Вернемся к различию между строками, вводимыми в непосредственном режиме для немедленного исполнения и строками программы. Для строк, вводимых для немедленного исполнения нет необходимости указывать метки и начальные символы строки, для строки программы они требуются. ┌──────────────────────────────────────────────────────────────┐ │ |Sqrt ;Вычисление квадратного корня от IN, результат в OUT | │ │ | Set OUT=0 Quit:IN'>0 | │ │o| Set OUT=IN+1/2 |o│ │ |Loop Set x=OUT,OUT=IN/x+x/2 Goto Loop:OUTSet IN=16 Do Sqrt Write OUT | │ │ |4 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 6.15 Несколько операторов в строке MUMPS программы. В этом примере, если сравнивать его с примером 6.14, использова- ние нескольких операторов в одной строке не больше чем дело вкуса программиста. Но некоторые из операторов, IF, ELSE и FOR, например, имеют влияние ТОЛЬКО на следующие за ними операторы строки программы. ┌──────────────────────────────────────────────────────────────┐ │ |Sqrt ;Вычисление квадратного корня от IN, результат в OUT | │ │ | Set OUT=0 If IN'>0 Quit | │ │o| Set OUT=IN+1/2 |o│ │ | For Set x=OUT,OUT=IN/x+x/2 Quit:OUT'Set IN=16 Do Sqrt Write OUT | │ │ |4 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 6.16 Использование строчно-ориентированных операторов В примере 6.16 операторы If (строка Sqrt+1) и For (Sqrt+3) воз- действуют только на операторы, расположенные в тех-же строках. В ка- честве примера можно привести использование в строке только одного строчно-ориентированного оператора : ┌──────────────────────────────────────────────────────────────┐ │ |Pause ;Управление задержкой | │ │ | For 1:1:DLY | │ │o| Quit |o│ └──────────────────────────────────────────────────────────────┘ Рис. 6.17 Строчно-ориентированный оператор без других операторов в строке. Не следует применять подобный прием на практике, так как: во-пер- вых есть специальный оператор для создания задержек исполнения прог- раммы - Hang, во-вторых, создаваемая подобным образом задержка слишком сильно зависит от многих параметров : - MUMPS реализации, скорости процессора, а также от загрузки MUMPS - системы в целом. Стр. 170 6.3 Процедуры В MUMPS процедурой называется группа строк программы, начинающую- ся с метки, выполняющих определенную функцию и завершающуюся операто- ром QUIT. Обычно в процедуре одна или больше строк, и вызывается про- цедура по DO /очень редко по GOTO/. Процедуры могут вызываться не только из других частей той-же программы, но и из других программ. Одна процедура может использоваться для исполнения различных функций в Вашем программном комплексе, но лучше, если она будет созда- на для выполнения одного, специфического действия. Ясно, что использо- вание в практике программирования процедур, исполняющих простые и хо- рошо определенные функции, повышает надежность, ясность программ, улучшает возможность их последующего сопровождения. "Растягивание" же по программам фрагментов, выполняющих одну и ту же функцию, может дать прямо противоположный эффект, и резко ухудшить возможности развития программного комплекса. ┌──────────────────────────────────────────────────────────────┐ │ |Sqrt ;Вычисление квадратного корня от IN, результат в OUT | │ │ | Set OUT=0 Quit:IN'>0 | │ │o| Set OUT=IN+1/2 |o│ │ |Loop Set x=OUT,OUT=IN/x+x/2 Goto Loop:OUTSet IN=16 Do Sqrt Write OUT | │ │ |4 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Рис. 6.18 MUMPS процедура В представленном примере строки, начиная с Sqrt и до Loop+1, представляют единичную процедуру, вызываемую оператором Do. 6.4 Программы Программа - это объединение командных строк, подразделяемых на отдельные процедуры. Как уже упоминалось в главе 2, СТАНДАРТ СОВМЕСТИ- МОСТИ ограничивает максимальный размер программы пределом в 5 000 байт, для того, чтобы быть уверенным, что программа может быть запуще- на в любой из MUMPS реализаций, поддерживающих этот стандарт. В связи с тем, что в этот размер невозможно уложить ни одну программу, имеющую практическое применение, все прикладные программные средства не что иное, как сложные программные комплексы, состоящие из десятков или со- тен программ. /*1/ По соглашению /и это особо подчеркивается/, первая строка прог- раммы содержит метку, совпадающую с именем программы. Имена программ организуются подобно именам меток. Первым символом может быть буква /большая или малая/, или символ процента (%), затем буквы или цифры. --------------------------------------------------------------------- *1 Как уже говорилось, в англоязычной программистской среде принято различать термины program и routine, хотя у нас они оба переводятся как "программа". Первый из них, в нашей практике, соответствует терми- ну - программный комплекс, второй - понимается как одна большая проце- дура, или группа процедур. Стр. 171 Имена программ поддерживают все соглашения по составлению имен меток строк, имя программы также может быть любой допустимой длины, но прог- раммы различаются только по первым 8 символам своего имени /см. приме- чание 1 на стр. 168/. В именах программ различаются большие и малые буквы, GetPat и GETPAT, например, являются именами разных программ. Программы, чьи имена начинаются с символа (%), обычно относятся к библиотечным прог- раммам MUMPS системы и могут вызываться из любой директории. /*1/ Также, по соглашению, первые две строки программы - это коммента- рий. Первая строка зарезервирована для идентификации программиста, а также даты и времени создания и редактирования программы. Во второй строке обычно кратко описывается назначение программы. Следует огово- риться, что это соглашение не является ограничением, накладываемым системой, а только чисто программистское соглашение, выработанное в целях упорядочения программных модулей. Приведем пример типичного на- чала программы : ┌──────────────────────────────────────────────────────────────┐ │ |PATDEM ;JML-NYSCVM; 10/8/90-14:55 | │ │ | ;Сбор паспортных данных по пациенту | │ │o|PATID Read !," Идентификатор пациента (ID): ",PID |o│ │ | . | │ └──────────────────────────────────────────────────────────────┘ Рис. 6.19 Соглашения по первым строкам MUMPS программ Как Вы видите первая строка программы начинается с метки, совпа- дающей с именем самой программы. Кроме того, в ней указан идентифика- тор программиста, создавшего программу (JML-NYSCVM - означает - John Meigs Lewkowicz, New York State College of Veterinary Medicine), а также дату и время последней корректировки программы. Большинство ре- дакторов текста программ вставляют дату и время редактирования и запи- си программ автоматически. Вторая строка программы содержит краткое пояснение функций ею выполняемых. 6.5 Программные комплексы Так как любое прикладное программное средство не что иное, как объединение многих программных модулей, то в целях их упорядочивания, многие программисты резервируют первую букву имени программы под иден- тификатор всего комплекса /Так, например, все программы, составляющие программный комплекс предназначенный для инвентаризации материальных ценностей начинаются на 'I', и т.д./ Кроме того, достаточно часто ре- зервируются для дальнейшего разделения программ на комплексы задач, вторая-третья буквы имен программ. /Так например, все программы пред- назначенные для режимов ввода/редактирования начинаются на IE (от анг- лийского Entry/Edit), все программы поиска на IS (Search), и так да- лее./ ------------------------------------------------------------------ *1 Это положение верно не для всех MUMPS реализаций. Например ISM /NTSM/ его не поддерживает. Стр. 172 6.6 Блочные структуры /вложенные блоки строк/ Блочные структуры /это название относится к структурам, связанным с использованием безаргументного оператора Do/, являются средством, обеспечивающим написание структурированных программ в MUMPS. В связи с тем, что MUMPS является строчно-ориентированным языком, а длина строки программы ограничена 255 символами, то часто требуется писать малень- кие подпрограммы, которые вызываются только из одного места программы, и представляющие собой не что иное, как средство продолжить строку программы за установленный предел. Рассмотрим их применение на примере процедуры, производящей проверку строки символов и ее интерпретацию как числа (в MUMPS системе это задание выполняется автоматически, см.раздел 4.2.2) ┌───────────────────────────────────────────────────────────────┐ │o|Ncnv ;Интерпретация строки "STR" как числа |o│ │ | New char,i,flag,num Set num="",flag=0 | │ │ | For i=1:1:$l(STR) Do Chkchar Quit:flag | │ │o| Set:num="" num=0 Set:num?1P.E num=$E(num,2,99) |o│ │ | Set STR=num Quit | │ │ |Chkchar ;проверка каждого символа, при завершении flag=1 | │ │o| Set char=$E(STR,i) If char?1N Set num=num_char Quit |o│ │ | If "+-"[char Do PMtest Quit | │ │ | If char="." Do Dtest Quit | │ │o| Set flag=1 |o│ │ | Quit | │ │ |PMtest ;+ и - проверка | │ │o| If num[char Set flag=1 Quit |o│ │ | Set num=char_num Quit | │ │ |Dtest ;. проверка | │ │o| If num["." Set flag=1 Quit |o│ │ | Set num=num_"." Quit | │ └───────────────────────────────────────────────────────────────┘ Рис. 6.20 Процедура, интерпретирующая строку как число Это процедура просматривает в цикле все символы, составляющие строку STR, по одному за каждый шаг цикла. Просмотр прерывается, если встречается символ, не удовлетворящий правилам числового преобразова- ния. Таким образом просмотр строки будет прерываться в следующих слу- чаях : ■ Встреченный символ не является ни цифрой /от 0 до 9/, ни специальным символом /+ , - или ./ ■ Если специальный символ /+ , - или . / встречается в строке повторно. Функция $L /$Length/ возвращает длину вводимой строки /см. раздел 8.1.3/, а функция $E /$Extract/ выделяет символы из указанной строки /см.раздел 8.1.1/ Процедуры Chkchar, PMtest, Dtest являются вспомогательными и соз- даны для расширения действия строковых операторов /For и If/. Вполне возможно было бы написать все проверки в одной строке с использованием постусловий, но результирующая строка программы получится слишком сложной и запутанной. Стр. 173 С использованием блочных структур пример может быть переписан следующим образом : ┌───────────────────────────────────────────────────────────────┐ │o|Ncnv ;Интерпретация строки "STR" как числа |o│ │ | New char,i,flag,num Set num="",flag=0 | │ │ | For i=1:1:$l(STR) Do Quit:flag | │ │o| . Set char=$E(STR,i) |o│ │ | . If char?1N Set num=num_char Quit | │ │ | . If "+-"[char Do Quit | │ │o| . . If num[char Set flag=1 Quit |o│ │ | . . Set num=char_num Quit | │ │ | . If char="." Do Quit | │ │o| . . If num["." Set flag=1 Quit |o│ │ | . . Set num=num_"." Quit | │ │ | . Set flag=1 | │ │o| Set:num="" num=0 Set:num?1P.E num=$E(num,2,99) |o│ │ | Set STR=num Quit | │ └───────────────────────────────────────────────────────────────┘ Рис. 6.21 Пример 6.21, переписанный с применением блочных структур. Безаргументный оператор Do инициирует исполнение ВЛОЖЕННОГО блока строк /отмеченных точкой в начале программной строки/. Для более глу- бокого понимания концепции блока строк следует вернуться к определению строки программы /см. пояснения к концепции строки программы в разделе 6.2/ Каждая строка программы может начинаться с необязательной метки и списка формальных параметров, за которыми следуют один или больше на- чальных символов строки программы. За этими символами может следовать 0 или больше пробелов /разделителей операторов/, используемых для по- вышения читаемости программы. Дальше в строке может быть 0 или больше операторов. Обратите внимание на дополнительные пробелы, включенные в предшествующем примере после безаргументного Do. /Между ним и последу- ющим оператором Quit стоит по три пробела, хотя требуется только два, можно было поставить и 10/. Сравнивая примеры 6.20 и 6.21 можно заметить, что используемые в 6.20 временные процедуры в 6.21 заменены блоками строк, которые иден- тифицированы различным количеством точек в своем начале. Здесь следует отметить разницу между используемыми терминами - уровень вложенности и уровень исполнения. Уровень вложенности блока строк отображается точ- ками, чем больше точек в начале командной строки, тем более высокий уровень она имеет /соответствует более глубокому уровню вложенности/. Возвращаясь к общей концепции строки программы можно сказать, что пос- ле первого стартового символа строки может быть 0 или больше точек, используемых для отображения уровня вложенности блока строк. Каждый встреченный в программе оператор Do повышает уровень исполнения на 1, каждый встреченный Quit /явный или неявный/, понижает его на 1. Резю- мируя, можно сказать, что уровень вложенности, не что иное, как подс- чет исполненных и незавершенных /по Quit/ безаргументных Do, а уровень исполнения относится ко всем незавершенным операторам Do. /стеку Do, как иногда говорят/ Безаргументный Do инициирует исполнение вложенного блока строк /строк с более высоким уровнем/, при этом в системной области сохраня- ется строка вызова и та точка с которой продолжится исполнение прог- раммы при завершении процедуры, уровень исполнения повышается на 1 и начинается исполнение блока строк. Стр. 174 При исполнении в этом блоке явного или неявного Quit уровень исполне- ния понижается на 1 и управление возвращается к оператору стоящему в строке за безаргументным Do. На безаргументный Do может накладываться постусловие. При этом, если результат оценки постусловия - ЛОЖЬ, уровень исполнения остается прежним, а весь вложенный блок строк игнорируется. Как уже говорилось перед началом исполнения вложенного блока строк уровень исполнения повышается на 1. При этом если следующая строка блока будет иметь более высокий уровень /быть с большим коли- чеством точек/, то она будет проигнорирована, если только она не вызы- вается из этой строки по безаргументному Do. Если же следующая строка - строка более низкого уровня /с меньшим количеством точек/, то при этом происходит исполнение неявного Quit, уровень исполнения и вложен- ности понижаются на 1, и исполнение текущего вложенного блока прекра- щается. Подводя вкратце итоги обсуждения уровней вложенности и исполнения хочется отметить еще один аспект, касающийся передачи управления по Goto. Оператор Goto НЕ влияет на уровень исполнения /в отличии от опе- ратора Do/, и поэтому передавать управление с его помощью можно только в пределах одного уровня исполнения. /В противном случае поведение программы становится непредсказуемым/. Касаясь использования Goto в блочных структурах можно напомнить, что любой строке, вне зависимости от уровня вложенности может быть назначена метка. Оператор Goto вполне может быть использован для организации передачи управления, если будут выполнены следующие условия : 1. Назначаемая строка должна быть строкой того же уровня вложения, что и строка содержащая оператор Goto. 2. Назначаемая строка должна находится в пределах того же блока, что и вызывающая строка. А также не должно быть строк более низкого уровня вложенности между строкой, адресуемой по оператору Goto, и строкой его содержащей. Но увы, использовать эти правила чрезвычайно трудно. Обратимся опять к примеру 6.21 и сравним его с 6.20. Точки в начале командных строк обозначают уровень строки. Безаргументные Do используются для инициации исполнения блока строк более высокого уровня (которым пред- шествует большее количество точек). Эти блоки более высокого уровня завершаются либо при исполнении оператора Quit, либо когда следующая строка имеет меньший уровень / в ее начале меньше точек/. Вложенный блок строк автоматически пропускается при завершении безаргументного Do. Так что Goto в таких структурах способен только крепко запутать ситуацию. Стр. 175 6.7 Основные положения ■ В операторах имеющих аргументы они должны быть отделены от самого оператора только ОДНИМ пробелом (6.1.1) ■ При использовании операторов с аргументами следующий оператор в строке, должен отделяться от аргументов предыдущего оператора ОДНИМ или БОЛЕЕ пробелами. (6.1.1) ■ При использовании безаргументных операторов, следующий оператор в строке должен отделяться от них ДВУМЯ и БОЛЬШЕ пробелами (6.1.1) ■ Аргументы оператора в списке отделяются друг от друга запятыми. (6.1.2) ■ Операторы в программы могут быть указаны при написании программ или полным своим именем, или сокращены до первой буквы своего имени (6.1.3) /*1/ ■ Имя оператора может быть написано большими или малыми буквами, - это не имеет значения для его распознавания MUMPS системой. (6.1.3) ■ Большинство операторов могут использоваться с постусловием, выра- жением, которое добавляется через двоеточие (:) непосредственно к концу имени оператора, и чье оценивание определяет исполнение опера- тора. (6.1.4.1) ■ Постусловия НЕ могут быть наложены на операторы IF, ELSE и FOR (6.1.4.1) ■ Постусловия не являются аргументами операторов (6.1.4.1) ■ Для трех операторов можно также указывать и отдельные постусловия накладываемые на аргументы оператора : DO, GOTO и XECUTE (6.1.4.2) ■ Постусловие при операторе оценивается раньше, чем постусловия при его аргументах (6.1.4.3) ■ Для 4-х операторов возможно указания предельного времени ожидания окончания их исполнения: JOB, LOCK, OPEN и READ (6.1.5) ■ Оператор с указанием времени ожидания исполнения оказывает влияние на значение системной переменной $TEST (6.1.5) ■ Комментарием является остаток строки после символа (;), он игнори- руется MUMPS системой (6.1.6) --------------------------------------------------------------------- *1 Как уже говорилось, это положение может не соблюдаться в конкретной MUMPS реализации. В части из них, особенно при реализации предложений Комитета по развитию, некоторые из имен операторов /функций и систем- ных переменных/, можно сокращать только до 2-х букв, некоторые до спе- циально оговоренной аббревиатуры, часть имен не допускает сокращения. Стр. 176 Глава 7 Косвенность Большинство компьютерных систем используют в своей работе инфор- мацию двух видов: - первый состоит из инструкций, используемых для управления исполнением прикладных программ - второй - обрабатываемые данные MUMPS, кроме этого, поддерживает такие языковые конструкции, в которых информация, записываемая как данные, может исполняться как фрагмент программы. Исполнение инструкций, содержащихся в полях дан- ных, называется "косвенностью" и обычно не поддерживается в других языках программирования высокого уровня. Эта глава посвящена описанию механизмов использования косвеннос- ти, которая является важным механизмом MUMPS среды, но требует хороше- го знания особенностей языка MUMPS и потому часто приводит начинающих программистов к ошибкам. Поэтому мы рекомендуем эту главу начинающим пропускать, либо бегло просматривать, чтобы затем вернуться к ней для более детального изучения, когда они наберутся опыта в практике прог- раммирования в MUMPS. Косвенность - это механизм, который позволяет заменить всю ко- мандную строку, или ее часть, весь оператор или только его аргументы, заменить на информацию, записанную в полях данных перед тем как начать исполнение. Рассмотрите следующий пример : ┌──────────────────────────────────────────────────────────────┐ │ |>Kill Set Var="XYZ",@Var=1 Write "Var=",Var," XYZ=",XYZ | │ │ |Var=XYZ XYZ=1 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.1 Вступление к косвенности В этом примере, символ @, называемый оператором косвенности, ука- зывает MUMPS системе использовать содержимое переменной Var в операто- ре Set. При этом значение 1 будет присваиваться не переменной Var, а переменной XYZ. MUMPS заменяет символы "@Var" на "XYZ" /содержащиеся в переменной Var/ прежде чем начать исполнение аргумента. В MUMPS есть два пути использования косвенности : (1) - оператор XECUTE, в котором поле данных, используемое как аргумент оператора, исполняется как набор MUMPS инструкций, и (2) - использование операто- ра косвенности @, который позволяет заменить полями данных аргументы операторов. 7.1 Оператор XECUTE Советуем просмотреть общее описание оператора XECUTE, описанное в разделе 3.5 перед началом изучения этого раздела. Оператор XECUTE ин- терпретирует строку данных как командную строку MUMPS, и исполняет ее. При этом исполняемые операторы записываются как элементы данных, а не как строки программы /без начальных символов строк программ/. Стр. 177 ┌──────────────────────────────────────────────────────────────┐ │ |>Set data="For i=1:1:5 Write !,i*i" | │ │ |>Write data | │ │o|For i=1:1:5 Write !,i*i |o│ │ |>Xecute data | │ │ |1 | │ │o|4 |o│ │ |9 | │ │ |16 | │ │o|25 |o│ │ |> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.2 Оператор XECUTE Обычно командные строки содержащиеся в строках данных управляются переменными создаваемыми в ходе исполняемого процесса. В качестве при- мера, для того чтобы пояснить действие оператора XECUTE, создадим фрагмент программы, который позволяет ввести с клавиатуры фрагмент MUMPS кода, и затем, исполнить его. /Аналогично вводу команд в непос- редственном режиме/. Такие управляющие программы высокого уровня часто называются "shell программами", или попросту "shell". ┌──────────────────────────────────────────────────────────────┐ │ |Shell Read !,": ",xeq Quit:xeq="" Xecute xeq Goto Shell | │ │ |----------------------------------------------------------| │ │o|>Do Shell<- |o│ │ |: Set a=22,b=3 Write "a*b=",a*b<- | │ │o|:If a>b Write "a больше чем b"<- |o│ │ |a больше чем b | │ │ |:<- | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.3 Оператор XECUTE и исполнение строк, вводимых с клавиатуры Любая правильная MUMPS команда, включая дополнительные Do и XECU- TE, может быть исполнена как часть аргумента XECUTE. Продолжая идею, предложенную в примере 7.3 можно написать MUMPS программу, которая бу- дет представлять собою Shell непосредственного режима исполнения MUMPS команд, в которой организовать подсказки, память на введенные команды, и т.д. Отметим, что подобный Shell организован в некоторых из MUMPS реализаций. /*1/ Исполнение оператора XECUTE в какой-то мере подобно исполнению оператора Do. Оператор Do передает управление на другую строку прог- раммы, а оператор XECUTE передает управление на строку данных. Оба оператора возвращают управление на следующий за ними оператор в стро- ке. Некоторые отличия связаны с нюансами исполнения оператора XECUTE. Так как строка, являющаяся его аргументом, либо создается в ходе ис- полнения процесса, либо использует переменные, формируемые им, то она должна интерпретироваться каждый раз при исполнении. Это означает, что в "компилирующих" реализациях MUMPS систем строка аргумента XECUTE бу- дет исполняться медленнее, чем аналогичная строка в программе. -------------------------------------------------------------------- *1 Например в DataTree MUMPS. Когда Вы находитесь в непосредственном режиме исполнения, то на самом деле это исполняется программа ^%mshell. Стр. 178 Кроме того, что возможно является более важным, программы, интенсивно использующие XECUTE, трудоемки в сопровождении и поддержке. В них час- то невозможно при просмотре листинга программ определить путь передачи управления, так как создаваемый в процессе исполнения код трудно "соб- рать" по статическому листингу. 7.2 Оператор косвенности - @ Оператор косвенности - @ указывает MUMPS системе на необходимость модификации аргумента (или его части) перед исполнением оператора. Возможно использование 4-х общих форм применения косвенности : 1. Косвенность при задании имен 2. Косвенность в аргументах 3. Косвенность в шаблонах 4. Косвенное задание ссылки на элемент массива Во всех формах использования косвенности перед исполнением производит- ся модификация оператора /его аргумента/ содержимым переменных или строковых литералов. Модификация операторов за счет полей данных представляет собой наиболее общий принцип использования косвенности, при описании каждой из 4-х форм будут даны прямые пояснения в каких случаях она применима, а где - нет. Все 4 формы идентифицируются при- сутствием оператора косвенности - @. 7.2.1 Косвенность в задании имени Косвенность в задании имени допустима при использовании имен /пе- ременных, программ или меток/ в аргументах операторов. При этом, зна- чение, содержащееся в переменной, чье имя указано вслед за символом - @, замещает подстроку, состоящую из оператора косвенности и имени пе- ременной. Рассмотрим пример косвенного задания имени: ┌──────────────────────────────────────────────────────────────┐ │ |Label Set A="B",C="ONE",B=1 | │ │ | Set @A=C If B'=1 Goto @C | │ │o| Quit |o│ │ |ONE Write !,"B = ",B | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Do Label<- | │ │ |B = ONE | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.4 Косвенное задание имени В этом примере косвенность управляет исполнением второй строки программы. Во время исполнения, значение, записанное в переменной А, замещает подстроку '@A', в операторе SET, которая приобретает следую- щий смысл : Set B=C Стр. 179 В операторе Goto подстрока '@C' замещается содержимым переменной С, в связи с чем оператор Goto становится эквивалентен : Goto ONE ┌─────────────────────────────────────────────────────────────────┐ │ Косвенность может быть использована для любого имени использу- │ │ емого в MUMPS /переменной, метки или программы/. │ └─────────────────────────────────────────────────────────────────┘ Кроме представленной в примере может быть использована и более расширенная форма косвенного задания имени в операторе Goto. Как Вы помните, общий формат использования этого оператора выглядит следующим образом: Goto LABEL^ROUTINE В приведенном примере использован простейший случай применения Goto, который используется при передаче управления в пределах одной программы. В более общих случаях возможно также косвенное задание име- ни программы /помимо косвенного задания имени метки/ при передаче уп- равления по Goto, как, например, в данном случае : ┌──────────────────────────────────────────────────────────────┐ │ | Set Label="TEST",Routine="GETANS" | │ │ | Goto @Label^@Routine | │ │o| |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.5 Косвенность в именах программ и меток Оба имени можно задать и в одной переменной (например Add- ress="TEST^GETANS") и использовать Goto @Address. Подобную форму ис- пользования косвенности называют косвенным заданием аргумента, а не косвенным заданием имени, так как в этом случае, Address содержит два разных имени /программы и метки/, разделенных символом '^'. К косвен- ному заданию аргументов операторов мы вернемся позднее. Про следующую форму косвенного задания имени, можно отметить, что она является наиболее часто употребляющейся, и что в большинстве слу- чаев альтернативные ей варианты слишком громоздки и сложны. Предположим, что мы прочитали в переменную Data, информацию об пациенте, которая была записана в узле глобального массива, и она выг- лядит следующим образом : Data="Doe,Jane;55 State St.;Any Town;NY;14850;(607)253-3333" Эту запись необходимо использовать для создания группы локальных переменных (Name,Street,City,State,ZIP,Phone), которые необходимы для последующей обработки обращения пациента. Эти переменные мы можем соз- дать следующим образом : ┌──────────────────────────────────────────────────────────────┐ │ | Set Name=$P(Data,";",1),Street=$P(Data,";",2) | │ │ | Set City=$P(Data,";",3),State=$P(Data,";",4) | │ │ | Set ZIP=$P(Data,";",5),Phone=$P(Data,";",2) | │ │o| |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.6 Прямое назначение значений переменным Стр. 180 В этом примере $P это сокращенное имя внутренней функции MUMPS $PIECE, которая используется для выделения подстроки из строки симво- лов. Конструкцию вида Set Street=$P(Data,";".2) можно интерпретировать как следующее предложение : "Выделить из строки, содержащейся в пере- менной Data, подстроку, которая расположена между второй и третьей точкой с запятой (;) в этой строке, и поместить ее в переменную Stre- et". Другой метод, используемый для достижения тех же результатов, что и в примере 7.6, показан ниже : ┌──────────────────────────────────────────────────────────────┐ │ | Set X="Name:Street:City:State:ZIP:Phone" | │ │ | For I=1:1:6 S @$P(X,":",I)=$P(Date,";",I) | │ │o| |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.7 Назначение значений переменным с использованием косвенного задания имени переменных Функция $PIECE, действие которой пояснено выше работает в этом примере аналогичным образом, назначая переменным Name, Street, City, State, ZIP и Phone соответствующие им поля данных из строки, содержа- щейся в переменной Data. 7.2.2 Косвенность в аргументах В двух предыдущих примерах мы рассмотрели случай, когда косвен- ность используется для замены части аргумента оператора. В этом разде- ле мы рассмотрим ее применение для замены всего аргумента. Косвенное задание аргумента есть не что иное, как полная замена аргумента значе- нием некой переменной. Мы уже встречали в наших примерах один такой случай (Goto @Address где Address="TEST^GETANS"), ниже приведем еще один : ┌──────────────────────────────────────────────────────────────┐ │ | Set A="ZIP=14850",B="Age=22" | │ │ | Set @A,@B Write !,"ZIP Code = ",ZIP,!,"Age = ",Age | │ │o| Quit |o│ │ |----------------------------------------------------------| │ │ |ZIP Code = 14850 | │ │o|Age = 22 |o│ │ | |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.8 Косвенное задание аргумента Начинающим программистам часто бывает трудно уловить разницу меж- ду косвенным заданием имени и косвенным заданием аргумента. В обоих случаях результат действия косвенности чрезвычайно похож : вместо име- ни переменной, указанного после оператора косвенности - @, использует- ся значение, которое записано в ней. Эта замена производится перед ис- полнением оператора. Рассмотрим в следующем примере наиболее часто встречающуюся форму ошибки в использовании косвенности : Стр. 181 ┌──────────────────────────────────────────────────────────────┐ │ |ONE Set @"Var1"=1+2" | │ │ |TWO Set @"Var1=1+2" | │ │o|THREE Set Var1=@"1+2" |o│ │ | |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.9 Ошибочное задание аргумента и имени В первых двух примерах (ONE и TWO) косвенность использована и ра- ботает правильно. Строка помеченная меткой ONE - представляет собой случай косвенного задания имени (Var1 - имя переменной), строка с мет- кой TWO - косвенное задание аргумента оператора SET (Var1=1+2 - аргу- мент оператора). Но строка с меткой THREE - является примером ошибки в использовании косвенности, так как строка "1+2" не является ни именем, ни аргументом оператора (Это только часть аргумента). В операторах у которых допустимо использование списка аргументов, возможно косвенное задание больше чем одного аргумента. В следующем примере показано косвенное задание трех аргументов оператора Write: ┌──────────────────────────────────────────────────────────────┐ │ |>Write @"!,""4 в квадрате = "",4*4"<- | │ │ | | │ │o|4 в квадрате = 16 |o│ │ |>Write "!,""4 в квадрате = "",4*4"<- | │ │ |!,"4 в квадрате = ",4*4 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.7.10 Косвенное задание аргумента при использовании списка аргументов Другой часто используемый случай использования косвенного задания аргумента операторов DO или GOTO показан на примере выбора режимов: ┌──────────────────────────────────────────────────────────────┐ │ |Opts ;Выбор режима работы | │ │ | Set o="Ввод/Поиск/Печать/Утилиты/Выход" | │ │o| For i=1:1:5 Write !,$P(o,"/",i) |o│ │ |Gopt Read !,"Номер режима: ",opt Quit:"5"[opt | │ │ | If opt'?1N!(opt>4)!'opt Write *7," ??" Goto Gopt | │ │o| Do @$p("EDIT,SRCH,PRINT,UTIL",",",opt) Goto Opts |o│ │ | . | │ │ | . | │ │o| . |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.11 Выбор режима работы с использованием косвенного задания аргумента Существует только одно исключение при косвенном задании аргумента операторов, его НЕЛЬЗЯ использовать для задания аргумента оператора FOR. В операторе FOR возможно только косвенное задание имени перемен- ной цикла, как это представлено на следующем примере : Стр. 182 ┌──────────────────────────────────────────────────────────────┐ │ |>Set A="I" For @A=1:1:3 Write !,I<- | │ │ |1 | │ │o|2 |o│ │ |3 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.12 Косвенное задание имени переменной цикла в FOR Попытка использовать косвенное задание аргумента FOR неизбежно вызывает синтаксическую ошибку: ┌──────────────────────────────────────────────────────────────┐ │ |>Set A="I=1:1:3" For @A Write I<- | │ │ | ERROR | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.13 Неправильное использование косвенности в FOR ┌───────────────────────────────────────────────────────────────┐ │ Косвенное задание аргумента может быть использовано │ │ для любого из MUMPS операторов, используемых с аргументами, │ │ за исключением оператора FOR. │ └───────────────────────────────────────────────────────────────┘ 7.2.3 Косвенность в шаблонах Оператор проверки по шаблону (?), используется для проверки соот- ветствия строки символов какому-то шаблону. (Подробнее в разделе 5.3.2.4). Результатом проверки является ИСТИНА или ЛОЖЬ, в зависимости от того, соответствует строку шаблону или нет. В качестве примера при- ведем командную строку, в которой производится проверка значения пере- менной ZIP, на содержание правильного идентификационного кода пациен- та, который должен быть пятизначным числом : ┌──────────────────────────────────────────────────────────────┐ │ | If ZIP?5N Write "Правильный код" | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.14 Проверка по шаблону Командная строка, приведенная в примере может быть интерпретиро- вана следующим образом: "Если переменная ZIP содержит пятизначное чис- ло, установить системную переменную $TEST в ИСТИНУ /записать в нее 1/, в противном случае записать в $TEST - ЛОЖЬ /записать в нее 0/". Специ- фикация проверки по шаблону (5N), как Вы можете заметить не относится ни к допустимым в MUMPS именам /меток программ или переменных/, не яв- ляется она и аргументом (аргументом оператора If является вся строка - 'ZIP?5N'), все это привело к тому, что в проверках по шаблону исполь- зуется особый вид использования косвенности, который продемонстрирован на следующем примере : ┌──────────────────────────────────────────────────────────────┐ │o| Set Pattern="5N" |o│ │ | If ZIP?@Pattern Write "Правильный код" | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.14 Косвенное задание шаблона Стр. 183 Косвенное задание шаблона проверки часто используется в приклад- ных программах, которые обслуживают вывод запросов на экран и проверку ответов на них. Такие прикладные программы /или как их иногда называют - "драйверы запросов"/, используют обычно описания запросов, которые содержатся в особых массивах, именуемых обычно "словарями данных". На Рис. 7.1 представлен пример подобного простого "словаря", где под ин- дексом, соответствующим имени переменной ("Name"), содержится текст связанного с ней запроса и шаблон проверки : ^Cdict ┌───────┐ └─┬─┬─┬─┘ │ │ │ │ │("Name") ┌────────────────┴─────────────┐ │Фамилия Пользователя \1A.AP │ /*1/ └──────────────────────────────┘ Рис. 7.1 Пример простого "словаря" для организации запросов и проверки ответов ┌──────────────────────────────────────────────────────────────┐ │ |Getdata ;Запрос данных пользователя | │ │ | Set mne="Name" Do Getans Quit:Name="" | │ │o| . |o│ │ | . | │ │ |Getans ;Вывести запрос, связанный с 'mne', прочитать | │ │o| ;ответ с клавиатуры, и возвратить результат в @mne |o│ │ | ;"Пустые" значения допустимы. | │ │ | Set q=^Cdict(mne),p=$P(q,"\",2),q=$P(q,"\",1) | │ │o|Read Write !,q Read ans Goto GetX:ans="" |o│ │ | If ans?@p Goto GetX | │ │ | Write " ??, повторите ввод " Goto Read | │ │o|GetX Set @mne=ans |o│ │ | Quit | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.16 Использование косвенного задания шаблона при проверке ввода с клавиатуры. Строка, введенная с клавиатуры, проверяется в строке Read+1, с использованием косвенного задания шаблона, если строка не соответству- ет шаблону, запрос повторяется. 7.2.4 Косвенное задание ссылки на элемент массива Косвенное задание ссылки - это специальный случай использования косвенности, который нельзя свести с к простому правилу подмены, кото- рый использовался выше. Эта форма косвенности используется для манипу- ляции узлами локальных и глобальных массивов. ------------------------------------------------------------------- *1 Обратите внимание на то, что вводимая фамилия должна в данном слу- чае состоять из любых латинских букв и символов пунктуации. Проверка на русские буквы обычно не предусматривается в MUMPS системах, в связи с тем, что шаблоны русских букв не оговорены в ANSI стандарте. Поэтому каждый изворачивается при проверках как может. (Или дополняет систему своими шаблонами, как например ТОО ДИМАС /Хабаровск/ дополнило DataT- ree MUMPS) Стр. 184 Синтаксически форма косвенного задания ссылки на узел массива состоит из двух частей - ссылки на локальный или глобальный массив и индексного дополнения. Оба элемента должны предваряться оператором косвенности - @, БЕЗ пробелов между всеми элементами. Продемонстрируем пример косвенного задания ссылки: ┌──────────────────────────────────────────────────────────────┐ │ | Set Ref="Ary(2,3)",Subs="4" | │ │ | Set @Ref@(Subs)="Magic" | │ │o| Write !,"Узел данных = ",Ary(2,3,4) |o│ │ | Quit | │ │ |----------------------------------------------------------| │ │o|Узел данных = Magic |o│ │ | | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.17 Косвенное задание ссылки на массив Выражение @Ref@(Subs) "разворачивается" перед исполнением с ис- пользованием значений переменных Ref и Subs в ссылку на массив следую- щего вида - 'Ary(2,3,4)'. Еще раз обращаем Ваше внимание на то, что в данном случае использование косвенности нельзя свести к простой подме- не значений, как это делалось раньше. Косвенное задание ссылки это сложная логическая операция с учетом контекста значений, указанных в индексированных элементах данных. Закрывающая скобка в Ref [Ary(2,3)] отбрасывается, открывающая скобка в Subs [(4)], отбрасывается, вместо них подставляется запятая. Представим на следующих примерах несколько форм косвенного задания ссылок и их интерпретацию MUMPS - системой : Дано: Ref1="A",Ref2="A(1)",Ref3="A(1,2,3)" Sub1="7",Sub2="7,8,9" Тогда: Используемая форма MUMPS - система косвенного задания индексов понимает ее как ссылку на узел массива -------------------------------------------------------- @Ref1@(Sub1) A(7) @Ref2@(Sub1) A(1,7) @Ref3@(Sub2) A(1,2,3,"7,8,9") @Ref2@(Ref1,Sub1) A(1,"A",7) -------------------------------------------------------- Если Вы собираетесь использовать индексное дополнение для первого уровня индексации массива /локального или глобального/, тогда нет неб- ходимости включать в ссылку на массив (Ref) круглые скобки. Внимательно изучите третий пример [@Ref3@(Sub2)]. В результате "разворачивания" этого выражения образуется ссылка на ЧЕТВЕРТЫЙ, а не на ШЕСТОЙ уровень индексации, как можно было бы подумать. В этом слу- чае значение, содержащееся в переменной Sub2 расценивается как простая строка, содержащая запятые. При задании косвенной ссылки можно использовать только одно кос- венное задание индексного дополнения. Продемонстрируем это положение на следующем примере: Стр. 185 ┌──────────────────────────────────────────────────────────────┐ │ |>Set Ary=(1,2,3)=1,ref="Ary(1)",sub2=2,sub3=3<- | │ │ |>Write @ref@(sub2,sub3)<- | │ │o|1 |o│ │ |>Write @ref@(sub2)@(sub3)<- | │ │ | ERROR | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 7.18 Запрещенная форма использования нескольких косвенных заданий индексных дополнений Во втором примере использована неправильная форма с двумя косвен- ными заданиями индексов, которая вызывает ошибку. ┌───────────────────────────────────────────────────────────────┐ │ При косвенном задании индексов в ссылках на элемент массива, │ │ ЗАПРЕЩЕНО использовать больше чем одно индексное дополнение. │ │ /Например такого вида - @Ref@(Sub1)@(Sub2) / (7.2.4) │ └───────────────────────────────────────────────────────────────┘ 7.3 Динамический вызов программ Еще одна дополнительная возможность, связанная с использованием косвенности относится к возможности производить динамический, /обус- лавливаемый складывающимися в ходе исполнения процесса условиями/ вы- зов программ и процедур. В разделе 2.1 мы описывали общую концепцию "вложенного" вызова в одном пользовательском разделе программ, которые используют одну общую таблицу локальных переменных. Используя механизм косвенности можно обеспечить вызов из прикладной программы процедуры, которую невозможно "жестко" задать при написании программы, и которая может быть определена только во время исполнения прикладной программы. ┌──────────────────────────────────────────────────────────────┐ │ |Opts ;Выбор режима работы | │ │ | Read !,"Номер режима (1-9): ",opt Quit:opt="" | │ │ | If opt'?1N!'opt Write *7," ??" Goto Opts | │ │o| Do @("^Opt"_opt) |o│ │ | Goto Opts | │ │ | . | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.19 Динамический вызов программ В этом примере конкретная программа вызывается только после того, как пользователь произведет выбор режима. При этом имя вызываемой программы создается непосредственно перед ее вызовом (См. также пример 7.11) 7.4 Косвенность: казусы и ловушки Косвенность - мощный инструмент при создании MUMPS программ, но одновременно одна из двух наиболее часто встречающихся причин ошибок в использовании языковых структур (вторая описана в главе 10, и относит- ся к употреблению "неполных" форм глобальных ссылок). Стр. 186 Еще одна проблема связанная с косвенностью - затруднение поддерж- ки уже написанных программ. Когда к тексту программы возвращаешься че- рез месяцы или годы, для исправления ли ошибок, или внесения дополне- ний, то использование в тексте программ косвенности сильно затрудняет понимание логики работы программы. Особенно при плохом документирова- нии. При использовании косвенности необходимо оговаривать в коммента- рии, где определяются переменные, ограничения накладываемые на их до- пустимые значения. Еще одним источником недоразумений и ошибок является "вложенная" косвенность. Попытайтесь определить, как будут работать два ниже при- веденных примера. /Все переменные в примерах определяются прямо перед использованием / ┌──────────────────────────────────────────────────────────────┐ │ | Set A="B",B="C",@@A=B | │ │ | Write !,A,", ",B,", ",C | │ │o|----------------------------------------------------------|o│ │ |B, C, C | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.20 Двойная косвенность ┌──────────────────────────────────────────────────────────────┐ │ | Set A="X(@E)",B=1,C=2,D="B",E="C" | │ │ | Set @A@(@D)=@E | │ └──────────────────────────────────────────────────────────────┘ Прим. 7.21 Косвенность доведенная до абсурда Что Вы видите во втором примере? Результатом исполнения его вто- рой строки будет создание элемента массива X(2,1)=2, но сможете ли Вы это сказать сразу? А теперь представьте себе сопровождение разветвлен- ных прикладных программ, написанных в подобном стиле, когда переменные определяются неизвестно где, а передача управления производится неиз- вестно куда, а значения переменных могут быть определены только по хо- ду исполнения программы. Насколько удобно вносить изменения в подобные командные строки? ┌───────────────────────────────────────────────────────┐ │ НЕ используйте косвенность без прямой необходимости, │ │ и хорошо документируйте ее применение. │ └───────────────────────────────────────────────────────┘ 7.5 Общие положения ■ Косвенность может быть использована с любым допустимым MUMPS име- нем /переменной, метки, программы/ (7.2.1) ■ Косвенное задание аргумента может быть использовано для любого из MUMPS операторов, используемых с аргументами, за исключением опера- тора FOR. (7.2.2) ■ При косвенном задании индексов в ссылках на элемент массива, ЗАП- РЕЩЕНО использовать больше чем одно индексное дополнение в ссылке. /Например такого вида - @Ref@(Sub1)@(Sub2) / (7.2.4) ■ НЕ используйте косвенность без прямой необходимости, и хорошо до- кументируйте случаи ее применения (7.4) Стр. 187 Глава 8 Внутренние функции Кроме операторов, описанных в главах 3 и 4 в языке MUMPS имеется большое количество встроенных /внутренних/ функций используемых для специальных операций. Эти функции определены как часть стандартного языка MUMPS, в отличии от внешних функций (описанных в главе 9), кото- рые могут определяться программистом. Функции оцениваются как отдель- ное выражение, и возвращают одиночное значение, которое интерпретиру- ется в зависимости от контекста. Каждая функция начинается со знака '$', за которым следует имя функции, затем список ее аргументов, заключенный в круглые скобки. Имя функции может быть записано большими или малыми буквами, так как малые буквы будут превращены в большие при интерпретации, кроме того оно мо- жет быть (в большинстве случаев), сокращено до первой буквы имени. (Например, вместо $EXTRACT можно записать $Е). Параметры функции ВСЕГ- ДА заключаются в круглые скобки и разделяются ЗАПЯТЫМИ. Многие функции имеют несколько форм применения, которые зависят от количества и вида задаваемых параметров функции. Функции могут быть разделены на три категории, в зависимости от вида информации, с которые они работают : ■ Строковые функции ■ Функции для работы с данными ■ Прочие функции СТРОКОВЫЕ ФУНКЦИИ обычно применяются при работе со строками символов. (Например, для извлечения символов из строки, определения длины стро- ки, и так далее). ФУНКЦИИ ДЛЯ РАБОТЫ С ДАННЫМИ используются для просмотра, поиска и про- верок элементов базы данных. Все остальные функции, отнесенные к ПРОЧИМ, служат для самых разнооб- разных целей. Особо следует отметить особую группу функции, чьи имена начинают- ся с символов '$Z...'. Эти внутренние функции разрабатываются в фир- мах-разработчиках MUMPS-реализаций и не являются стандартными /НЕ под- держиваются во всех MUMPS реализациях/. Использование подобных функций может ограничить способность прикладного программного средства к пере- несению из одной реализации в другую. Алфавитный список внутренних функций, с кратким их описанием, приведен в приложении D. --------------------------------------------------------------------- Строковые функции Функции для работы с Прочие функции данными --------------------------------------------------------------------- 8.1.1 $EXTRACT 8.2.1 $DATA 8.3.1 $SELECT 8.1.2 $PIECE 8.2.2 $ORDER 8.3.2 $RANDOM 8.1.3 $LENGTH 8.2.3 $NEXT 8.3.3 $VIEW 8.1.4 $FIND 8.2.4 $QUERY 8.1.5 $TRANSLATE 8.2.5 $GET 8.1.6 $JUSTIFY 8.1.7 $FNUMBER 8.1.8 $ASCII 8.1.9 $CHAR 8.1.10 $TEXT --------------------------------------------------------------------- Стр. 188 8.1 Строковые функции Строковые функции используются для исполнения операций со строка- ми. Они возвращают либо строку, либо число, характеризующее каким-то образом исследуемую строку. 8.1.1 $EXTRACT Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ │(EXPRESSION,FROM) │ $Extract("Characters",4)="r" $EXTRACT │(EXPRESSION) │ $Extract("Characters")="C" │(EXPRESSION,FROM,TO) │ $Extract("Characters",1,4)="Char" │ │ ┌───────────┐ Форма сокращения имени │ $E │ $E("MUMPS",1,3) Возвращает │ Строку │ "MUM" └───────────┘ Функция $EXTRACT используется для выделения подстроки из указан- ной строки. Результатом действия функции $EXTRACT является строка /в том числе может быть и пустая строка/. Может быть использовано три формы функции $EXTRACT, в зависимости от диапазона выделяемых симво- лов. 8.1.1.1 Аргумент = (EXPRESSION,FROM) Результатом выражения EXPRESSION является строка, из которой /с позиции FROM/ выделяется один символ. Значение FROM - целое положи- тельное число, которое интерпретируется как позиция, с которой из исс- ледуемой строки выделяется символ. Если FROM меньше 1, или больше чем длина строки /в символах/, то функция $EXTRACT возвращает пустую стро- ку. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Name="Lewkowich,John" Write $E(Name,11)<- | │ │ |J | │ │o|>If $E(Name,0)="" Write "NULL"<- |o│ │ |NULL | │ │ |>For i=14:-1:11 Write $e(Name,i)<- | │ │o|nhoJ |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.1 $EXTRACT(EXPRESSION,FROM) Обратите внимание, что здесь и далее в примерах, вместо полного имени функции $EXTRACT используется сокращенное $Е /в связи с ограни- ченным пространством в листингах примеров/. 8.1.1.2 Аргумент = (EXPRESSION) Эта форма ни что иное, как сокращение предыдущей формы, в которой значение FROM не явно понимается как равное 1. [то есть она эквива- лентна $E(EXPRESSION,1)]. Стр. 189 Эта форма функции $EXTRACT возвращает строку, состоящую из одного сим- вола, стоящего в обрабатываемой строке на первой позиции, (или пустую строку, если обрабатываемая строка - пустая строка). ┌──────────────────────────────────────────────────────────────┐ │ |>Write $E("Cornell")<- | │ │ |C | │ │o|>Write $E(22/7)<- |o│ │ |3 | │ │ |>Write $E("22/7")<- | │ │o|2 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.2 $EXTRACT(EXPRESSION) Обратите особое внимание на различные результаты действия второй и третьей командных строк в этом примере. Во второй командной строке строка 22/7 оценивается как выражение и вычисляется его значение /пе- ред исполнением функции $EXTRACT/, в третьей же она понимается как строковый литерал /так как заключена в кавычки/. 8.1.1.1 Аргумент = (EXPRESSION,FROM,TO) Эта форма функции $EXTRACT возвращает подстроку символов, выде- ленную из исходной строки /которая задается выражением EXPRESSION/, c позиции задаваемой параметром FROM, по позицию задаваемую параметром TO. /Параметры FROM и TO - целые неотрицательные числа/. Если ТО мень- ше FROM, или FROM больше длины строки, то возвращается пустая строка. Если ТО=FROM, то функция возвращает один символ /или пустую строку/. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Name="Doe,Jane" Write $E(Name,5,99)<- | │ │ |Jane | │ │o|>Write $E(Name,5,8)," ",$E(Name,1,3)<- |o│ │ |Jane Doe | │ │ |>Write $E(22/7,1,4)<- | │ │o|3.14 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.3 $EXTRACT(EXPRESSION,FROM,TO) 8.1.2 Функция $PIECE Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────── │ │ │(EXPRESSION,DELIM,FROM) │ $Piece("abc;def",";",2)="def" $PIECE │(EXPRESSION,DELIM) │ $Piece("abc;def",";")="abc" │(EXPRESSION,DELIM,FROM,TO)│ $Piece("abcabdrabx","ab",2,3)= │ │ ="dr" ┌───────────┐ Форма сокращения имени │ $P │ $P("MUMPS","P",1) Возвращает │ Строку │ "MUM" └───────────┘ Стр. 190 Действие функции $PIECE аналогично действию функции $EXTRACT, в том смысле, что она также возвращает подстроку выделенную из обрабаты- ваемой строки, задаваемой выражением EXPRESSION. Но, в отличии от $EXTRACT, которая извлекает символы из строки, согласно их позиции от- носительно начала строки, $PIECE извлекает символы относительно некое- го символа /группы символов/, находящихся в этой строки и "разделяю- щих" обрабатываемую строку на подстроки. Рассмотрим выделение подстро- ки из следующей строки : Doe,Jane;55 State Street;Any Place;State;99999 Когда эта строка создавалась, то было произвольно принято, что символ ';' /точка с запятой/ будет разделять в этой строке поля данных определяющие адрес. Если в качестве символа-разделителя /здесь и даль- ше просто "разделителя"/ принимается ';', то третьей подстрокой в этой строке является 'Any Place'. Если же в качестве разделителя принимает- ся пробел, то третьей подстрокой будет 'Street;Any'. Выделяемая таким образом подстрока, можно заметить, зависит как от принятого разделите- ля, так и от относительной позиции в обрабатываемой строке. 8.1.2.1 Аргумент = (EXPRESSION,DELIM,FROM) EXPRESSION это MUMPS выражение оцениваемое как строка символов. DELIM - второе выражение, определяющее строку символов, которая будет рассматриваться как разделитель в обрабатываемой строке. FROM интерп- ретируется как целое положительное число, определяющее вхождение выде- ляемой подстроки /относительно разделителя, задаваемого DELIM/ в обра- батываемую строку. Если FROM=0, отрицательное число, или больше, чем число подстрок в обрабатываемой строке, то функция $PIECE возвращает пустую строку. В противном случае функция возвращает подстроку, входя- щую в обрабатываемую строку после разделителя, который встречается в строке FROM раз. $PIECE возвращает пустую строку и в том случае, если выделяемое поле находится между двумя стоящими вплотную друг к другу разделителями. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Adr="Doe,J./99 B St./Ithaca/NY//257-3323;273-2214"<- | │ │ |>Write $P(Adr,"/",4)<- | │ │o|NY |o│ │ |>Write $P(Adr,",",1)<- | │ │ |Doe | │ │o|>Write $P(Adr,"/",5)<- |o│ │ | | │ │ |>Write $P(Adr,"/",10)<- | │ │o| |o│ │ |>Write $P($P(Adr,"/",6),";",2)<- | │ │ |273-2214 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 8.4 $PIECE(EXPRESSION,DELIM,FROM) Обратите внимание на пример "вложенного" использования функции $PIECE в этом примере. Так как функция возвращает строку, то она может быть использована как аргумент в другой функции. Стр. 191 Также обратите внимание на то, что при необходимости подстрока может быть в свою очередь разбита на поля / с использованием другого разде- лителя/, как это показано при выделении второго номера телефона. Поля, разделяемые строкой определяемой DELIM никогда не могут пе- рекрываться в исходной строке. Например, если в строке 10$$$20$$$30, в качестве разделителя принята подстрока '$$', то исходная строка будет пониматься как три подстроки, соединенные следующим образом : 10_$$_$20_$$_$30 1-я 2-я 3-я подстроки /Символ строковой конкатенации '_' применен в этом случае для визуали- зации подстрок и разделителей/ В следующем примере мы рассматриваем чрезвычайно часто встречаю- щуюся ситуацию "разбора" списка значений для присвоения этих значений локальным переменным. В этом случае, мы рассматриваем два взаимно со- ответствующих списка - список значений и список имен локальных пере- менных этим значениям соответствующий. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Vars="Sex,DOB,Height,Weight,HairClr",com=","<- | │ │ |>Set Data="Male;7/15/52;6-3;195;Black"<- | │ │o|>For i=1:1:5 Set @$P(Vars,com,i)=$P(Data,";",i)<- |o│ │ |>For i=1:1:5 Write !,$P(Vars,com,i),"=",@$P(Vars,com,i)<- | │ │ |Sex=Male | │ │o|DOB=7/15/32 |o│ │ |Height=6-3 | │ │ |Weight=195 | │ │o|HairClr=Black |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.8.5. $PIECE(EXPRESSION,DELIM,FROM) вместе с использованием косвенности В этом примере активно используется косвенность /глава 7/ и можно посоветовать начинающему программисту еще раз обратиться к материалу этой главы при затруднениях. Подобный прием можно также использовать для выбора пути передачи управления, с помощью оператора DO /или GO- TO/, как показано в следующем примере : ┌──────────────────────────────────────────────────────────────┐ │ |Opt ;Выбор режима работы | │ │ | Read !,"Номер режима: ",opt | │ │ | Quit:opt<1!(opt>3) | │ │o| Do @$P("EDIT,SRCH^PAT,PRINT",",",opt) Goto Opt |o│ └──────────────────────────────────────────────────────────────┘ Прим. 8.6 Выбор режима работы с использованием $PIECE и косвенности 8.1.2.2 Аргумент = (EXPRESSION,DELIM) Эта двухаргументная форма использования функции представляет со- бой сокращенную форму (EXPRESSION,DELIM,FROM), где опущенное значение FROM предполагается равным 1. Стр. 192 ┌──────────────────────────────────────────────────────────────┐ │ |>Set Days="Monday,Tuesday,Wednesday,Thusday,Friday"<- | │ │ |>Write $P(Days,",")<- | │ │o|Monday |o│ │ |>Write $P(Days,",Th")<- | │ │ |Monday,Tuesday,Wednesday | │ │o|>Write $P(Days,"XX")<- |o│ │ |Monday,Tuesday,Wednesday,Thusday,Friday | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.8.6 $PIECE(EXPRESSION,DELIM) В последней строке 'Write $P(Days,"XX")', обрабатываемая строка не содержит указанного разделителя (подстроку "ХХ"), поэтому, функция $PIECE возвращает в качестве результата всю исходную строку. 8.1.2.3 Аргумент = (EXPRESSION,DELIM,FROM,TO) Эта форма подобна первой, но в этом случае возвращается подстрока символов, входящая в исходную строку с позиции FROM разделителя DELIM, по позицию TO. FROM и TO интерпретируются как положительные целые чис- ла. Если TO меньше FROM, возвращается пустая строка. Возвращаемая подстрока будет содержать разделители DELIM, разделяющие поля внутри нее. ┌─────────────────────────────────────────────────────────────────┐ │ |>S Mnths="Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"<- | │ │ |>Set Spring=$P(Mnths,",",4,6) Write !,"Spring=",Spring<- | │ │o|Spring=Apr,May,Jun |o│ │ |>Set Winter=$P(Mnths,",",11,99)_","_$P(Mnths,",",0,3)<- | │ │ |>Write "Winter=",Winter," (в Нью-Йорке) "<- | │ │o|Winter=Nov,Dec,Jan,Feb,Mar (в Нью-Йорке) |o│ │ |>Write $P(Mnths,",",4,3)<- | │ │ | | │ │o|>Write $P(Mnths,",",-10,1)<- |o│ │ |Jan | │ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Прим. 8.8 $PIECE(EXPRESSION,DELIM,FROM,TO) 8.1.2.4 SET $PIECE Функция $PIECE является не единственной функцией, которую можно использовать в левой части оператора SET /до знака равенства/. Исполь- зование этой формы функции, в сочетании с оператором SET позволяет напрямую записать значение в конкретное поле строки, без использование громоздких операций с разделением строки и конкатенацией. Во многих аспектах эта форма функции противоречит поведению "нормальных" функ- ций, так как обычно функция возвращает какое-либо значение, а не моди- фицирует свои аргументы. Однако, ради полноты описания и одновременно удобства, SET $PIECE мы опишем здесь, вместе с другими формами исполь- зования функции $PIECE. Часть информации, приведенная здесь дополняет описание оператора SET, из раздела 3.1.1.3 Стр. 193 ┌──────────────────────────────────────────────────────────────┐ │ |>Set Var="",$P(Var,";",5)="Insert" Write Var<- | │ │ | ;;;;Insert | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.9 Set $PIECE Второй пример применения SET $PIECE приведен одновременно с аль- тернативным способом достижения той-же цели. ┌───────────────────────────────────────────────────────────────────┐ │ |>Set Nmbrs="10-20-30-40-50-60-70-80-90"<- | │ │ |>Set $P(Nmbrs,"-",4)="сорок" Write Nmbrs<- | │ │o|10-20-30-сорок-50-60-70-80-90 |o│ │ |>Set Nmbrs=$P(Nmbrs,"-",1,2)_"-тридцать-"_$P(Nmbrs,"-",4,9)<- | │ │ |>Write Nmbrs<- | │ │o|10-20-тридцать-сорок-50-60-70-80-90 |o│ │ |>Set $P(Nmbrs,"-",5,6)="пятьдесят-шестьдесят"<- | │ │ |10-20-тридцать-сорок-пятьдесят-шестьдесят-70-80-90 | │ │o|> |o│ └───────────────────────────────────────────────────────────────────┘ Прим. 8.10 Set $PIECE в несколько полей Когда производится одновременная запись нескольких подстрок, то необходимо обеспечить наличие в записываемой строке внутренних разде- лителей (дефис в строке "тридцать-сорок"). Когда $PIECE используется в левой части оператора SET, то первый аргумент функции должен быть до- пустимым именем переменной (См. раздел 3.4), а не общим случаем MUMPS выражения. ┌──────────────────────────────────────────────────────────────┐ │ |>Set $Piece("10\20\30\40\50","\",3)="тридцать"<- | │ │ | ERROR | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 8.11 Set $PIECE оцениваемый как имя переменной Ошибка вида " ERROR" генерируется вследствии того, что строка "10\20\30\40\50" не является именем переменной. Кроме вставки полей данных в запись, форма SET $PIECE может применяться для создания строк, в соответствии с заданным шаблоном. ┌──────────────────────────────────────────────────────────────┐ │ |>Set x="",$Piece(x,"-=",29)="" Write x<- | │ │ |-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 8.12 Set $PIECE используемый для создания строки Эта форма использования $PIECE более подробно рассмотрена в раз- деле 3.1.1.3. В настоящее время, в реализациях опирающихся на стандарт MUMPS 1991 года, допускается использование функции $EXTRACT, в левой части оператора SET. /Аналогичная рассмотренной форме использования $PIECE/. Подробнее смотрите в описании языка используемой Вами MUMPS реализации. Стр. 194 8.1.3 Функция $LENGTH Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $LENGTH │(EXPRESSION) │ $LENGTH("Abcdef")=6 │(EXPRESSION,DELIM) │ $LENGTH("10-20-30-40","-")=4 │ │ ┌───────┐ Форма сокращения имени │ $L │$L("MIMPS") $L("A,B,C",",") Возвращает │ число │5 3 └───────┘ Функция $LENGTH возвращает число, означающее : - длину строки, задаваемой выражением EXPRESSION в символах, - или число вхождений подстроки символов DELIM в строку задаваемую выражением EXPRESSION Результаты оценки выражений EXPRESSION и DELIM ВСЕГДА интерпретируются как строки символов. 8.1.3.1 Аргумент = (EXPRESSION) В этом случае функция возвращает число, равное длине строки. Возвращаемое число лежит в диапазоне от 0 /пустая строка/, до предела длины строки, в данной MUMPS-реализации /обычно 255 символов/. Управ- ляющие символы в строке /которые обычно не отображаются на экране и не выводятся на печать/, тоже учитываются. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Test="The ""Complete"" MUMPS"<- | │ │ |>Write $L(Test)," *",Test,"*"<- | │ │o|20 *The "Complete" MUMPS* |o│ │ |>Set X=""<- | │ │ |>For i=1:1:$L(Test) Set Y=$E(Test,i) Set:Y'=" " X=X_Y | │ │o|>Write X," = ",$L(X)," bytes"<- |o│ │ |The"Complete"MUMPS = 18 bytes | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.13 $LENGTH(EXPRESSION) Другой пример использования функции приведен на следующем приме- ре, где она используется для проверки того, что строка ответа на зап- рос, введенная с клавиатуры, точно равна одному символу. ┌──────────────────────────────────────────────────────────────┐ │ |Save ;Сохранение редактируемых данных | │ │ |GetYN Read !,"Сохранять данные (Y или N): Yes ::",ans | │ │o| If "YyNn?"[ans'=$L(ans) Write *7," ??" Goto GetYN |o│ │ | Set:ans="" ans="Y" If ans="?" Do HELP Goto GetYN | │ │ | If "Yy"[ans Do FILE | │ │o| Quit |o│ └──────────────────────────────────────────────────────────────┘ Прим.8.14 Проверка длины строки с помощью $LENGTH Стр. 195 В третьей строке оператор If используется для того, чтобы прове- рить что с клавиатуры введен один из следующих символов : "Y", "y", "N", "n", "?". Первый часть аргумента If ИСТИННА /=1/, если переменная 'ans' содержится в строке "YyNn?"; однако, в этой строке содержатся также и сочетания символов "Yy", "yN" и т.д. а также и пустая строка /соответствующая одному нажатию в ответ на запрос/. Вторая часть аргумента оператора проверяет, чтобы вводимая строка была равна 1 символу. 8.1.3.2 Аргумент = (EXPRESSION,DELIM) В этой форме функция $LENGTH возвращает число вхождений НЕПЕРЕСЕ- КАЮЩИХСЯ строк задаваемых выражением DELIM в строку задаваемую EXPRES- SION увеличенное на 1. Если DELIM - пустая строка, то функция возвра- щает 1, вне зависимости от значения EXPRESSION. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Str="Jones,John;55 State Street;Davis,CA"<- | │ │ |>Write $L(Str,";")<- | │ │o|3 |o│ │ |>Write $L(Str," ")<- | │ │ |3 | │ │o|>Write $L(Str,"CA")<- |o│ │ |2 | │ │ |>Write $L(Str,"?")<- | │ │o|1 |o│ │ |>Write $L(Str,"")<- | │ │ |0 | │ └──────────────────────────────────────────────────────────────┘ Прим.8.15 $LENGTH(EXPRESSION,DELIM) Если строка DELIM не содержится в обрабатываемой строке, $LENGTH возвращает 1 /0 вхождений+1 =1, поэтому и $L(Str,"#")=1/ Ранее мы уже упоминали, что в этой форме использования функции $LENGTH возвращает число непересекающихся строк, теперь продемонстрируем это положение на примере : ┌──────────────────────────────────────────────────────────────┐ │ |>Set X="abababab" Write $L(X,"ab")<- | │ │ |5 | │ │ |>Write $L(X,"aba")<- | │ │ |3 | │ └──────────────────────────────────────────────────────────────┘ Прим.8.15 $LENGTH(EXPRESSION,DELIM), поиск непересекающихся подстрок Если не обращать внимание на строгое следование принципу опреде- ления НЕПЕРЕСЕКАЮЩИХСЯ строк, то во втором случае непонятно, какое значение должна возвращать функция 3 или 4? В некоторых случаях /см. например третий в примере 9.19/ это может относиться только к стилю программирования. Полезно вернуться к обсуждению концепции непересека- ющихся строк в разделе 8.1.2.1, посвященном обсуждению непересекающих- ся разделителей для $PIECE. Стр. 196 8.1.4 Функция $FIND Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $FIND │(EXPRESSION,FIND) │ $FIND("Female;5/21/49;130",";")=8 │(EXPRESSION,FIND,FROM)│ $FIND("Female;5/21/49;130",";",8)= │ │ =16 ┌────────┐ Форма сокращения имени │ $F │ $F("Test Data"," ") Возвращает │ число │ 6 └────────┘ Функция $FIND ищет в исследуемой строке (EXPRESSION) вхождение подстроки (FIND), и возвращает целое число, соответствующее позиции следующего символа в исходной строке, после найденного вхождения подс- троки FIND. Если подстрока FIND не обнаружена, $FIND возвращает 0. Не- обязательный параметр FROM указывает начальную позицию с которой начи- нается поиск. Если FROM меньше 1, или отсутствует, то просмотр строки начинается с первой позиции, если больше, чем длина строки, поиск не начинается и $FIND возвращает 0. В функции $FIND EXPRESSION и FIND всегда интерпретируются как строки, FROM - как целое число. 8.1.4.1 Аргумент = (EXPRESSION,FIND) Эта форма функции предназначена для поиска первого вхождения подстроки FIND в строку EXPRESSION. Если FIND не входит в EXPRESSION, функция возвращает 0. Если вхождение имеет место, то возвращается це- лое число, соответствующее позиции следующего символа в исходной стро- ке, после последнего символа подстроки FIND. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Target="abc.de,fg.h.ij.kl.mn.op"<- | │ │ |>Write $Find(Target,".")<- | │ │o|5 |o│ │ |>Write $Extract(Target,1,$F(Target,","))<- | │ │ |abc.de,f | │ │o|>Write $F(Target,"$"),",",$F(Target,"") |o│ │ |0,1 | │ └──────────────────────────────────────────────────────────────┘ Прим.8.17 $FIND(EXPRESSION,FIND) Обратите внимание, на результат поиска пустой строки (""), в этом случае $FIND возвращает 1, так как подразумевается, что впереди каждой строки входит пустая строка. Еще раз напоминаем Вам, что функция $FIND возвращает позицию первого символа после обнаруженной строки FIND в строке EXPRESSION, а не позицию, соответствующую началу строки FIND в ней. ┌───────────────────────────────────────────────────────────────┐ │ |>Set X="Давайте уберем спаренные пробелы."<- | │ │ |>F i=0:0 s F=$F(X," ") Q:'F S X=$E(X,0,F-3)_$E(X,F,255)<-| │ │o|>Write X<- |o│ │ |Давайте уберем спаренные пробелы. | │ └───────────────────────────────────────────────────────────────┘ Прим.8.18 Повторный поиск Стр. 197 В примере 8.18 мы продолжаем поиск в строке до тех пор, пока в ней не кончатся строенные пробелы. В каждом шаге из строки удаляются два пробела из трех. -------------------------------------------------------------------- Хочется отметить, что используемый в этом примере способ организации бесконечного цикла 'For i=0:0 ..." , можно заменить простым использо- ванием оператора For без аргументов /См. раздел 3.2.3/ - Примечание переводчика 8.1.4.2 Аргумент = (EXPRESSION,FIND,FROM) Трехаргументная форма использования функции $FIND подобна двухар- гументной, за исключением того, что поиск начинается не с первого сим- вола в строке, а с позиции определяемой параметром FROM. В двухаргу- ментной форме этот параметр полагается равным 1 (и поиск начинается с первого символа строки). FROM всегда интерпретируется как целое число, если этот параметр меньше 1, поиск все равно начинается с первого сим- вола строки. Если FROM больше, чем длина строки, функция возвращает 0. ┌────────────────────────────────────────────────────────────────┐ │ |>Set Tar=",Monday,Tuesday,Wednesday,Thusday,Friday"<- | │ │ |>S F=0 F i=0:0 S F=$F(Tar,",",F) Q:F=0 W !,$E(Tar,F,F+2)<- | │ │o|Mon |o│ │ |Tue | │ │ |Wed | │ │o|Thu |o│ │ |Fri | │ │ |> | │ └────────────────────────────────────────────────────────────────┘ Прим.8.19 $FIND(EXPRESSION,FIND,FROM) 8.1.5 Функция $TRANSLATE Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $TRANSLATE│(EXPRESSION,REPLACE,WITH)│ $TRANSLATE("testing","ei","EI") │(EXPRESSION,REPLACE) │ $TRANSLATE("re move spaces"," ") │ │ ┌────────┐ Форма сокращения имени │ $TR │ $TR("уда лить пробелы"," ","") Возвращает │ строку │ "удалитьпробелы" └────────┘ Функция $TRANSLATE производит посимвольную замену символов обрабатываемой строки, задаваемой выражением EXPRESSION, руководствуясь соответствием между строками REPLACE и WITH, следующим образом : - строка EXPRESSION просматривается, символ за символом - если очередной символ не обнаруживается в строке REPLACE, то он остается без изменений - если символ обнаруживается в строке REPLACE, то вместо него в стро- ку EXPRESSION вставляется символ из строки WITH (стоящий в ней на той же относительной позиции, что и обнаруженный символ в строке REPLACE) Стр. 198 Строка WITH может отсутствовать, в этом случае она полагается равной пустой строке, таким образом, для этой формы использования функции, из обрабатываемой строки удаляются все символы, указанные в строке REPLACE. 8.1.5.1 Аргумент = (EXPRESSION,REPLACE,WITH) Символ из строки EXPRESSION ищется в строке REPLACE, и если он там есть, то в исходной строке он заменяется на символ из строки WITH /стоящий на той же позиции, что и первое вхождение исходного символа в строке REPLACE/. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Test="Как живете, караси?"<- | │ │ |>Write $TR(Test,"K","к")<- | │ │o|как живете, караси? |o│ │ |>Write $TR(Test," ,?","*-!")<- | │ │ |Как*живете-*караси! | │ │o|>Write $TR(Test," ,?","-")<- |o│ │ |Как-живете-караси | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.20 $TRANSLATE(EXPRESSION,REPLACE,WITH) Функция $TRANSLATE обеспечивает посимвольную, а не строковую за- мену. Если REPLACE длиннее, чем WITH, то все символы REPLACE, которым нет соответствия в WITH, удаляются из строки EXPRESSION. Если WITH пустая строка, то все символы входящие в REPLACE удаляются из исходной строки. Если символ встречается в REPLACE больше, чем один раз, то функция $TRANSLATE рассматривает только первое его вхождение в строку REPALCE. И, следовательно $TRANSLATE не может использоваться для уда- ления повторного вхождения символа в строку, что и продемонстрировано в следующем примере : ┌──────────────────────────────────────────────────────────────┐ │ |>Set X="Не удалить двойных пробелов!"<- | │ │ |>Write $TR(X," "," ")<- | │ │o|Не удалить двойных пробелов! |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.21 $TRANSLATE осуществляет только посимвольную замену Те, кто не любят головоломки, могут сразу перейти к следующему параграфу. Остальные могут попробовать решить следующую проблему ис- пользуя функцию $TRANSLATE. Задача: Необходимо написать процедуру для инвертирования порядка следова- ния символов в произвольной строке длиной до 15 символов с использова- нием только одной функции $TRANSLATE. /Процедура должна "выворачивать" строку задом наперед, так строка "Хороший день", должна превратиться в "ьнед йишороХ"/ Решение этой задачи приведено в разделе 8.1.5.3 Стр. 199 Приведем еще одно полезное применение функции $TRANSLATE - замену в строках больших букв на малые и наоборот. Рассмотрите следующий при- мер: ┌──────────────────────────────────────────────────────────────┐ │ |>Set Test="Как живете, караси?"<- | │ │ |>Set Upper="АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ"<- | │ │ |>Set Lower="абвгдежзийклмнопрстуфхцчшщьыъэюя"<- | │ │ |>Write $TR(Test,Upper,Lower)<- | │ │o|как живете, караси? |o│ │ |>Write $TR(Test,Lower,Upper)<- | │ │ |КАК ЖИВЕТЕ, КАРАСИ? | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.22 Трансформация букв с использованием $TRANSLATE Замена букв может осуществляться и в созданных пользователем внешних функциях /См. главу 9/, и использоваться, затем, при необходи- мости. В следующем примере определены две внешние функции : $$UPPER и $$LOWER, используемые для трансформации малых символов в большие и на- оборот. В данном примере мы также создаем фрагмент программы, запраши- вающей фамилию и имя, в общем формате : "Фамилия,Имя". Затем произво- дится удаление пробелов из введенной строки и трансформация первых букв фамилии и имени в большие, а всех остальных в малые. ┌──────────────────────────────────────────────────────────────┐ │o|Start Read !,"Фамилия,имя : ",Name Q:Name="" |o│ │ | Set Name=$TR(Name," ","") | │ │ | Set First=$P(Name,",",2),Last=$P(Name,",") | │ │o| Set First=$$UPPER($E(First))_$$LOWER($E(First,2,99))|o│ │ | Set Last=$$UPPER($E(Last))_$$LOWER($E(Last,2,99)) | │ │ | Write " --> ",Last_","_First Goto Start | │ │o|LOWER(X) ;перевод Х в малые буквы |o│ │ | Quit $TR(X,"АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ","абвгд| │ │ | ежзийклмнопрстуфхцчшщьыъэюя") | │ │o|UPPER(X) ;перевод Х в большие буквы |o│ │ | Quit $TR(X,"абвгдежзийклмнопрстуфхцчшщьыъэюя","АБВГД| │ │ | ЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ") | │ │o|----------------------------------------------------------|o│ │ |>Do Start<- | │ │ |Фамилия,имя : петров,ИВАН --> Петров,Иван | │ │o|Фамилия,имя : МИККИ, маус --> Микки,Маус |o│ │ |Фамилия,имя : <- | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.23 Трансформация имен с использованием $TRANSLATE 8.1.5.2 Аргумент = (EXPRESSION,REPLACE) Эта форма использования функции полностью идентична уже рассмот- ренной, за исключением того, что в ней строка WITH не определена и по- лагается равной пустой строке. Все символы составляющие строку REPLACE удаляются из строки EXPRESSION. Стр. 200 8.1.5.3 Задача для разминки ЗАДАНИЕ : Необходимо написать процедуру для инвертирования порядка следова- ния символов в произвольной строке длиной до 15 символов с использова- нием только одной функции $TRANSLATE. /Процедура должна "выворачивать" строку задом наперед, так строка "Хороший день", должна превратиться в "ьнед йишороХ"/ РЕШЕНИЕ : ┌──────────────────────────────────────────────────────────────┐ │o|Start Read !,"Строка : ",Str Q:Str="" |o│ │ | Write " --> ",$$Invert(Str) Goto Start | │ │o|Invert(X) ;инвертация символов в Х |o│ │ | Quit $TR("abcdefghijklmno","onmlkjihgfedcba",X) | │ │o|----------------------------------------------------------|o│ │ |>Do Start<- | │ │ |Строка : have a nice day --> yad ecin a evah | │ │ |Строка : <- | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.24 Инверсия строки с использованием $TRANSLATE В этом решении есть две тонкости, первая - строка EXPRESSION должна быть не короче инвертируемой строки и быть составлена из непов- торяющихся символов, вторая - REPLACE должна быть такой же длины, как и EXPRESSION, но иметь обратный порядок символов, по отношению к ней. 8.1.6 Функция $JUSTIFY Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $JUSTIFY │(EXPRESSION,WIDTH) │ $JUSTIFY("Testing",15) │(EXPRESSION,WIDTH,DECIMAL)│ $JUSTIFY(22/7,10,2) │ │ ┌────────┐ Форма сокращения имени │ $J │ $J(22/7,10,2) Возвращает │ строку │ " 3.14" └────────┘ Функция $JUSTIFY используется для выравнивания по правому краю строки или числа (EXPRESSION) в поле шириной WIDTH символов. При необ- ходимости EXPRESSION дополняется слева необходимым числом пробелов. Эта функция широко применяется для форматирования строк данных при вы- воде. 8.1.6.1 Аргумент = (EXPRESSION,WIDTH) В этой форме использования функции $JUSTIFY EXPRESSION интерпре- тируется как строка, WIDTH - как целое число, означающее ширину поля /в символах/, в котором строка EXPRESSION будет выравниваться по пра- вому краю. Стр. 201 Если длина строки EXPRESSION, меньше или равна WIDTH, то результирую- щая строка будет равна исходной /выравнивание игнорируется/. ┌──────────────────────────────────────────────────────────────┐ │ |>Set width1=8,width2=4<- | │ │ |>Write $J("Right",width1)<- | │ │o| Right |o│ │ |>Write $J("Right",width2)<- | │ │ |Right | │ │o|>Write $J(22.6789,width1*width2)<- |o│ │ | 22.6789 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.25 $JUSTIFY(EXPRESSION,WIDTH) 8.1.6.2 Аргумент = (EXPRESSION,WIDTH,DECIMAL) Эта форма использования функции отличается от предыдущей. В ней EXРRESSION всегда интерпретируется как число, а не как строка /для предыдущей формы/. Число, определяемое выражением EXPRESSION, выравни- вается в поле шириной WIDTH, с одновременным математическим округлени- ем до числа знаков после десятичной точки (.), определяемым числом DE- CIMAL /которое всегда интерпретируется как целое неотрицательное чис- ло/. При математическом округлении дробная часть числа при необходи- мости дополняется нулями после десятичной точки, так, чтобы число цифр в дробной части было равно DECIMAL. Если целая часть округляемого чис- ла = 0, то нуль вставляется слева от десятичной точки /в отличие от правил числовой интерпретации строк, когда ноль отбрасывается/. Если число EXPRESSION короче, чем WIDTH, то оно будет дополнено слева про- белами (" "), до ширины WIDTH, в противном случае оно останется без изменений. ┌──────────────────────────────────────────────────────────────┐ │ |>Set X=1234.5678<- | │ │ |>Write $J(X,10,2)<- | │ │o| 1234.57 |o│ │ |>Write $J(X,10,0)<- | │ │ | 1235 | │ │o|>Write $J(X,12,6)<- |o│ │ | 1234.567800 | │ │ |>Write $J(X-1234,8,1)<- | │ │o| 0.6 |o│ │ |>Write $J(-X,10,2)<- | │ │ | -1234.57 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.26 $JUSTIFY(EXPRESSION,WIDTH,DECIMAL) Заметьте, что при выравнивании отрицательных чисел, знак минус (-) тоже учитывается как часть WIDTH. Стр. 202 8.1.7 Функция $FNUMBER Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $FNUMBER │(EXPRESSION,CODE) │ $FNUMBER(2*4,"+")="+8" │(EXPRESSION,CODE,DECIMAL)│ $FNUMBER(-3.1428,"P,",2)="(3.14)" │ │ ┌────────┐ Форма сокращения имени │ $FN │ $FN(-3.1428,"P,",2) Возвращает │ строку │ "(3.14)" └────────┘ Функция $FNUMBER используется для различных способов форматирова- ния чисел. EXPRESSION интерпретируется как число, а затем форматирует- ся в соответствии с кодом, задаваемым выражением CODE. Необязательное число DECIMAL определяет число цифр в дробной части числа /выводимых после десятичной точки/. 8.1.7.1 Аргумент = (EXPRESSION,CODE) Список значений параметра CODE, определяющих форматирование чис- ла, получаемого как результат интерпретации выражения EXPRESSION. CODE="+" Дополнение чисел знаком '+' для положительных и '-' отрицательных чисел. CODE="-" Подавление знака '-' у отрицательных чисел, т.е. в этом случае число может интерпретироваться как абсолютное значение. CODE="," Между группами из трех чисел вставляется разделитель - <,>, т.е. число будет выглядеть следующим образом: 126,455,777.78 CODE="." Числа представляются в Европейской форме записи - т.е. разделитель групп - <.>, а <,> разделяет целую и дробную части числа.Т.е. число будет выглядеть следующим образом: 126.455.777,78 CODE="Т" Перенос знака числа из первой позиции на последнюю /к правому концу числа/. Обратите внимание на то, что производится именно перенос знака, если CODE="+" не указано, то положительные числа не дополняются справа знаком '+'. Если параметр CODE содержит '-', то к пра- вому краю отрицательных чисел минус (-) не будет до- полняться. Если перенос знака не выполняется, то на место пропущенного знака вставляется пробел. CODE="Р" Вывод отрицательных чисел заключенными в круглые скоб- ки, а положительных - дополненных сзади и спереди про- белами. Параметр CODE может содержать несколько управляющих ключей. При задании ключей размер букв не имеет значения. ('t' будет интерпретиро- ваться в 'T' и так далее) Стр. 203 ┌──────────────────────────────────────────────────────────────┐ │ |>Set Pos=31428.34,Neg=-Pos<- | │ │ |>Write $FN(Pos,"+,")<- | │ │o|+31,428.34 |o│ │ |>Write $FN(Pos,"t")<- | │ │ |31428.34 | │ │o|>Write $FN(Pos,"t+,")<- |o│ │ |31,428.34+ | │ │ |>Write $FN(Neg,",P")< | │ │o|(31,428.34) |o│ │ |>Write $FN(Neg,"-,")< | │ │ |31,428.34 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.8.27 $FNUMBER(EXPRESSION,CODE) Обратите внимание на то обстоятельство, что некоторые сочетания ключей не имеют никакого смысла, и, более того, могут вызвать ошибку. Например, если Вы указываете ключ "T" для переноса знака '-' на правый край числа, и одновременно указываете ключ "Р" для представления отри- цательных чисел заключенными в круглые скобки - возникнет ошибка из конфликта назначенных видов форматирования. Управляющие коды могут ис- пользоваться в различных сочетаниях, с учетом того, что код "P" не мо- жет сочетаться с "+", "-", и "Т". Функция $FNUMBER полезна при вычислениях абсолютного значения числа, выражение : Set abs=+$FN(number,"T") возвращает любое число в виде положительного целого числа. Код "Т" в функции $FNUMBER передвигает знак минус у отрицательных чисел на пос- леднюю позицию, в связи с этим перед функцией в выражении поставлен знак '+', вызывающий числовую интерпретацию результирующего числа, и вместе с этим, отбрасывание всех прицепленных к нему символов. 8.1.7.2 Аргумент = (EXPRESSION,CODE,DECIMAL) В этой форме использования функции DECIMAL интерпретируется как целое число и определяет количество цифр в дробной части числа, выво- димых справа от десятичной точки ПОСЛЕ округления числа. В этом смысле параметр DECIMAL аналогичен такому же параметру в функции $JUSTIFY. Если это DECIMAL меньше, чем число цифр в дробной части числа, то про- исходит округление числа до указанного числа знаков. Если этот пара- метр меньше, или равен нулю, то число округляется до целого значения, а десятичная точка подавляется. Если он больше, чем длина дробной час- ти числа, то дробная часть дополняется слева нулями до указанного чис- ла цифр. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Numbr=-3.1428 Write $FN(Numbr,"P",3)<- | │ │ |(3.143) | │ │o|>Write $FN(3+Numbr,"",2) |o│ │ |0.14 | │ │ |>Write $FN(Numbr*1000,",T",0)<0 | │ │o|3,143- |o│ └──────────────────────────────────────────────────────────────┘ Прим.8.28 $FNUMBER(EXPRESSION,CODE,DECIMAL) Стр. 204 Функция $FNUMBER действует аналогично функции $JUSTIFY, но при этом не выполняет всех ее действий. Часто эти функции комбинируются для получения сформатированного и выравненного по правому краю значе- ния. Но при этом необходимо быть внимательным, особенно при использо- вании округления в обоих функциях. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Value=1234.547 Write $J($FN,"+,"),12,2)<- | │ │ | +1,234.55 | │ │o|>Write $FN(Value,"",1),?15,$J($FN(Value,"",2),10,1)<- |o│ │ |1234.5 12345.6 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.8.29 Потенциальная возможность возникновения ошибок округления при совместном использовании $JUSTIFY и $FNUMBER Обратите внимание на различные результаты, производимые исполне- нием второй командной строки. Правильный результат из них - '1234.5', но "вложенная" $FNUMBER с округлением внутри $JUSTIFY с округлением производит два округления подряд и, в результате накапливается ошибка. 8.1.8 Функция $ASCII Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $ASCII │(EXPRESSION) │ $ASCII("Test")=84 │(EXPRESSION,POSITION)│ $ASCII(String,8)=110 │ │ ┌────────┐ Форма сокращения имени │ $A │ $A("W") Возвращает │ Число │ 67 └────────┘ Функция $ASCII возвращает число, соответствующее десятичному представлению кода ASCII символа, стоящего в строке EXPRSSION в пози- ции POSITION. EXPRESSION всегда интерпретируется как строка, POSITION - как целое число. Если POSITION отсутствует, его значение полагается равным 1 (соответствует получению кода ASCII первого символа в строке EXPRESSION). Если POSITION больше, чем длина строки, или меньше 1, то функция $ASCII возвращает -1. В приложении А приведен полный набор ко- дов ASCII и соответствующих им символов. Функция $CHAR (см. следующий раздел), являясь логической инверси- ей функции $ASCII конвертирует десятичное представление кода ASCII в соответствующий ему символ. Стр. 205 ┌──────────────────────────────────────────────────────────────┐ │ |>Set X="AbCd1-?"<- | │ │ |>For i=1:1 Write !,$E(X,i),?5,$A(X,i) Q:$A(X,i)=-1 | │ │o|A 65 |o│ │ |b 98 | │ │ |C 67 | │ │o|d 100 |o│ │ |1 49 | │ │ |- 45 | │ │o|? 63 |o│ │ | -1 | │ │ |>Write $E(X),"-",$A(X)<- | │ │o|A-65 |o│ │ |>Write $A(X,0)<- | │ │ |0 | │ │o|>Write $A(X,3.99)<- |o│ │ |67 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.30 $ASCII 8.1.9 Функция $CHAR Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $CHAR │(EXPRSSSION) │ $CHAR(65)="A" │(EXPRESSIO,EXPRESSION,...)│ $CHAR(65,66,67)="ABC" │ │ ┌────────┐ Форма сокращения имени │ $C │ $С(97,98,99) Возвращает │ Строку │ "abc" └────────┘ Функция $CHAR является логической инверсией функции $ASCII (см. предыдущий раздел) конвертирует десятичное представление кода ASCII,( задаваемое выражением EXPRESSION интерпретируемым как целое число), в соответствующий ему символ. (В приложении А приведен полный набор ко- дов ASCII и соответствующих им символов.) Если EXPRESSION оценивается как отрицательное число, функция $CHAR возвращает пустую строку ("") Согласно Стандарта MUMPS каждому целому значению EXPRЕSSION в ди- апазоне от 0 до 127, соответствует символ из основной части таблицы ASCII кодов. В различных реализациях значениям EXPRESSION лежащим в диапазоне 128-255 могут соответствовать различные символы, зависящие как от используемой дополнительной части таблицы ASCII кодов, так и от встроенных кодовых таблиц многих устройств ввода-вывода /*1/. В связи с этим обстоятельством MUMPS реализации ведут себя по разному, когда значение EXPRESSION превышает 127. ---------------------------------------------------------------------- *1 Используемые у нас MUMPS реализации имеют три основных варианта ASCII таблиц : - эксплуатируемые на СМ-ЭВМ имеют обычно измененную основную часть ASCII таблицы, где вместо малых латинских букв расположены большие русские. /продолжение на следующей странице/ Стр. 206 При этом часть реализаций оперирует строго с 7-ми битными символами и поэтому вычитает 128 из значений, превышающих 127 (при этом 128 прев- ращается в 0, 129 в 1 и т.д.) Другие реализации (особенно те, что экс- плуатируются на мини- и микро-компьютерах) возвращают для значений EXPRESSION лежащих в диапазоне 128-255 символ из расширенной части AS- CII-таблицы. Проверьте на своем компьютере, или найдите в документации как обрабатываются такие символы. В отличие от внешне сходной формы Write *CHAR при выводе Write $CHAR модифицирует значения системных переменных $X и $Y, как и при выводе обыкновенной строки. ┌──────────────────────────────────────────────────────────────┐ │ |>For Dec=64:3:70 Write $C(Dec,Dec+1,Dec+2)<- | │ │ |@ABCDEFGH | │ │o|>Set X="Convert to Upper Case",Y=""<- |o│ │ |>F i=1:1:$L(X) S Z=$E(X,i) S:Z?1L Z=$C($A(Z)-32) S Y=Y_Z<-| │ │ |>Write Y<- | │ │o|CONVERT TO UPPER CASE |o│ │ |>Set X=$C(34,65,98,-22,66,03,34) Write X,"-",$L(X)<- | │ │ |"AB" - 4 | │ │o|>Set X=$C(36,32,0,0,50,46,48,48) Write X,"-",$L(X)<- |o│ │ |$ 2.00 - 8 | │ └──────────────────────────────────────────────────────────────┘ Прим.8.31 $CHAR В примере 8.31 в строке значений преобразуемых в ASCII символы вставлены дважды значения = 0, для того, чтобы напомнить, что символ ASCII с десятичным значением кода =0 (NUL) не тоже самое, что и пустая строка /поскольку, хотя на экран он и не выводится но вставляется в создаваемую строку/. Большинство из управляющих символов /их десятич- ные коды меньше 32/ не выводятся на печать, и не отображаются на экра- не, либо непосредственно управляют действиями терминала. Так символ CR /ASCII=13/ на большинстве терминалов переводит курсор в левую позицию следующей строки и одновременно устанавливает системную переменную $Х в 0. Будьте внимательны при вставке управляющих символов в строку для вывода, это может привести к непредсказуемым искажениям выводимого текста и даже к возможному "зависанию" терминала и исполняемого про- цесса. /Кстати говоря, то, что говорится об управляющих кодах в диапа- зоне от 0 до 31 относится и к их аналогам в некоторых вариантах кодо- вых таблиц располагаемых на кодах 128-161/ ---------------------------------------------------------------------- /продолжения примечания к предыдущей странице/ Причем русские буквы расположены не в алфавитном порядке. Это свя- зано с тем, что большинство используемых терминальных устройств поддерживают только 7-ми битное кодирование. - Большинство из MUMPS реализаций, эксплуатируемых на IBM совмести- мых компьютерах используют так называемую ASCIIальтернативную таб- лицу кодов, где русские символы соответствуют кодом 128-239 с раз- рывом посередине на кодах 176-224Б которые соответствуют символам псевдографики. - Часть терминальных устройств, поддерживающих 8-ми битовое кодиро- вание, используют так называемую "болгарскую" кодировку, где русс- кие буквы соответствуют кодам 192-255 и идут не в алфавитном по- рядке, что заставляет заниматься "перекодировкой" вводимых и выво- димых данных с такого терминала. Вообще проблема "русификации" заслуживает отдельного рассмотрения и мы вернемся к ней в особом приложении. Стр. 207 8.1.10 Функция $TEXT Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │(LABEL) │ $TEXT(Label) $TEXT │(LABEL+OFFSET) │ $TEXT(Label+5) │(+OFFSET) │ $TEXT(+22) │([LABEL]^ROUTINE[+OFFSET])│ $TEXT(Start^Test+2) │ │ ┌─────────┐ Форма сокращения имени │ $T │ $TEXT(label) Возвращает │ Строку │ "label Set Age=35 Q" └─────────┘ Функция $TEXT возвращает текст командной строки программы в виде строки текста, для дальнейшей обработки. LABEL используется как аргу- мент или часть аргумента функции и представляет собой одну из меток программы. При этом желаемая метка не может быть задана в переменной. Если же Вы все таки желаете указать метку в переменной, то придется использовать косвенность для получения текста строки, ссылаясь на эту метку. Примеры, приведенные ниже поясняют формы использования функции. 8.1.10.1 Аргумент = (LABEL) В этой форме функция $TEXT возвращает строку исходного текста программы, отмеченную меткой LABEL. Отмеченная меткой строка должна присутствовать в текущей программе /загруженной в данный момент в раз- дел/. Функция возвращает полную строку программы, включая метку. В возвращаемой строке ВСЕ начальные символы строки (Раздел 6.2) заменены пробелами (ASCII=32), вне зависимости от вида начальных символов строк программы. Метка отделяется от остальной строки одним, или больше, пробелами. Если строка с меткой LABEL не определена в текущей програм- ме, функция возвращает пустую строку. ("") ┌──────────────────────────────────────────────────────────────┐ │ |Start Set L="Start" Write !,$T(Start) | │ │ |L Write !,$T(L) | │ │o| Write $T(@L),!,"Now $T(""Start"")",$T("Start") |o│ │ | Quit | │ │ |----------------------------------------------------------| │ │o|>Do Start |o│ │ |Set L="Start" Write !,$T(Start) | │ │ |Write !,$T(L) | │ │o|Set L="Start" Write !,$T(Start) |o│ │ |Now $T("Start") | │ │ | ERROR | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.8.32 $TEXT(LABEL) Обратите внимание на то, что в функции $TEXT LABEL - не общий случай выражения, а правильное имя метки. Стр. 208 Конструкция 'Write $T(L)' указывает системе на необходимость искать в текущей программе метку с именем L и вывести на экран строку ее исход- ного кода отмеченного этой меткой. При этом следует заметить, что ни- какой связи между меткой L и используемой в этой программе переменной L нет никакой связи. Конструкция 'Write $T("Start")' вызывает ошибку так как в метке НЕ может быть кавычек. 8.1.10.2 Аргумент = (LABEL+OFFSET) Эта форма функции в общих чертах аналогична описанной выше, за исключением того, что она возвращает строку, расположенной со смещени- ем, задаваемым целым числом OFFSET, от строки отмеченной меткой LABEL. Если метка не определена в текущей программе, то вне зависимости от указанного смещения функция $TEXT возвращает пустую строку (""). ┌──────────────────────────────────────────────────────────────┐ │ |Opts ; Выбор режима работы | │ │ | ; | │ │o| ;1) Ввод и редактирование данных |o│ │ | ;2) Поиск выбранных ссылок | │ │ | ;3) Печать результатов поиска | │ │o| ;4) Завершение работы |o│ │ | Write For i=0:1:5 Write $P($T(Opts+i),";",2,99) | │ │ |Ropt Read !!,"Введите номер режима:",opt Quit:"4"[opt | │ │o| . |o│ │ | . | │ │ | . | │ │o|----------------------------------------------------------|o│ │ |>Do Opts<- | │ │ | | │ │o| Выбор режима работы |o│ │ | | │ │ |1) Ввод и редактирование данных | │ │o|2) Поиск выбранных ссылок |o│ │ |3) Печать результатов поиска | │ │ |4) Завершение работы | │ │o| |o│ │ |Введите номер режима: | │ └──────────────────────────────────────────────────────────────┘ Прим.8.33 $TEXT(LABEL+OFFSET) В примере 8.33 отображен довольно часто используемый способ выво- да заголовков программ и других фиксированных полей данных. В нем иск- лючается необходимость повторного использования оператора Write. Ана- логичный подход часто используется для вывода на экран подсказок, свя- занных с текстом запроса. Но надо помнить о том, что для ряда "компи- лирующих" MUMPS-реализаций такое обращение к исходному тексту чревато потерей скорости исполнения программы, а текстовые строки можно хра- нить в глобальном массиве. Подробнее см. в разделе 8.1.10.5 8.1.10.3 Аргумент = (+OFFSET) Эта форма использования прямое продолжение предшествующей, в ко- торой опускается ссылка на метку LABEL. В этом случае OFFSET интерпре- тируется как номер строки программы /иными словами, в этом случае OFF- SET - это смещение от начала программы/.$TEXT(+1) Стр. 209 возвращает исходный текст первой строки текущей программы, (+2) - вто- рой, и так далее. Если в текущей программе нет строки с номером OFF- SET, то функция возвращает пустую строку. OFFSET равный 0 (+0) является особым случаем использования данной формой функции $TEXT, которая в этом случае возвращает имя текущей программы, а не строку ее текста. В главе 6 мы обсуждали ряд соглаше- ний по составлению текста программ, одним из которых была необходи- мость поддержания в первой строке программы метки, совпадающей с ее именем. Однако соглашение может быть нарушено, и программа будет запи- сана на диск под именем не совпадающим с меткой в первой своей строке. В этом случае $TEXT(+0) может быть использовано для получения того имени, с которым текущая программа записана на диске. Во всех случаях значение, возвращаемое $TEXT(+0), будет отличаться от значения, возв- ращаемого $TEXT(+1), так как имя программы не является правильной строкой текста программы. ┌──────────────────────────────────────────────────────────────┐ │ |Start ;!- Эта программа записана как TEST | │ │ | For off=0:1 Set X=$T(+Off) Q:X="" Write !,X | │ │o| Quit |o│ │ |----------------------------------------------------------| │ │ |>Do Start<- | │ │o|TEST |o│ │ |Start ;!- Эта программа записана как TEST | │ │ | For off=0:1 Set X=$T(+Off) Q:X="" Write !,X | │ │o| Quit |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.8.34 $TEXT(+OFFSET) 8.1.10.4 Аргумент = ([LABEL]^ROUTINE[+OFFSET]) Дополнение ранее описанных форм ссылкой на имя программы (^ROUTI- NE), позволяет получить строку исходного текста любой из программ за- писанных на диске./*1/ Все остальные аспекты использования этой формы функции совпадают с описанными выше возможностями. 8.1.10.5 $TEXT в "компилирующих" реализациях MUMPS систем В "компилирующих" MUMPS реализациях использование функции $TEXT имеет несколько особенностей. В компилирующих реализациях /*2/ при за- писи программы происходит ее "компилирование" и в дальнейшем на диске ОТДЕЛЬНО хранятся исходный текст программы, и "скомпилированный", ко- торый чаще называют объектным. Когда Вы редактируете программы, прос- матриваете ее на экране, или выводите ее текст на печать, то при этом используется исходный текст, при запуске программы на исполнение - объектный. Это разделение происходит незаметно для пользователя систе- мы. Но $TEXT всегда возвращает строку исходного кода программы, поэто- му, когда в текущей программе используется $TEXT, то происходит обра- щение к к той области диска, где хранится исходный текст. ------------------------------------------------------------------ *1 К которым пользователь может получить доступ. Обычно, без особых ухищрений, можно обращаться к программам своей области /КИПа/ и к библиотечным /если у них есть исходный код/. *2 Примеры "компилирующих" MUMPS систем : MSM - Micronetic's Standart MUMPS DTM DataTree MUMPS Стр. 210 Так как при исполнении программы в раздел загружается только ее объек- тный код. Это приводит к возникновению неприятной и довольно заметной задержки в исполнении программ. Другая, более существенная проблема состоит в том, что исходный код программы может быть преднамеренно удален, как это и производится в большинстве коммерческих пакетов в целях защиты своего программного обеспечения. В этом случае использование в программах $TEXT становится бессмысленным, так как она всегда будет возвращать пустую строку. Для преодоления этой проблемы в некоторых из реализаций приняты особые соглашения, следую которым можно указать на строку, которая должна быть вставлена в объектный код. Обычно это использование сдвоенной точки запятой, весь последующий комментарий будет помещен в объектный текст программы. 8.2 Функции работающие с элементами базы данных MUMPS системы В этом разделе мы рассмотрим некоторое количество специфических функций, работающих с элементами данных MUMPS. Раньше мы вкратце каса- лись описания уникальных особенностей базы данных MUMPS, в которой поддерживаются иерархические и разреженные массивы и отсутствия необ- ходимости инициализации переменных перед их использованием в MUMPS программах. Для того, чтобы заниматься поддержкой и написанием прик- ладных программ, требуется ясное понимание основных возможностей свя- занных с иерархическими структурами, и отказ от традиционной техноло- гии обработки последовательных файлов. 8.2.1 Функция $DATA Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $DATA │(VARIABLE) │ $DATA(Name) │ │ ┌────────┐ Форма сокращения имени │ $D │ $D(ABC(1,2)) Возвращает │ Число │ └────────┘ Функция возвращает число, характеризующее статус переменной VARI- ABLE использованной в аргументе функции. VARIABLE может быть любым правильным именем переменной, в том числе : именем локальной перемен- ной, индексированной переменной /элементом массива - (локального или глобального)/, а также именем массива (локального или глобального). Возвращаемое функцией значение указывает на то, существует переменная или нет, а также, если она существует, то связаны с ней данные или нет, имеются ли у нее "потомки" в массиве или нет. Функция $DATA может возвращать следующие значения : $DATA=0 В этом случае переменная, или элемент массива, использованные в аргументе функции - не существуют. Стр. 211 $DATA=1 Переменная или элемент массива существуют и содержат данные. $DATA=10 В аргументе указано имя массива, или элемент массива, которые не имеют при себе данных, и являются только ло- гическими указателями для прохода "вниз" к другим эле- ментам массива. Использование имени такой переменной в общем случае выражения EXPRESSION приведет к ошибке, так как при этом узле нет данных. $DATA=11 Использованное в аргументе имя массива /с индексами или без/, имеет при себе данные и одновременно является ло- гическим указателем для прохода к другим узлам массива. Использование имени такой переменной в общем случае вы- ражения EXPRESSION допустимо, так как при этом узле есть данные. Заметьте, что функция $DATA не возвращает тип данных содержащихся в указанной переменной, или элементе массива, - / то есть строка, це- лое и т.д/, а возвращает только флаг, указывающий содержится или нет данное при указанной ссылке. При этом Важно понять разницу между узла- ми массивов, содержащими данные и теми, что используются только как логические указатели к другим узлам индексной структуры. ┌──────────────────────────────────────────────────────────────┐ │ |Start K Set X=0,Y="",^X(1,2,3,4)=10 | │ │ | Write $D(X)," ",$D(Y)," ",$D(Z) | │ │o| Write !,$D(^X),",",$D(^X(1,2)),",",$D(^X(1,2,3,4)) |o│ │ | Set ^X(1,2)="Test" | │ │ | Write !,$D(^X(1,2))," ",$D(^X(1,2,3)) | │ │o| Quit |o│ │ |----------------------------------------------------------| │ │ |>Do Start<- | │ │o|1 1 0 |o│ │ |10,10,1 | │ │ |11,10 | │ │ |11,10 | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.35 $DATA Глава 10 (в особенности раздел 10.2.2), содержит дополнительные примеры поясняющие использование функции $DATA. 8.2.2. Функция $ORDER Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $ORDER │(SUBCRIPTED VARIABLE) │ $OREDER(^XYZ("one")) $ORDER │(SUBCRIPTED VARIABLE,EXPR) │ $OREDER(^XYZ("one",-1)) /*1/ │ │ ┌────────┐ Форма сокращения имени │ $O │ $O(ABC(1,2)) Возвращает │ Строку │ "NextSubscript" └────────┘ ----------------------------------------------------------------------- *1 Предложение MDC группы А 1991 года. Стр. 212 Так как данные записываются на диск в виде иерархических и разре- женных структур, то необходимо иметь некий механизм, который обеспечи- вает для программ возможность искать данные в таких структурах. Такую возможность обеспечивает, в частности, функция $ORDER. Читатель может также обратиться к описаниям функций $GET и $QUERY, которые также ис- пользуются для извлечения данных из массивов. Аргументом функции $ORDER является индексированная ссылка на ло- кальный или глобальный массив, функция возвращает следующий индекс СЛЕДУЮЩИЙ ЗА УКАЗАННЫМ НА ЭТОМ УРОВНЕ ИНДЕКСАЦИИ. Индексами в ссылке могут быть любые выражения (числа, строки), за одним исключением - в ссылке может задаваться в качестве индекса пустая строка (""). Пустая строка в качестве индекса зарезервирована для двух целей - она являет- ся начальной и конечной точкой обхода индексов при использовании функ- ций $ORDER и $QUERY - в массиве не может встретиться индекса = "пус- той" строке. Когда в локальный или глобальный массив записывается новое дан- ное, MUMPS автоматически вставляет в список индексов сопутствующий этому данному индекс в соответствии с установленным порядком сортиров- ки. /Сначала цифры, затем все строки, сначала латинские, затем русс- кие/. Используемая в MUMPS системах упорядочивающая последовательность в общих чертах совпадает с упорядочивающей последовательностью ASCII, описанной в разделе 5.3.2.3 (при описании оператора "следует" - ']'), за исключением некоторых нюансов, относящихся к упорядочиванию чисел. Полностью упорядочивающая последовательность MUMPS систем будет описа- на в разделе 10.2.1, сейчас же, для краткости, примем, что числа упо- рядочиваются в соответствии с порядком их возрастания, а строки - в соответствии с относительными позициями кодов символов, их составляю- щих, в таблице ASCII кодов. Например, если в глобальном массиве определено два узла - ^X("ABC") и ^X("GHI"), и мы желаем записать в качестве третьего узла - ^X("DOG")="Canine", то этот узел будет вставлен МЕЖДУ имеющимися. /Так строка "DOG" следует за строкой "ABC", но предшествует строке "GHI"/ Действие функции $ORDER мы будем пояснять на примерах, которые будут производить поиск в массиве показанном на Рис.8.1. ┌───┐ ^X └─┬─┘ ┌─────────────┴─────────────────────┐ ┌─┴─┐("ANIMALS") ┌─┴─┐ ("PLANTS") └─┬─┘ └─┬─┘ ┌───────┴──┬───────────┐ ┌──┴────────┐ ("GRASSES") │("BIRDS") │("MAMMALS")│("REPTILIES") │ ("TREES") │ ┌───┴───┐ ┌───┴───┐ ┌────┴──┐ ┌──┴────┐ ┌───┴────┐ │"Имеют │ │"Имеют │ │"Имеют │ │"Имеют │ │ "Без │ │ перья"│ │ мех" │ │чешую" │ │стволы"│ │стволов"│ └───────┘ └───┬───┘ └───────┘ └───────┘ └────────┘ ┌─────┴───┐ ("CATS")┌─┴─┐ ┌─┴─┐ ("DOGS") └─┬─┘ └─┬─┘ │ └─┬────────┬─────────────┬──────────┐ │ (23) │ (17) │(56) │(63) │(231) ┌───┴────┐ ┌──┴──┐ ┌───┴───────┐ ┌───┴────┐ ┌───┴─────────┐ │"Angora"│ │"Pug"│ │"Dalmatian"│ │"Beagle"│ │"Fox Terrier"│ └────────┘ └─────┘ └───────────┘ └────────┘ └─────────────┘ Рис. 8.1 Структура демонстрационного массива ^X Стр. 213 На этом рисунке прямоугольники с текстом представляют узлы массива со- держащие данные, пустые прямоугольники соответствуют узлам без данных - логическим указателям для прохода к другим узлам массива, располо- женным "ниже". Мы начинаем просмотр, указав в качестве стартового индекса пустую строку - Sub="". При первом проходе цикла For функция $ORDER возвраща- ет следующее значение /в соответствии с упорядочивающей последователь- ностью MUMPS/, индекса на этом уровне индексации, - ANIMALS. Это значение индекса помещается в переменную Sub. При последующем проходе цикла, $ORDER возвращает следующее за ANIMALS значение индекса - PLANTS. В третьем проходе цикла последующих значений индексов не обнаружено, поэтому $ORDER возвращает пустую строку, и обнаружив это значение мы прерываем цикл FOR с помощью Quit с постусловием. А теперь зададимся следующим вопросом - как получить все индексы второго уровня индексации глобального массива НИЖЕ узла "ANIMALS" ? ┌─────────────────────────────────────────────────────────────────┐ │ |>Set S1="ANIMALS",S2="" | │ │ |>For i=0:0 S S2=$O(^X(S1,S2)) Q:S2="" W !,S2,?10,^X(S1,S2)<-| │ │o|BIRDS Имеют перья |o│ │ |ANIMALS Имеют мех | │ │ |REPTILIES Имеют чешую | │ │o|> |o│ └─────────────────────────────────────────────────────────────────┘ Прим.8.37 $ORDER по более низкому уровню индексации массива Мы можем слегка расширить пример 8.37, превратив его в небольшую программу,/*1/ которая будет выводить на экран все индексы из массива ^X : ┌────────────────────────────────────────────────────────────────┐ │ |Start Set (S1,S2,S3,S4)="" | │ │ | F i=0:0 Set S1=$O(^X(S1)) Q:S1="" Do Level1 | │ │o| Write !,"Закончено" |o│ │ | Quit | │ │ |Level1 W !," ",S1 | │ │o| F i=0:0 Set S2=$O(^X(S1,S2)) Q:S2="" Do Level2 |o│ │ | Quit | │ │ |Level2 W !," ",S2 | │ │o| F i=0:0 Set S3=$O(^X(S1,S2,S3)) Q:S3="" Do Level3 |o│ │ | Quit | │ │ |Level3 W !," ",S3 | │ │o| F i=0:0 Set S4=$O(^X(S1,S2,S3,S4)) Q:S4="" Do Level4|o│ │ | Quit | │ │ |Level4 Write " ",S4 | │ │o| Quit |o│ │ |------------------------------------------------------------| │ │ |>Do Start<- | │ │o|ANIMALS BIRDS MAMMALS CATS DOGS 17 51 63 231 REPTILIES |o│ │ |PLANTS TREES GRASSES | │ │ |Закончено | │ │o|> |o│ └────────────────────────────────────────────────────────────────┘ Прим.8.38 $ORDER по нескольким уровням массива ------------------------------------------------------------------- *1 Еще раз хочется отметить, что конструкция 'For i=0:0 ... ' избыточ- на, хотя бы потому, что создается лишняя переменная, достаточно ис- пользовать безаргументный For. Стр. 214 $ORDER возвращает все существующие индексы на указанном уровне индексации, остается только определить имеется данное при этом узле или это только логический указатель для прохода к другим узлам гло- бальной структуры. Для этого обычно используется функция $DATA (См. раздел 8.2.1). 8.2.2.1 Аргумент = (SUBCRIPTED VARIABLE,EXPR) В соответствии с предложениями MDC Type A функция $ORDER может теперь использоваться с двумя аргументами. /Проверьте в документации на свою MUMPS систему, реализована ли в ней эта возможность./ Второй аргумент функции определяет направление обхода уровня массива. Если expr=1, то $ORDER осуществляет проход в прямом направлении /аналогично форме с одним аргументом/, если expr=-1 - в обратном. И в том и в дру- гом случае, $ORDER возвращает пустую строку, когда больше нет индексов на данном уровне индексации в заданном направлении прохода уровня. ┌──────────────────────────────────────────────────────────────┐ │ |>Set ^ABC(1)=1,^ABC(2)=2,^ABC(3)=3 | │ │ |>Write $O(^ABC(""),-1) | │ │o|3 |o│ │ |>Write $O(^ABC(3),-1) | │ │ |2 | │ │o|>Write $O(^ABC(2),-1) |o│ │ |1 | │ │ |>Write $O(^ABC(1),-1) | │ │o| |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. $ORDER(SUBCRIPTED VARIABLE,EXPR) 8.2.3 Функция $NEXT (устаревшая) Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $NEXT │(SUBSCRIPTED VARIABLE)│ $NEXT(^XYZ(230)) │ │ ┌────────┐ Форма сокращения имени │ $N │ $N(ABC(1,2)) Возвращает │ Строку │ "NextSubscript" └────────┘ Функция $NEXT предшественница функции $ORDER и НЕ должна исполь- зоваться в разрабатываемых вновь программах. Функция $ORDER полностью обеспечивает выполнение всех возможностей, ранее выполняемых функцией $NEXT. Разница между этими функциями состоит только в значениях индек- сов, которые для функции $NEXT являются стартовыми и конечными при об- ходе массива. Функция $NEXT использует в качестве стартового и конечного значе- ний индексов минус единицу (-1). Начиная с 1983 года минус единица яв- ляется разрешенным индексом в массивах /До этого MUMPS не мог исполь- зовать отрицательные индексы/, и функция $NEXT осталась для того, что- бы обеспечить совместимость с ранее написанным программным обеспечени- ем. Стр. 215 ┌──────────────────────────────────────────────────────┐ │ │ │ Используйте функцию $ORDER вместо функции $NEXT. │ │ │ └──────────────────────────────────────────────────────┘ 8.2.4 Функция $QUERY Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $QUERY │(GLOBAL REFERENCE) │ $QUERY(^X("ANIMALS")) │ │ ┌────────┐ Форма сокращения имени │ $Q │ $Q(^ABC(1,2)) Возвращает │ Строку │ "Next GLOBAL REFERENCE" └────────┘ Функция $QUERY используется для прохода через весь глобальный массив и возвращает следующую ПОЛНУЮ глобальную ссылку связанную с данным, расположенную в массиве за указанной в аргументе. Проход осу- ществляется "вниз и вправо" от указанной GLOBAL REFERENCE. Все ссылки, расположенные "НИЖЕ" указанной, возвращаются раньше, чем те, что рас- полагаются "ВПРАВО" на заданном уровне. Лучше всего продемонстрировать действие этой функции на примере, используя в качестве глобальный мас- сив, описанный на Рис. 8.1. ┌──────────────────────────────────────────────────────────────┐ │ |>Set Ref="^X" F i=0:0 S Ref=$Q(@Ref) Q:Ref="" W !,Ref<- | │ │ | | │ │o|^X("ANIMALS","BIRDS") |o│ │ |^X("ANIMALS","MAMMALS") | │ │ |^X("ANIMALS","MAMMALS","CATS",23) | │ │o|^X("ANIMALS","MAMMALS","DOGS",17) |o│ │ |^X("ANIMALS","MAMMALS","DOGS",51) | │ │ |^X("ANIMALS","MAMMALS","DOGS",63) | │ │o|^X("ANIMALS","MAMMALS","DOGS",231) |o│ │ |^X("PLANTS","TREES") | │ │ |^X("PLANTS","GRASSES") | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.8.39 $QUERY В отличии от функции $ORDER функция $QUERY возвращает не следую- щий индекс, а полную глобальную ссылку. Функция $QUERY рассматривает глобальную ссылку /GLOBAL REFERENCE/, задаваемую в аргументе и с по- мощью косвенности, как показано в примере. Кроме того, $QUERY обеспе- чивает как горизонтальный, так и вертикальный обход массива, причем возвращает только те узлы, которые связаны с данными, игнорируя узлы являющиеся логическими указателями в массиве. Для того, чтобы получить данные со ссылки, которая возвращается функцией $QUERY, достаточно использовать косвенность с возвращаемым значением. На следующем примере представлен фрагмент программы, в ко- тором производится обход ветки "PLANT" массива ^X и вывод на экран ссылок и связанных с ними данных. Стр. 216 ┌──────────────────────────────────────────────────────────────┐ │ |>Set Ref="^X(""PLANTS"")" | │ │ |>F i=0:0 S Ref=$O(@Ref) Q:Ref="" W !,Ref," = ",@Ref<- | │ │o| |o│ │ |^X("PLANTS","TREES") = Имеют стволы | │ │ |^X("PLANTS","GRASSES") = Без стволов | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим. 8.40 $QUERY и обход ветки массива Заметьте, что в отличие от функции $ORDER функция $QUERY возвра- щает пустую строку, только тогда, когда нет больше ссылок ниже, или вправо от заданной ссылки. 8.2.5 Функция $GET Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $GET │(VARIABLE REFERENCE) │ $GET(^X("XyZ")) │ │ ┌────────┐ Форма сокращения имени │ $G │ $G(^XYZ(1,22)) Возвращает │ Строку │ "Data" └────────┘ Функция $GET оценивает свой аргумент как ссылку на переменную /простую или индексированную, локальную или глобальную/, и возвращает значение этой переменной, если эта переменная определена и ей назначе- но данное, или пустую строку (""), если эта переменная не определена, или является логическим указателем в массиве. Функция $GET очень полезна при извлечении данных из локальных или глобальных массивов, так как позволяет производить эту операцию без осуществления дополнительных проверок с помощью $DATA. Если ссылка, к которой производится обращение, не существует, или является логическим указателем в массиве, то функция возвращает пустую строку, а не гене- рируется ошибка . Более подробное обсуждение использования функции $DATA для определения наличия данных при узле массива см. в разделе 8.2.1 ┌──────────────────────────────────────────────────────────────┐ │ |GetPat Set Pdata=$GET(^Pat(PatID)) | │ │ | If Pdata="" Write *7," --> Пациент не обнаружен." | │ │o| Quit |o│ │ |----------------------------------------------------------| │ │ |>Set PatID=1234 Do GetPat<- | │ │o|--> Пациент не обнаружен. |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.8.41 $GET В примере 8.42, который является функциональным эквивалентом действий, выполняемых фрагментом программы из примера 8.41 показаны варианты замены функции $GET. Стр. 217 ┌──────────────────────────────────────────────────────────────┐ │ |GetPat If $DATA(^Pat(PatID)) Set Pdata=^Pat(PatID) | │ │ | Else Set Pdata="" | │ │o| If Pdata="" Write *7," --> Пациент не обнаружен." |o│ │ | Quit | │ │ | | │ │o| - или - |o│ │ | | │ │ |GetPat Set Pdata=$S($D(^Pat(PatID)):^(PatID),1;"") | │ │o| If Pdata="" Write *7," --> Пациент не обнаружен." |o│ │ | Quit | │ │ |----------------------------------------------------------| │ │o|>Set PatID=1234 Do GetPat<- |o│ │ |--> Пациент не обнаружен. | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.42 Альтернативы применению $GET 8.3 Прочие функции Часть внутренних функций MUMPS не может быть отнесена ни к стро- ковым, ни к функциям для работы с базой данных. Поэтому мы отнесли их рассмотрение в этот раздел. Здесь мы будем рассматривать функцию $SE- LECT, используемую для выбора значений определяемых несколькими усло- виями, $RANDOM - генерирующую псевдослучайное число и $VIEW. Функция $VIEW и все прочие функции, чьи имена начинаются с симво- лов $Z..., специфичны для каждой из MUMPS реализаций. Однако функция $VIEW входит в MUMPS стандарт и должна быть определена в любой реали- зации, вне зависимости от интерпретации, или аргументов. В то время как $Z.. функции обычно уникальны для каждой из реализаций. Поэтому очень часто происходят ошибки, связанные с использованием в программах $VIEW или $Z.. функций, при перенесении программ из одной реализации в другую. $Z... функции вкратце описаны в разделе 8.4. 8.3.1 Функция $SELECT Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $SELECT │(EXPR:VAL,EXPR:VAL,...) │ $SELECT(A=1:1,1:"") │ │ ┌────────┐ Форма сокращения имени │ $S │ $S($D(XYZ):XYZ,1:"") Возвращает │ Строку │ "Значение XYZ" └────────┘ Аргументы функции $SELECT представляются парами : EXPRESSION:VA- LUE и разделяются между собой двоеточием, пары разделяются между собой запятыми. При исполнении, функция $SELECT начинает просматривать выра- жения EXPRESSION в этих парах начиная с самого левого в списке. Первое встреченное выражение, которое оценивается как ИСТИНА /не равно 0/, прерывает процесс просмотра и значение VALUE /может быть тоже MUMPS выражением/, соответствующее этому выражению возвращается как резуль- тат функции $SELECT. Стр. 218 В списке пар может быть произвольное количество пар EXPRESSI- ON:VALUE, но хотя бы в одной из них EXPRESSION должно быть ИСТИННЫМ /не нулевым/, в противном случае возникнет ошибка. При просмотре списка аргументов оценивание выражений производится до тех пор, пока не встретится ИСТИННОЕ, только в этом случае оценива- ется в этой паре выражение для VALUE, возвращаемое в качестве резуль- тата. Следовательно, ГЛОБАЛЬНЫЕ ССЫЛКИ в $SELECT могут оказывать, а могут и нет, влияние на указатель НЕПОЛНОГО ГЛОБАЛЬНОГО СИНТАКСИСА. Поэтому необходимо обеспечить переустановку указателя ГЛОБАЛЬНОГО син- таксиса непосредственно после исполнения функции $SELECT. ┌──────────────────────────────────────────────────────────────┐ │ |>Set X=234 W $S(X<0:"Отрицат.",X=0:"Нуль",1:"Положит.")<- | │ │ |Положит. | │ │o|>Set X=-2 W $S(X<0:"Отрицат.",X=0:"Нуль")<- |o│ │ |Отрицат. | │ │ |>Set X=1 W $S(X<0:"Отрицат.",X=0:"Нуль")<- | │ │o| ERROR |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.8.43 $SELECT Вторая строка в представленном примере исполняется, в то время как третья генерирует ошибку. Дело в том, что во второй командной строке одно из оцениваемых выражений оказывается истинным, в то время как в третьей во всем списке просмотренных выражений ни одно из них не оказывается истинным. Первое встреченное истинное выражение прерывает исполнение функции $SELECT. ┌──────────────────────────────────────────────────────────────┐ │ |>Set A=24,B=2 W $S(B:A/B,1:"Деление на 0")<- | │ │ |12 | │ │o|>Set B=0 W $S(B:A/B,1:"Деление на 0")<- |o│ │ |Деление на 0 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.44 Дополнительные примеры к использованию $SELECT Заметьте, что выражение в последней паре аргументов всегда не равно 0, и следовательно всегда ИСТИННО (1:"Деление на 0"). Представ- ленный подход гарантирует, что использование $SELECT никогда не вызо- вет ошибки при исполнении, какие бы сочетания условий не складывались в процессе исполнения программ. И наконец, еще раз вернемся к фрагмен- ту, представляющему замену использования функции $GET, где использует- ся функция $SELECT. ┌──────────────────────────────────────────────────────────────┐ │ |GetPat Set Pdata=$S($D(^Pat(PatID)):^(PatID),1;"") | │ │o| If Pdata="" Write " --> Новый пациент " |o│ │ | Quit | │ │ |----------------------------------------------------------| │ │o|>Set PatID=1234 Do GetPat<- |o│ │ |--> Новый пациент | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.45 Альтернатива применению $GET с использованием $SELECT Стр. 219 Заметьте, что подобное использование неполного глобального син- таксиса вполне оправдано, так как используемая ссылка ясно видна. И кроме того, указатель не может быть испорчен вставкой какого либо до- полнительного фрагмента программы при ее редактировании, так как пре- дыдущая полная глобальная ссылка непосредственно предшествует паре вы- ражений, где использован неполный глобальный синтаксис. 8.3.2 Функция $RANDOM Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $RANDOM │(RANGE) │ $RANDOM(100) │ │ ┌───────┐ Форма сокращения имени │ $R │ $R(33) Возвращает │ Число │ 20 └───────┘ Функция $RANDOM оценивает аргумент RANGE, как положительное целое число, и возвращает случайное целое число лежащее в диапазоне чисел от 0 до RANGE-1. В соответствии со стандартом значение RANGE, меньшее, чем 1 вызывает ошибку, но в некоторых реализациях этот запрет снят, и допускается задавать даже отрицательные значения RANGE. Обычно, если Вам необходимо получить отрицательное случайное число, достаточно до- бавить перед функцией $RANDOM знак минус /например - 'Set X=-$R(100)'/. Целое случайное число, возвращаемое функцией $RANDOM не может быть равным RANGE, например, используя $RANDOM с RANGE=100, Вы получи- те случайные числа в диапазоне 0-99, а не 0-100. Для того, чтобы сгенерировать случайные числа в диапазоне от -99.99 до +99.99 с точностью до двух знаков после запятой, Вы можете воспользоваться приемом, представленном в следующем примере: ┌──────────────────────────────────────────────────────────────┐ │ |Drnd ;Возвращает R как случайное число /-99.99 +99.99/ | │ │ | S R=$S($R(2):"-",1:"") | │ │o| S R=+(R_$R(100)_"."_$R(100)) |o│ │ | Quit | │ │ |----------------------------------------------------------| │ │o|>For i=1:1:10 D Drnd W R," "<- |o│ │ |-23.24 68.16 77.63 7.99 87.72 -6.34 90.19 -22.6 5.43 3.63 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.46 $RANDOM Обратите внимание на использование $R(2), это выражение возвраща- ет либо 0, либо 1, в связи с чем генерируемое случайное число может быть либо положительным ($SELECT возвращает либо "" - пустую строку, /знак '+' не обязателен/, либо знак '-'). Знак плюс перед выражением в предпоследней строке процедуры используется для отбрасывания лишних символов и приведению числа к каноническому виду. Того же результата можно добиться и используя внешнюю переменную /подобно использованию внешней функции, см. раздел 9.3/, генерирующую случайное число, как показано на следующем примере: Стр. 220 ┌──────────────────────────────────────────────────────────────┐ │ |Drnd() ;Возвращает R как случайное число /-99.99 +99.99/ | │ │ | Quit $R(10000)/100 | │ │ |----------------------------------------------------------| │ │o|>For i=1:1:10 W $S($i=1:"",1:","),$$Drnd<- |o│ │ |-23.24 68.16 77.63 7.99 87.72 -6.34 90.19 -22.6 5.43 3.63 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.47 $RANDOM во внешней переменной Внешние функции и переменные кратко обсуждались в предшествующих разделах и будут подробно рассмотрены в главе 9. 8.3.3 Функция $VIEW Функция │ Аргумент Пример ─────────┴────────────────────── ──────────────────────────────────── │ │ $VIEW │(в зависимости от кон- │ │кретной MUMPS реализа- │ │ции) │ ┌───────┐ Форма сокращения имени │ $V │ Возвращает │ │ └───────┘ Функция $VIEW определяется по разному в каждой MUMPS реализации, разработчики могут самостоятельно определять как синтаксис ее исполь- зования, так и возвращаемый ее результат. Поэтому, при необходимости обращайтесь к документации на свою MUMPS реализацию. Чаще всего $VIEW используется для прямого чтения из оперативной памяти или с диска, в паре с оператором VIEW прежде всего в системных программах. ПРЕДУПРЕЖДЕНИЕ: Функция $VIEW не совместима в различных MUMPS ре- ализациях. Избегайте ее применения в прикладных программах. Используй- те только при острой необходимости, выделяя все случаи ее использова- ния в отдельные процедуры, и хорошо их документируйте. 8.4 $Z... функции Все функции, чьи имена начинаются на $Z... специфичны для каждой из MUMPS реализаций. Будьте осторожны в применении подобных функций при написании переносимого программного обеспечения. Ниже приведены имена некоторых из $Z... функций, которые поддер- живаются в нескольких реализациях MUMPS: $ZCONVERT(string,mode) Преобразовывает все символы в строке string, в зависимости от параметра mode в большие /mode="U"/, или малые /mode="L"/ буквы. Эта функция может быть заменена использованием функции $TRANSLATE /см. раздел 8.1.5/ Стр. 221 $ZDATE(expr,mode) Оценивает expr как дату в формате $HOROLOG /5 цифр/, и возвращает ее во "внешней" /читаемой/ форме /вид возвращаемой формы даты управляется параметром mode/, например - "12/18/90" Математические функции $ZCOS Тригонометрический косинус угла $ZLOG Десятичный логарифм числа $ZLN Натуральный логарифм числа $ZSIN Тригонометрический синус угла $ZSQR Извлечение квадратного корня $ZTAN Тригонометрический тангенс угла и т.д.. В большинстве случаев эти функции появились до того момента, ког- да в MUMPS-стандарт была введена возможность создания и использования так называемых "внешних" функций и переменных /см. в главе 9 - Общая концепция использования "внешних" функций/. Сейчас эти функции /и мно- гие другие/ могут быть заменены развитой библиотекой "внешних" функ- ций. 8.5. Обобщение В этой главе мы обсудили аспекты применения большинства из дос- тупных программисту внутренних функций языка MUMPS, которые обеспечи- вают широкий набор возможностей для манипулирования строками и данными в приложениях. Когда функция оценивается во время исполнения программы, то возв- ращаемый ею результат может быть использован в другом выражении, то есть функции могут вкладываться друг в друга. В представленном примере продемонстрировано подобное использование внутренних функций для выде- ления первого символа имени, из строки содержащей адресную информацию. ┌──────────────────────────────────────────────────────────────┐ │ |>Set str="123;Smith,Jane;44 West Road;Ithaca;NY;14850"<- | │ │ |>Write $E($P($P(str,";",2),",",2),1)<- | │ │o|J |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 8.48 "Вложенное" использование функций Оценивание "вложенных" функций производится изнутри - наружу и представленная строка может быть представлена как комбинация следующих действий : 1. $P(str,";",2)="Smith,Jane" 2. $P("Smith,Jane",",",2)="Jane" 3. $E("Jane",1)="J" Хотя внутренние функции являются мощным механизмом обработки дан- ных, но, тем не менее, они не могут обеспечить всех потребностей воз- никающих при написании прикладных программ. В следующей главе мы будем обсуждать написание процедур и управление ими, а также способы созда- ния каждым программистом своих собственных функций, называемых "внеш- ними функциями". Стр. 222 Глава 9 Передача параметров и внешние функции Мы уже обсуждали концепцию среды локальных переменных, общей для всех программ вызываемых в разделе (Раздел 2.2.3), и исключения из этого правила (оператор DO с параметрами, раздел 3.3.2.2, оператор QU- IT для завершения внешних функций - раздел 3.3.3.2, оператор NEW - раздел 3.1.3, оператор JOB - раздел 3.5.3) В этой главе аспекты ис- пользования таблицы локальных переменных будет обсуждаться более под- робно. Оператор NEW и передача параметров - это одновременно и техноло- гия передачи информации в вызываемую процедуру, и способ использования в этой процедуре локальных переменных без опасности вступить в конф- ликт с уже определенными в разделе переменными с теми же именами. Су- ществует только три способа вызвать дополнительные процедуры : опера- тор JOB, оператор DO и вызов внешней функции /обращение к внешней пе- ременной/. Хотя каждая форма имеет свой специфический синтаксис ис- пользования, но все три - продолжение одной концепции. 9.1 Передача параметров в операторе DO Основная форма вызова процедур с передачей параметров при помощи оператора DO выглядит следующим образом : DO SUBROUTINE(PARAMETERs) где SUBROUTINE - имя вызываемой процедуры (в одной из форм - LABEL, ^ROUTINE или LABEL^ROUTINE), PARAMETERs- список передаваемых в эту процедуру параметров. Причем передача параметров в процедуру необяза- тельна, в общем случае, процедура может использовать переменные, уже существующие в разделе. Кроме того, все переменные, чьи значения изме- няются процедурой, остаются в своем измененном виде и после того, как управление вернется в вызывающую программу. Тоже положение относится и ко всем переменным, которые были вновь созданы в процедуре, или наобо- рот, удалены в ней. Исключения из этого общего правила будут относится только к двум случаям - при передаче параметров в процедуру в виде списка, и при использовании оператора NEW. При вызове процедуры и передаче ей параметров, тот список, кото- рый указывается в операторе DO при ее вызове называется списком ДЕЙС- ТВИТЕЛЬНЫХ параметров, соответствующий ему список параметров при имени ( 'LABEL(PARAMETER's)' ) вызываемой процедуры - списком ФОРМАЛЬНЫХ па- раметров. DO SUBROUTINE(Actual1,Actual2,Actual3,...ActualN) SUBROUTINE(Formal1,Formal2,Formal3,...FormalN) ┌─────────────────────────────────────────────────────────────────┐ │ Перечень параметров указываемый в операторе DO при │ │ вызове процедуры называется списком ДЕЙСТВИТЕЛЬНЫХ параметров, │ │ соответствующий ему перечень параметров при имени вызываемой │ │ процедуры - списком ФОРМАЛЬНЫХ параметров. │ └─────────────────────────────────────────────────────────────────┘ Точка входа в вызываемую процедуру должна начинаться с имени мет- ки, затем открывающаяся круглая скобка, список формальных параметров, разделенный запятыми, закрывающая круглая скобка. Стр. 223 После чего может быть вставлено произвольное количество стартовых символов строки программы, затем собственно строки процедуры. Каждый параметр из списка формальных параметров должен быть неиндексированным именем локальной переменной. ┌───────────────────────────────────────────────────────┐ │ Параметры в списке формальных параметров являются │ │ именами неиндексированных локальных переменных. │ └───────────────────────────────────────────────────────┘ Имена локальных переменных из списка формальных параметров не обязательно должны совпадать с соответствующими им параметрам из спис- ка действительных параметров. Каждой переменной из списка формальных параметров соответствует ЗНАЧЕНИЕ из списка действительных параметров. ┌──────────────────────────────────────────────────────────────┐ │ |Expon(Value) ;Возведение Value в степень Value | │ │ | New i,x Set x=1 | │ │o| For i=1:1:Value Set x=Value*x |o│ │ | Write Value,"^",Value,"=",x | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Do Expon(3)<- | │ │ |3^3=27 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.9.1 Вызов процедуры с передачей параметра Перед исполнением описанной процедуры MUMPS система выполняет не- явный NEW на все переменные из списка формальных параметров. Затем производится инициализация этих переменных с назначением им значений из списка действительных параметров. ┌───────────────────────────────────────────────────────┐ │ MUMPS система исполняет неявно оператор NEW для │ │ всех переменных из списка формальных параметров. │ └───────────────────────────────────────────────────────┘ Список действительных параметров может быть короче списка фор- мальных параметров. В том случае, если для имени переменной из списка формальных параметров нет соответствующей ей значения в списке дейс- твительных параметров, то она не будет создаваться перед началом ис- полнения процедуры. Хотя список действительных параметров может быть короче, чем список формальных параметров, но он не может иметь пропус- ков. Например, список вида Actual1,,Actual3 - некорректен, и "лишние" запятые НЕ могут включаться в список действительных параметров. ┌───────────────────────────────────────────────────────┐ │ Список действительных параметров может быть короче │ │ списка формальных параметров, но он не может иметь │ │ пропусков. │ └───────────────────────────────────────────────────────┘ Используя положение, что список действительных параметров может быть короче списка формальных параметров в процедуру можно передавать необязательные параметры. Стр. 224 В этом случае в процедуре должна быть использована явная проверка на наличие необязательных параметров. ┌──────────────────────────────────────────────────────────────┐ │ |Pwr(N,P) ;Возведение N в степень Р /По умолчанию Р=2/ | │ │ | New i,x Set x=N Set:'$D(P) P=2 | │ │o| For i=1:1:P-1 Set x=x*N |o│ │ | Write N,"^",P,"=",x | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Do Pwr(3,3)<- | │ │ |3^3=27 | │ │o|>Do Pwr(5)<- |o│ │ |5^2=25 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.9.2 Необязательные параметры При использовании пустого списка действительных параметров, при операторе DO все равно должны быть круглые скобки, заключающие между собой пустой список действительных параметров. /Например DO TEST()/. Дополнительный оператор NEW в теле процедуры используется для то- го, чтобы любые назначения переменных в теле процедуры не отразились на значениях переменных используемых в вызывающей программе. Нельзя использовать NEW для любой переменной из списка формальных параметров. /В противном случае эти переменные будут в процедуре неопределенными/. Оба NEW /явный и неявный/ сохраняют прежние значения упомянутых в ар- гументе переменных /или всех переменных/ раздела, обеспечивая возмож- ность создания новых значений переменных в процедуре с теми же имена- ми. Когда в процедуре встречаться оператор QUIT, завершающий ее испол- нение, то все прежние значения переменных за NEW-ченных в процедуре, восстанавливаются. Если переменная была определена до вызова процеду- ры, ей будет назначено старое значение, если она была неопределена, то она и останется неопределенной после возврата управления в вызывающую программу. ┌──────────────────────────────────────────────────────────────┐ │ |Pwr(N,P) ;Возведение N в степень Р | │ │ | New i,x Set x=N | │ │o| For i=1:1:P-1 Set x=x*N |o│ │ | Write N,"^",P,"=",x | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Set i=10,x=2 Do Pwr(i,x) Write !,"i=",i,?10,"x=",x<- | │ │ |10^2=100 | │ │o|i=10 x=2 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.9.3 Списки действительных и формальных параметров Существует только две формы назначения параметров в списке дейс- твительных параметров : параметры определяемые ЗНАЧЕНИЕМ, и параметры определяемые ССЫЛКОЙ. Эти формы в вызывающей программе различны и син- таксисом и исполняемым действием. В списке действительных параметров могут одновременно использоваться обе формы. Стр. 225 9.1.1 Передача параметров ЗНАЧЕНИЕМ Действительные параметры, передаваемые ЗНАЧЕНИЕМ, могут быть лю- бым корректным MUMPS выражением, результат оценивания этого выражения назначается соответствующей переменной из списка формальных парамет- ров. Если в действительных параметрах используются имена переменных, то эти имена не имеют НИКАКОЙ связи с именами переменных из списка формальных параметров. Так как на все переменные из списка формальных параметров исполняется неявный NEW, то процедура может произвольно из- менять значения этих переменных, в то время, как в вызывающей програм- ме они сохранять прежние значения. Таким образом, осуществляемая по- добным образом передача данных однонаправлена, вызывающая программа передает через список параметров информацию в процедуру, но не может получить назад результат через этот же список параметров. ┌─────────────────────────────────────────────────────────────────┐ │ Передача данных ЗНАЧЕНИЕМ - однонаправлена, передаваемые дан- │ │ ные доступны процедуре, но процедура не может использовать па- │ │ раметр для возврата результатов в вызывающую программу. │ └─────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────┐ │ |Display(Dlr) ;Отображение $J от Dlr, с запятой и '$' | │ │ | Set Dlr="$"_$FN(Dlr,",",2) | │ │o| Set Dlr=$J(Dlr,15) Write Dlr |o│ │ | Quit | │ │ |----------------------------------------------------------| │ │o|>Do Display(200*300.5)<- |o│ │ | $6,010.00 | │ │ |>S Pay=100,Tax=.05 W !,"Всего = " D Display(Pay*(1+Tax))<-| │ │o|Всего = $105.00 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.9.4 Параметры передаваемые значением 9.1.2 Передача параметров ССЫЛКОЙ Передача параметра ссылкой принципиальным образом отличается от передачи значением, в этом случае передаваемый параметр должен быть корректным именем локальной переменной или неиндексированным именем локального массива. Использование глобальных переменных и индексиро- ванных имен не допускается. Для того, чтобы указать что параметр пере- дает ссылку, а не значение, имени параметра должна предшествовать точ- ка (.), непосредственно перед именем переменной. ┌─────────────────────────────────────────────────────────────────┐ │ В списке действительных параметров, имени переменной, передаю- │ │ щей ссылку в процедуру, должен быть неиндексированным именем │ │ локальной переменной. Имени этой переменной должна предшество- │ │ вать точка - (.) │ └─────────────────────────────────────────────────────────────────┘ Когда параметр передается ссылкой, то в вызываемой процедуре, имя из списка формальных параметров непосредственно связывается со значе- нием переменной /массива/, в таблице символов раздела. Стр. 226 Причем все произведенные изменения, (вне зависимости от того, что имя в списке формальных параметров может не совпадать с именем передавае- мом в ссылке), непосредственно отражаются на исходной переменной /мас- сиве/. Таким образом осуществляется двусторонняя передача данных между вызывающей программой и процедурой. Отметим, что удаление в вызванной процедуре этой переменной, удалит ее и из таблицы символов вызывающей программы. Упрощенно передачу значений в процедуру в форме ссылки мож- но назвать еще одной формой использования косвенности. Действия произ- водятся с переменной /с именем из списка формальных параметров/, нахо- дящейся в процедуре, но все изменения, произведенные с ней, вернутся "назад" и отразятся на значении переменной /с именем из списка дейс- твительных параметров/, в вызывающей программе. ┌─────────────────────────────────────────────────────────────────┐ │ Когда параметр передается ССЫЛКОЙ, имя переменной из списка дей-│ │ствительных параметров связывается с именем переменной из списка │ │формальных параметров. Все изменения, произведенные в процедуре │ │с переменной из списка формальных параметров, отразятся и на │ │переменной из списка действительных параметров при возврате уп- │ │равления в вызывающую программу. │ └─────────────────────────────────────────────────────────────────┘ Так как точка (.) используется для указания вида передачи пара- метров в процедуру, она не может быть использована в качестве первого символа в передаваемых числах. Если Вы все-таки попытаетесь это сде- лать - возникнет ошибка. Для того, чтобы ее избежать, числовые литера- лы должны иметь первым символом '0', '+' или '-'. ┌──────────────────────────────────────────────────────────────┐ │ |Pwr(N,P) ;Возведение N в степень Р | │ │ | New i,x Set x=N | │ │o| For i=1:1:P-1 Set x=x*N |o│ │ | Write N,"^",P,"=",x | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Do Pwr(3,4)<- | │ │ |3^4=81 | │ │o|>Do Pwr(.5,2)<- |o│ │ | ERROR | │ │ |>Do Pwr(0.5,2)<- | │ │o|0.5^2=.25 |o│ │ |>Set X=.5 Do Pwr(X,2)<- | │ │ |.5^2=.25 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.9.5 Использование точки как первого символа передаваемого параметра. Когда мы пытаемся передать значение '.5' в процедуру, она интерп- ретирует его, как передаваемый ссылкой параметр /так как первым симво- лом в нем стоит точка - '.'/. А так как все передаваемые ссылкой пара- метры должны быть корректным именем переменной, а '.5' не является корректным именем переменной - возникает ошибка. Если в списке действительных параметров точка (.) требуется как флаг, указывающий на то, что параметр передается ссылкой, то в списке формальных параметров, использование точки НЕ допускается. В вызывае- мой процедуре не надо указывать вид передаваемых параметров. Стр. 227 ┌──────────────────────────────────────────────────────────────┐ │ |Avg(X,R) ;Перебрать узлы массива X, и вернуть результат | │ │ | ;в переменной Х как "Количество,Всего,Среднее" | │ │o| New i,n,s,t Set(n,t)=0,s="" |o│ │ | F i=0:0 S s=$O(X(s)) Q:s="" s n=n+1,t=t+X(s) | │ │ | Set R=n_","_t_","_(t/n) | │ │o| Quit |o│ │ |----------------------------------------------------------| │ │ |>Kill F i=1:1:10 Set Ary(i)=i*10<- | │ │o|>Do Avg(.Ary,.Ans) Write "Результат = ",Ans<- |o│ │ |Результат = 10,550,55 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.9.6 Массив, передаваемый в процедуру ссылкой Имя массива может передаваться в процедуру только ссылкой. Если мы уберем точку перед именем 'Ary' в вызове процедуры Avg, то получим ошибку вида , так как переменная Ary не определена. Все действительные параметры, передаваемые значением, должны быть опреде- лены перед исполнением оператора DO. В этом примере, мы передаем имя массива Ary, только для того, чтобы указать его процедуре для обработ- ки, но не возвращаем данные назад в программу. Когда массив передается ссылкой, то все произведенные с ним в процедуре изменения, отразятся на массиве с именем из списка действи- тельных параметров, когда управление вернется в вызывающую программу. ┌────────────────────────────────────────────────────────────┐ │ │ │ В процедуру передается массив целиком, если его имя │ │ передается ССЫЛКОЙ │ │ │ └────────────────────────────────────────────────────────────┘ Обратите внимание также и на то, что переменная Ans не определена перед передачей в процедуру ссылкой. Переменные, передаваемые ссылкой не должны быть определены перед исполнением оператора DO. Неопределен- ными будут и соответствующие им переменные из списка формальных пара- метров, при начале исполнения процедуры. ┌────────────────────────────────────────────────────────────┐ │ │ │ Переменные из списка действительных параметров, переда- │ │ ваемые ССЫЛКОЙ, не должны быть определены перед вызовом │ │ процедуры. │ │ │ └────────────────────────────────────────────────────────────┘ Хотя мы не можем передать в процедуру ссылку на массив ЗНАЧЕНИЕМ, но мы можем передать выражение, содержащее ссылку на массив, и затем использовать косвенное задание индексной ссылки, для достижения тех же результатов. Представим это в следующем примере, где передаем в проце- дуру Avg имя массива для обработки значением, а не ссылкой. Стр. 228 ┌──────────────────────────────────────────────────────────────┐ │ |Avg(X,R) ;Перебрать узлы массива X, и вернуть результат | │ │ | ;в переменной Х как "Количество,Всего,Среднее" | │ │o| New i,n,s,t Set(n,t)=0,s="" |o│ │ | F S s=$O(@X@(s)) Q:s="" s n=n+1,t=t+@X@(s) | │ │ | Set R=n_","_t_","_(t/n) | │ │o| Quit |o│ │ |----------------------------------------------------------| │ │ |>Kill F i=1:1:10 Set Ary(i)=i*10<- | │ │o|>Do Avg("Ary",.Ans) Write "Результат = ",Ans<- |o│ │ |Результат = 10,550,55 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.9.7 Массив, передаваемый в процедуру значением Массив, с именем Ary, передается в процедуру значением, как стро- ковой литерал и используется в косвенном задании ссылки. Подробнее о косвенном задании индексной ссылки в разделе 7.2.4. 9.2. Внешние функции Внешние функции подобны использованию оператора DO с передачей параметров, за исключением того, что по завершению исполнения вызов внешней функции заменяется ее результатом. С логической точки зрения внешние функции обрабатывая выражение /-ия/, возвращают одно значение, подобно внутренним функциям /См. главу 8/ Внешние функции не являются элементом языка, они могут быть напи- саны как разработчиками конкретной MUMPS реализации, так и прикладным программистом /в отличии от внутренних функций, которые являются частью языка и доступны во всех системах/. Вызов внешней функции выг- лядит следующим образом: $$FUNCTION(PARAMETERs) Каждый вызов внешней функции начинается с двух знаков доллара ($$), затем указывается имя функции (FUNCTION) и список ее параметров (PARAMETERs), заключенный в круглые скобки. Также как и для оператора DO имя внешней функции (FUNCTION) может быть задано в виде LABEL, ^RO- UTINE и LABEL^ROUTINE. Имя функции указывает на программу /фрагмент программы/, исполняемую при обращении к внешней функции. Два знака доллара в имени внешней функции не являются частью метки программы, связанной с этим именем. Также как и для оператора DO список парамет- ров внешней функции является не чем иным, как списком действительных параметров. А список параметров при имени вызываемой программы /фраг- мента программы/ - списком формальных параметров. В обоих списках па- раметры разделяются друг от друга запятыми. Set A=$$FUNCTION(Actual1,Actual2,Actual3,...ActualN) FUNCTION(Formal1,Formal2,Formal3,...FormalN) . . . Quit EXPRESSION Стр. 229 Так же как и процедура, вызываемая по оператору DO, все внешние функции должны начинаться со строки, отмеченной меткой. За именем мет- ки указывается заключенный в круглые скобки список формальных парамет- ров /Если он имеется, при его отсутствии после открывающей круглой скобки сразу указывается закрывающая, например вот так - LABEL() /, затем один или больше начальных символов строки программы, а затем собственно строка программы. Но, в отличие от процедур вызываемых по DO, завершается внешняя процедура по оператору QUIT с аргументами. Аргументом оператора QUIT может быть любое корректное MUMPS выражение. Значение, получаемое в результате оценивания этого выражения, подставляется вместо имени внешней функции в точке ее вызова. Рассмотрим следующий пример: ┌──────────────────────────────────────────────────────────────┐ │ |%ZZM ;JML-NYSCVM | │ │ | ;Утилиты содержащие внешние функции | │ │o|Abs(X) ;Возвращает абс.значение Х /Если Х<0, то Х=-Х/ |o│ │ | Quit $S(X<0:-X,1:X) | │ │ |----------------------------------------------------------| │ │o|>For i=10,-23.4,17.6,-.34 W !,i,?5,$$Abs^%ZZM(i)<- |o│ │ | | │ │ |10 10 | │ │o|-23.4 23.4 |o│ │ |17.6 17.6 | │ │ |-.34 .34 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.9.8 Внешние функции Внешние функции во всем подобны процедурам вызываемым по операто- ру DO, за исключением : 1. Возвращаемое значение заменяет вызов внешней функции. Возвращае- мое значение является результатом оценки выражения использованного в аргументе оператора QUIT завершающего внешнюю функцию. Внешняя функ- ция всегда должна завершаться оператором QUIT с параметром. Исполь- зование для завершения процедуры безаргументного QUIT вызывает ошиб- ку. 2. Значение системной переменной $TEST сохраняется перед вызовом внешней функции, и восстанавливается перед возвращением управления в вызывающую программу. Это важная особенность внешних функций и отли- чие от вызова процедур по DO. Когда вызывается процедура по DO, то использованные в ней операторы IF, а также LOCK, READ, JOB и OPEN с временем ожидания, могут изменить значение системной переменной $TEST. При вызове внешней функции, вне зависимости от используемых в ней операторов, значение $TEST сохраняется перед вызовом функции, и восстанавливается при возврате управления в вызывающую программу. ┌─────────────────────────────────────────────────────────────────┐ │ Значение, заменяющее вызов внешней функции, является результа- │ │том оценивания MUMPS выражения, использованного в аргументе опе- │ │ратора QUIT завершающего внешнюю функцию. │ └─────────────────────────────────────────────────────────────────┘ Стр. 230 ┌─────────────────────────────────────────────────────────────────┐ │ Значение системной переменной $TEST сохраняется перед вызовом │ │внешних функций и восстанавливается ее исходное значение перед │ │возвратом управления в вызывающую программу. │ └─────────────────────────────────────────────────────────────────┘ Также как и при вызове процедур по DO, во внешнюю функцию пара- метры могут передаваться ЗНАЧЕНИЕМ и ССЫЛКОЙ. Все положения описанные в разделах 9.1.1 и 9.1.2 применимы к передаче параметров во внешнюю функцию. То же относится и описанным выше правилам использования опе- ратора NEW для сохранения переменных вызывающей программы. Так как внешние функции обычно возвращают свои результаты значением, то внеш- ние функции могут использоваться для реализации любых MUMPS функций, включая ввод-вывод, обращения к глобальным массивам и так далее. Ре- зультаты работы внешней функции могут также возвращаться назад и через список параметров, также как и для процедур вызываемых по DO. Рассмот- рим на следующем примере внешнюю функцию, которая вычисляет среднее абсолютное значение элементов массива, и кроме этого, все отрицатель- ные значения элементов массива конвертируются в положительные. ┌──────────────────────────────────────────────────────────────┐ │ |AvgAry(A) ;среднее значение по модулю из А | │ │ | ;кроме того, отрицательные -> в положительные | │ │o| New i,n,s,t,x Set (n,t)=0,s="" |o│ │ | F i=1:1 Set s=$O(A(s)) Q:s="" S x=A(s) D AddX | │ │ | Quit t/n | │ │o|AddX If x<0 Set x=-x,A(s)=x |o│ │ | Set n=n+1,t=t+x | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Kill F i=1:1:25 Set Ary(i)=$S($R(2):"-",1:"")_i<- | │ │ |>Set Neg=0 F i=1:1:25 If Ary(i)<0 Set Neg=Neg+1<- | │ │o|>Write Neg<- |o│ │ |12 | │ │ |>Write $$AvgAry(.Ary)<- | │ │o|>13 |o│ │ |>Set Neg=0 F i=1:1:25 If Ary(i)<0 Set Neg=Neg+1<- | │ │ |>Write Neg<- | │ │o|0 |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.9.9. Передача параметров во внешнюю функцию Обратите внимание на использование оператора NEW для сохранения переменных вызывающей программы. Заметьте также, что Ary передается в AvgAry ССЫЛКОЙ, во-первых потому, что это массив, а во вторых потому, что результат должен возвратиться обратно в вызывающую программу. Ко- нечно, фрагмент программы, связанный с меткой AddX, это тоже подпрог- рамма. Настоящая точка выхода из внешней функции - оператор QUIT, в строке предшествующей строке с меткой AddX. Стр. 231 9.3 Внешние переменные Внешние переменные это не что иное, как разновидность внешних функций. В этом случае в подпрограмму, связанную с именем внешней пе- ременной, НЕ передается никаких параметров, поэтому при вызове внешней переменной не требуется использования круглых скобок. Фрагмент прог- раммы, соответствующий имени внешней переменной должен начинаться с метки, после которой должны быть открывающая и закрывающая круглые скобки. Представим пример создания и использования внешней переменной, где она применяется для перевода показаний системных часов в более "читаемую" форму. ┌──────────────────────────────────────────────────────────────┐ │ |Time() ;Перевод $Н в ЧЧ:ММ /ЧЧ = 0-24 часа/ | │ │ | New minutes,hours,seconds Set seconds=$p($H,",",2) | │ │o| Set hours=seconds\3600 |o│ │ | Set minutes=seconds-(hours*3600)\60 | │ │ | Set minutes<10 minutes="0"_minutes | │ │o| Quit hours_":"_minutes |o│ │ |----------------------------------------------------------| │ │ |>Write $$Time<- | │ │o|12:59 |o│ │ |>Write $$Time<- | │ │ |13:01 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.9.10 Внешние переменные 9.4 Библиотеки подпрограмм После того, как мы разобрали варианты передачи параметров и нюан- сы использования оператора NEW в языковых конструкциях MUMPS, то те- перь возможно обсудить концепцию создания библиотек стандартных прог- рамм, которые будут вызываться независимо от среды вызова. Такие биб- лиотеки могут содержать программы необходимые для пользовательских приложений. В этом разделе мы будем обсуждать некоторое количество подпрог- рамм, внешних функций, и внешних переменных, которые Вы можете вклю- чить в собственную библиотеку подпрограмм и использовать для развития своих прикладных программ. Основной целью данного раздела является де- монстрация возможностей языка MUMPS, которые были представлены в пред- шествующих разделах, в конкретных примерах, где эти возможности актив- но используются. 9.4.1 Преобразование системной переменной $Н в более читаемый формат. В предыдущем примере мы уже представили пример процедуры преобра- зующей значение системной переменной $H, где дата и время содержатся во внутреннем представлении (5 цифр - число дней истекших с 31 декабря 1841 года и число секунд истекших с прошлой полуночи) в более "читае- мый" формат. Этот пример мы расширим, для более общего использования, дополнив процедуру следующими функциями: Стр. 232 ■ Процедура будет преобразовывать любое выражение, задающее в фор- мате $H дату и время. Если не передается никакого выражения, то процедура должна преобразовывать текущие дату и время из систем- ной переменной $H. ■ В процедуре будет производиться управление форматом преобразова- ния времени (24-х часовой формат, или 12-ти часовой с указанием, до - AM, или после полудня - PM). Если формат не будет задан, то по умолчанию будет использоваться 24-х часовой формат. ■ В процедуре будет обеспечиваться механизм передачи информации об ошибке, если передаваемое выражение не сможет быть корректно пре- образовано. ┌────────────────────────────────────────────────────────────────┐ 1 │ |TimeFH(Secs,Format) ;преобразование секунд от полуночи | │ 2 │ | New ampm,hours,minutes | │ 3 │o| Set:$S('$D(Secs):1,Secs="$H":1,1:0) Secs=$P($H,",",2) |o│ 4 │ | Set Format=$S('$D(Format):24,Format=12:12,1:24) | │ 5 │ | Set:Secs=0 Secs=86400 | │ 6 │o| If Secs<1!(Secs>86400) Set Time="" Goto TimeFHX |o│ 7 │ | Set hours=Secs\3600,minutes=Secs-(hours*3600)\60 | │ 8 │ | Set ampm=$S(Format=24:"",hours<12:" AM",1:" PM") | │ 9 │o| If Format=24 Set hours=$S(hours<10:"0",1:"")_hours |o│ 10│ | Else Set hours=hours-$S(hourse>12:12,1:0) | │ 11│ | Else Set hours=$S(hours<10:" ",1:"")_hours | │ 12│o| Set minutes=$S(minutes<10:"0",1:"")_minutes |o│ 13│ | Set Time=hours_":"_minutes_ampm | │ 14│ |TimeFHX Quit Time | │ │o|------------------------------------------------------------|o│ │ |>Write $$TimeFH()<- | │ │ |13:22 | │ │o|>Write $$TimeFH("$H",12)<- |o│ │ | 1:22 PM | │ │ |>Write $$TimeFH(81000)<- | │ │o|22:30 |o│ │ |> | │ └────────────────────────────────────────────────────────────────┘ Прим. 9.11 Внешняя функция преобразующая время из формата $Н /Описание - в приложении G./ В этом примере продемонстрировано сразу несколько из возможностей MUMPS, о которых шла речь в этой главе, в том числе - число переменных в списке действительных параметров, передаваемом в процедуру. Мы уже упоминали ранее, что число действительных параметров, не обязательно должно соответствовать числу формальных параметров. В том случае, ког- да список действительных параметров, короче, чем список формальных, то переменные, с именами, для которых не нашлось соответствующего элемен- та в списке действительных параметров, не будут при старте процедуры определены. В строках 3 и 4 примера производится проверка на существо- вание параметров Secs и Format. Если они не определены, то им назнача- ются значения, принимаемые по "умолчанию". Хорошее описание процедурам необходимо в том случае, если Вы со- бираетесь активно их применять при построении прикладных программ. Стр. 233 Программист должен знать к какому типу относится процедура (вызывается ли она как внешняя функция, или как внешняя переменная, или ее необхо- димо вызывать по DO), какие параметры в нее передаются, и какие из па- раметров являются обязательными, а какие - нет. (А также значения, назначаемые необязательным параметрам по умолчанию) Кроме того, необ- ходимо знать, как возвращается результат работы процедуры. Описание для этого примера, (также, как и многих других, приводи- мых в этой главе), представлено в приложении G. Это описание содержит краткую информацию о функциях, выполняемых процедурой, и виде ее вызо- ва. Кроме информации, представляемой в приложении G, описание процеду- ры может содержать - описание алгоритма ее работы, используемые в ней переменные, и т.д. Как уже упоминалось, хоть список действительных параметров может быть короче, чем список формальных, но в нем не может быть пропусков. В следующем примере продемонстрирован некорректный список действитель- ных параметров, вызывающий ошибку. ┌──────────────────────────────────────────────────────────────┐ │ |>Write $$TimeFH(,12)<- | │ │ | ERROR | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.9.12 Пропущенный параметр Так как все параметры в описанной процедуре являются необязатель- ными, то она может быть также вызвана как внешняя переменная, возвра- щающая текущее время в 24-х часовом формате. ┌──────────────────────────────────────────────────────────────┐ │ |>Write $$TimeFH<- | │ │ |16:07 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.9.13 Использование $$TimeFH без необязательных параметров А теперь мы рассмотрим более сложный процесс преобразования даты из формата $Н (дни, истекшие с 31 декабря 1840), в более читаемый фор- мат (Месяц/День/Год). Преобразование усложняется тем, что месяца в го- ду имеют различное число дней, и, кроме того, приходится учитывать ви- сокосные годы, для которых в феврале появляется "лишний" день. Висо- косными являются только те года, которые без остатка делятся на 4, за исключением годов перемен столетий, которые будут високосными, если только без остатка делятся на 400. В примере 9.14 представлена проце- дура преобразования даты из формата $H в формат Месяц/День/Год (напри- мер 5/22/90). Обратите внимание на две проверки; - на год перемены ве- ка -1900, при вычислении числа високосных дней (Ldays), и на то, что является ли текущий год високосным, или нет. Значение 21608 соответс- твует 2/28/1900, которые не был високосным годом (не делится без ос- татка на 400). Кроме того, разберите сочетание операций в строке Da- teFH+7, использованное при проверке - является ли текущий год високос- ным, или нет. /* Отметим, что можно создать более "элегантный" алго- ритм преобразования даты/ Стр. 234 ┌──────────────────────────────────────────────────────────────┐ │ |DateFH(Date) ;Преобразование даты из $Н в "ММ/ДД/ГГ" | │ │ | New Days,i,Ldays,leap,mon,Year,Years | │ │o| Set Date=+$S('$D(Date):$H,Date="":$H,1:Date) |o│ │ | If Date '?1.5N!'Date Set results="" Goto DateX | │ │ | Set Ldays=Date\365\4-(Date>21608) ;Число високосных | │ │o| Set Years=Date-Ldays-1\365 ;Число лет после 1841 |o│ │ | Set Days=Date-(Years*365)-Ldays ;Дней в текущем году | │ │ | Set Year=Year+1841,leap='(Year#4)*(Year'=1900),mon=1 | │ │o| For i=31,28+leap,31,30,31,30,31,31,30,31,30 Quit:i'Dec | │ 5 │ | Set:'$D(Range) Range="" Set:Range="" Range="-1E25:1E25" | │ 6 │o| If $P(Range,":")'="" Goto NumChkE:Data<$P(Range,":") |o│ 7 │ | If $P(Range,":",2)'="" Goto NumChkE:Data>$P(Range,":",2)| │ 8 │ | Set Data=+Data Goto NumChkX | │ 9 │o|NumChkE Set Data="" |o│ 10│ |NumChkX Quit Data | │ └─────────────────────────────────────────────────────────────────┘ Прим.9.15 Внешняя функция для проверки числовых значений /Описание - в приложении G./ 9.4.3 Преобразование чисел между системами счисления Так как MUMPS всегда использует только десятичную систему предс- тавления чисел, то иногда становится необходимым производить преобра- зование чисел из десятичной в другие системы счисления (например вось- меричную, или шестнадцатиричную). В примере 9.16 демонстрируется один из методов, которые могут быть для этого использованы. Эта подпрограм- ма имеет две возможные точки входа, если она вызывается Do ^%Convert, то производится запрос пользователю на ввод преобразуемого значения, его системы счисления, и системы счисления в которую необходимо произ- вести преобразование значения; вторая точка входа используется как внешняя функция (Cnvrt^%Convert) и может быть вызвана из других прог- рамм с передачей числа и систем счисления. ┌──────────────────────────────────────────────────────────────┐ │ |%Convert ;JML-NYSCVM | │ │ | ;Преобразования чисел между системами счисления | │ │o| New num,from,to,result |o│ │ |Read Read !,"Преобразуемое число: ",num Quit:num="" | │ │ | Read ?30,"ИЗ сист.счисления: ",from Quit:from="" | │ │o| Read ?45," В сист.счисления: ",to Quit:to="" |o│ │ | Set result=$$Cnvrt(num,from,to) | │ │ | Write " = ",$S(result'="":result,1:" ОШИБКА ввода") | │ │o| Goto Read |o│ │ | ; | │ │ |Cnvrt(n,f,t) ;Преобр.полож.целого 'n' по основанию 'f' | │ │o| ;к основанию 't', если ошибка возвр.пустую строку |o│ │ | If n<0!(f<2)!(f>16)!(t<2)!(t>16)!(+n[".") Goto ConX | │ │ | Set n=$$Dec(n,f) If t=10 Set ans=n Goto ConX | │ │o| For Set x=n#t,n=n\t,ans=$S(x>9:$E("ABCDEF",x-9),1:x|o│ │ | ---)_ans Quit:'n | │ │ |ConX Quit ans | │ │o|Dec(n,f) ;Преобразов.'n' по основ. 'f' в десятичн. |o│ │ | New ans,i Set ans=0 If f=10 Set ans=n Goto DecX | │ │ | For i=1:1:$L(n) Set ans=ans*f+($F("01234567890ABCDEF| │ │o| ---",$E(n,i))-2) |o│ │ |DecX Quit ans | │ └──────────────────────────────────────────────────────────────┘ Прим.9.16 Преобразование чисел между системами счисления /Описание - в приложении G./ Стр. 236 Подпрограмма может быть использована для преобразования целых по- ложительных чисел из одной системы счисления в другую. Системы счисле- ния могут лежать в диапазоне от 2 /двоичная/, до 16 /шестнадцатирич- ная/. Обратите внимание на использование конструкции +n["." в строке Cnvrt+3, для проверки на целые числа. Впрочем для подобной проверки можно также использовать и другую конструкцию - n\1'=n 9.4.4. Простая статистика Перейдем к разбору более сложных примеров, рассмотрим подпрограм- му, которая может быть использована для статистических подсчетов со- держания списка чисел среди элементов массива /локального или глобаль- ного/. ┌────────────────────────────────────────────────────────────────┐ 1 │ |Stats(Ref,Results) ;Статистический подсчет в уэлах массива | │ 2 │ | New High,i,Low,Mean,Num,StDev,StdErr,s,Sum,SumSQ,Var | │ 3 │o| Set High=-1E25,Low=1E25,(Sum,SumSQ,Num)=0,s="" |o│ 4 │ | For i=0:0 Set s=$O(@Ref@(s)) Q:s="" Do StatsV(@Ref@(s))| │ 5 │ | If 'Num Set Results="" Goto StatsX | │ 6 │o| Set Mean=Sum/Num,StdErr=Mean/$$SQroot(Mean) |o│ 7 │ | Set (StDev,Var)="" | │ 8 │ | If Num>1 Set Var=-Num*Mean*Mean+SumSQ/(Num-1) | │ 9 │o| If Set StDev=$$SQroot(Var) |o│ 10│ | Set Results=Num_";"_Low_";"_High_";"_Mean | │ 11│ | Set Results=Results_";"_Var_";"_StDev_";"_StdErr | │ 12│o| Goto StatsX |o│ 13│ |StatsV(Val) ;Обработка одного значения | │ 14│ | Set Val=$$NumChk^%Zu(Val) Quit:Val="" | │ 15│o| Set Num=Num+1,Sum=Sum+Val,SumSQ=Val*Val+SumSQ |o│ 16│ | Set:ValHigh High=Val | │ 17│ | Quit | │ 18│o|StatsX Quit |o│ └────────────────────────────────────────────────────────────────┘ Прим. 9.17 Простая статистика /Описание - в приложении G./ Эта подпрограмма может вызываться следующим образом: Do ^Stats("Data(1)",.Results) В подпрограмме анализируются значения, записанные в указанном массиве /ветви массива/, (в данном примере вызова в ветви массива ^Data - ^Da- ta(1,1), ^Data(1,2) и т.д.), результат анализа возвращается в перемен- ной с именем, задаваемым вторым параметром вызова подпрограммы. (то есть в данном случае - Results). Имя обрабатываемого массива передает- ся значением, а место для помещения результата задается ссылкой. Но ранее в этой же главе мы говорили, что в процедуру не может переда- ваться ссылка на массив /локальный или глобальный" с индексами, не в виде значения, не в виде ссылки. Противоречия в этом нет и оба положе- ния правильны. В данном случае индексированная ссылка на массив пере- дается значением в процедуру как строковый литерал, и будет использо- ваться с косвенным заданием аргумента. Косвенное задание индексов используется в строке 4 подпрограммы Stats. Такая форма использования косвенности позволяет задавать почти любую структуру массива для обработки. Стр. 237 Просматриваемые индексы могут быть самыми различными - строками симво- лов. последовательно или не последовательно возрастающими числами. Косвенное задание индексной ссылки вместе с функцией $ORDER [$Or- der(@Ref@(s))] позволяет переходить от одного индекса к другому и по- лучать значения данных при них. В создаваемой процедуре предполагается, что значения, связываемые с узлами массива содержаться в виде отдельных чисел, а не строк из нескольких полей. Впрочем, Вы можете добавить в процедуру необязатель- ные параметры, задающие разделитель в таких строках, и номер подстроки для выделения. Обратите внимание на экспоненциальное задание начальных значений переменным High и Low. Таким образом этим переменным назначаются соот- ветственно минимально и максимально доступные числовые значения, раз- решенные стандартом языка. Из тела подпрограммы Stats производится вызов двух других проце- дур - StatsV - для обработки одного данного, и SQroot - для вычисления значения квадратного корня от задаваемого числа. Кроме того, в проце- дуре StatsV производится вызов внешней функции NumChk (см. пример 9.15) для проверки числового значения на корректность. Для завершения построения подпрограммы обработки статистики мы нуждаемся во внешней функции SQroot ┌────────────────────────────────────────────────────────────────┐ 1 │ |SQroot(Num) ;Возвращает корень квадратный от abs(Num) | │ 2 │ | New prec,Root Set Root=0 Goto SQrootX:Naum=0 | │ 3 │o| Set:Num<0 Num=-Num Set Root=$S(Num>1:Num\1,1:1/Num) |o│ 4 │ | Set Root=$E(Root,1,$L(Root)+1\2) Set:Num'>1 Root=1/Root | │ 5 │ | For prec=1:1:6 Set Root=Num/Root+Root*.5 | │ 6 │o|SQrootX Quit Root |o│ └────────────────────────────────────────────────────────────────┘ Прим.9.18 Внешняя функция для вычисления квадратного корня /Описание - в приложении G./ Процедура SQroot (вызываемая как внешняя функция), использует ме- тод последовательного приближения для вычисления значения квадратного корня числа. В строке 4 этого примера назначается наиболее приближен- ное значение квадратного корня, используемое при начале вычислений. Точность получаемого результата определяется количеством исполненных шагов цикла в строке 5. Для читателей, слабо знакомых с методом последовательных прибли- жений, рекомендую произвести один раз вычисление квадратного корня вручную. Произведите несколько раз операции, описанные в этом цикле, отслеживая значения, назначаемые переменным Num и Root, наблюдая при этом, как значение в переменной Root приближается к значению квадрат- ного корня от Num. В качестве стартового значения Num, при этом возь- мите, например, 16. Весьма поучительно будет попытаться разобраться как влияет алгоритм вычисления начального значения, и количество ис- полненных шагов цикла на точность вычисления конечного результата. Для завершения цикла также можно попробовать применить другой способ, вместо фиксированного задания числа проходов. Например, мы мо- жем задать бесконечный цикл, прекращаемый оператором Quit со следующим постусловием: Quit:Root*Root=Num Стр. 238 К сожалению, для большинства чисел невозможно определить точное значение квадратного корня, и потому такой цикл будет в большинстве случаев бесконечным. Вместо этого можно предложить прерывать цикл в том случае, если результат текущей итерации совпадает с предшествующим вплоть до 12-ой цифры после запятой. Однако, эти, как и другие возмож- ные приемы, требуют добавления в цикл таких конструкций, которые све- дут на нет все преимущества от сокращения числа проходов цикла, за счет отказа от фиксированного числа проходов. Короче говоря, усложне- ния себя не оправдают. 9.4.5 Внешняя функция для замены подстрок И в качестве последнего примера создания внешних функций для биб- лиотеки подпрограмм, мы рассмотрим внешнюю функцию для замены подстрок символов в исходной строке. Внутренняя функция $TRANSLATE используется только для замены отдельных символов, но не может применяться для за- мены символьных последовательностей. Однако, в практике программирова- ния мы постоянно сталкиваемся с необходимостью замены в строках одних символьных последовательностей на другие, или даже с необходимостью замены подстрок соответствующих определенному шаблону. В качестве при- мера напомним о часто встречающейся необходимости удаления "спаренных" пробелов из строки. ┌─────────────────────────────────────────────────────────────────┐ │ |Rep(T,F,W) ;Замена From на With в строке Target | │ │ | For Quit:T'[F Set T=$P(T,F)_W_$P(T,F,2,999) | │ │o| Quit T |o│ │ | | │ │ | -- или -- | │ │o| |o│ │ |Rep(T,F,W) ;Замена From на With в строке Target | │ │ |Repl If T[F Set T=$P(T,F)_W_$P(T,F,2,999) Goto Repl | │ │o| Quit T |o│ │ | | │ │ | -- но не так -- | │ │o| |o│ │ |Rep(T,F,W) ;Замена From на With в строке Target | │ │ | New i For i=1:1:$L(T,F) Set T=$P(T,F)_W_$P(T,F,2,999) | │ │o| Quit T |o│ │ | | │ └─────────────────────────────────────────────────────────────────┘ Прим.9.19 Функции для замены подстрок /Описание - в приложении G./ Первые две формы внешней функции выполняют одинаковую операцию, но в них использованы разные способы организации цикла - в первом слу- чае использован оператор For, во втором - оператор Goto. Обычно опыт- ные MUMPS программисты считают, что организация циклов с помощью опе- ратора For - это самая быстрая технология. Ну что ж, в тех реализациях MUMPS систем которые являются "полными" интерпретаторами /*1/ это по- ложение верно, так как оператор Goto каждый раз принимается "просмат- ривать" программу с первой строки для того, чтобы обнаружить указанную метку. Но в "компилирующих" реализациях /*2/ некоторое преимущество будет иметь использование Goto. ---------------------------------------------------------------------- *1 "Интерпретирующие" MUMPS реализации - DSM, ISM /NTSM/ и т.д. *1 "Компилирующие" MUMPS реализации - MSM, DTM и т.д. Стр. 239 Так как в этом случае адрес перехода уже вычислен и нет необходи- мости в обработке аргументов и заголовка For /особенно в For с конеч- ным числом проходов/. Но, безаргументный For в "компилирующих" MUMPS реализациях и использование в этих же целях Goto дают практически оди- наковые результаты. Третий подход, продемонстрированный в примере 9.19, имеет опреде- ленные преимущества перед первыми. Общее число проходов вычисляется в нем перед началом цикла, а не в ходе исполнения каждого прохода. Таким образом уменьшается число исполняемых MUMPS операций и уменьшается об- щее время исполнения цикла. Время исполнения действительно будет уменьшаться, но к сожалению, данная внешняя функция не всегда будет возвращать требуемый результат. Возникающая проблема может быть проде- монстрирована на примере попытки удаления подстроки сдвоенных символов из исходной строки. Возьмем в качестве примера следующую строку: Targ="This***is**a***test" Если мы захотим удалить из строки "спаренные" звездочки (то есть получить на выходе строку "This*is*a*test"), то последняя внешняя функция из примера 9.19 вернет строку "This*is*a**test" - не обработав последний набор "спаренных" звездочек. Причину ошибки мы сможем понять только проанализировав отдельно работу функции $LENGTH с этой строкой. Функция $LENGTH возвращает чис- ло НЕПЕРЕСЕКАЮЩИХСЯ вхождений указанной подстроки, понимаемой как раз- делитель полей, в исходную строку. Поэтому : $L("This***is**a***test","**")=4 Так как $LENGTH возвращает только непересекающиеся вхождения подстроки в исходную строку, то количество вхождений двойных звездочек в строку (5), не совпадает с числом полей, разделенных подстрокой - "**" (4). Следовательно, третий пример сможет работать правильно в 99% случаев, но выбираемые им условия будут некорректны. 9.5 Передача параметров в операторе JOB Передача параметров в операторе JOB в общем подобна передаче па- раметров в операторе DO, но имеет несколько важных отличий. Оператор JOB запускает задание в новом разделе, и, следовательно, порожденное задание будет иметь свою собственную среду локальных переменных. Целью передачи параметров, назначаемых оператору JOB является инициализация переменных, используемых в запускаемом задании. В этом случае не может быть обратного возврата значений в "породившее" задание. Списки формальных и действительных параметров подчиняются всем уже обсужденным выше положением, за исключением того, что в списке действительных параметров все значения передаются только ЗНАЧЕНИЕМ. Никакой связи между переменными в двух разделах не устанавливается, так как новому заданию выделяется своя таблица локальных переменных. ┌─────────────────────────────────────────────────────────────────┐ │ │ │ В запускаемое задание параметры передаются только ЗНАЧЕНИЕМ. │ │ │ └─────────────────────────────────────────────────────────────────┘ Стр. 240 Есть еще одно важное замечание - в запускаемое задание НЕ могут быть переданы локальные массивы. ┌─────────────────────────────────────────────────────────────┐ │ │ │ Так как в запускаемое задание параметры передаются только │ │ ЗНАЧЕНИЕМ, массивы не могут передаваться в операторе JOB │ │ как параметры. │ │ │ └─────────────────────────────────────────────────────────────┘ Во всех остальных аспектах запускаемое задание ведет себя подобно программе, запускаемой по DO, за исключением того, что новому заданию назначается при старте пустая таблица локальных переменных. Всем пере- менным из списка формальных параметров, назначаются соответствующие им значения из списка действительных параметров. Больше никаких перемен- ных при начале работы "порожденного" задания нет. 9.6 Стиль и техника программирования Введем еще два действующих фактора, отражающихся на создаваемых программных продуктах - это стиль и техника программирования. Техника программирования - это не что иное, как следование неким общим прави- лам и соглашениям, а стиль - больше относится к личным предпочтениям каждого программиста. Хорошая техника программирования указывает, чтобы внутри всего текущего процесса имена переменных соответствовали одним и тем же объ- ектам. Так например, если мы используем переменную Minutes в одной из программ для записи в нее числа минут, то недопустимо в другой прог- рамме записывать в нее, скажем число секунд. Это же положение относит- ся и к временным переменным. Нарушение этого правила требует либо под- робного документирования каждого программного модуля, либо больших затрат времени при последующем обращении к уже готовым программам в целях их модификации. ┌────────────────────────────────────────────────────┐ │ Внутри всего программного средства имена всегда │ │ должны соответствовать одним и тем же объектам. │ └────────────────────────────────────────────────────┘ И кроме того, хотя это уже больше относится к стилю программиро- вания, имена переменных должны быть мнемоническим отражением данных, в них содержащихся. Так в примере 9.11 мы можем использовать переменные X и Y, вместо переменных Secs и Format, но после этого, понимание ра- боты даже небольшой процедуры затрудняется. Можно даже порекомендовать вместо сокращения Secs использовать Seconds, что сделает текст прог- раммы еще более читаемым. Но это суждение больше субъективное, и Secs и Seconds вполне допустимые имена для переменной, хотя Secs короче, и, следовательно ввод программы будет короче, а объем занимаемый ее ис- ходным кодом - меньше. ┌────────────────────────────────────────────────┐ │ Назначайте переменным мнемонические имена │ └────────────────────────────────────────────────┘ Стр. 241 И наконец, признаком хорошей техники программирования является наличие общей точки выхода у процедур и подпрограмм. В примере 9.11 мы можем произвести следующие модификации: 6 If Secs<1!(Secs>86400) Quit "" 13 - эта строка удаляется - 14 Quit hours_":"_minutes_ampm В этом случае исключается необходимость во временной переменной Time. Такая маленькая процедура может иметь несколько точек выхода, но сопровождение больших и сложных программ с несколькими точками выхода будет сильно затруднено. ┌────────────────────────────────────────────────────┐ │ Процедура должны иметь одну, общую точку выхода. │ └────────────────────────────────────────────────────┘ Мы уже упоминали о стиле программирования в примерах этой и пред- шествующих глав. Хочется заострить Ваше внимание на соглашениях по ис- пользуемым именам переменных, а также по аббревиатурам имен операторов и функций. Лично я предпочитаю поддерживать соглашения по именам переменных описанные выше, при этом имена переменных составляются из больших букв. Для переменных, передаваемых в, или из программы (а также упот- ребляемых в списках действительных и формальных параметров), предпочи- таю первую букву имени писать большой буквой, а все прочие произволь- но, (хотя обычно все остальные - малые буквы, только первая - боль- шая). И наконец, лично я предпочитаю все имена временных локальных пе- ременных составлять только из малых букв. Хочется подчеркнуть, что это соглашения предлагаемые и поддерживаемые только мной лично, и они от- ражают только мой личный стиль программирования. /Дж. Левковича/ Мы уже встречались с использованием аббревиатур имен операторов и функций в предыдущей главе. В большинстве примеров имена даны следую- щим образом - первая буква большая, остальные - малые. Я обычно не пи- шу программы следующим образом, но, так как, в основном эти примеры необходимы для начинающих, то это сделано для увеличения ясности пони- мания. На практике, я лично предпочитаю вводить имена однобуквенным /или другим разрешенным/ сокращением - малыми буквами. Это упрощает разбор текста программы и ускоряет ввод программ. 9.7 Общие положения . Список параметров связываемый с оператором DO - называется списком действительных параметров, а список при вызываемой метке - список формальных параметров. (9.1) . Параметры в списке формальных параметров должны быть именами переменных без индексов. (9.1) . Список действительных параметров может быть короче списка фор- мальных параметров. (9.1) . MUMPS система исполняет неявное NEW на все имена переменных из списка формальных параметров перед началом исполнения процедуры. (9.1) Стр. 242 . Передача данных ЗНАЧЕНИЕМ одно-направлена, передаваемые в пара- метрах данные доступны процедуре, но она не может, используя па- раметры, вернуть результат в вызывающую программу. (9.1) . Параметр, передаваемый ССЫЛКОЙ может быть именем локальной пе- ременной, или массива. В этом случае имени в списке формальных параметров предшествует точка. (9.1.2) . Массив целиком передается в вызываемую процедуру, если имя это- го массива передается ССЫЛКОЙ. (9.1.2) . Переменные из списка действительных параметров, передаваемые ссылкой, могут быть неопределенными перед вызовом подпрограммы. (9.1.2) . После исполнения внешней функции, ее результат "заменяет" обра- щение к внешней функции в месте ее вызова. (9.2) . Значение, возвращаемое внешней функцией, является результатом выражения указанного в аргументе оператора QUIT, завершающего ис- полнение внешней функции. (9.2) . Значение системной переменной $TEST сохраняется перед вызовом внешней функции, и восстанавливается при возврате управления в вызывающую программу. (9.2) . В запускаемое оператором JOB задание параметры могут переда- ваться только ЗНАЧЕНИЕМ. (9.5) . Массивы не могут передаваться в запускаемое оператором JOB за- дание. (9.5) . Внутри всего программного средства/пакета/ используемые имена переменных всегда должны соответствовать одним и тем же объектам. (9.6) . Назначайте переменным мнемонические имена. . Процедура должны иметь одну, общую точку выхода. Стр. 243 Глава 10 Технология работы с глобальными массивами В главах 1-3 мы уже неоднократно упоминали о том, что глобальные массивы в MUMPS системах используются для построения и управления ба- зами данных. В этой главе мы расширим все данные раньше определения и продемонстрируем на примерах многосторонность подходов к управлению данными в MUMPS. Структура глобальных массивов уникальная особенность языка MUMPS. Эти массивы автоматически записываются на диск, без необходимости ре- зервирования и адресации пространства для записи, относятся к иерархи- ческим и разреженным структурам по самой своей природе. В массивах мо- жет содержаться произвольное количество уровней иерархии /индексов/, причем данные могут содержаться на любом из уровней массива. MUMPS не требует специальных средств для того, чтобы связывать массивы с актив- ными заданиями, а также не требует открывать, объявлять или закрывать массивы используемые заданием. В процессе исполнения каждому процессу может быть доступно любое количество глобальных массивов. Индексом массива может быть любое корректное MUMPS выражение, результатом оцен- ки которого может быть число, или строка символов. Кроме того, MUMPS система автоматически производит упорядочивание индексов в соответс- твии с упорядочивающей последовательностью, основанной на значениях ASCII кодов символов, из которых построены индексы. При этом происхо- дит вставка новых индексов между уже существующими, удаление индексов вместе с ветвями целыми уровнями и т.д.. И наконец, глобальные массивы могут одновременно использоваться несколькими процессами. Глобальные массивы настолько отличаются от привычной файловой структуры баз данных, что многие программисты, приходящие работать в MUMPS, первоначально затрудняются понимать как работают прикладные па- кеты со своими базами данных, а также как разрабатывается структура массивов базы данных. 10.1 Введение в технологию глобальных массивов. Перед тем, как переходить к обсуждению аспектов конструирования и использования глобальных массивов, давайте рассмотрим в качестве при- мера небольшую прикладную базу данных реализованную в MUMPS. Предположим, что мы хотим создать небольшой массив содержащий ин- формацию об фамилии, имени, возрасте и поле служащих /Name, Age и Sex, соответственно/. При этом запись данных в массив /*1/ должна произво- диться в соответствии с алфавитным порядком следования фамилий и имен, для того, чтобы можно было производить также и быстрый поиск. Для это- го, фамилию и имя мы будем записывать как индексы в глобальный массив, а возраст и пол, разделяемые символом ($), как данные при этом индек- се. В примере 10.1 демонстрируется простая программа для сбора необхо- димой информации и записи ее на диск в глобальный массив ^Employee. При построении данной демонстрационной программ предполагалось, что двойных имен встречаться не будет, и кроме того, не проводится ло- гическая проверка вводимых данных /нет проверки на корректность вводи- мого возраста и пола/. Пользователь может сам дополнить демонстрацион- ный пример необходимыми ему дополнениями. ---------------------------------------------------------------------- *1 В тексте оригинала используется термин 'file', я же стараюсь по возможности заменять его термином "массив" прежде всего для того, что- бы подчеркнуть разницу между обычным последовательным файлом и гло- бальным массивом в MUMPS. Стр. 244 ┌──────────────────────────────────────────────────────────────┐ │ |Read ;Чтение данных с клавиатуры, запись в ^Employee | │ │ | Read !,"Фамилия, имя: ",nam Quit:nam="" | │ │o| Read ?25,"Возраст: ",age,?35,"Пол: ",sex |o│ │ | Set ^Employee(nam)=age_"$"_sex | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>For Do Read Quit:nam="" | │ │ | | │ │o|Фамилия, имя: LEWIS,AL<- Возраст: 45<- Пол: М<- |o│ │ |Фамилия, имя: LARCH,MARY<- Возраст: 22<- Пол: Ж<- | │ │ |Фамилия, имя: MILLER,RUTH<- Возраст: 37<- Пол: Ж<- | │ │o|Фамилия, имя: ALLEN,ETHAN<- Возраст: 61<- Пол: М<- |o│ │ |Фамилия, имя: LOCKWOOD,JOE<- Возраст: 43<- Пол: М<- | │ │ |Фамилия, имя: MORRIS,JANE<- Возраст: 28<- Пол: Ж<- | │ │o|Фамилия, имя: KING,ROBERT<- Возраст: 48<- Пол: М<- |o│ │ |Фамилия, имя: KING,ALLICE<- Возраст: 46<- Пол: Ж<- | │ │ |Фамилия, имя: <- | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.10.1 Процедура для сбора и записи информации Теперь, когда мы уже имеем небольшой массив с информацией, поду- маем, как мы сможем использовать его. В примере 10.2 показана другая небольшая демонстрационная процедура, которая отображает на экране ин- формацию содержащуюся в созданном массиве. Функция $ORDER, использованная в примере 10.2 в основном применя- ется для обхода узлов разреженных массивов. Обратите внимание на одну важную особенность функции $ORDER: она возвращает индексы в порядке их сортировки в массиве (то есть в алфавитном порядке), вне зависимости от порядка добавления узлов в глобальный массив. ┌──────────────────────────────────────────────────────────────┐ │ |Display ;вывод содержимого ^Employee | │ │ | Set sub="" For Do Displ Quit:sub="" | │ │o| Quit |o│ │ |Displ Set sub=$O(^Employee(sub)) Quit:sub="" | │ │ | Set data=^Employee(sub) | │ │o| Set age=$P(data,"$",1),sex=$p(data,"$",2) |o│ │ | Write !,sub,?20,"Возраст=",age," Пол=",sex | │ │ | Quit | │ │o|----------------------------------------------------------|o│ │ |>Do ^Display<- | │ │ | | │ │o|ALLEN,ETHAN Возраст=61 Пол=М |o│ │ |KING,ALLICE Возраст=46 Пол=Ж | │ │ |KING,ROBERT Возраст=48 Пол=М | │ │o|LARCH,MARY Возраст=22 Пол=Ж |o│ │ |LEWIS,AL Возраст=45 Пол=М | │ │ |LOCKWOOD,JOE Возраст=43 Пол=М | │ │o|MILLER,RUTH Возраст=37 Пол=Ж |o│ │ |MORRIS,JANE Возраст=28 Пол=Ж | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 10.2 Вывод содержимого массива на экран Стр. 245 Вы видите, что хотя мы вводили имена в произвольном порядке MUMPS автоматически упорядочивает их при записи в массив. Кроме того, Вы мо- жете также увидеть, что фамилия и имя служащих, записываемые как ин- дексы могут быть использованы в программах как данные. ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Функция $ORDER возвращает индексы массива в порядке сортировки, │ │ вне зависимости от порядка добавления узлов в массив. │ │ │ └─────────────────────────────────────────────────────────────────┘ 10.2 Индексы Как мы только что говорили, индексы используются для создания, и уникальной идентификации узла глобального массива. Они никак не корре- лируются с записываемыми при них данными, но могут использоваться в программах в качестве данных. Кроме того, они выстраиваются в порядке обуславливаемом набором символов их составляющих, а не порядком запи- си, что имеет значение при просмотрах массивов с помощью функции $OR- DER /и ей подобных/. В следующих разделах мы рассмотрим логическую организацию глоба- лей (и массивов в общем), а также те средства, которые используются для обработки данных, содержащихся в массивах (функции $ORDER и $DA- TA). 10.2.1 Последовательность упорядочивания индексов Когда в MUMPS системе в массив записывается новый узел, то он вставляется в массив в соответствии со значением индекса /ов/, а не дописывается в конец файла /как это бывает в альтернативных базах дан- ных/. Порядок упорядочивания индексов в массивах, оказывает глубокое и непосредственное воздействие на методы программирования используемые для записи, поиска и извлечения данных. Как Вы могли заметить из примера 10.2 основной принцип упорядочи- вания индексов базируется на алфавитном порядке следования букв. Сим- вол 'А' предшествует символу 'В', 'В' предшествует 'С', строка 'АВ' предшествует строке 'АВС' и так далее. /Речь идет о латинских символах - Прим. переводчика/ Этот порядок упорядочивания обуславливается набо- ром ASCII кодов (подробнее см. в приложении A, где приводится полный набор ASCII кодов и раздел 5.3.2.3), и относительным положением симво- лов в нем. Например, в этом списке буква 'А' стоит за символом '?', за 'Z' стоит 'а' и так далее. То есть имеет значение для порядка размеще- ния и величина букв, так строка 'abc' будет следовать ЗА строкой 'ZZ'. В таблице 10.1 демонстрируется упорядочивание в соответствии с набором ASCII кодов для различных строк. Но то, что хорошо работает для строк, дает менее желательные результаты для числовых индексов. Порядок сле- дования в массиве индексов 1, 2, 3, 10, 20, 30 и 300, в соответствии с упорядочивающей последовательностью ASCII приведен в таблице 10.2. Яс- но, что в некоторых случаях желательно будет упорядочивать индексы в соответствии с их числовыми значениями, а не в соответствии с упорядо- чивающей последовательностью ASCII кодов. Стр. 246 Таблица 10.1 Упорядочивающая последовательность ASCII для строк --------------------------------------------------- Порядок следования Строка --------------------------------------------------- 1 'A' 2 'AA' 3 'ABCD' 4 'AaB' (буква 'a' следует за 'B') 5 'a' 6 'aa' --------------------------------------------------- Таблица 10.2 Упорядочивающая последовательность ASCII для чисел --------------------------------------------------- Порядок следования Строка --------------------------------------------------- 1 '1' 2 '10' 3 '100' 4 '2' 5 '20' 6 '200' 7 '3' 8 '30' 9 '300' --------------------------------------------------- В действительности, в большинстве MUMPS систем используется спе- циальная последовательность упорядочивания индексов, обычно именуемая упорядочивающей последовательностью MUMPS. /*1/ В ней производится различное упорядочивание для числовых индексов представленных числами в каноническом виде и строковых индексов. Под каноническим видом пони- маются числа (целые или действительные, положительные и отрицатель- ные), не содержащие незначащих нулей справа от десятичной точки, а также '0' перед десятичной точкой, и знака '+' перед положительными числами. И кроме того, если такое число не содержит дробной части, оно не должно иметь десятичной точки. Для проверки того содержит ли пере- менная число в каноническом виде достаточно проверить ее значение с помощью унарного оператора '+', например, следующим образом: If Var=+Var Write "Var содержит число в каноническом виде" ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Число в каноническом виде не содержит не значащих символов. │ │ И поэтому, для таких чисел Number=+Number │ │ │ └─────────────────────────────────────────────────────────────────┘ ---------------------------------------------------------------- *1 Для справки: в DSM-11 можно выбрать вид упорядочивания индексов в массиве /цифровой или строковый/, DataTreeMUMPS поддерживает упорядо- чивающую последовательность ASCII кодов. Я не могу привести пример MUMPS системы где реализована описываемая упорядочивающая последова- тельность. Стр. 247 Таблица 10.3 содержит различные числа в их каноническом и некано- ническом представлении. Во всех случаях, когда числа в неканонической форме подвергаются числовой интерпретации, (когда происходит исчисле- ние выражений), то результирующее значение, представляющее число в ка- нонической форме не будет совпадать с исходной строкой. Уяснив разницу между числами в канонической форме и неканоническими строками (все прочие строки, которые не являются числами в канонической форме), пе- рейдем к правилам, используемым в MUMPS системах для упорядочивания индексов. Таблица 10.3 Числа в каноническом и в не-каноническом представлении --------------------------------------------------- Каноническое Не-каноническое --------------------------------------------------- 1.01 +1.01, 01.01, 1.010 22 +22, 22., 0022, 22.0 -3.4 -3.40, -03.4 .25 0.25, .250, +.25 --------------------------------------------------- 10.2.1.1. Упорядочивающая последовательность MUMPS ■ Пустая строка ("") предшествует всем прочим строкам. Все прочие значения индексов следуют за ней. Однако пустая строка НЕ может быть использована как индекс в массиве (она используется в функ- циях $ORDER и $QUERY как начальное и конечное значение при обходе индексов) ■ Числа в канонической форме следуют за пустой строкой в порядке возрастания их значения. Так 147 следует за 12, 212 за 29 и так далее. ■ Все прочие строки, кроме упомянутых в первых двух пунктах следуют за ними в порядке упорядочивания ASCII кодов. ┌─────────────────────────────────────────────────────────────────┐ │ Числа в каноническом виде упорядочиваются в соответствии с их │ │значениями и предшествуют всем не-каноническим строкам. Строки │ │упорядочиваются в соответствии с упорядочивающей последовательно-│ │стью ASCII кодов. │ └─────────────────────────────────────────────────────────────────┘ Очень важно помнить, что в MUMPS используются две последователь- ности упорядочивания - одна для индексов /упорядочивающая последова- тельность MUMPS - см. описание выше/ , вторая - используемая в опера- торе СЛЕДУЕТ /см. раздел 5.3.2.3/ упорядочивающая последовательность кодов ASCII. Для более глубокого понимания организации массивов в MUMPS внима- тельно рассмотрим еще несколько примеров. В примере 10.1 мы создали небольшую базу данных проиндексированную по фамилиям и именам служа- щих. В примере 10.2 демонстрируется как создать алфавитный список всех служащих. В примере 10.3 мы рассмотрим подпрограмму производящую поиск среди всех имен служащих только тех, которые начинаются со строки сим- волов, введенной с клавиатуры. Стр. 248 ┌───────────────────────────────────────────────────────────────┐ │ |Search(Nam) ;Поиск в ^Employee имен начинающихся с Nam | │ │ | New len,sub | │ │o| Set sub=Nam,len=$L(Nam) For Do Srch1 Quit:sub="" |o│ │ | Quit | │ │ |Srch1 Set sub=$O(^Employee(sub)) Quit:sub="" | │ │o| If $E(sub,1,len)'=Nam Set sub="" Quit |o│ │ | Write !,?10,sub | │ │ | Quit | │ │o|-----------------------------------------------------------|o│ │ |>For Read !,"Служащий: ",emp Quit:emp="" Do Search(emp)<-| │ │ |Служащий: L<- | │ │o| LARCH,MARY |o│ │ | LEWIS,AL | │ │ | LOCKWOOD,JOE | │ │o|Служащий: <- |o│ │ |> | │ └───────────────────────────────────────────────────────────────┘ Прим. 10.3 Поиск в глобальном массиве. Этот пример во многом подобен примеру 10.2, за исключением того, что ищется только небольшое подмножество из всех узлов массива (только те, чьи индексы начинаются с символа 'L'). Обратите внимание на то обстоятельство, что выполняется поиск не по всему глобальному массиву. Он начинается с первого индекса следующего за указанной строкой (в данном случае за 'L'), и завершается тогда, когда индекс не содержит в первым символе букву 'L'. В следующем, чуть более сложном примере, демонстрируется типичный для MUMPS подход к сортировке глобальных массивов. ┌──────────────────────────────────────────────────────────────┐ │ |Sort ;Сортировка ^Employee по полу и возрасту | │ │ | Kill ^Temp Set s="" For Do srt Quit:s="" | │ │o| Do lst |o│ │ | Quit | │ │ |srt Set s=$O(^Employee(s)) Quit:s="" | │ │o| Set x=^Employee(s),age=$p(x,"$"),sex=$P(x,"$",2) |o│ │ | Set ^Temp(sex,age)=s | │ │ | Quit | │ │o|lst Set (age,sex)="" |o│ │ |lst1 Set sex=$O(^Temp(sex)) Quit:sex="" Write !,sex | │ │ |lst2 Set age=$O(^Temp(sex,age)) Goto lst1:age="" | │ │o| Write !,?5,^Temp(sex,age),?25,"Возраст=",age |o│ │ | Goto lst2 | │ │ |----------------------------------------------------------| │ │o|>Do Sort<- |o│ │ |Ж | │ │o| LARCH,MARY Возраст=22 |o│ │ | MORRIS,JANE Возраст=28 | │ │ | KING,ALLICE Возраст=46 | │ │o|M |o│ │ | LOCKWOOD,JOE Возраст=43 | │ │ | LEWIS,AL Возраст=45 | │ │o| KING,ROBERT Возраст=48 |o│ │ | ALLEN,ETHAN Возраст=61 | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 10.4 Сортировка в глобальном массиве Стр. 249 Используя ту же базу данных мы сортируем служащих по полу и возрасту, и затем выводим на экран отсортированный список. В этом примере де- монстрируется сразу несколько типичных подходов: - использование временного глобального массива автоматически упорядочивающего узлы в необходимом нам порядке, - использование во временном массиве нескольких уровней индексации - один из методов перебора многоуровневого массива ┌─────────────────────────────────────────────────────────────────┐ │ Сортировка данных в MUMPS обычно решается путем создания вре- │ │менных массивов (локальных или глобальных), в которых сортируе- │ │мые данные (поля), используются как индексы создаваемого массива │ └─────────────────────────────────────────────────────────────────┘ В большинстве MUMPS приложений все сортировки реализуются создани- ем временных массивов с использованием присущей им упорядочивающей последовательности организации индексов. Сортировка по нескольким клю- чам достигается использованием записи ключей на разные уровни индекса- ции временного массива. Основной ключ - на первом уровне индексации, второстепенный - на втором, и так далее. Сортировка данных производи- мая подобным образом достигается легко и быстро, требуя только созда- ния временного массива (локального или глобального), для хранения про- межуточных результатов. ┌─────────────────────────────────────────────────────────────────┐ │ Когда сортировка производится по нескольким ключам, то наиболее │ │значащий ключ используется на первом уровне индексации, наименее │ │значащий - на последнем. │ └─────────────────────────────────────────────────────────────────┘ В качестве заключительного примера рассмотрим еще одну, относи- тельно общую ситуацию, в которой необходимо внимательно использовать упорядочивающую последовательность MUMPS. Пусть нам для примера необ- ходимо упорядочить список адресов с их 5-ти значными ZIP кодами (поч- товыми индексами). Исходный список ZIP кодов с соответствующими им на- именованиями городов и штатов приведен в таблице 10.4. Таблица 10.4 Города и ZIP коды ------------------------------------------ Город ZIP код ------------------------------------------ Albany,NY 12201 Bath,MI 48808 Forest,VA 24551 Ithaca,NY 14850 Plymouth,MA 02215 Sackets Harbor,NY 13585 Salinas,Puerto Rico 00751 ------------------------------------------ Если мы отсортируем эти города по их ZIP коду, использованному как первый индекс в массиве сортировки (например Set ^set(ZIP)=adr), и затем будем использовать функцию $ORDER для получения списка, то возв- ращаемый результат показан в таблице 10.5. Стр. 250 Таблица 10.5 Упорядоченный список ZIP кодов ----------------------------------------- Индекс Город ----------------------------------------- 12201 Albany,NY 13585 Sackets Harbor,NY 14850 Ithaca,NY 24551 Forest,VA 48808 Bath,MI 00751 Salinas,Puerto Rico 02215 Plymouth,MA ----------------------------------------- В списке, представленном в таблице 10.5 коды 12202-48808 пред- шествуют кодам 00751 и 02215, 12202-48808 - это числа в каноническом представлении и потому они следуют раньше всех прочих неканонических строк. Давайте попробуем еще раз пересортировать список, но с исполь- зованием числовой интерпретации всех индексов, приведя таким образом все индексы к числам в каноническом представлении. Этой цели можно достичь следующим образом : Set ^srt(+ZIP)=adr Знак '+', предшествующий ZIP коду приводит к его числовой интерп- ретации перед записью в качестве индекса, преобразуя все числа в кано- ническое представление. Список кодов упорядоченный таким образом представлен в таблице 10.6. Таблица 10.6 Список ZIP кодов, упорядоченный в соответствии с числовой интерпретацией кодов ----------------------------------------- Индекс Город ----------------------------------------- 751 Salinas,Puerto Rico 2215 Plymouth,MA 12201 Albany,NY 13585 Sackets Harbor,NY 14850 Ithaca,NY 24551 Forest,VA 48808 Bath,MI ----------------------------------------- Заметьте, что теперь не все ZIP коды, использованные в индексах имеют длину в 5 символов, при числовой интерпретации отбрасываются все предшествующие не-значащие нули. И в качестве последнего примера при- ведем сортировку ZIP кодов в строковой интерпретации, для чего можно использовать конкатенацию строки "ZIP " к значению кода, например сле- дующим образом. Set ^srt("ZIP "_ZIP)=adr Таким образом все индексы становятся неканоническими строками и список получаемый в результате представлен в таблице 10.7. Приведение индексов при записи к каноническому (числовому) или к неканоническому, (строковому), виду определяет последовательность возвращения индексов функцией $ORDER при обходе указанного уровня массива. Поэтому запомни- те, что метод формирования индексов при записи определяет последова- тельность их чтения. Стр. 251 Таблица 10.7 Список ZIP кодов, упорядоченный в соответствии со строковой интерпретацией ----------------------------------------- Индекс Город ----------------------------------------- ZIP 00751 Salinas,Puerto Rico ZIP 02215 Plymouth,MA ZIP 12201 Albany,NY ZIP 13585 Sackets Harbor,NY ZIP 14850 Ithaca,NY ZIP 24551 Forest,VA ZIP 48808 Bath,MI ----------------------------------------- 10.2.2. Функции $ORDER и $DATA Как Вы помните, при обсуждении одного из примеров мы уже говорили, что узлы массива могут содержать, а могут и не содержать при себе данных. То есть узел массива (индексированная ссылка на элемент массива), может содержать при себе данные, может быть только логическим указателем для прохода к более низким уровням структуры массива, но может также одновременно иметь при себе данные и быть логическим указателем в массиве (то есть иметь потомков). Разберем это положение на примере создания следующего массива: Ary(4)="A" Ary(4,10,3)="B" Ary(4,10,"xyz")="C" Ary(4,25)="D" Ary(5,10,3)="E" В этом массиве некоторые узлы только содержат при себе данные, (например, Ary(4,10,3), Ary(4,25) и так далее), некоторые являются только логическими указателями для прохода к более низким уровням массива, (например, Ary(4,10) и Ary(4,5)), а некоторые содержат при себе данное и одновременно являются логическими указателями (например Ary(4)). Заметьте, что хотя явного их создания с назначением им данного не производилось, но в массиве они существуют, они создаются неявным образом как логические указатели тогда, когда Вы назначаете данные узлам на последующих уровнях индексации. Функция $ORDER возвращает значение следующего индекса на данном уровне индексации, а функция $DATA используется для того, чтобы определить к какому из видов узлов массива относится указанный узел. ┌─────────────────────────────────────────────────────────────────┐ │ Функция $ORDER используется для обхода всех индексов на данном │ │уровне индексации массива (локального или глобального), вне за- │ │висимости от того, содержат эти узлы при себе данные или нет. │ └─────────────────────────────────────────────────────────────────┘ Функция $ORDER всегда возвращает индекс, следующий за указанным в ее аргументе на данном уровне индексации массива, вне зависимости от вида конкретного узла массива (с данными, без них, с потомками, или без них). Однако, если Вы попытаетесь прочитать данные от узла, являющегося логическим указателем {например Set x=Ary(5)}, то произойдет ошибка, так как для данного узла не производилось явного назначения данного. Стр. 252 Все возможные значения, возвращаемые функцией $DATA, приведены в таблице 10.8 Таблица 10.8 Результат возвращаемый функцией $DATA ------------------------------------------------------------ Значение $DATA Пояснение Пример ------------------------------------------------------------ 0 Не определено $D(Ary(50))=0 1 Данные, потомков нет $D(Ary(4,25))=1 10 Нет данных, указатель $D(Ary(4,10))=10 11 Данные и указатель $D(Ary(4))=11 ------------------------------------------------------------ ┌─────────────────────────────────────────────────────────────────┐ │ Функция $DATA используется для того, чтобы определить существу-│ │ет или нет узел массива (или переменная), и кроме того, провери- │ │ть не является ли данный узел массива логическим указателем для │ │прохода к более низким уровням массива. │ └─────────────────────────────────────────────────────────────────┘ Значения, возвращаемые $DATA организованы так, что используя простое выражение можно определить, содержит данный узел при себе данные и/или является логическим указателем для прохода к другим уровням массива. ┌──────────────────────────────────────────────────────────────┐ │ |>If $D(Ary(4))#2 Write "Узел содержит данные"<- | │ │ |Узел содержит данные | │ │o|>If $D(Ary(4))\10 Write "Узел является указателем"<- |o│ │ |Узел является указателем | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 10.5 Использование функции $DATA На рисунке 10.1 представлена структура массива 'Ary' и соответствие между узлами этого массива и результатами, которые возвращают функции $ORDER и $DATA от ссылок на эти узлы. Ary(4)="A" Ary Ary(4,10,3)="B" ┌───────┐ Ary(4,10,"xyz")="C └─┬───┬─┘ Ary(4,25)="D" ┌─────────┘ └────────────┐ Ary(5,10,3)="E" │(4) $O=5 │ (5) $O="" ┌────┴──┐ $D=11 ┌───┴───┐ $D=10 │ "A" │ │ │ └──┬──┬─┘ └───┬───┘ ┌──────┘ └────┐ │ $O=25 (10) │ (25) │ $O="" │ $D=10 ┌───┴───┐ ┌───┴───┐$D=1 │ (10) $O="" │ │ │ "D" │ ┌───┴───┐ $D=10 └─┬───┬─┘ └───────┘ │ │ ┌────┘ └─────────────┐ └───┬───┘ (3) │ $O="xyz" ("xyz") │ $O="" │ (3) $O="" ┌───┴───┐$D=1 ┌───┴───┐$D=1 ┌───┴───┐ $D=1 │ "B" │ │ "C" │ │ "E" │ └───────┘ └───────┘ └───────┘ Рис. 10.1 Функции $ORDER и $DATA Стр. 253 Обычно нет необходимости всегда проводить проверку на наличие данных при получении данных с узла, поиск в глобалях обычно производится с знанием того, назначались ли данные этим узлам или нет. Большинство массивов в приложениях организуются в более строгом порядке, чем те, что использовались в наших примерах. Обычно каждый уровень глобального массива соответствует логическому уровню организации сохраняемых в нем данных, а при этом наличие, или отсутствие данных на каждом уровне массива уточняется при разработке структуры массива. Кроме того, при извлечении данных из глобальных массивов можно применять функцию $GET (См. раздел 8.2.5) 10.3 Неполный синтаксис в глобальных ссылках Неполный синтаксис /*1/ это сокращенная форма записи глобальных ссылок в программах. Основной формат использования неполного синтаксиса глобальных ссылок, подобен обычному формату их использования, но без имени глобального массива. Рассмотрим следующий пример: ┌──────────────────────────────────────────────────────────────┐ │ |>Set ^XYZ(1,2,3,1)="Test"<- | │ │ |>Set ^(2)="New Test"<- | │ │o|>Write ^XYZ(1,2,3,2)<- |o│ │ |New Test | │ │ |>Write ^(2)<- | │ │o|Test |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 10.6 Глобальные ссылки с использованием неполного синтаксиса В первом операторе SET использована обычная форма глобальной ссылки [^XYZ(1,2,3,1)], а во втором - неполная форма синтаксиса для глобальной ссылки - [^(2)]. Неполный синтаксис глобальных ссылок - это форма сокращения записи ссылки на узел глобального массива, опирающаяся на последнюю исполненную ссылку (безразлично, обычную или неполную). MUMPS система запоминает последнее обращение к глобальному массиву {например, ^XYZ(1,2,3,1) }, для каждого из заданий, эта ссылка сохраняется как внутренний индикатор, и будет использована для построения полной ссылки при использовании неполного синтаксиса глобальной ссылки. ┌─────────────────────────────────────────────────────────────────┐ │ Неполный синтаксис это сокращенная форма обращения к узлу гло-│ │бального массива, базирующаяся на последнем обращении к узлу │ │массива. │ └─────────────────────────────────────────────────────────────────┘ Вы можете подумать что использование неполного синтаксиса для глобальных ссылок сродни подмене строк, так в примере 10.6 первая ссылка с использованием неполного синтаксиса равносильна полной ссылке - ^XYZ(1,2,3,3). ----------------------------------------------------------------- *1 В оригинале, так же как и во всей англо-язычной литературе используется понятие 'naked syntax' - "голый" синтаксис. Но в русско-язычной программисткой среде утвердился другой термин для обозначения этого понятия - "неполный" синтаксис. Стр. 254 И в этом случае в ссылке с неполным синтаксисом имя массива и все индексы как бы подставляются вместо опущенных. Но неполный синтаксис может также использоваться и для ссылок обращающихся к узлам массива находящихся на более низких уровнях индексации массива. ┌──────────────────────────────────────────────────────────────┐ │ |>Set ^XYZ(1,2,3,1)="Test"<- | │ │ |>Set ^(1,5)="New Test"<- | │ │o|>Write ^XYZ(1,2,3,1,5)<- |o│ │ |New Test | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 10.7 Изменение уровня глобальной ссылки при использовании неполного синтаксиса. В этом примере второй оператор SET с неполным синтаксисом производит обращение к узлу ^XYZ(1,2,3,1,5). При использовании неполного синтаксиса в операторе SET следует учесть, что сначала исчисляется выражение стоящее СПРАВА от знака равенства, а потом конструируется ссылка стоящая СЛЕВА от знака равенства. Указатель неполного синтаксиса устанавливается во время исчисления этого выражения, поэтому небрежное использование неполного синтаксиса может привести к непредсказуемым результатам. Для иллюстрации: ┌──────────────────────────────────────────────────────────────┐ │ |>Set ^XYZ(22)=10<- | │ │ |>Set ^ABC(22)=250<- | │ │o|>Set ^XYZ(22)=^(22)<- |o│ │ |>Write ^XYZ(22)<- | │ │ |250 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.10.8 Влияние порядка оценивания выражения на указатель неполного синтаксиса. Ссылка с неполным синтаксисом в третьей строке обращается к массиву ^ABC, а не к ^XYZ, так как выражение справа от знака равенства оценивается раньше, чем выражение слева. А когда оценивается выражение в левой части, то в нем используется последняя произведенная глобальная ссылка в предыдущей командной строке. ┌─────────────────────────────────────────────────────────────────┐ │ В операторе SET выражение справа от знака равенства оценивается│ │раньше, чем выражение слева от знака равенства. │ └─────────────────────────────────────────────────────────────────┘ Порядок оценивания выражений в правой части оператора SET следует учитывать также как для операторов с постусловиями (или с постусловиями при их аргументах), так при использовании функции $SELECT. Во всех случаях, когда на оператор, или аргумент оператора накладывается постусловие, то постусловие оценивается в первую очередь. И если результат - ЛОЖЬ, то дальнейшего оценивания оператора, или его аргумента не производится. Так как результат оценивания постусловия при условном SET во второй строке - ЛОЖЬ [^Test(1) не равен пустой строке], то выражение ^Test(1,2) не оценивается, и потому указатель неполного синтаксиса остается указывать на первый уровень индексации массива. Следующий оператор Set с неполным синтаксисом глобальной ссылки назначает данное узлу ^Test(2), а не ^Test(1,2). Стр. 255 ┌──────────────────────────────────────────────────────────────┐ │ |>Kill ^Test Set ^Test(1)=1<- | │ │ |>Set:^Test(1)="" ^Test(1,2)="Null" Set ^(2)=2<- | │ │o|>Write ^Test(1),!,^Test(2)<- |o│ │ |1 | │ │ |2 | │ │o|>Write ^Test(1,2)<- |o│ │ | ERROR | │ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим. 10.9 Влияние порядка оценивания выражения на указатель неполного синтаксиса. Обработка функции $SELECT производится схожим образом. Основной формат использования этой функции выглядит следующим образом: $SELECT(EXPRESSION:VALUE,EXPRSSSION:VALUE,...) Производится последовательное оценивание выражений из списка (слева - направо), до тех пор, пока не встречается выражение дающее в результате оценки - ИСТИНУ (не ноль). В этом случае функция возвращает соответствующее этому выражению значение VALUE. А если в качестве выражений используются те или иные ссылки на глобальные массивы, то устанавливаемый после использования функции $SELECT указатель неполного глобального синтаксиса непредсказуем. Концепция неполного глобального синтаксиса, также как использование аббревиатур имен операторов и функций, разрабатывалась с целью уменьшения количества символов оцениваемых во время исполнения программы. С появлением в настоящее время "компилирующих" версий MUMPS систем использование неполного синтаксиса уже не может сказаться на скорости исполнения. Напротив, его использование может явиться только лишним источником неприятностей при модификации существующих программ и неосмотрительной вставке дополнительных глобальных ссылок. Вставка дополнительных ссылок может изменить положение указателя неполного синтаксиса и привести к неприятным последствиям. Кроме того, указатель неполного синтаксиса НЕ сохраняется и НЕ восстанавливается при вызовах подпрограмм или внешних функций. /*1/ ┌─────────────────────────────────────────────────────────────────┐ │ Программисты должны быть осторожны в использовании неполного │ │ синтаксиса при обращениях к узлам глобальных массивов. │ └─────────────────────────────────────────────────────────────────┘ 10.4. Оператор LOCK Оператор LOCK обеспечивает MUMPS программисту механизм для ограничения доступа другим процессам к массиву целиком, его ветви, или отдельному узлу, используемому в текущем процессе. Необходимо заметить, что реализуемое через оператор LOCK ограничение доступа не является абсолютным, а реализуется только через поддерживаемые в работе соглашения. -------------------------------------------------------------------- *1 В некоторых MUMPS реализациях предусмотрены определенные средства для того, чтобы получать, сохранять и восстанавливать указатель неполного глобального синтаксиса - Подробнее см. Описание языка реализации DataTreeMUMPS. Стр. 256 Массивы, на которые наложено действие оператора LOCK, остаются доступными для чтения, записи и удаления, всем остальным процессам, невозможным становится только применение другими процессами оператора LOCK к этим массивам. ┌─────────────────────────────────────────────────────────────────┐ │ Оператор LOCK используется для ограничения доступа других про- │ │цессов к ветвям глобальных массивов, при этом ограничивается не │ │физический доступ к этим ветвям, а возможность других процессов │ │использовать оператор LOCK к этим ветвям массивов. │ └─────────────────────────────────────────────────────────────────┘ Обычно, оператор LOCK используется для резервирования одной или более глобальных ссылок при проведении в них записи данных или их модификации. Таким образом может быть исключена ситуация, что другой процесс произведет модификацию данных внутри промежутка времени между извлечением данных и записью измененных данных производимых текущим процессом. Когда используется не-дополняющая форма оператора LOCK, (например, LOCK ^X), то перед ее исполнением производится снятие всех ранее наложенных блокировок, и только затем наложение блокировки на указанный массив (ветвь массива). ┌─────────────────────────────────────────────────────────────────┐ │ Не-дополняющая форма оператора LOCK отменяет все ранее нало- │ │женные блокировки перед попыткой произвести блокировки ссылок │ │указанных в ее аргументе. │ └─────────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────┐ │ |Edit ;редактирование данных пациента | │ │ |GetID Set PID=$$GetPat Quit:PID="" | │ │o| Lock +^PatRec(PID):0 |o│ │ | Else Write *7,"Извините, используется" Goto GetID | │ │ | Set OLD=$$GetRec(PID) | │ │o| Do EDIT(OLD,.NEW) Goto Done:OLD=NEW |o│ │ | Do Save(PID,NEW) | │ │ |Done Lock -^PatRec(PID) | │ │o| Goto GetID |o│ └──────────────────────────────────────────────────────────────┘ Прим.10.10 Дополняющая и исключающая формы оператора LOCK В примере 10.10, Вы можете увидеть, что как только идентифицируется редактируемый идентификатор пациента, но ПЕРЕД получением данных, записанных по этому идентификатору, мы накладываем блокировку на эту ветвь глобального массива. Теперь, если все прочие процессы будут пытаться производить изменения этой ветви с подобным использованием оператора LOCK, мы предотвратили конфликты в процессе произведения модификаций, так как всем прочим процессам будет отказано в чтении данных по этой ветви массива. Дополняющая и исключающая формы оператора LOCK имеют то преимущество,что они не отменяют всех прочих блокировок, наложенных в текущем процессе. Такие формы особенно полезно использовать в подпрограммах общего назначения, которые могут быть вызваны из большого количества различных программ. При использовании дополняющей формы оператора LOCK необходимо уделить особое внимание тому, чтобы каждой дополняющей форме LOCK соответствовала исключающая. Стр. 257 ┌─────────────────────────────────────────────────────────────────┐ │ Дополняющая и исключающая формы оператора LOCK не оказывают │ │влияния на все остальные установки оператора LOCK в текущем │ │процессе. │ └─────────────────────────────────────────────────────────────────┘ При исполнении каждой дополняющей формы LOCK, информация записывается в стек блокировок. Исключающая форма LOCK "выбрасывает" один из элементов стека блокировок. (Не обязательно последний или первый) Если Вы по невнимательности записали в стек больше блокировок глобальных ссылок, чем затем "выбросили" из него, то часть ссылок может остаться "заблокированной" для остальных процессов. Обеспечение "парности" дополняющих и исключающих форм оператора LOCK особенно важно обеспечить при обеспечении обработки ошибок в процессе записи, или прерываниях. Запомните, что обычно оператор LOCK без аргументов отменяет ВСЕ произведенные блокировки текущего процесса, вне зависимости от того, как они производились и от их количества. В общем, необходимо накладывать блокировки на элементы массивов только при произведении дозаписи, или редактирования. Программы поиска, которые только читают данные из глобали, обычно не должны пытаться блокировать элементы массивов перед доступом к ним. 10.5 Дополнительные возможности при работе с глобальными массивами В части MUMPS реализаций допускается использование особых возможностей повышающих эффективность работы с базами данных MUMPS систем, в том числе предлагается возможность "сквозного" доступа между рабочими областями, или базами данных компьютеров локальной сети при обработке данных в глобалях. Эти возможности будут описаны только самым общим образом, так как они специфичны для каждой из MUMPS реализаций. При желании их использования Вы должны обратиться к описанию используемой Вами MUMPS системы. 10.5.1 Распределенные базы данных В разделе 2.2.3 мы коснулись концепции каталогов программ и глобальных массивов. При запуске процесса, ему назначается раздел и именованная область /каталог/ на диске к которой адресуются запросы записи и чтения (как программ, так и массивов). Каждая из таких областей уникальна, и отделена от всех прочих, при этом различные версии одних и тех же программ и массивов могут быть размещены в разных областях. Именованные области могут размещаться на одном диске, или на нескольких дисковых накопителях, или (в сетевых реализациях), даже на дисковых накопителях разных компьютеров (включенных в сеть). Во многих случаях желательно, а иногда и просто необходимо, текущему процессу осуществлять доступ к данным, размещенных в других областях, поэтому большинство реализаций обеспечивают в той, или иной форме такую возможность. Внутри ограничений, накладываемых системой администрирования доступа (Раздел 10.5.4), в MUMPS реализациях поддерживается концепция распределенной базы данных, в которой процессам разрешается читать, изменять или накладывать блокировки в глобальных массивах, расположенных в других областях, или даже на других компьютерах локальной информационной сети. Стр. 258 Для обеспечения этого механизма MUMPS система должна иметь информацию к какой из глобалей относится ссылка. При этом должна быть идентифицирована область /каталог/ к которой производится обращение, а в сетевых реализациях, дополнительно должно быть идентифицирована имя узла локальной сети, где расположена эта область. Для получения этой информации используются два подхода: неявное определение размещения каждого глобального массива и явное распределение глобальных ссылок. Неявное определение размещения каждой из глобалей обычно производится системной утилитой, разрабатываемой пользователем. Для этого каждый из массивов снабжается особым флагом, содержащим информацию о узле сети, и области, в которой расположен массив, в соответствии с этим флагом и производится адресация обращений к этому массиву. При этом не требуется производить в программах никаких изменений в синтаксисе используемых глобальных ссылок. В большинстве систем библиотечные массивы можно отнести к категории неявно определяемых. Все имена библиотечных массивов начинаются с символа '%', и обычно расположены в так называемой системной области MUMPS системы. Все обращения к таким глобалям автоматически адресуются, вне зависимости от того, из какой области они произведены, в системную область. /*1/ Другие примеры неявного определения размещения массивов могут быть подобны описанному примеру с библиотечными массивами, но флаги обычно устанавливаются вне текущего компьютера в сетевых средах. Вторая форма поддержки распределенной базы данных - использование явно распределенных ссылок к глобальным массивам. В этом случае синтаксис глобальной ссылки расширяется за счет добавления идентификатора области к которой производится обращения и/или узла локальной сети (системы), где эта область находится. Обычно эти идентификаторы, заключенные в квадратные скобки ([]), включаются между символом '^' и именем глобального массива. ^[System,Directory]Name(Subscripts) /*2/ Обозначения системы и области (System и Directory) специфичны для каждой из MUMPS реализаций. Обычно пропущенный идентификатор System обозначает, что ссылка относится к области расположенной в той же системе /на том же узле сети/, что и текущий процесс. Опущенный идентификатор области (Directory), указывает, что обращение производится к области назначенной текущему процессу при его запуске. Использование квадратных скобок в синтаксисе глобальной ссылки не является частью MUMPS стандарта. /*3/ ---------------------------------------------------------------------- *1 Не все MUMPS реализации поддерживают это соглашение. Пример такого исключения - ISM-NTSM всех версий. /InterfaceStandartMUMPS- NewTechnologiesStandartMUMPS - разработки группы И.Фетисова (Москва) довольно широко распространенные по СССР/ *2 Например в DTM v.4.2+ - ^["MAIN","USER"]Stat(X) *3 Ссылки такого вида обычно именуются "расширенным" синтаксисом глобальной ссылки. Обратите внимание на разницу между понятиями - "неполный" , полный и "расширенный" синтаксис глобальной ссылки. Начиная с 1991 года стандарт поддерживает идентификацию в глобальной ссылке системы и области, где расположен массив, заключенных между символами - '|'. Впрочем, в целях совместимости поддерживается "де - факто" прежняя форма - с квадратными скобками. Стр. 259 10.5.2 Дублирование Дублирование базы данных это процесс, позволяющий автоматически копировать каждый SET и KILL, относящийся к указанным глобальным массивам в другой области, или, в сетевых реализациях, на другом узле сети. Определение того, какие глобали будут копироваться подобным образом, и где будут размещаться копии, обычно производится либо специальными утилитами, либо при конфигурировании системы. Поэтому за подробностями обращайтесь к описанию Вашей MUMPS системы. Дублирование наиболее часто поддерживается в сетевых реализациях, когда копии одного массива поддерживаются на нескольких компьютерах. Дублирование массива на других дисках, или на других компьютерах - один из способов предотвращения потери данных при авариях аппаратного обеспечения. При поддержании нескольких копий одной базы данных на разных компьютерах можно уменьшить время исчерпывающего поиска, и тем самым минимизировать задержки при проверках во время диалогового ввода данных. Преимущества, получаемые при дублировании баз данных выходят за рамки использования явного и неявного распределения глобальных ссылок. Обычно, изменения производимые в базе данных (SET и KILL в ее элементах), ограничивают число процессов, которые могут производить в этой базе данных исчерпывающий поиск, дублирование базы данных на нескольких узлах сети позволяет проводить частый поиск в ней "насквозь" между узлами. 10.5.3 Журналирование Журналирование глобальных транзакций - это технология используемая совместно с периодическим копированием глобальных массивов, с целью предотвращения потери данных при авариях аппаратного обеспечения, или в каких-либо других случаях. Когда устанавливается журналирование глобального массива, то все изменения, производимые в этом массиве, записываются в файле журнала как отдельные элементы. Файл журнала обычно содержит записи, содержащие изменения производимые в нескольких массивах, информация о всех производимых изменениях добавляется в файл последовательно, в соответствии с реальным процессом работы с данными в глобальных массивах. Обычно файл журнала представляет собой серию последовательных записей относящихся ко всем SET'ам и KILL'ам в журналируемых глобалях. Для каждого SET в журнал добавляется две записи; первая из них представляет собой полную глобальную ссылку, для которой производится SET [например, ^Test(1,"CBC",36445)], вторая - значение данного, назначенное узлу [например, "36457\+ +\Rotochem"]. Для каждого KILL в журнал заносится только одна запись содержащая полную глобальную ссылку на удаляемый узел. Обращаю Ваше внимание на то, что в файл журнала заносится только полная ссылка, которая указана в операторе KILL. При этом в файл журнала не записывается ни информация о данных, связанных с этим узлом, ни о том есть или нет у него потомки, которые также удаляются в этой операции KILL. Кроме этого, каждая запись в файле журнала содержит информацию о типе операции (SET или KILL), а также, в зависимости от реализации может содержаться информация об основном устройстве и номере задания, производящего изменения в глобальном массиве. Стр. 260 Используя совместно периодическое полное копирование базы данных (отнимающее достаточно много времени), вместе с дополняющим его копированием производимых изменения в журнальный файл (более быстрый процесс), Вы сможете существенно повысить устойчивость баз данных к различным катастрофическим случайностям. Журнальный файл обычно используется для восстановления всех изменений, произведенных в базе данных со времени проведения ее полного копирования. В случае восстановления разрушенной базы данных все записанные в журнальном файле изменения записываются "поверх" восстановленной последней полной копии базы данных. Еще раз подчеркиваю - журнал не содержит полное представление глобального массива, он содержит только список произведенных в нем изменений, этот список имеет ценность только при наличии ранее созданной копии базы данных. ┌─────────────────────────────────────────────────────────────────┐ │ Журналирование глобальных массивов не отменяет необходимость │ │ резервного копирования базы данных с помощью специальных утилит.│ └─────────────────────────────────────────────────────────────────┘ Этим можно завершить общий очерк о журналировании глобальных массивов. Можно порекомендовать проводить журналирование только основных рабочих массивов, а из инвертированных, как исключение, только тех, что будет сложно построить на основе информации из основных массивов. Не следует журналировать временные рабочие глобальные массивы. 10.5.4 Администрирование доступа В MUMPS системах администрирование доступа к глобалям обеспечено довольно слабо. В части реализаций обеспечивается ограничение доступа к указанным глобальным массивам, для процессов, запускаемых вне области, где находятся эти глобали. В этих реализациях, например, весь массив может быть защищен для процессов запускаемых в других областях установкой режимов доступа ТОЛЬКО ДЛЯ ЧТЕНИЯ /READ only/, ЧТЕНИЯ и ЗАПИСИ /READ and WRITE/, ЧТЕНИЯ, ЗАПИСИ и УДАЛЕНИЯ /READ, WRITE and DELETE/ или вообще запретом доступа /NO ACCESS/ к нему /*1/. В большинстве разрабатываемых пользовательских приложений можно обеспечить полный запрет доступа к базе данных с пользовательских терминалов. Но ни в одной из MUMPS систем не предусмотрена возможность ограничения доступа к некой части массива, или к части полей данных в массиве. Разумеется программным путем такие ограничения могут быть наложены, но программист, работающий в непосредственном режиме всегда имеет потенциальную возможность доступа к любому из узлов глобальных массивов в той области где он работает. -------------------------------------------------------------------- *1 Например в DSM-11 эти режимы устанавливаются утилитой %GLOMAN В части реализаций /например, DTM/ вся рабочая область может быть "закрыта" паролем. В этом случае для доступа к массивам этой области необходимо указывать ее имя и корректный пароль доступа. Кроме того, автор опускает пока рассмотрение такой возможности администрирования доступа, как установка кодов регистрации в системе и кодов идентификации пользователей. Стр. 261 10.6 Дополнительные примеры: Печать и копирование массивов В качестве заключения мы рассмотрим несколько специальных приемов используемых для просмотра содержимого глобальных массивов, или указанной ветви глобального массива. Среди прочего мы рассмотрим конкретные примеры использования: передачи параметров в вызываемую подпрограмму, оператор NEW, косвенное задание индексных ссылок и применение функций $ORDER и $DATA. Описываемые подпрограммы могут быть использованы в качестве утилит общего назначения для просмотра массивов (или ветвей массивов) произвольной глубины (то есть с произвольным количеством индексов). Кроме того, узлы в исследуемых массивах могут содержать данные и/или быть только логическими указателями для прохода к более низким уровням глобальной структуры. Подпрограмма из примера 8.35 хороша только тогда, когда мы знаем структуру массива и его максимальную глубину, но в общем случае подобный подход является слишком объемным и избыточным для обработки многоуровневых массивов. В рассматриваемом в этом разделе примере мы используем прием называемый РЕКУРСИВНЫМ ПРОГРАММИРОВАНИЕМ. в этом случае подпрограмма вызывает сама себя столько раз, сколько это необходимо для обработки объектов неопределенной заранее размерности (например, обработке произвольного количества переменных, или массивов неопределенной глубины). В недавнем прошлом использование этого приема было затруднено в связи с возникающими трудностями при его реализации, прежде всего то, что используется одна и та же среда локальных переменных. В связи с этим приходилось сохранять необходимые переменные в рабочем массиве, проиндексированном по глубине вложенности вызова подпрограммы. С появлением в MUMPS реализациях таких механизмов, как передача параметров, а также оператора NEW, позволяющего записывать в стек и выбрасывать из него часть среды локальных переменных позволяет надеяться что рекурсивное программирование получит более широкое распространение. В первом примере мы строим утилиту общего назначения для вывода на экран содержимого массива или его ветви. В примере 10.11 содержится процедура запрашивающая у пользователя начальную глобальную ссылку с которой начнется обход массива. Эта часть утилиты не требует особых пояснений. Функция $$GR используется для чтения начальной ссылки с клавиатуры. "Пустой" ввод (нажатие клавиши ), используется как сигнал для завершения работы подпрограммы. При вводе "?", или ошибочной глобальной ссылки на экран выводится краткое пояснение, при этом на экран выводится символ "?", и запрос повторяется. Во всех прочих случаях функция $$GR возвращает корректную глобальную ссылку. Эта функция сделана универсальной и будет использоваться в обсуждаемом ниже примере создания утилиты для копирования глобальных массивов (ветвей массивов). Обратите внимание на то, что и общая часть утилиты, и функция $$GR используют внутренние переменные с именами начинающимися с символа "%" (%g и %rf). Имена переменных, начинающихся с символа "%" обычно зарезервированы для библиотечных программ, и в этом случае, хоты мы и используем NEW, это сделано с целью избежать возможных конфликтов в таблице локальных переменных. Программист может задать значения одного или нескольких индексов для начальной ссылки в локальных переменных. И если только имена этих переменных не начинаются с символа "%", то они будут доступны для использования и их значения не будут изменены. А теперь перейдем к подробному рассмотрению работы программы. Стр. 262 ┌────────────────────────────────────────────────────────────────────┐ │ |Gutil ;Утилиты для работы с глобалями | │ │ |dump ;Вывод массива /или ветви/ на экран | │ │o| New %g |o│ │ | Write !!,$T(+0),"- Распечатка ветви глобали" | │ │ |GETgr Write !,"Начальная ссылка: " Set %g=$$GR | │ │o| Goto GETgr:%g="?" Quit:%g="" |o│ │ | Do Display(%g) Goto GETgr | │ │ | | │ │o|GR() ;Возвращает корректную ссылку -или- "" -или- "?" |o│ │ | New %rf | │ │ | Read %rf Goto GRend:"?"[%rf Set:%rf'?1"^".E %rf="^"_%rf | │ │o| If %rf'?1"^"1A.E Write *7,"??" Set %rf="?" Goto GRend |o│ │ | If '$D(@%rf) Write *7,"?? неопределено" Set %rf="?" | │ │ |GRend Do:%rf="?" Help | │ │o| Quit %rf |o│ │ | | │ │ |Help Write !!,"Глобальная ссылка, напр. ""^XYZ""" | │ │o| Write !," или ""^xyz(1,2)""" |o│ │ | Write !,"Переменные, используемые в индексах, должны | │ │ | Write "существовать в разделе",! | │ │o| Quit |o│ └────────────────────────────────────────────────────────────────────┘ Прим.10.11 Подпрограмма распечатки массива. Продолжение в прим.10.12, описание в приложении G.  ┌────────────────────────────────────────────────────────────────────────┐ │ | ; | │ 1 │ |Display(%gr) ;Вывод ветви определяемой %gr | │ 2 │o| New %d,%s Set %s="" |o│ 3 │ | Set %d=$D(@%gr) Write:%d#10 !,%gr,"=",@%gr Quit:'(%d\10)) | │ 4 │ |Dloop Set %s=$O(@%gr@(%s)) Quit:%s="" | │ 5 │o| If %gr?.E1")" Do Display($E(%gr,1,$L(%gr)-1)_","""_%s_""")") |o│ 6 │ | Else Do Display(%g_"("""_%s_""")") | │ 7 │ | Goto Dloop | │ └────────────────────────────────────────────────────────────────────────┘  Прим.10.12 Рекурсивная подпрограмма для распечатки ветви глобального массива Процедура Display рекурсивная (то есть в процессе работы она вызывает сама себя) и используется для вывода на экран всех узлов глобального массива содержащих данные, заданного параметром, передаваемом при вызове (%gr), вне зависимости от структуры массива. Давайте пошагово разберем действия производимые в каждой из строк этой процедуры. ------------------------------------------------------------------ Строка Описание ------------------------------------------------------------------ 1 Это основная точка входа в подпрограмму. Передаваемый параметр (%gr), является глобальной ссылкой на массив, который распечатывается и может быть задана в двух форматах ^Global /без индексов, если обрабатывается массив целиком/, или ^Global(sub1,sub2,...) /при обработке одной из ветвей массива/ Стр. 263 ------------------------------------------------------------------ 2 В этой строке мы исполняем оператор NEW на две используемые локальные переменные, %d (используется в функции $DATA для оценки текущей глобальной ссылки) и %s, (используется для поиска всех индексов, лежащих ниже текущей глобальной ссылки). Затем мы присваиваем %s пустую строку, для того, чтобы начать обход индексов на данном уровне с помощью функции $ORDER. 3 Если текущая глобальная ссылка содержит данное (то есть %d=1, или %d=11), то используя оператор WRITE мы выводим на экран ссылку и связанное с ним значение данного. Работа процедуры завершается в том случае, если в массиве нет ниже расположенных элементов (в том случае если %d<10 и %d'=11) 4 Строка, начинающаяся с метки Dloop, используется для перехода к следующему индексу на текущем уровне индексации. Если такого индекса нет, производится завершение исполнения процедуры по Quit. 5 Если текущая глобальная ссылка содержит индексы (наиболее частый случай), то производится рекурсивный вызов процедуры с новой глобальной ссылкой, вычисленной из текущей ссылки и необходимостью перехода на более низкий уровень индексации. Для этого отбрасывается последний символ ссылки - ')', и добавляется новый индекс. Например, если %gr="^Test(1,2)" и %s=4, то будет произведен рекурсивный вызов с передачей параметра "^Test(1,2,4)". 6 В этой строке производится дополнительная проверка на тот случай, если текущая ссылка не содержит индексов (так как например, в имени "^Test"). Это может иметь иметь место при первом вызове процедуры Display, когда пользователь хочет просмотреть весь массив. Каждый последующий вызов процедуры Display будет производиться с параметром, содержащим индексы, связанные с обрабатываемой глобальной ссылкой. 7 Передача управления на строку с меткой Dloop, где будет производиться вычисление нового индекса, или завершение процедуры, если такового не окажется. ------------------------------------------------------------------ ┌──────────────────────────────────────────────────────────────┐ │ |>Kill ^Test Set ^Test="Test Gloabl"<- | │ │ |>Set ^Test(1,2,3)="one,two,three"<- | │ │o|>Set ^Test(2)="two",^Test(2,3)="two,three"<- |o│ │ |>Set ^Test(2,4,1)="two,four,one"<- | │ │ |>Set ^(2)="two,four,two",^(3)="two,four,three"<- | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.10.13 Создание массива ^Test После создания тестового массива ^Test мы можем проверить работу созданной утилиты, как показано в следующем примере. Стр. 264 ┌──────────────────────────────────────────────────────────────┐ │ |>Kill Set S1=2,S2=4 Do dump^Gutil<- | │ │ | | │ │o|Gutil- Распечатка ветви глобали |o│ │ |Начальная ссылка: ^Test<- | │ │ |^Test=Test Global | │ │o|^Test(1,2,3)="one,two,three" |o│ │ |^Test(2)="two" | │ │ |^Test(2,3)="two,three" | │ │o|^Test(2,4,1)="two,four,one" |o│ │ |^Test(2,4,2)="two,four,two" | │ │ |^Test(2,4,3)="two,four,three" | │ │o| |o│ │ |Начальная ссылка: ^Test(S1,S2)<- | │ │ |^Test(2,4,1)="two,four,one" | │ │o|^Test(2,4,2)="two,four,two" |o│ │ |^Test(2,4,3)="two,four,three" | │ │ | | │ │o|Начальная ссылка: <- |o│ │ |> | │ └──────────────────────────────────────────────────────────────┘ Прим.10.14 Распечатка ветвей массива Большинство из значений обрабатываемых в описанной процедуре могли бы быть получены и с помощью функции $QUERY, но в следующем примере, демонстрирующем копирование одной ветви массива в другую, это было бы затруднительно. Пример 10.15 объединяет в себе много приемов, использованных в предыдущем примере, применяемых для копирования одного массива (или ветви массива), в другой массив. Исходная и назначаемая ссылки не должны быть с одинаковым уровнем индексации. Мы не приводим здесь текст управляющей программы (той, где производится чтение исходной и назначаемой ссылок), а только текст процедуры исполняющей действительное копирование. ┌──────────────────────────────────────────────────────────────┐ │ |Copy(%f,%t) ;Копирование ветви %f в %t | │ │ | New %d,%s Set %s="" | │ │o| Set %d=$D(@%f) Set:%d#10 @%t=@%f Quit:'(%d\10) |o│ │ |Cloop Set %s=$O(@%f@(%s)) Quit:%s="" | │ │ | Do Copy($$Cref(%f),$$Cref(%t)) | │ │o| Goto Cloop |o│ │ |Cref(%g) ;создание ссылки из %g и %s | │ │ | Quit:%g?.E1")" $E(%g,1,$L(%g)-1)_","""_%s_""")" | │ │o| Quit %g_"("""_%s_""")" |o│ └──────────────────────────────────────────────────────────────┘ Прим. 10.15 Копирование ветви массива После того, как мы более чем подробно, разобрали работу процедуры, представленной в примере 10.12, нет необходимости разбирать приведенный выше пример. Управляющая программа, вызывающая процедуру Copy, должна запрашивать исходную и назначаемую ссылки с клавиатуры. Кроме того, должна производиться дополнительная проверка на существование назначаемой ссылки. Если она уже определена, то необходимо произвести дополнительный запрос, желает ли пользователь произвести "слияние" двух массивов, или удалить существующую ссылку перед произведением копирования. Стр. 265 10.7 Планирование баз данных База данных состоит из большого количества полей, каждое из которых описывает уникальный параметр объекта, или информацию какого-либо наблюдения. Такие поля называются иногда также элементами данного. Отдельные поля объединяются в записи, где каждый элемент данного отделяется от другого каким-либо образом. Записи группируются в файлы (а если опираться на MUMPS терминологию - в глобальные массивы), а весь набор файлов (глобальных массивов), необходимый для работы с прикладным программным средством не что иное как его база данных. ┌────────────────────────────────────────────────────────────────────┐ │ │ │ Данные можно классифицировать на : │ │ Поле: элемент данных характеризующий уникальный параметр объекта│ │ Запись: одно или несколько полей, соединенных через разделитель │ │ Массив: набор записей │ │ База данных: - все массивы хранения данных │ │ │ └────────────────────────────────────────────────────────────────────┘ Отдельные записи идентифицируются (отмечаются) ключами, или указателями. В MUMPS такими ключами являются индексы глобального массива. Каждый из массивов может одновременно содержать записи различных видов, идентифицируемых значением, или последовательностью ключей (индексов). ┌─────────────────────────────────────────────────────────────────┐ │ Ключи в файле идентифицируют запись среди прочих данных. │ │ В MUMPS такими ключами являются индексы глобального массива. │ └─────────────────────────────────────────────────────────────────┘ Представим, в качестве примера, вариант разработки структуры глобального массива, предназначенного для хранения информации относящейся к визитам пациентов в больницу. В этом массиве мы будем записывать записи, состоящие из элементов данных, связанных с каждым обратившимся пациентом. Некоторые из элементов данных уникальные и не могут повторяться (фамилия и имя, адрес /*1/, дата рождения и так далее), в тоже время другие могут повторяться несколько раз (даты обращения и даты выписки, посещения специалистов, анализы, жалобы, назначения и так далее). Примерный список элементов данных приведен в таблице 10.9. Те элементы данных, что могут встречаться несколько раз, в соответствии с концепции структурного подхода приведены в этой таблице с некоторым сдвигом относительно того элемента, который будет определять их множественность. Так, например, каждая дата обращения в больницу определяет то, что будет записана дата выписки, а также связанные с обращением посещения специалистов, а значит и назначения процедур, лекарств, постановку диагнозов и так далее. Эти элементы данных могут быть организованы в записи /группы/, как показано в таблице 10.10. ------------------------------------------------------------------- *1 Хоть это и учебный пример, но хочется заметить, что на практике адресов может быть несколько, например, адрес по прописке (или адрес проживания в другом городе), адрес временного проживания и т.д. и т.п. Конкретные потребности могут быть выявлены только на этапе практической проработки базы данных. Стр. 266 Таблица 10.9 Элементы данных в записи о посещении пациентом больницы. ---------------------------------------------------------------- Идентификационный номер пациента (ID) Фамилия, имя и отчество пациента (FIO) Пол пациента (Sex) Дата рождения пациента (DOB) Адрес пациента (Adress) (улица, город, штат, ZIP код /почтовый индекс/) Телефонный номер пациента (Phone) Дата обращения (Admission Date) Дата выписки (Disharge Date) Посещения специалистов (Attending Physician) Жалобы (Problem) Выполненные лечебные процедуры (Procedure) Назначенные лекарства (Drug) Диагнозы (Diagnosis) ---------------------------------------------------------------- Если мы начнем исследовать взаимосвязи между записями, составленными так, как они представлены в таблице 10.10, то можно увидеть, что каждый пациент имеет одну идентификационную запись (ID record), каждой идентификационной записи будут соответствовать одна или более записей обращений (VISIT record), каждому из обращений будут соответствовать одна или более записей о жалобах, и так далее. Задача при этом состоит в том, чтобы организовать эти записи таким образом, чтобы: во-первых, сохранить все логические взаимосвязи между записями, а во-вторых, оптимизировать доступ к информации относящейся к конкретному пациенту. Таблица 10.10 Организация элементов данных в записи ---------------------------------------------------------------- ┌──┬───┬───┬───┬──────┬─────┐ Идентификационная запись пациента: │ID│FIO│Sex│DOB│Adress│Phone│ └──┴───┴───┴───┴──────┴─────┘ ┌──────────────┬─────────────┬───────────────────┐ Запись обращений: │Admission Date│Disharge Date│Attending Physician│ └──────────────┴─────────────┴───────────────────┘ ┌─────────┬─────────┬──────────────────┬─────────┐ Запись жалоб: │Problem 1│Problem 2│... │Problem n│ └─────────┴─────────┴──────────────────┴─────────┘ ┌───────────┬───────────┬────┬───────────┐ Запись лечебных процедур: │Procedure 1│Procedure 2│... │Procedure n│ └───────────┴───────────┴────┴───────────┘ ┌───────┬───────┬──────────────┬──────┐ Запись назначенных лекарств: │Drug 1 │Drug 2 │... │Drug n│ └───────┴───────┴──────────────┴──────┘ ┌───────────┬───────────┬────────────┬───────────┐ Запись диагнозов: │Diagnosis 1│Diagnosis 2│... │Diagnosis n│ └───────────┴───────────┴────────────┴───────────┘ ---------------------------------------------------------------- Организация взаимного расположения записей внутри массива называется индексацией и достигается в MUMPS с помощью индексов глобальных массивов. Нам необходимо определить ключи (индексы), которые будут указывать на отдельные записи. Стр. 267 Эти ключи (индексы) в каждом массиве должны быть уникальными в пределах уровня индексации, в противном случае невозможно будет достичь логической идентификации записи. На Рис. 10.2 представлен один из способов организации записей из таблицы 10.10 в индексированном массиве. ^PAT ┌────────┐ └─┬─┬──┬─┘ ┌─────────┘ │ (ID) ┌────┴──────────────────────────────┐ │ Идентификационная запись пациента │ └──┬──┬─┬─┬──┬──┬──┬──┬──┬──┬──┬────┘ ┌──────┘ │ (Дата обращения) ┌───┴──────────────┐ │ Запись обращения │ └─┬───┬───┬────┬───┘ │ │ │ └───────────────────────────┐ ┌────┘ │ └────────────────┐ │ │("Diag")└─────┐ ("Drug") │ ("Prob") │ ("Proc") ┌───┴───────┐ ┌───┴───────┐ ┌───┴────┐ ┌──────┴────┐ │ Диагнозы │ │ Лекарства │ │ Жалобы │ │ Процедуры │ └───────────┘ └───────────┘ └────────┘ └───────────┘ Рис. 10.2 Запись информации по пациенту в индексированном файле Ключи, по которым записывается информация в этом файле представлены в виде индексов, которые на рисунке заключены в круглые скобки. Все индексы на каждом из уровней индексации должны быть уникальными. Например, идентификатор пациента (ID) должен быть уникальным номером, каждая дата обращения для конкретного ID должна быть уникальной, и так далее, для избежания конфликтов при записи. Отдельные записи в массиве состоят из одного или больше полей отделенных друг от друга символом/ами/ разделителем/ями/, каждая запись связана с одним из узлов массива. Таким образом, например, идентификационная запись пациента может быть организована следующим образом: Идентификационная запись = "FIO;Street;City;State;ZIP;Phone" где точка с запятой (;), использована как разделитель полей. Выбор разделителя полей определяется каждым программистом индивидуально, при этом можно указать только несколько общих закономерностей. Вне зависимости от того, какой из символов выбран разделителем, программист должен быть уверен в том, что этот символ не будет введен с клавиатуры. Все программы доступа к данным используют $PIECE для выделения отдельного поля из записи, и потому, наличие символа-разделителя внутри поля приведет к непредсказуемым результатам. ┌─────────────────────────────────────────────────────────────────┐ │ Каждое из полей внутри MUMPS записи обычно соответствует пере-│ │менной, может содержать произвольное число символов и разделяются│ │между собой фиксированной последовательностью символов /символом/│ │задаваемой как разделитель полей. │ └─────────────────────────────────────────────────────────────────┘ Стр. 268 В некоторых прикладных программах, обслуживающих ввод данных производится проверка всех ответов на запросы на наличие управляющих символов (десятичное значение кодов ASCII меньше 32). Такие символы могут встретиться из-за того, например, что пользователь случайно нажмет клавишу "Ctrl" вместе с какой-либо из алфавитных клавиш, или нажмет какую-либо из управляющих клавиш. Такая проверка обычно выглядит примерно так - If ans?.E1C.E Do Error. И в этом случае программисты обычно не могут устоять от соблазна использовать один из управляющих символов в качестве символа-разделителя полей в записи. (Например, Set rec=Name_$C(10)_Street_$C(10)_...) Давайте внимательно рассмотрим подобное решение. Во-первых, большинство управляющих символов не выводится на устройство печати, а некоторые и на экран, поэтому могут возникнуть затруднения при анализе содержимого глобальных массивов. Второе затруднение непосредственно связано с первым, и заключается в том, что часть управляющих символов оказывает непосредственное влияние на процесс вывода на устройства. Так например, если $C(10) будет использован как символ-разделитель, то при выводе записей на экран или принтер будет осуществляться перевод строки, каждый раз, как будет встречаться этот символ. В общем, наиболее желательным представляется выбор в качестве символа-разделителя одного из специальных графических /псевдографических/ символов, в этом случае можно быть одновременно уверенным и в том, что это символ будет правильно выводиться как на экран, так и на принтер, так и в том, что этот символ не может быть введен с клавиатуры. Глобальные массивы в прикладных программных продуктах доступны различными способами для реализаций тех или иных функций. Поэтому вполне оправдан подход к планированию глобальных массивов, основанный на наиболее частых обращениях к нему. Но, ни один из реальных массивов баз данных не создается только лишь для одних целей, и всегда необходимо идти на компромисс между логической организацией данных и удобством доступа к ним. Так, массив представленный на Рис.10.2 удобен для доступа к информации по конкретному пациенту, но при необходимости получить сводку по всем пациентам, которым назначено конкретное лекарство, то необходимо будет произвести исчерпывающий поиск по всему массиву. Впрочем, еще раз повторим, лучше всего организовывать массивы хранения информации исходя из наиболее частых обращений к ним, так как мы всегда можем организовать дополнительные массивы, в которых будут поддерживаться ссылки другого вида на те же элементы данных, что и в основном массиве. ┌─────────────────────────────────────────────────────────────────┐ │ Глобальные массивы должны, как правило, организовываться так, │ │чтобы обеспечивать оптимальный доступ к данным для наиболее часто│ │используемых ссылок. │ └─────────────────────────────────────────────────────────────────┘ Дополнительные массивы, организуемые в поддержку данных, записываемых в основном массиве, могут быть нескольких типов. Одним из них являются ИНВЕРТИРОВАННЫЕ или КЛЮЧЕВЫЕ массивы, содержащие альтернативные ссылки на данные, расположенные в основном массиве. Другим широко распространенным типом дополнительных массивов являются так называемые СПРАВОЧНЫЕ массивы, используемые для кодирования данных, записываемых в основной массив. Стр. 269 10.7.1 Инвертированные /ключевые/ массивы Структура массива, представленная на Рис.10.2 представляет собой только один из возможных способов организации записи информации по каждому обратившемуся в больницу пациенту. Если мы собираемся получить информацию по конкретному пациенту, то, естественно, мы должны знать его идентификационный номер (ID). А как быть в тех случаях, когда мы не знаем его, а известно только ФИО пациента? В предложенной структуре глобального массива нам не остается ничего иного, как произвести исчерпывающий поиск подходящих вхождений по всему глобальному массиву на первом уровне индексации. Явно сказывается необходимость организации более быстрой возможности. Одной из альтернативной возможностей является организация дополнительного массива, индексированного по ФИО пациентов и содержащего указатель (идентификатор пациента -ID) на запись в основном файле. Такой массив называется ключевым или инвертированным /по отношению к основному файлу/. Массивы подобного вида создаются в целях быстрого поиска записей в основном файле базируясь на другом порядке организация глобальных ссылок. Используя массив подобной структуры мы можем создать подпрограмму для быстрого поиска идентификационных номеров пациентов по их фамилии и имени, или даже по части фамилии и/или имени. На Рис.10.3 представлена структура подобного ключевого массива. ┌─────────────────────────────────────────────────────────────────┐ │ Ключевыми, или инвертированными массивами называют массивы, │ │ в которых используется другой порядок ссылок, чем тот, что при- │ │ менен в основном массиве хранения информации. │ └─────────────────────────────────────────────────────────────────┘ Ключевые массивы представляют собой компромисс между степенью использования дискового пространства и скоростью работы программ. В ключевом массиве дублируются элементы информации, уже записанные в основном файле, только организованные в другом порядке. С одной стороны использование такого массива уменьшает время поиска по вторичному ключу, с другой стороны увеличивает занимаемое базой данных дисковое пространство и требует корректности в работе программ модификации данных /нельзя забывать про необходимость модификации ключевого массива, при внесении изменений в информацию основного массива нельзя забывать про связанный с ним ключевой/. К сожалению проблема свободного дискового пространства иногда стоит /и стоит/ очень остро, и потому рекомендуется создавать ключевые массивы только в тех случаях, когда скорость поиска является остро критичным параметром. ^PATname ┌────────┐ └─┬┬┬┬┬┬─┘ ┌─────────┘ │(FIO) ┌──┴─────┐ └──┬─┬─┬─┘ │ │(ID) ┌──┴─┐ │ "" │ └────┘ Рис.10.3 Ключевой файл, индексированный по ФИО пациента Стр. 270 Заметьте, что вся информация в массиве, представленном на Рис. 10.3 содержится в индексах, единственное данное содержится при узле ^PATName(FIO,ID)="", и им является пустая строка. Такая структура ключевого массива обеспечивает уникальность записи идентификатора пациента, так как могут встретиться пациенты с полностью совпадающими именами и фамилиями. В примере 10.16 приведена подпрограмма, которая может быть использована для поиска идентификатора пациента по его фамилии и имени (или по их фрагменту). Для избежания возможных неопределенностей, примем, что фамилия и имя пациента записаны в качестве индексов на первом уровне индексации ключевого массива, соединенные через запятую. Причем перед записью в качестве индексов все малые буквы преобразованы в большие. ┌────────────────────────────────────────────────────────────────┐ │ |GetPat() ;Возвращает ID пациента, иначе - пустую строку | │ │ | New first,FIRST,ID,last,LAST,lf,ll,pat,sub,x | │ │o|Read Read !,"Идентификатор (или ФИО для поиска): ",pat |o│ │ | Goto GPx:pat=""!(pat?1N.N) Set pat=$$UP^%Zu(pat) | │ │ | Set last=$P(pat,","),first=$P(pat,",",2) | │ │o| Set sub=last,ll=$L(last),lf=$L(first) |o│ │ |Loop1 Set sub=$O(^PATName(sub)) Goto Read:sub="" | │ │ | Set LAST=$P(^(sub),","),FIRST=$P(^(sub),",",2) | │ │o| Goto Read:$E(LAST,1,ll)'=last |o│ │ | Goto Loop1:$E(FIRST,1,lf)'=first Set ID="" | │ │ |Loop2 Set ID=$O(^PATName(sub,ID)) Goto Loop1:ID="" | │ │o| Set x=^PAT(ID) Write !,ID,?5,$P(x,";")," " |o│ │ | Write $P(x,";",2),", ",$P(x,";",3),", ",$P(x,";",4) | │ │ | Write ", ",$P(x,";",5) | │ │o| Goto Loop2 |o│ │ |GPx Quit pat | │ └────────────────────────────────────────────────────────────────┘ Прим. 10.16 Внешняя функция для поиска идентификатора пациента /ID/ по его фамилии и имени. /*1/ Приведенная в примере внешняя функция запрашивает у пользователя идентификационный номер пациента (ID), или фамилию и имя для поиска. Любой не-числовой ввод предполагается как ввод фамилии и имени, при этом на экран выводятся все возможные вхождения, вместе с дополнительной информацией, которая способна облегчить идентификацию пациента. Этот пример построен исходя из предположения, что идентификационная запись пациента в основном массиве сделана в следующем формате: ^PAT(ID) = "FIO;Street;City;State;ZIP;Phone" То есть с использованием точки с запятой в качестве символа-разделителя полей данных в записи. В следующем примере продемонстрирован сеанс использования этой функции и результат ее работы. ------------------------------------------------------------------- *1 Для русского читателя напомним, что речь идет об американской организации имен и фамилий, first соответствует имени, last - фамилии. Как правило, отчество у них не используются. В примерах я попытался заменить, где можно на более "русскую" интерпретацию, но следует понимать, что под ФИО (FIO), в примерах, в соответствии с вышесказанным понимается только фамилия и имя, соединенные через запятую. Стр. 271 ┌──────────────────────────────────────────────────────────────┐ │ |Идентификатор (или ФИО для поиска): Smith,R<- | │ │ |32145 Smith,Rodger 55 State St., Kingston, NY | │ │o|15469 Smith,Roy 1231 West King Rd, Syracuse, NY |o│ │ |Идентификатор (или ФИО для поиска): SM,<- | │ │ |72334 Small,Allice 15 West Way, New Bolton Center, PA | │ │o|87910 Small,Allice 100 Highbush Rd., Boston, MA |o│ │ |19034 Smith,Allen 1291 West 57th St., New York, NY | │ │ |32145 Smith,Rodger 55 State St., Kingston, NY | │ │o|15469 Smith,Roy 1231 West King Rd, Syracuse, NY |o│ │ |10034 Smith,Tanya 100 Updike Rd., Dryden, NY | │ │ |51034 Smithe,Alex 26 Garden Ave., Schenectady, NY | │ │o|Идентификатор (или ФИО для поиска): 87910<- |o│ └──────────────────────────────────────────────────────────────┘ Прим. 10.17 Пример применения функции для поиска идентификатора по имени из примера 10.16 Другим интересным приемом, используемым при организации поиска по имени является создание массивов, где в качестве индекса записывается код "подобия" фамилии пациента. Код "подобия" фамилии вычисляется исходя из фонетических правил организации англоязычных фамилий. Этот прием также часто используется при записи ключей и в основных массивах с целью избежать ошибок при вводе, которые могут затруднить последующий доступ к данным. Использование кода "подобия" фамилии позволяет записывать все сходные фамилии в одной группе, исходя из преобразования каждой из фамилий в код, состоящий из 4-х, или меньше, символов. Алгоритм вычисления кода "подобия" фамилии выглядит следующим образом: ■ Сохраняем первую букву фамилии ■ Назначаем цифры соответствующие каждой из следующих букв: 0 буквам A, E, H, I, O, U, W, и Y 1 буквам B, F, P, и V 2 буквам C, G, J, K, Q, S, X, и Z 3 буквам D, и T 4 букве L 5 буквам M, и N 6 букве R ■ В том случае, если встречаются две /или больше/ буквы, которые преобразуются в одну цифру, отбрасываем все такие повторы, оставляя только первое вхождение. ■ Исключаем все нули, стоящие на 2 и последующих позициях цифрового кода. (То есть исключаются буквы A, E, H, I, O, U, W, и Y, кроме той, что стоит первой в получаемом коде.) ■ Заменяем первую цифру полученного кода на букву сохраненную в шаге 1. ■ Обрезаем полученный код до 4-х символов.(То есть до следующего формата - буква, цифра, цифра, цифра.) Обратим Ваше внимание на то, что обычно при вычислении кода "подобия", в том случае, когда он содержит меньше чем 4-е символа, то он дополняется нулями справа, но в MUMPS в этом нет необходимости. Стр. 272 ┌──────────────────────────────────────────────────────────────┐ │ |SDX(Name) ;Внешняя функция, возвращает код "ПОДОБИЯ" | │ │ | New c,i,fl,Last,lc,sdx,x | │ │o| Set Last=$$UP^%Zu($P(Name,",")),fl=$E(Last) |o│ │ | Set sdx=$TR(Last,"BFPVCGJKSXZDTLMNRAEHIOUWY","11112222| │ │ | ...222233455600000000") | │ │o| Set (c,lc,x)="" |o│ │ | For i=1:1:$L(sdx) Set c=$E(sdx,i) If c'=lc Set x=x_c,l| │ │ | ...c=c | │ │o| Quit fc_$E($TR($E(x,2,99),"0",""),2,3) |o│ └──────────────────────────────────────────────────────────────┘ Прим. 10.18 Внешняя функция вычисляющая код "подобия" С помощью функции $TRANSLATE производится преобразование букв в соответствующие им цифры, а затем в цикле удаляются все стоящие рядом и повторяющиеся более одного раза цифры. Используя этот алгоритм фамилия Lewkowicz будет преобразована в код подобия L22, также как и фамилии LaCasse, Laczak, Lagoze, Laskowski, Lissick, Lucas и Lyczkowski. Проанализируем поэтапно вычисление кода "подобия" для фамилии Lewkowicz: ■ Сохраняем первую букву фамилии -> "L" ■ Заменяем буквы на соответствующие им цифры -> "400200022" ■ Отбрасываем повторяющиеся вхождения цифр -> "40202" ■ Отбрасываем нули стоящие на не-первых позициях -> "422" ■ Заменяем первую цифру кода на сохраненную букву -> "L22" Глобальная структура, описанная на Рис.10.3 может быть легко преобразована для использования кода подобия вместо фамилии и имени в качестве индекса на первом уровне индексации. Гораздо большие изменения потребуется произвести в процедуре 10.16 для осуществления поиска фамилий по коду их подобия. Как уже упоминалось ранее, инвертированные /ключевые/ массивы не что иное, как компромисс между степенью использования дискового пространства, временем модификации данных и скоростью исполнения прикладных программ. Количество и вид создаваемых ключевых массивов определяется только обоснованными потребностями конечного пользователя. Когда появляется явная потребность в просмотре записей элементов данных не являющихся основными ключами, то создание такого ключевого массива становится желательным и можно примириться с уменьшением свободного дискового пространства. Для возможного же поиска по произвольно задаваемым элементам данных /или их сочетаниям/, проще применять исчерпывающий поиск по всему массиву, несмотря на то, что он будет производиться достаточно медленно. 10.7.2 Справочные массивы Справочные массивы используются для определения тех элементов данных, которые записываются в основном массиве закодированными вхождениями (например список жалоб или кодов диагнозов). В качестве примера рассмотрим элементы данных представленные в таблице 10.10. Мы можем записать специалиста, к которому произведено обращение, полным наименованием, но можем назначить каждому специалисту УНИКАЛЬНЫЙ код и записывать в основной массив лишь его. Стр. 273 ┌─────────────────────────────────────────────────────────────────┐ │ Справочные массивы содержат описания закодированных полей │ │данных из основных массивов хранения информации. │ └─────────────────────────────────────────────────────────────────┘ Первоначально необходимость записи закодированных данных объяснялась плохими возможностями обработки длинных строк и необходимостью сохранения дискового пространства. Сейчас острота проблемы с дисковым пространством снята, возросли объемы используемых дисковых накопителей и одновременно снизилась их цена. Если в начале 70-х годов 1 Мегабайт дискового пространства стоил около 10 000 $, то к 1986 году его стоимость упала до 25 $. И потому, сейчас затраты на увеличение сложности программирования, связываемые с кодированием и декодированием данных зачастую сводят на нет потенциальный выигрыш в степени использования дискового пространства. Вторую причину кодирования данных часто приводят как необходимость при работе со стандартной номенклатурой. Но, как мне кажется, стандартная номенклатура не требует для себя кодирования данных, а только стандартизированного, ключевого словаря, аналогичного тому, что используется в большинстве библиотечных поисковых систем. Наиболее важным резоном использования принципа кодирования данных - это достоверность данных. Критерием качества любой информационной системы является достоверность получения записанных данных. При этом важными становятся два основных качества точность поиска и идентифицируемость. Точность поиска - это отношение между числом отобранных при поиске записей к общему числу просмотренных при поиске записей. В общем, чем более тщательно будет проработана система кодирования, тем выше точность поиска системы. Идентифицируемость - это число отобранных записей к общему числу записей в базе данных, которые удовлетворяют условию поиска. В общем точность поиска и идентифицируемость, величины, которые находятся в обратно пропорциональной зависимости, усилия связанные с повышением первого качества, зачастую приводят к снижению второго. ┌─────────────────────────────────────────────────────────────────┐ │ Наиболее существенной причиной, обуславливающей кодирование │ │данных, является повышение достоверности получения данных. │ └─────────────────────────────────────────────────────────────────┘ Достаточная идентифицируемость системы требует стандартизации номенклатуры. Каждый уникальный элемент должен иметь одно, стандартное представление (код или наименование). Если несколько синонимов /подобий/ ссылаются на один и тот же элемент, то этот элемент может быть записан под несколькими различными заголовками. Естественно, что поиск в таких условиях не может быть исчерпывающим, кроме того, повышается степень ошибочных заключений при проведении поиска. В то же время, информационная система должна иметь существенную точность поиска для идентификации записей при проведении исчерпывающего поиска по всей базе данных. Если будет отобрано большое количество записей, которые, в конечном счете, не будут удовлетворять условиям поиска, то во-первых, будет затрачено на сортировку неопределенное количество времени, а во-вторых, может потребоваться ручная сортировка данных, что существенно снизит эффективность работы. Стр. 274 Большинство информационных систем разрабатывается для одновременной реализации различных целей, и потому ее управляющая система должна иметь возможность анализировать базу данных с многих точек зрения. При этом не может быть использован поиск ключевых слов, даже если разработан стандартизированный словарь, в котором разрешена проблема множественности синонимов одного элемента. Потому, что такая система сможет извлечь все записи, содержащие уникальный ключ, или записи, содержащие общий ключ, но в ней не сможет быть /или потребует чрезмерных ухищрений, связанных с большими затратами времени при работе программ/ осуществлено выделение групп для подобных случаев. Например, в базе данных, содержащей информацию о всех работающих в фирме, очень трудно будет организовать поиск группы служащих относящихся к "техническому персоналу", в том случае, если в базе данных записаны конкретные специальности - инженеры, программисты, лаборанты и тому подобные, которые относятся к общей группе работников "технического персонала". Такие ситуации могут встретиться во многих общих случаях, при этом невозможно предвидеть все общие случаи использования базы данных, и трудно будет поддерживать точность и последовательность ее функционирования. Эти резоны заставляют обращаться к разработке иных кодирующих систем, в которых будет производиться попытка одновременно повысить и точность поиска и идентифицируемость. Такие системы являются иерархическими структурами кодирования, потому, что каждый записанный в них элемент может быть легко идентифицирован по своему конкретному положению в структуре /повышение точности поиска/, и одновременно автоматически отнесен к более высоким уровням структуры или общности. /повышение идентифицируемости/. Например, фрагмент организации иерархической структуры кодирования областей рынков сбыта показан на Рис.10.4. Domestic marketing /Отечественный рынок/ Northeastern U.S. /Северо-восток США/ Maine Portland New York New York Albany Buffalo Pensylvania Philadelphia Scranton Massachusets Boston . . Southwestern U.S /Юго-запад США/ . . Foreign marketing /Иностранный рынок/ Europe Japan . . Рис. 10.4 Одна из возможных иерархий организации кодирования областей рынков сбыта Стр. 275 При иерархической организации кодирования каждая общая категория включает в себя несколько менее общих разделов. Данный подход имеет впрочем и свои ограничения. В приведенной иерархической структуре может применено только одно направление ссылок (как на Рис.10.4), или функция (рекламное дело, розничная продажа и пользовательские отношения), но несколько одновременно. Частный запрос может быть адресован более удачно с одного направления, чем с другого, и выбор иерархической структуры должен определяться наиболее частым направлением поисков. Другое направление может быть связано с дополнительными файлами перекрестных ссылок или с системой "многоосевого" кодирования. "Многоосевое" кодирование часто используется в медицинских информационных системах, для кодирования диагнозов и лечебных процедур которые могут иметь множественные ссылки в своем коде. Диагноз может иметь в себе элемент указывающий на поражение определенного органа тела или конкретной части органа, (например, левая вентрикулярная стенка сердца), другое поле указывает на функциональные изменения в этом месте (воспаление), третье поле указывает на причину, вызвавшую заболевание (травма). Каждое из полей является элементом иерархической структуры, комбинация трех полей создает единый код заболевания, несущий в себе три ссылки. При выборе системы кодирования, внимательно рассмотрите, как будет сказываться на работе прикладного программного средства рост числа последовательных доступов к базе данных. 10.8 Поиск и сортировка Мы уже приводили несколько примеров сортировки в глобальных массивах, (например, Прим.10.16), с использованием записи элементов данных в качестве индексов, но такой подход обычно требует исчерпывающего поиска по одному или больше уровням индексации. Сортировка данных в MUMPS будет очень проста, если использовать все возможности представляемые глобальными структурами и действующим в них порядком упорядочивания индексов. Предположим, нам требуется в глобальном массиве, описанном на Рис.10.2 произвести поиск всех пациентов, обращавшихся к конкретному специалисту, и вывести на указанное устройство отчет, в котором все найденные пациенты будут упорядочены в алфавитном порядке по штату, затем городу, а затем по фамилии пациента. Это может потребовать двух-уровневого поиска по массиву ^PAT, для идентификации конкретного специалиста (запись об обращении к специалисту содержится в записи о визите по конкретному идентификатору пациента (ID) после даты обращения). Предположим, что запись данных осуществляется в следующем формате: ^PAT(ID)="Name;Street;City;State;ZIP;Phone" ^PAT(ID,VISIT)="Admission Data;Discharge Data;Physician Code" В примере 10.19 содержится описание процедуры, которая ищет и выводит на указанное устройство вывода все случаи обращения к конкретному специалисту (клинический код - 101). Печатаемые результаты сортируются по улице, городу и имени пациента. Стр. 276 ┌──────────────────────────────────────────────────────────────┐ │ |Search Set (ID,VISIT)="",CLIN=101 Kill ^Srch($J) | │ │ |GetID Set ID=$O(^PAT(ID)) Goto Print:ID="" | │ │o| Set x=^PAT(ID) |o│ │ | For i=1:1:5 Set @$P("Name;Street;City;State;ZIP;Ph| │ │ | ...one",";",i)=$P(x,";",i) | │ │o|GetVIS Set VISIT=$O(^PAT(ID,VISIT)) Goto GetID:VISIT="" |o│ │ | Goto GetVIS:$P(^PAT(ID,VISIT),";",3)'=CLIN | │ │ |Sort Set ^Srch($J,State,City,Name,ID)=x Goto GetID | │ │o|Print . |o│ │ | . | │ │ | . | │ └──────────────────────────────────────────────────────────────┘ Прим. 10.19 Простая процедура для организация поиска и сортировки Как Вы смогли заметить, в приведенном примере сортировка данных действительно чрезвычайно проста. Для сортировки результатов мы используем временный массив (^Srch), который проиндексирован на первом уровне по номеру задания ($JOB), так как одновременно несколько заданий могут обратиться к этой процедуре. Порядок записи сортирующих ключей в массиве очень важен, наиболее значащий ключ должен стоять на первом месте, наименее значащий - на последнем. Мы специально добавляем идентификатор пациента - ID, на последний уровень индексации, для гарантированного обеспечения уникальности записи информации по каждому пациенту. (Может оказаться вполне возможным совпадение фамилий и мест жительств для разных пациентов) Записываемые поля сортируются на основе упорядочивающей последовательности ASCII кодов для строк символов. Числовые поля будут сортироваться в соответствии с их значениями. Для сортировки числовых значений в порядке уменьшения можно инвертировать знак числа. Например, для сортировки возрастов пациентов /AGE/, достаточно записать - AGE, в массив сортировки. Конечно, мне кажется, сложно на этом закончить обсуждение проблем сортировки в системах манипулирования базами данных. Так, например, осталось в стороне использование структурности, присущей языку MUMPS, и описание приемов сортировки данных получилось достаточно незавершенным. Но это является предметом особого рассмотрения, лежащего за пределами данной книги. Главное, на что хочется еще раз обратить Ваше внимание, в абсолютном большинстве случаев в MUMPS нет необходимости писать специальные процедуры сортировки, так как глобальные массивы автоматически упорядочивают записываемые в них в качестве индексов значения. 10.9 Общие положения ■ Функция $ORDER возвращает индекс, следующий за указанным в ее аргументе в порядке сортировки, на данном уровне индексации глобального массива, вне зависимости от последовательности записи узлов в массив. (10.1) ■ Число в каноническом виде не содержит не-значащих символов. (Например, для таких чисел выполняется Number=+Number) (10.2.1) Стр. 277 ■ В индексах числа в каноническом виде упорядочиваются перед всеми прочими не-каноническими строками. Числа в каноническом виде упорядочиваются в соответствии с их числовыми значениями, все прочие строки - в соответствии с упорядочивающей последовательностью ASCII кодов. (10.2.1) ■ Сортировка в MUMPS решается путем создания временных массивов (локальных или глобальных), в которых сортируемые элементы данных записываются в качестве индексов. (10.2.1) ■ При проведении сортировки по двум и более ключам наиболее значащий ключ записывается как индекс первого уровня в массиве сортировки, наименее значащий - последним индексом. (10.2.1) ■ Функция $ORDER возвращает все индексы, находящиеся на данном уровне индексации массива (локального или глобального), вне зависимости от того, содержат ли узлы массива, соответствующие этим индексам, данные или нет. (10.2.2) ■ Функция $DATA используется для того, чтобы определить, содержит ли данный узел массива данное и/или является только логическим указателем для прохода к более низким уровням индексации массива. (10.2.2) ■ Неполный синтаксис глобальной ссылки - это сокращенная форма обращения к узлу глобального массива, базирующаяся на последнем обращении к узлу массива. (10.3) ■ В операторе SET выражение справа от знака равенства оценивается раньше, чем выражение слева от знака равенства. (10.3) ■ Программисты должны быть осторожны в использовании неполного синтаксиса при обращениях к узлам глобальных массивов. (10.3) ■ Оператор LOCK используется для ограничения доступа других процессов к ветвям глобальных массивов, при этом ограничивается не физический доступ к этим ветвям, а возможность других процессов использовать оператор LOCK к этим ветвям массивов. (10.4) ■ Не-дополняющая форма оператора LOCK отменяет все ранее нало- женные блокировки перед попыткой произвести блокировки ссылок указанных в ее аргументе. (10.4) ■ Дополняющая и исключающая формы оператора LOCK не оказывают влияния на все остальные установки оператора LOCK в текущем процессе. (10.4) ■ Журналирование глобальных массивов не отменяет необходимость резервного копирования базы данных с помощью специальных утилит. (10.5.3) Стр. 278 ■ Данные можно классифицировать на : Поле: элемент данных характеризующий уникальный параметр объекта Запись: одно или несколько полей, соединенных через разделитель Массив: набор записей Базу данных: - все массивы хранения данных (10.7) ■ Ключи в файле идентифицируют запись среди прочих данных. В MUMPS такими ключами являются индексы глобального массива. (10.7) ■ Каждое из полей данных внутри MUMPS записи обычно соответствует переменной, может содержать произвольное число символов и разделяются между собой фиксированной последовательностью символов /символом/ задаваемой как разделитель полей. (10.7) ■ Глобальные массивы должны, как правило, организовываться так, чтобы обеспечивать оптимальный доступ к данным для наиболее часто используемых ссылок. (10.7) ■ Ключевыми, или инвертированными массивами называют массивы, в которых используется другой порядок ссылок, чем тот, что применен в основном массиве хранения информации. (10.7.1) ■ Справочные массивы содержат описания закодированных полей данных из основных массивов хранения информации. (10.7.2) ■ Наиболее существенной причиной, обуславливающей кодирование данных, является повышение достоверности. (10.7.2) Стр. 279 Глава 11 Нюансы MUMPS Эта глава предназначается для тех читателей, кто желает углубить свои представления о внутренних операциях производимых MUMPS-системой, и о том как они отражаются на приемах программирования и принципах построения прикладных программ. При этом мы коснемся многих приемов и концепций отраженных в различных MUMPS реализациях, и которые не могут быть применены без подробного изучения конкретной реализации. Где воз- можно, предметы, относящиеся к конкретным реализациям описаны как раз- личия в подходах, сказывающиеся на приемах программирования, особенно те, что имеют отношение к скорости исполнения программ и степени ис- пользования дискового пространства. Глава разделена на две основные части: первая посвящена описанию системной поддержке глобальных масси- вов и их организации на диске, вторая - основам исполнения MUMPS прог- рамм. Рассматривая внутреннюю организацию глобальных массивов мы скон- центрируем свое внимание на логической организации массивов и их физи- ческой структуре, поддерживаемых в большинстве MUMPS систем. При этом мы коснемся некоторых факторов, сказывающихся на объемах, занимаемых базами данных и затратах времени на запись и чтение данных. В разделе посвященном исполнению MUMPS программ мы постараемся противопоставить "истинно" интерпретирующие MUMPS реализации тем, что используют "компиляцию" исходного кода программ в объектный. Здесь же мы рассмотрим некоторое количество приемов оптимизации текста прог- рамм, и обратимся к некоторым темам, имеющим непосредственное отноше- ние к производительности MUMPS систем. Все объекты, рассматриваемые в этой главе лежат вне MUMPS стан- дарта, их существование и воплощение зависит от конкретной MUMPS реа- лизации. Все представляемые концепции рассматриваются на некоторой ги- потетической модели, созданной для понимания основных принципов функ- ционирования MUMPS систем. 11.1 Организация глобальных массивов Сразу следует оговориться, что логическая структура баз данных имеет небольшое отношение к физической структуре, используемой для поддержания файлов на компьютере, а физическая структура имеет непос- редственное отношение к общей производительности системы. При оценке производительности различных файловых структур мы должны сопоставлять по крайней мере шесть основных их параметров: 1. Время требуемое для добавления записи в файл 2. Время требуемое для модификации записи в файле 3. Время требуемое для удаления записи 4. Время требуемое для поиска конкретной записи в файле 5. Время требуемое для поиска всего файла 6. Пространство, требуемое для размещения файла В многочисленных программных продуктах, представленных в настоя- щее время на рынке, реализовано множество различных файловых структур поддерживающих ту или иную базу данных. В числе использованных струк- тур можно встретить: последовательные файлы, индексированные последо- вательные файлы, индексированные файлы, кольцевые структуры, деревья, и т.д. и т.п. Стр. 280 Структуры баз данных описаны во многих книгах, блестящий обзор этой сложной темы, и ссылки на многие другие издания, Вы сможете найти в следующей книге: Richard F.Walters Database Principles for Personal Computers. Prentice Hall Inc., Englewood Gliffs. N.J 1987 /имеется русский перевод/ По многим причинам в большинстве MUMPS реализаций глобальные структуры основываются на концепции так называемых сбалансированных деревьев (именуемых также и В-деревьями). В-деревья представляют собой мощный инструмент для организации разряженных структур с использовани- ем ключей (индексов). При этом обеспечивается эффективный механизм за- писи и чтения данных с минимальным количеством обращений к диску. Для дальнейшего обсуждения необходимо упомянуть о достаточном внешнем сходстве между физической и логической организацией глобальных масси- вов, хотя на самом деле такого соответствия нет. ┌─────────────────────────────────────────────────────────────────┐ │ В большинстве MUMPS систем глобальные массивы реализуются │ │с использованием так называемых "сбалансированных" деревьев, │ │хотя глобали могут быть, в принципе, организованы и посредством │ │других файловых структур. │ └─────────────────────────────────────────────────────────────────┘ В В-деревьях имеется два основных компонента: блоки данных и бло- ки указателей. Блок - это единица объема дисковой и оперативной памяти выделяемой для операций с данными. Большинство из MUMPS систем поддер- живает блоки размеров в 1024 и 2048 байт. /*1/ Любой блок может содер- жать много узлов глобальных массивов (как указателей, так и узлов с данными) ┌────────────────────────────────────────────────────────────┐ │ В-деревья состоят из блоков указателей и блоков данных. │ └────────────────────────────────────────────────────────────┘ Существует много видов организации сбалансированных деревьев. От- личия касаются дополнительной информации, записываемой на каждом физи- ческом уровне дерева (ключей, указателей и данных), а также использо- вания необязательных межблочных указателей (связей) с различными уров- нями. В последующем обсуждении мы не будем останавливаться на различ- ных специфических видах В-деревьев, заменив это обсуждением общих принципов их организации. Структура сбалансированных деревьев, описы- ваемая в тексте, не может быть в точности соотнесена ни с одной из ре- ализаций, но основные принципы их организации сходны. ----------------------------------------------------------------- *1 Следует отметить, что существует разница между так называемыми фи- зическими и логическими блоками. Физический блок - это элемент распре- деления дискового пространства /или после форматирования диска, или тот, который выделяет операционная система/. Для СМ ЭВМ /семейство PDP-11/, это, как правило, 512 байт. Для персональных ЭВМ - это сектор в 512 байт /большинство MUMPS реализаций работают с диском напрямую, минуя функции DOS/. В то же время MUMPS системы в своей работе опери- руют с логическим блоком, который может иметь размер от 512 байт до 8-ми, и даже 16-ти Кбайт. Стр. 281 11.1.1 Добавление данных в В-дерево Для большей наглядности всех последующих примеров, примем, что в один блок можно записать 4-е узла данных, или 6-ть узлов указателей. Также, во всех приводимых примерах под узлом мы будем понимать узел глобального массива с соответствующим ему данным. В действительности в один блок может быть записано гораздо больше узлов указателей и дан- ных, их количество определяется прежде всего установленным размером блока, а также длиной индексной ссылки и размером поля данного при уз- ле. Принятие, в качестве примера, ограничения на количество узлов, за- писываемых в блоке, необходимо для понимания процессов, происходящих в физической структуре массива при добавлении новых узлов данных в мас- сив. При добавлении данных в массив происходят одновременные изменения и в блоках данных и в блоках указателей, но эти изменения носят абсо- лютно различный характер. Все дальнейшее обсуждение, и все иллюстра- ции, будут относится к глобальному массиву создаваемому в примере 11.1. ┌─────────────────────────────────────────────────────────────────┐ │ |>Set Subs="ZYXWVUTSRQPONMLKJIHGFEDCBA"<- | │ │ |>Set Data="zyxwvutsrqponmlkjihgfedcba"<- | │ │o|>For i=1:1:26 Set sub=$E(Subs,i),dat=$E(Data,i),^X(sub)=dat<-|o│ │ |> | │ └─────────────────────────────────────────────────────────────────┘ Прим.11.1 Создание массива ^X в порядке обратном упорядочивающей последовательности ASCII кодов. В этом примере создается массив ^X, причем индексы в него добав- ляется в порядке, обратном упорядочивающей последовательности ASCII кодов. [ то есть ^X("Z")="z", затем ^X("Y")="y", и так далее] Порядок, в котором индексированные узлы добавляются в глобальный массив сказы- вается на распределении данных и указателей в блоках. Причины, обус- лавливающие эту разницу, и ее следствия будут подробно рассмотрены в дальнейшем обсуждении. На Рис. 11.1 представлена физическая структура В-дерева после четвертого шага цикла из примера 11.1. В верхней части рисунка 11.1 представлен блок глобального каталога (блок 103), в котором содержатся имена глобальных массивов, существующих в данной области и ссылки на их блоки указателей. Элемент блока глобального каталога, который со- держит имя массива ^X, указывает на первый блок указателей этого мас- сива (блок 214). Обратите внимание на то, что хотя мы создали четыре узла с данны- ми, но в блоке указателей присутствует только один элемент. Этот эле- мент содержит ключ "W" и указывает на первый узел с данными в блоке данных на который он указывает (блок 171). Элемент в блок указателей добавляется только необходимости назначения нового блока данных. Поэ- тому указатель создается не для каждого узла с данными, а только для каждого блока с данными. Указатели вставляются в блок в соответствии с упорядочивающей последовательностью ASCII кодов. ┌─────────────────────────────────────────────────────────────────┐ │ В блоке указателей содержатся элементы, каждый из которых ука- │ │зывает на блок указателей нижнего уровня, или на блок данных. │ └─────────────────────────────────────────────────────────────────┘ Стр. 282 Блок 103 ┌─────┬─────┬─────┬─────┐ Блок │ ^A │ ^C │ ^X │ │ глобального │ │ │ │ │ каталога │ 640 │ 501 │ 214 │ │ └─────┴─────┴─────┴─────┘ | V Блок 214 | ┌─────┬─────┬─────┬─────┬─────┬─────┐ Блок │ "W" │ │ │ │ │ │ указателей │ │ │ │ │ │ │ │ 171 │ │ │ │ │ │ └─────┴─────┴─────┴─────┴─────┴─────┘ | V Блок 171 | ┌────────────┐ │ "W" : "w" │ Блок ├────────────┤ данных │ "X" : "x" │ ├────────────┤ │ "Y" : "y" │ ├────────────┤ │ "Z" : "z" │ └────────────┘ Рис.11.1 Физическая структура В-дерева Каждый элемент данных (например,"W" : "w" ) содержит одновременно ключ (индексную ссылку) узла ("W") и данное ("w"), связанное с этим узлом. Все элементы данных вставляются в блок в соответствии с упоря- дочивающей последовательностью MUMPS, опираясь на значения ключа. (В блоке расположены - сначала "W", затем "X", "Y", и "Z", несмотря на то, что эти узлы создавались в обратном порядке). ┌─────────────────────────────────────────────────────────────────┐ │ Каждому узлу массива соответствует элемент в блоке данных. Этот│ │элемент содержит одновременно ключ (индексную ссылку), и данное, │ │связанное с этим узлом. │ └─────────────────────────────────────────────────────────────────┘ Номера блоков, приведенные в примере с Рис.11.1, выбраны произ- вольным образом, и будут последовательно проходить через все приводи- мые в главе примеры организации физической структуры В-дерева. При не- обходимости в новом блоке MUMPS система назначает первый из свободных блоков доступных на данный момент в системе. Поэтому, блоки могут иметь любой произвольный номер. Используя описанную структуру разберем как MUMPS обрабатывает ко- мандную строку вида Write ^X("Y"), или, в более общем случае, разбира- ет глобальную ссылку. 1. Первым делом система обращается к блоку глобального каталога (103), и ищет в нем элемент соответствующий массиву ^X. Такой массив в области существует. Его имени в блоке соответствует ссылка на блок указателей верхнего уровня массива (блок 214). Стр. 283 2. Во втором шаге, система читает с диска блок указателей верхнего уровня. (214) В этом блоке производится попытка найти указа- тель на ключ "Y". Но в блоке находится только один элемент - "W". Это означает, что в массиве ^X нет ключей предшествующих ключу "W", а значит, если ключ "Y" определен, то он записан в блоке данных, на который указывает ссылка при ключе "W". (блок 171) 3. В заключительном шаге система осуществляет загрузку блока дан- ных (171), и начинает в нем последовательно искать элемент с заданным ключом. Третий элемент блока данных содержит этот ключ ("Y") и результатом поиска является получение данного, связанного с заданным для поиска ключом. Теперь давайте посмотрим, что будет происходить при дальнейшем добавлении данных в массив ^X. В следующем шаге цикла (Прим. 11.1), производится назначение следующего узла массива ^X("V")="v", который уже не может быть размещен в текущем блоке данных. (По принятому нами условию в блок данных может быть записано только 4-е узла массива). Поэтому перед записью в массив нового узла блок данных должен быть "расщеплен" на два новых блока. Процесс "расщепления" и запись нового узла массива можно представить в виде следующей последовательности ша- гов: 1. MUMPS-система находит новый свободный блок для использования. 2. MUMPS-система производит "расщепление" блока данных и запись нового узла в один из них. В связи с различными возможными зна- чениями ключа вновь записываемого узла массива расщепление и запись элемента может производиться двумя путями: а). Если ключ вставляемого узла следует ЗА последним из существующих ключей глобального массива, то все блоки данных останутся неизменными и вставляемый элемент бу- дет записан в выделенный для использования блок. б). Если вставляемый ключ не является последним в глобаль- ном массиве, то часть элементов блока данных будет пе- реписана в выделенный для использования блок, а встав- ляемый элемент будет размещен в одном из блоков в со- ответствии со своим значением ключа. 3. Производится модификация блока указателей, после чего в нем до- бавляется ссылка на новый блок данных, и все указатели упорядочи- ваются в соответствии с упорядочивающей последовательностью MUMPS. После "расщепления" блока данных состояние блоков отображено на Рис. 11.2. Еще раз напомним, что указатели всегда указывают на значе- ние ключа первого элемента блока данных. Все элементы и в блоках ука- зателей и в блоках данных всегда размещены в соответствии с упорядочи- вающей последовательностью MUMPS. При дальнейшей записи узлов в массив мы неизбежно подойдем к необходимости производить "расщепление" блока указателей. Разберем действия MUMPS системы в этом случае. Стр. 284 Блок 103 ┌─────┬─────┬─────┬─────┐ Блок │ ^A │ ^C │ ^X │ │ глобального │ │ │ │ │ каталога │ 640 │ 501 │ 214 │ │ └─────┴─────┴─────┴─────┘ | V Блок 214 | ┌─────┬─────┬─────┬─────┬─────┬─────┐ Блок │ "V" │ "Y" │ │ │ │ │ указателей │ │ │ │ │ │ │ │ 171 │ 268 │ │ │ │ │ └─────┴─────┴─────┴─────┴─────┴─────┘ | | V V Блок 171 | | Блок 268 ┌────────────┐ ┌────────────┐ │ "V" : "v" │ │ "Y" : "y" │ Блоки ├────────────┤ ├────────────┤ данных │ "W" : "w" │ │ "Z" : "z" │ ├────────────┤ ├────────────┤ │ "X" : "x" │ │ │ ├────────────┤ ├────────────┤ │ │ │ │ └────────────┘ └────────────┘ Рис.11.2 Структура В-дерева после "расщепления" блока данных ┌─────────────────────────────────────────────────────────────────┐ │ При переполнении блока данных производится его "расщепление". │ │Часть данных из него, или только вновь добавляемый элемент, │ │(в зависимости от значения его ключа), будет записана в новый │ │блок данных. В блок указателей будет добавлен элемент, указываю- │ │щий на первый ключ нового блока данных. При необходимости будет │ │модифицирован и указатель на прежний блок данных. │ └─────────────────────────────────────────────────────────────────┘ Но перед тем, как перейти к рассмотрению процесса "расщепления" блока указателей посмотрим, какую дополнительную информацию мы можем получить с Рис.11.3, на котором отражено состояние блоков на момент непосредственно предшествующий "расщеплению". Первым элементом в блоке указателей является ключ "M", это означает, что в массиве ^X НЕТ клю- чей предшествующих ключу "М". Все ключи узлов массива, (и связанные с ними данные), начиная с "М" и по "Q" (но не включая его) будут разме- щены в блоке данных 171. Ключи с "Q" по "S" будут размещены в блоке 106 и так далее. При записи нового узла массива ^X("L")="l" произойдет "расщепле- ние" блока данных (171), как описано выше, и одновременно блока указа- телей массива ^X. "Расщепление" блока указателей производится совер- шенно по другому, чем "расщепление" блока данных. При "расщеплении" блока указателей в структуру массива добавляются блоки нового физичес- кого уровня, как это показано на Рис.11.4 ┌─────────────────────────────────────────────────────────────────┐ │ При переполнении блока указателей он также "расщепляется", но │ │при этом создается новый уровень указателей на блоки данных. │ └─────────────────────────────────────────────────────────────────┘ Стр. 285 Блок 103 ┌─────┬─────┬─────┬─────┐ Блок │ ^A │ ^C │ ^X │ │ глобального │ │ │ │ │ каталога │ 640 │ 501 │ 214 │ │ └─────┴─────┴─────┴─────┘ | V Блок 214 | ┌─────┬─────┬─────┬─────┬─────┬─────┐ Блок │ "M" │ "Q" │ "S" │ "U" │ "W" │ "Y" │ указателей │ │ │ │ │ │ │ │ 171 │ 268 │ 937 │ 371 │ 97 │ 268 │ └──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘ ┌────────────┘ │ │ ┌─┘ │ │ │ ┌─────────┘ │ │ │ └─────┐ │ │ ┌─────┘ │ └─┐ │ │ │ │ │ │ │ ┌────┴──┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │"M":"m"│ │"Q":"q"│ │"S":"s"│ │"U":"u"│ │"W":"w"│ │"Y":"y"│ Блоки ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ данных │"N":"n"│ │"R":"r"│ │"T":"t"│ │"V":"v"│ │"X":"x"│ │"Z":"z"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"O":"o"│ │ │ │ │ │ │ │ │ │ │ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"P":"p"│ │ │ │ │ │ │ │ │ │ │ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ Блок Блок Блок Блок Блок Блок 171 106 937 371 97 268 Рис.11.3 Структура В-дерева перед "расщеплением" блока указателей Создание нового физического уровня указателей при "расщеплении" блока указателей интересно тем, что оно обуславливает "ветвление" чте- ния блоков при доступе к конкретному узлу массива. В В-дереве для дос- тупа к любому узлу массива требуется одинаковое количество чтений дис- ковых блоков. Опираясь на приближенные к реальным значения заполнения блоков указателей и блоков данных, скажем 100 указателей и 20 узлов данных в одном блоке, можно рассчитать количество блоков, которые должны быть прочитаны для доступа к любой записи в массиве в зависи- мости от размера массива. Эти данные представлены в таблице 11.1. Таблица 11.1 Число чтений блоков для доступа к конкретному узлу массива -------------------------------------------------------------------- Число узлов в массиве Число чтений блоков -------------------------------------------------------------------- 1 - 2 000 2 2 001 - 200 000 3 200 001 - 20 000 000 4 20 000 001 - 2 000 000 000 5 -------------------------------------------------------------------- Анализируя информацию в таблице можно сделать вывод, что В-дерево оптимизирует доступ к отдельной записи, и число требуемых обращений к блокам относительно невелико, и практически не зависит от размеров ба- зы данных. Стр. 286 Сравним, в качестве примера, его с количеством обращений к диску при чтении записи из последовательного файла. Если это будет файл, из 200 000 тысяч записей, упорядоченных по какому-либо ключу, то, если в од- ном блоке будет 20 записей, этот файл займет 10 000 блоков. Используя двоичный поиск для обращения к конкретной записи нам потребуется обра- щение к 12 или 13 блокам, в то время, как для получения записи из В-дерева нам потребуется только 3. ┌──────────────────────────────────────────────────────────────┐ │ Число чтений блоков при доступе к конкретному узлу массива │ │ НЕ зависит от числа индексов в ссылке на этот узел (глубины │ │ индексации). │ └──────────────────────────────────────────────────────────────┘ Блок 103 ┌─────┬─────┬─────┬─────┐ Блок │ ^A │ ^C │ ^X │ │ глобального │ │ │ │ │ каталога │ 640 │ 501 │ 214 │ │ └─────┴─────┴─────┴─────┘ | V Блок 287 | ┌─────┬─────┬─────┬─────┬─────┬─────┐ │ "M" │ "Q" │ │ │ │ │ │ │ │ │ │ │ │ │ 431 │ 214 │ │ │ │ │ └──┬──┴──┬──┴─────┴─────┴─────┴─────┘ │ └──────────────────┐ Блок 431 │ │Блок 214 ┌───┬───┬───┬┴──┬───┬───┐ ┌───┬───┬┴──┬───┬───┬───┐ │"L"│"O"│"Q"│"S"│ │ │ │"U"│"W"│"Y"│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │171│240│106│937│ │ │ │371│ 97│268│ │ │ │ └─┬─┴─┬─┴─┬─┴─┬─┴───┴───┘ └─┬─┴─┬─┴─┬─┴───┴───┴───┘ ┌───┘ │ │ └─────────────┐ │ │ └──────────────────┐ │ └─┐ └───────┐ │ │ └────────────┐ │ │ │ │ │ └──────┐ │ │ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │"L":"l"│ │"O":"o"│ │"Q":"q"│ │"S":"s"│ │"U":"u"│ │"W":"w"│ │"Y":"y"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"M":"m"│ │"P":"p"│ │"R":"r"│ │"T":"t"│ │"V":"v"│ │"X":"x"│ │"Z":"z"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"N":"n"│ │ │ │ │ │ │ │ │ │ │ │ │ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ Блок Блок Блок Блок Блок Блок Блок 171 240 106 937 371 97 268 Рис.11.4 Структура В-дерева после "расщепления" блока указателей Однако, плотность хранения записей в дисковых блоках в В-дереве ниже, чем в последовательном файле, что сказывается при проведении ис- черпывающего поиска по всему массиву. Плотность хранения элементов массива зависит от порядка добавления узлов в массив, мы более подроб- нее обсудим этот аспект позднее, и еще раз вернемся к нему в разделе 11.1.2 Стр. 287 Действительно, физическая структура В-дерева зависит не только от ключей и данных, но также и от порядка создания узлов массива. Это не является свойством В-деревьев, а только побочным эффектом их практи- ческой реализации. В большинстве MUMPS реализаций, в том случае, когда узлы массива создаются в порядке, совпадающем с упорядочивающей после- довательностью, то при расщеплении блока данных данные из него не де- лятся между старым и новым блоком, как это было показано ранее. Все ранее заполненные блоки остаются в неприкосновенности, а вновь созда- ваемый узел массива записывается в новый блок данных, в следствии чего плотность хранения элементов данных оказывается более высокой. Если мы изменим порядок в котором создавались узлы массива ^X, то эффект от этого скажется на физической структуре его В-дерева. ┌──────────────────────────────────────────────────────────────────┐ │ |>Kill ^X | │ │ |>Set Subs="ABCDEFGHIJKLMNOPQRSTUVWXYZ"<- | │ │o|>Set Data="abcdefghijklmnopqrstuvwxyz"<- |o│ │ |>For i=1:1:26 Set sub=$E(Subs,i),dat=$E(Data,i),^X(sub)=dat<- | │ │ |> | │ └──────────────────────────────────────────────────────────────────┘ Прим. 11.2 Создание массива ^X в порядке совпадающем с упорядочивающей последовательностью На Рис.11.5 представлена структура массива создаваемого после 15 прохода цикла из Прим.11.2. (Обратите внимание на аналогичную структу- ру с Рис.11.4) Блок 103 ┌─────┬─────┬─────┬─────┐ Блок │ ^A │ ^C │ ^X │ │ глобального │ │ │ │ │ каталога │ 640 │ 501 │ 214 │ │ └─────┴─────┴─────┴─────┘ | V Блок 214 | ┌─────┬─────┬─────┬─────┬─────┬─────┐ Блок │ "A" │ "E" │ "I" │ "M" │ │ │ указателей │ │ │ │ │ │ │ │ 171 │ 268 │ 97 │ 371 │ │ │ └──┬──┴──┬──┴──┬──┴──┬──┴─────┴─────┘ ┌──────┘ │ │ └───┐ │ ┌───┘ │ │ ┌────┴──┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │"A":"a"│ │"E":"e"│ │"I":"i"│ │"M":"m"│ Блоки ├───────┤ ├───────┤ ├───────┤ ├───────┤ данных │"B":"b"│ │"F":"f"│ │"J":"j"│ │"N":"n"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"C":"c"│ │"G":"g"│ │"K":"k"│ │"O":"o"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"D":"d"│ │"H":"h"│ │"L":"l"│ │ │ └───────┘ └───────┘ └───────┘ └───────┘ Блок Блок Блок Блок 171 268 97 371 Рис.11.5 Структура В-дерева при добавлении узлов в массив в порядке совпадающем с упорядочивающей последовательностью. Стр. 288 В результате сравнения двух структур представленных на Рис.11.4 и Рис.11.5 мы можем заметить, что хотя в обоих из них одинаковое коли- чество элементов данных в последней плотность хранения элементов дан- ных выше (использовано 4-е блока данных, а не 6-ть), и кроме того, в ней меньше физических уровней. ┌─────────────────────────────────────────────────────────────────┐ │ Порядок добавления узлов в массив сказывается на физической │ │структуре В-дерева. Если ключи массива создаются в порядке сов- │ │падающем с упорядочивающей последовательностью, то в результате │ │плотность хранения элементов данных выше, а В-дерево будет иметь │ │меньше уровней физической организации структуры, по сравнению с │ │тем случаем, когда ключи создаются в случайном порядке. │ └─────────────────────────────────────────────────────────────────┘ 11.1.2 Варианты В-деревьев В качестве достаточно интересного варианта основной концепции В-деревьев является добавление в него дополнительных указателей. Для работы с В-деревом наличие указателей является достаточным условием. Однако для реализации некоторых дополнительных возможностей в каждый из блоков могут быть добавлены особые указатели, указывающие на связи между блоками. Блок 103 ┌─────┬─────┬─────┬─────┐ Блок │ ^A │ ^C │ ^X │ │ глобального │ │ │ │ │ каталога │ 640 │ 501 │ 214 │ │ └─────┴─────┴──┬──┴─────┘ V Блок 214 │ ┌─────┬─────┬─────┬──┴──┬─────┬─────┐ Блок │ "A" │ "E" │ "I" │ "M" │ │ │ указателей │ │ │ │ │ │ │ │ 171 │ 268 │ 97 │ 371 │ │ │ └──┬──┴──┬──┴──┬──┴──┬──┴─────┴─────┘ ┌──────┘ │ │ └────────────┐ │ ┌┘ └─────┐ │ ┌────┴──┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │"A":"a"│ ┌─>│"E":"e"│ ┌─>│"I":"i"│ ┌─>│"M":"m"│ Блоки ├───────┤ │ ├───────┤ │ ├───────┤ │ ├───────┤ данных │"B":"b"│ │ │"F":"f"│ │ │"J":"j"│ │ │"N":"n"│ ├───────┤ │ ├───────┤ │ ├───────┤ │ ├───────┤ │"C":"c"│ │ │"G":"g"│ │ │"K":"k"│ │ │"O":"o"│ ├───────┤ │ ├───────┤ │ ├───────┤ │ ├───────┤ │"D":"d"│ │ │"H":"h"│ │ │"L":"l"│ │ │ │ ├───────┤ │ ├───────┤ │ ├───────┤ │ ├───────┤ │ 268 ├─┘ │ 97 ├─┘ │ 371 ├─┘ │ 0 │ "Правосторонняя └───────┘ └───────┘ └───────┘ └───────┘ ссылка" Блок Блок Блок Блок 171 268 97 371 Рис.11.6 Структура В-дерева с так называемыми "правосторонними ссылками" между блоками данных. На Рис.11.6 представлен вариант структуры В-дерева с так называе- мыми "правосторонними ссылками" на последующий блок данных. Стр. 289 При этом обеспечивается возможность перехода к последующему блоку дан- ных в цепочке без обращения к блокам указателей. Такие же и другие, связи могут быть добавлены и в блоки указателей, причем эти дополни- тельные связи могут использоваться в различных MUMPS реализациях осо- бым образом. Все закладываемые в структуру В-дерева дополнительные связи избы- точны для его нормального функционирования, но они могут служить для реализации многих дополнительных полезных возможностей. Как уже упоми- налось, прежде всего они могут быть использованы для прохода через все блоки данных без обращения к указателям. Эта возможность особенно не- обходима при проведения исчерпывающего поиска в базах данных. Кроме того, эти указатели могут анализироваться в контексте информации полу- чаемой из блоков указателей для проверки целостности физической струк- туры массива. И наконец, эти связи могут использоваться при восстанов- лениях повреждений структуры В-дерева, происходящих, например, при сбоях в аппаратном обеспечении вычислительного комплекса. ┌─────────────────────────────────────────────────────────────────┐ │ Избыточные связи часто закладываются в структуру В-деревьев │ │ с целью обеспечить возможность перемещения внутри структуры │ │ без обращения к блокам указателей при проверках целостности. │ └─────────────────────────────────────────────────────────────────┘ Дополнительные связи добавляются также с целью обеспечения перед- вижения по всем направлениям внутри структуры(влево, вправо, вверх и вниз). Поэтому, в блоке могут быть указатели 4-х типов для создания таких связей. 1. Указатель на ниже расположенные блоки. (DP - Downward pointer) Такой указатель связывается с блоком указателей или блоком глобаль- ного каталога и содержат адрес блока с более низкого уровня физичес- кой структуры с которым связан данный блок. 2. Указатель на выше расположенные блоки. (UP - Upward pointer) Та- кой указатель часто записывается в блоках данных и содержит адрес блока указателей из которого производится ссылка на этот блок. 3. Правосторонний указатель. (RL - Right-link pointer) Этот указа- тель содержит адрес следующего блока в физической структуре В-дере- ва. Этот указатель в основном используется вместе с указателем на ниже расположенные блоки из порождающего блока. 4. Левосторонний указатель. (LL - Left-link pointer) Этот указатель содержит адрес предыдущего блока в физической структуре В-дерева. Аналогичен правостороннему указателю, но действует в противоположном направлении. Этот указатель в основном используется вместе с указа- телем на ниже расположенные блоки из порождающего блока. На Рис.11.7 представлена структура В-дерева с использованием свя- зей всех типов. В основном, как видите, она совпадает с Рис.11.6, но в ней Вы заметите дополнительные связи. В каждом из блоков присутствуют право-, лево-сторонние указатели, а также указатели на ниже- и вы- ше-расположенные блоки структуры. Указатель на ниже-расположенные бло- ки это собственно указатели из блоков каталога и указателей, и потому особо не выделяются. Стр. 290 Блок 103 ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┐ Блок ┌──────────>│ UP │ LL │ ^A │ ^C │ ^X │ │ RL │ глобального │ │ │ │ │ │ │ │ │ каталога │ │ 0 │ 0 │ 640 │ 501 │ 214 │ │ 0 │ │ └─────┴─────┴─────┬─────┴─────┴─────┴─────┘ │ │ │ │ │ Блок 214 V │ ┌─────┬─────┬────┬─────┬────┴┬─────┬─────┬─────┐ Блок │ │ UP │ LL │"A" │ "E" │ "I" │ "M" │ │ RL │ указателей └─┤ │ │ │ │ │ │ │ │ │ 103 │ 0 │171 │ 268 │ 97 │ 371 │ │ 0 │ └─────┴─────┴─┬──┴──┬──┴──┬──┴──┬──┴─────┴─────┘ V V V V ┌─────────┘ │ │ └─────────┐ │ ┌───┘ └──┐ │ ^ ^ ^ ^ ┌────┴──┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │UP=214 │ │UP=214 │ │UP=214 │ │UP=214 │ Блоки ├───────┤ ├───────┤ ├───────┤ ├───────┤ данных │LL=0 │<───┤LL=171 │<───┤LL=268 │<───┤LL=97 │ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"A":"a"│ │"E":"e"│ │"I":"i"│ │"M":"m"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"B":"b"│ │"F":"f"│ │"J":"j"│ │"N":"n"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"C":"c"│ │"G":"g"│ │"K":"k"│ │"O":"o"│ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │"D":"d"│ │"H":"h"│ │"L":"l"│ │ │ ├───────┤ ├───────┤ ├───────┤ ├───────┤ │RL=268 ├───>│RL=97 ├───>│RL=371 ├───>│RL=0 │ └───────┘ └───────┘ └───────┘ └───────┘ Блок Блок Блок Блок 171 268 97 371 Рис.11.7 Структура В-дерева с несколькими вспомогательными указателями в каждом из блоков. 11.1.3 Запись массивов с несколькими уровнями индексации До сих пор мы использовали в примерах глобальные массивы относи- тельно простой структуры - одноуровневые. Теперь рассмотрим аспекты записи многоуровневых глобальных массивов в В-дерево с только одним физическим уровнем записи данных. Прежде всего вспомним определение индексной ссылки как ключа для элемента массива. Каждый ключ (ссылка), однозначно определяет один из узлов массива, при этом записывается и в блок указателей, и в блок данных. Во всех предшествующих примерах этой главы ключи массива были представлены односимвольными индексами. На практике, записываемые ключи представляют собой полную глобальную ссылку, иногда с включением имени массива. Для поддержания целостности и предотвращения возможных пересечений разных ключей при записи запя- тую, разделяющую индексы заменяют на какой-либо специальный знак, ко- торый упорядочивается ранее всех прочих символов встречающихся в ин- дексах. В структуре, представленной на Рис.11.8 в качестве такого сим- вола принята звездочка - (*). Стр. 291 ┌──────────────────────────────────────────────────────────────┐ │ |>Kill ^X Set ^X("A")="a",^X("AA")="aa",^X("AB")="ab"<- | │ │ |>Set ^X("A","B")="a comma b",^X("A,B")="A,B"<- | │ │o|>Set ^X("AB",1)="ab,1"<- |o│ └──────────────────────────────────────────────────────────────┘ Прим. 11.3 Многоуровневый глобальный массив. Как Вы можете заметить, из Рис.11.8 многоуровневые глобальные ссылки являющиеся ключами массива сохраняются таким же образом как и простые ключи. Таким образом в одном физическом уровне данных (более того в одном и том же блоке), могут быть ссылки на более чем один ло- гический уровень. Блок 103 ┌─────┬─────┬─────┬─────┬─────┐ Блок │ ^A │ ^C │ ^X │ │ │ глобального │ │ │ │ │ │ каталога │ 640 │ 501 │ 214 │ │ │ └─────┴─────┴───┬─┴─────┴─────┘ │ Блок 214 │ ┌─────────┬──────┴──┬─────────┬─────────┐ Блок │ ^X(A) │ ^X(AA) │ │ │ указателей │ │ │ │ │ │ 171 │ 268 │ │ │ └──┬──────┴──────┬──┴─────────┴─────────┘ ┌─────┘ └─────┐ │ │ ┌────────┴────────────┐ ┌──────────┴──────────┐ │ ^X(A) :"a" │ │ ^X(AA) :"aa" │ Блоки ├─────────────────────┤ ├─────────────────────┤ данных │ ^X(A*B):"a comma b" │ │ ^X(AB) :"ab" │ ├─────────────────────┤ ├─────────────────────┤ │ ^X(A,B):"A,B" │ │ ^X(AB*1):"ab,1" │ ├─────────────────────┤ ├─────────────────────┤ │ │ │ │ └─────────────────────┘ └─────────────────────┘ Блок Блок 171 268 Рис.11.8 Структура В-дерева с несколькими уровнями индексации глобального массива. 11.1.4 "Сжатие" ключа В предшествующих примерах мы представили различные варианты орга- низации указателей и элементов данных. Необходимо при этом упомянуть еще об одной широко применяемой технологии, используемой для уменьше- ния объема, занимаемого базами данных MUMPS - "сжатии" ключа. Вернемся к ранее упомянутым положениям - во-первых, вместе со значением данного записывается соответствующая ему полная индексная ссылка, во-вторых все элементы данных внутри блока размещаются в соответствии с упорядо- чивающей последовательностью MUMPS. Во многих случаях "соседние" индексные ссылки внутри блока данных содержат много совпадающих символов, как, например, узлы массива, представленные в таблице 11.2 Стр. 292 Списки в этой таблице приведены в порядке, совпадающем с упорядо- чивающей последовательностью, также, как они будут размещаться и в блоке данных. В этом случае избыточность подобного способа записи пря- мо бросается в глаза, ведь каждый ключ начинается с полностью идентич- ных подстрок - '^X("Inventory","Bolts",'. Таблица 11.2 Сходные ключи ------------------------------------------------------ Глобальная ссылка Значение данного ------------------------------------------------------ ^X("Inventory","Bolts","1/4") 1500 ^X("Inventory","Bolts","3/8") 2500 ^X("Inventory","Bolts","7/16") 750 ^X("Inventory","Bolts","7/8") 2200 ------------------------------------------------------ Метод "сжатия" ключа состоит в том, что для каждого из элементов данных записываются только те символы ключа, которые не совпадают с предыдущим ключом. /*1/ ┌─────────────────────────────────────────────────────────────────┐ │ В большинстве MUMPS реализаций используется технология │ │"сжатия" ключа, для уменьшения пространства требуемого для записи│ │ сходных ключей. │ └─────────────────────────────────────────────────────────────────┘ В этом случае каждый блок данных начинается с полной глобальной ссылки, в противном случае невозможно будет обеспечить проверку це- лостности массива, и, тем более, коррекцию повреждений структуры. Конкретный выигрыш, получаемый за счет использования метода "сжа- тия" ключа зависит от многих факторов: - длины ключей, количества "сходных" ключей, от эффективности реализации метода в конкретной реа- лизации. Но можно определенно сказать, что он достаточно заметен. Впрочем у метода "сжатия" ключа есть и определенные отрицательные сто- роны, главная из которых - потребность в дополнительной загрузке про- цессора при "сжатии" и "восстановлении" ключа при работе с глобальными массивами. 11.1.5 Удаление данных из В-дерева В одном из предыдущих примеров мы видели, как при расщеплении блока указателей образуется новый физический уровень структуры, для того, чтобы поддержать сбалансированность В-дерева. (Одно из основных свойств В-деревьев - потребность обращения к ОДИНАКОВОМУ количеству блоков при осуществлении доступа к ЛЮБОМУ из узлов массива). В идеаль- ном случае при удалении данных должна, при необходимости, производится и обратная процедура - удаление "лишнего" физического уровня и "пере- паковка" данных. ------------------------------------------------------------------ *1 Если разобрать способ "сжатия" ключа для конкретной реализации, то, например, в DSM-11 это выглядит следующим образом. Для каждого элемен- та данных записывается общая длина ключа, затем количество символов в ключе не совпадающих с предшествующим ключом, затем отличающиеся сим- волы ключа. Стр. 293 Однако трудности связанные с реализацией (в том числе и уменьше- ние скорости работы с глобалями, и уменьшение устойчивости к сбоям, и т.д. и т.п), такой возможности настолько велики, что большинство из разработчиков MUMPS систем сознательно отказываются от нее. Процесс удаления данных из массива в большинстве случаев выглядит следующим образом: блоки удаленных данных и соответствующих им указателей "объ- являются" пустыми и система может их использовать, в остающихся блоках данных производятся необходимые корректировки (связанные с удалением части данных из блока), ко при этом НЕ производится реорганизация всех блоков данных, и НЕ изменяется физическая структура В-дерева (даже в том случае, если какой-либо из ее уровней структуры становится избы- точным). В-дерево, в котором производится много удалений данных может иметь избыточные уровни указателей и низкую плотность данных в блоках. Единственный способ борьбы с этим явлением регулярное сохранение на внешнее устройство, удалении с диска и восстановление с внешнего уст- ройства таких массивов. ┌─────────────────────────────────────────────────────────────────┐ │ В большинстве из MUMPS реализаций при удалении данных из │ │В-дерева его структура не подвергается "переупаковке". Для │ │оптимизации структуры требуется сохранение массива на какое- │ │нибудь внешнее устройство, скажем на магнитную ленту, удаление │ │его с диска, а затем восстановление с внешнего устройства. │ └─────────────────────────────────────────────────────────────────┘ 11.1.6 Буферизация Работа с диском MUMPS-системой производится по-блочно, так как блок это наименьший объем информации пересылаемый между дисковым нако- пителем и оперативной памятью компьютера. Блок, как правило, имеет размер 1-2 Кбайт, впрочем, встречаются MUMPS - реализации поддерживаю- щие блоки размером до 16 Кбайт. /*1/ Раздел оперативной памяти, куда помещается дисковый блок называется БУФЕРОМ /а иногда КЭШ'ем, или КЭШ-БУФЕРОМ/. Чтение значения узла массива, содержащегося в кэш-буфере происхо- дит гораздо быстрее, чем чтение его с диска. По этой причине, во мно- гих MUMPS реализациях поддерживается пул буферов для увеличения ско- рости обработки данных. Максимально возможное количество буферов в MUMPS системе ограничивается многими факторами, прежде всего общим размером оперативной памяти в компьютере, а также количеством и разме- ром пользовательских разделов. В большинстве MUMPS реализаций, все пространство памяти, не используемое для других нужд, отводится под пул буферов для данных читаемых с диска. ┌─────────────────────────────────────────────────────────────────┐ │ Буферизация операций дискового ввода/вывода в оперативной │ │ памяти прямо ускоряет доступ к данным, содержащимся на диске. │ └─────────────────────────────────────────────────────────────────┘ ------------------------------------------------------------------- *1 Например, DTM-PC v.4.2+ поддерживает блоки размером до 8 К, а DT-MAX v.4.3+ до 16 К. Стр. 294 Когда система обращается к блоку данных, то сначала поиск блока производится в пуле буферов. Если блок находится там, то он считывает- ся оттуда, и обращения к диску не производится. Если же его там нет, то блок считывается с диска и записывается в пул буферов. При этом, если нет свободных буферов, а в блоки данных, содержащихся в них вне- сены изменения (по сравнению с блоками содержащимися на диске), то из- мененные блоки будут записаны на диск, а на их место считаны блоки данных, к которым производится обращение. Алгоритм выбора буферов с блоками данных для сброса на диск определяется конкретной MUMPS реали- зацией, но обычно первыми сбрасываются или раньше всех вызванные, или раньше прочих измененные блоки. Блоки содержащиеся в буферах памяти используются как для чтения, так и для записи данных. При изменении значения узла глобального мас- сива (в результате операции SET или KILL), изменения производятся в блоке данных, который размещен в пуле буферов. Как правило содержимое блока не записывается на диск после каждого произведенного в нем изме- нения. В этом случае измененный блок остается в буфере до тех пор, по- ка не потребуется освобождение пространства для обработки новых бло- ков, в этом случае измененные блоки сбрасываются на диск. Хотя применение буферизации существенно ускоряет обработку дан- ных, записанных на диске (что означает как ускорение обработки запро- сов к глобалям, так и уменьшение времени, необходимого для вызова программ), но, имеет и одно, довольно значимое, слабое место. Так как все изменения в действительности производятся в памяти, то авария, или сбой компьютера происшедшие в тот промежуток времени, когда произве- денные в блоках данных изменения еще не перенесены на диск, приведет к потере данных и/или нарушению общей целостности структуры данных. Ко- нечно потеря данных всегда неприятна, но в этом случае обычно теряются только те данные что введены в последний момент, и их достаточно прос- то восстановить. Но, с другой стороны, потеря целостности структуры данных может повлечь за собой гораздо более существенные потери. (В том числе и возможность потери ВСЕХ данных). Существует определенное число приемов для уменьшения вероятности повреждения целостности базы данных. Обычно каждому буферу соответс- твует некий флаг, который будет указывать на то, произведены ли в бло- ке изменения, или нет. При необходимости записать измененные блоки на диск производится сброс всех измененных блоков, и, одновременная очистка их флагов. Уменьшение промежутка времени, в течении которого существуют различия между блоками на диске и блоками из пула буферов увеличивает устойчивость системы в целом. В дополнении к описанному выше механизму в большинстве MUMPS реализаций существует фоновый про- цесс, который периодически просматривает пул буферов и сбрасывает все измененные буфера на диск и переустанавливает их флаги. 11.1.7 Принципы повышения эффективности записи данных Вставка данных в В-дерево, как оно описано в разделе 11.1.1., имеет несколько интересных особенностей влияющих на эффективность хра- нения данных в глобальном массиве. При добавлении данных первоначально пустые блоки фиксированной длины начинают последовательно заполняться до тех пор, пока не будут заполнены целиком. В этом случае происходит расщепление заполненного блока на два, которые заполнены на 50%. Поэ- тому в типичном глобальном массиве, в который данные записываются в произвольном порядке, блоки заполнены на 60-80%. Стр. 295 Степень заполненности блоков данных сказывается также на время доступа к данным. MUMPS системы всегда производит чтение блоков с дис- ка целиком, даже если эти блоки наполовину заполнены. Глобальный мас- сив, в котором блоки заполнены только наполовину, потребует вдвое больше блоков, чем массив такого же размера, но у которого блоки дан- ных заполнены на 100%. В соответствии с этим, для просмотра данных в таком массиве потребуется вдвое больше операций чтения. Но с другой стороны, дополнение данных в массив, у которого блоки заполнены на по- ловину имеет свои преимущества, по сравнению с до-записью в полностью заполненные блоки. Поскольку в этом случае потребуются дополнительные затраты времени на получение нового пустого блока с диска и распреде- ление данных между старым блоком и новым, до-запись новых данных и за- пись обоих блоков на диск. В общем случае, для глобалей, в которые узлы добавляются в произ- вольном порядке представляется желательным поддерживать эффективность хранения данных около 75%. Это значение представляется компромиссным между получаемым выигрышем при чтении данных из "плотно" записанного массива, и затратами времени на "расщепление" блоков при добавлении данных. Для баз данных относительно фиксированного размера (например справочных), желательно поддерживать высокую эффективность записи дан- ных. В такие массивы данные добавляются не часто, напротив, обращения к таким массивам требуют максимальной скорости получения данных. ┌─────────────────────────────────────────────────────────────────┐ │ Следует прилагать большие усилия по повышению эффективности │ │ записи данных в статические справочные массивы, чем в те │ │ массивы, в которые данные постоянно добавляются. │ └─────────────────────────────────────────────────────────────────┘ Какие методы может использовать программист для повышения эффек- тивности записи данных в блоках? Как уже упоминалось ранее, существует определенная разница между добавлением данных в массив в соответствии с упорядочивающей последовательностью, и добавлением в произвольном порядке. В том случае, когда добавлении данных достигнут предел емкос- ти блока, и добавляемый узел следует за последним узлом блока, то "расщепления" данных блока не происходит, и в новый блок записывается только добавляемое данное, а заполненный блок остается неизменным. То есть, записывая в массив индексы в порядке совпадающем с упорядочиваю- щей последовательностью можно добиться эффективности хранения данных близкой к 100%. Поэтому наиболее простым способом повышения эффективности хране- ния данных в глобальном массиве является следующая последовательность действий: - сохранение массива на любом внешнем последовательном устройстве (в DOS файле, на дискете или на магнитной ленте) в соответствии с упорядочивающей последовательностью индексов - удаление массива (KILL ^GLOBAL) - восстановление массива с последовательного устройства Практически в любой MUMPS системе существует набор утилит, которые можно использовать для этих целей. Величина фактора эффективности хра- нения данных указывает на характер расщепления блока при его перепол- нении. 75% фактор эффективности указывает на то, что при расщеплении блока в новый блок будет переноситься около 1/4 объема блока. Не стре- митесь довести эффективность хранения данных до 100% в массивах, кото- рые будут часто модифицироваться. Стр. 296 11.1.8 Ошибки баз данных Практически все проблемы связанные с базой данных вызываются внешними факторами, которые нарушают процесс переноса модифицированных буферов на диск. Это может быть и сбой/авария компьютера, пропадание электропитания, проблемы связанные с аппаратным обеспечением дисковых накопителей. Тогда, когда процесс переноса модифицированных блоков по какой-либо причине не происходит до конца, очень часто происходит на- рушение целостности базы данных. При этом подобные нарушения могут ос- таться незамеченными после восстановления работы системы, и проблемы, которые возникнут из-за этого нарушения, могут проявиться и через нес- колько дней. Программист начинает тревожиться о состоянии базы данных обычно лишь тогда, когда исполнение программ прерывается ошибками с сообщениями типа : "Database Degradation ERROR" или "Block Number ER- ROR". /*1/ Лучший способ избежать потери данных - проводить планомерное и исчерпывающее копирование наборов данных и использовать журналирование наиболее важных массивов. Если Вам приходится часто "латать" наборы данных после их восстановления с резервной копии - это скорее всего следствие некорректного или неупорядоченного копирования. Наиболее правильный способ восстановления базы данных - удалить поврежденные наборы, восстановить последнее сохраненное состояние и внести в него все произведенные со времени копирования изменения используя файлы системного журнала. ┌─────────────────────────────────────────────────────────────────┐ │ Лучший способ избежать ошибок, связанных с базами данных - │ │ - проводить регулярное и исчерпывающее копирование. │ └─────────────────────────────────────────────────────────────────┘ При копирования баз данных разрушения в них не всегда обнаружива- ются. Все приемы копирования можно разделить на две большие группы - логическое и физическое копирование. При физическом копировании произ- водится копирование всего диска или его блоков на внешнем устройстве. Физическое копирование - наиболее быстрый процесс, но в этом случае не производится никаких проверок содержащейся в копируемых файлах инфор- мации, кроме проверки на читаемость блоков. С другой стороны, логичес- кое копирование исполняется более медленно, так как в этом случае осу- ществляется полное сканирование копируемых массивов, и на внешнем уст- ройстве сохраняется глобальная ссылка вместе с данным при ней. Увели- чение продолжительности компенсируется повышением надежности и безо- пасности, так как логическое копирование способно выявить ошибки физи- ческой структуры глобальных массивов, например, несоответствия между блоками указателей и блоками данных. Регулярное проведение логического копирования может служить своего рода исчерпывающей проверкой на це- лостность структуры базы данных. ------------------------------------------------------------------- *1 Список ошибок подобного рода можно дополнить следующим списком наиболее часто встречающихся ошибок баз данных и для других операционных систем : DSM-11 : DKHER - блок не читается DKSER - ошибка типа блока или ошибка в указателях ISM/NTSM : system 30 DTM PC : Bad response block structure Bad request block structure Disk I/O error .... Стр. 297 Но все же, следует пользоваться и другими утилитами для проверки це- лостности структуры базы данных, которые обычно включаются в библиоте- ку системных утилит. Конечно, не каждый сбой, происшедший даже во время интенсивной работы с диском, приводит к разрушению структуры базы данных, посколь- ку все разработчики MUMPS систем прилагают большие усилия по обеспече- нию их устойчивости. Обычно, когда возникает необходимость записи хотя бы одного блока на диск, одновременно с ним на диск сбрасываются ВСЕ измененные буфера. Кроме того, если в системе существует отдельное фо- новое задание, просматривающее содержимое буферов, частота его прохо- дов достаточно велика (Обычно буфера просматриваются с интервалом, максимум в 5-10 секунд). Поэтому в момент времени, соответствующий по- тере данных из буферов, в большинстве случае, информация на диске бу- дет уже модифицирована. Что делать, если Вы все же столкнулись с ошибкой, указывающей на возможное разрушение базы данных? Прежде всего - остановиться и обду- мать ситуацию в целом. Не впадайте в панику, и не бросайтесь сразу восстанавливать разрушенный глобальный массив. Неспланированные дейс- твия могут только запутать ситуацию и нанести еще больший ущерб. Перед проведением восстановления, выполните следующие шаги: 1. Приостановите работу пользователей в системе. Очень часто до- бавление новых данных затрудняет последующий процесс восста- новления ошибок. Имейте ввиду, что разрушение структуры базы данных может затрагивать не только тот массив, на котором Вы встретились с ошибками. 2. Если Вы журналировали изменения в базе данных, сделайте допол- нительную копию файлов журнала. Она может Вам понадобиться при проведении полного восстановления. 3. Попробуйте сделать физическую копию всех наборов данных на внешнем устройстве, например на магнитной ленте. Не стремитесь делать логические копии отдельных массивов, так как скорее всего столкнетесь с ошибками базы данных, которые воспрепятс- твуют завершению процесса логического копирования. Кроме того, при проведении логического копирования в разрушенной базе дан- ных, даже если не будет встречаться явных ошибок возможна неп- редсказуемая потеря данных. Физическая копия в этом случае предпочтительнее, если конечно, проблема не связана с невоз- можностью чтения блоков диска. 4. С помощью соответствующих утилит проверьте целостность всех наборов данных. Начните проверку с системных наборов данных, затем проверяйте целостность глобальных массивов. Утилиты про- верки базы данных обычно высвечивают информацию обо всех встреченных ошибках и несоответствиях, в каждом конкретном блоке. После выполнения описанных выше шагов Вы можете начать восстанов- ление базы данных. Проще всего (и безопаснее), удалить поврежденные массивы, восстановить их с последней полной копии, внести в них изме- нения, из файлов журнала, и еще раз проверить целостность базы данных. Можно конечно, используя специальные утилиты корректировки структуры, попытаться исправить ошибки в базе данных. Если в процессе такого восстановления Вы встретитесь с непредвиденными ошибками, Вы имеете возможность вернуться к состоянию наборов данных, сохраненных в шагах 2 и 3. Стр. 298 11.2 Исполнение MUMPS программ В этом разделе мы коснемся некоторых специфических особенностей MUMPS систем, касающихся "чисто интерпретирующих" и "компилирующих" реализаций языка. Так же как и в главе, посвященной описанию структур данных, мы будем обсуждать только общие принципы, не касаясь специфи- ческих особенностей конкретных систем. Все языки программирования высокого уровня сталкиваются с общей проблемой, связанной с тем, что различные типы компьютеров имеет раз- личающиеся наборы инструкций (машинных команд), и для исполнения прог- рамм написанных на языке высокого уровня их необходимо преобразовать в эти инструкции. Для реализации этого процесса возможны два подхода - первый заключается в интерпретации исходного кода в машинные команды непосредственно во время исполнения программы, второй - в преобразова- нии (компиляции) исходного кода в машинные инструкции, и после этого запуск подготовленной программы на исполнение. Первые MUMPS системы имели в своем составе только интерпретатор, во время исполнения, каждый оператор из исходного текста программы преобразовывался в последовательность вызовов машинных инструкций. Ин- терпретация исходного кода включает в себя две фазы: 1 - синтаксичес- кую проверку языковых конструкций, и 2 - преобразование исходного кода в машинные инструкции. Так как обе фазы исполняются непосредственно во время исполнения программы, то "чисто интерпретирующие" MUMPS реализа- ции представляют блестящие возможности для отладки программных комп- лексов. Исходный код может быть введен на исполнение непосредственно с клавиатуры. Традиционные, скажем так, компьютерные системы производят оценку и преобразование исходного кода в машинные инструкции в отдельной фа- зе, перед исполнением. Так как все синтаксические и семантические про- верки выполняются прежде исполнения программ, то все "компилирующие" системы гораздо быстрее "интерпретирующих". Но, с другой стороны, в "компилирующих" системах требуется выполнение дополнительных шагов между написанием программ и запуском их на исполнение. В сложных прик- ладных комплексах такая многоступенчатость может вызвать серьезные затруднения при компоновке, отладке и модификации программ. Для того, чтобы улучшить производительность MUMPS систем в части реализаций сделаны шаги по совмещению преимуществ "интерпретирующих" и "компилирующих" систем. При этом пришлось учитывать некоторые из ос- новных концепций MUMPS, которые мешали такому совмещению: ■ В MUMPS не декларируются тип и имя используемых в программах переменных. ■ MUMPS допускает использование косвенности, при которой оператор, или его аргумент может быть заменен значением данного, которое может создаваться и изменяться в процессе исполнения программы. Для того, чтобы преодолеть эти проблемы, пришлось пойти на опре- деленные компромиссы. В "компилирующих" MUMPS системах исходный код программ не преобразуется в машинные инструкции, вместо этого произво- дятся все необходимые проверки, и генерируется так называемый "объект- ный" код, который занимает промежуточное место между исходным кодом и машинными инструкциями. "Объектный" код, в силу того, что проверки производить уже не надо исполняется гораздо быстрее исходного. Стр. 299 "Объектный" код содержит в себе преобразованные операторы и функции языка, ссылки на переменные и литералы, а также внутренние команды уп- равления интерпретатором. ┌─────────────────────────────────────────────────────────────────┐ │ В части MUMPS систем, для повышения производительности, из │ │ исходного кода генерируется объектный, который подается затем │ │ на вход упрощенного интерпретатора. │ └─────────────────────────────────────────────────────────────────┘ Компиляция программ в MUMPS представляет собой построчный разбор программ, затем разбор каждой строки на операторы и генерация объект- ного кода. В качестве примера рассмотрим генерацию объектного кода из следующей строки: ┌──────────────────────────────────────────────────────────────┐ │ |>Write 22+3<- | │ │ |25 | │ │o|> |o│ └──────────────────────────────────────────────────────────────┘ Прим.11.4 Строка исходного MUMPS кода При компиляции этой строки, сначала проводится проверка на кор- ректность синтаксиса, затем, исходя из семантического контекста, гене- рируется объектный код, который подается на вход упрощенного интерпре- татора. Одним из наиболее общих подходов, реализуемых в современных компиляторах, является организация исполняемого кода так, чтобы все значения, наиболее рационально использовали стек, последовательно "проталкивая" и "выталкивая" данные из стека. Для реализации этого ме- тода используется так называемая "обратная польская запись". (Reverse Polish Notation - RPN). С ее использованием, исходный код, приведенный на Прим.11.4 генерирует, в общем виде, объектный код показанный на Рис.11.9. При его исполнении стек будет использоваться следующим обра- зом: 1. Первое значение (22) проталкивается в стек 2. Второе значение (3) проталкивается в стек 3. Когда встречается оператор сложения, два прежде введенных в стек значения выталкиваются из него, складываются между собой, а результат (25), заносится в стек. 4. Когда встречается оператор Write, значение извлекается из стека и выводится на текущее устройство. ┌──────┬──────┬─────┬─────┐ │ 22 │ 3 │ + │WRITE│ └──────┴──────┴─────┴─────┘ Рис.11.9 Объектный код генерируемый для Прим.11.4 Во время исполнения, все элементы объектного кода организуются в соответствии с их оценкой в порядке управляемом "обратной польской за- писью" - RPN. Каждый элемент объектного кода идентифицируется типом (например - переменная, строковой или цифровой литерал, оператор или MUMPS функция), а тип определяет как данное будет обрабатываться в стеке. Разберем более сложный пример: ┌──────────────────────────────────────────────────────────────┐ │o|>Write:X=3 Y+Z |o│ └──────────────────────────────────────────────────────────────┘ Прим.11.5 MUMPS оператор с постусловием Стр. 300 В этом примере встречается большее количество языковых элементов, кроме этого, требуется оценка значений переменных. Результирующий объ- ектный код представлен на Рис.11.10. ┌──────┬──────┬─────┬─────┬──────┬─────┬─────┬─────┐ │ X │ 3 │ = │ pc │ Y │ Z │ + │WRITE│ └──────┴──────┴─────┴─────┴──────┴─────┴─────┴─────┘ Рис.11.10 Объектный код генерируемый для Прим.11.5 Оценка такой строки объектного кода производится в следующем порядке: 1. Первым элементом в строке является переменная Х, ее значение берется из таблицы локальных переменных и заносится в стек. 2. В стек заносится число 3 3. Третий элемент это строковый оператор сравнения, первые два значения выталкиваются из стека, и сравниваются между собой. Если они эквивалентны, в стек заносится 1, в противном случае - зано- сится 0. 4. Следующий элемент это указатель на постусловие, он представляет собой внутреннюю инструкцию MUMPS интерпретатору, и содержит в се- бе адрес элемента, которым заканчивается данное выражение. Извле- кается первый элемент стека, и, если это 0, то интерпретатор игно- рирует остаток выражения, переходя к следующему. Если же это 1 об- работка выражения продолжается. 5. Все последующие шаги аналогичны рассмотренным в предшествующем примере. Кроме того, при работе с программами в объектном коде выигрыш в скорости исполнения достигается и за счет других, достаточно неявных, с первого взгляда, аспектов. Во - первых, в "чисто" интерпретирующих MUMPS системах для орга- низации перехода на метку LABEL интерпретатору приходится просматри- вать построчно всю программу, начиная с первой строки, в то же время в компилирующих версиях при генерации объектного кода вычисляется смеще- ние для адресации перехода. В связи с этим переход по GOTO или вызов по DO производится гораздо быстрее. /*1/ Во-вторых, выигрыш может быть получен за счет преобразования встречающихся в тексте программ числовых и строковых литералов. Как мы уже неоднократно подчеркивали, MUMPS понимает все данные, содержащиеся в переменных, как строки, и интерпретирует их значение лишь в зависи- мости от контекста выражения, в котором они участвуют. Поэтому в "чис- то" интерпретирующих версиях в процессе исполнения приходится преобра- зовывать внутренний строковый формат в числовое значение перед выпол- нением выражения. Компилирующие версии производят такое преобразование при генерации исходного кода, снижая тем самым время, необходимое для вычисления выражения во время исполнения программы. В-третьих, компилирующие версии, упрощают процесс перехода на следующий оператор при использовании условных операторов, или операто- ров с постусловиями. В объектном коде могут быть проставлены действи- тельные адреса переходов на следующий оператор, или на следующую стро- ку, поэтому, если в процессе исполнения программы, оцениваемое выраже- ние дает 0 /ЛОЖЬ/, то переход выполняется сразу, без сканирования ис- ходного кода. ------------------------------------------------------------------- *1 Некоторые реализации идут дальше, и строят таблицу для переходов между программами-как, например, это реализовано в Data Tree MUMPS на- чиная с version 4.3. Стр. 301 И, наконец, в компилирующих версиях производится оптимизация про- цесса просмотра таблицы локальных переменных. Для этого, каждому имени переменной, встреченному в исходном тексте программы, назначается уни- кальный номер, по которому она (переменная) и будет идентифицироваться в объектном тексте программы (вместо имени). Кроме того, в объектный код, вместе с идентификатором переменной (Variable Token или VT), вставляется ссылка на нее в таблице адресов переменных (Table of Vari- able Adress или TVA). Таблица адресов переменных, в свою очередь, со- держит ссылки на данные в переменных, находящихся в таблице локальных переменных раздела. Когда в объектном коде встречается ссылка на переменную, то из таблицы адресов считывается ее адрес в таблице локальных переменных, в "чисто" интерпретирующих MUMPS системах вместо этого производится ска- нирование всей таблицы в поисках необходимого имени. Конечно, исполь- зование таблицы адресов вводит необходимость динамического поддержания ее соответствия изменениям в таблице локальных переменных раздела, но, выигрыш, получаемый за счет снижения затрат времени на сканирование таблицы локальных переменных, оправдывает такое усложнение. Возвраща- ясь к примеру 11.5, взаимодействие между исполняемым объектным кодом, таблицей адресов переменных и таблицей локальных переменных можно представить так, как это изображено на Рис.11.11. Использование таблицы адресов переменных явно ускоряет скорость исполнения прикладных программ, но, в то же время, размер получаемого выигрыша зависит от структуры программы. Для того, чтобы понять суть этого ограничения, мы должны разобраться в процессе взаимодействия между таблицей адресов и таблицей локальных переменных в ходе исполне- ния программы. Каждая программа имеет свою собственную таблицу адре- сов, индивидуально создаваемую в процессе ее компиляции. При генерации объектного кода компилятор не имеет информации о том, как будут связы- ваться между собой программы. ┌──────┬──────┬─────┬─────┬──────┬─────┬─────┬─────┐ │VT=3 │ 3 │ = │ pc │VT=1 │VT=6 │ + │WRITE│ └──┬───┴──────┴─────┴─────┴──┬───┴──┬──┴─────┴─────┘ ┌───────┘ │ │ │ ┌───────────────────────────────┘ │ │ │ ┌────────────────────────────────────┘ │ │ │ ┌─────────────┐ │ └─┼─>│ Адрес VT1 ├─────┐ │ │ ├─────────────┤ │ ┌───────────────────────────┐ │ │ │ Адрес VT2 │ │ │ │ │ │ ├─────────────┤ │ ├───────────────────────────┤ └───┼─>│ Адрес VT3 ├─────┼────┐ │ │ │ ├─────────────┤ │ │ ├───────────────────────────┤ │ │ Адрес VT4 │ │ │ │ │ │ ├─────────────┤ │ │ ├───────────────────────────┤ │ │ Адрес VT5 │ │ └────>│ "Данное связанное с Х" │ │ ├─────────────┤ │ ├───────────────────────────┤ └─>│ Адрес VT6 ├──┐ └─────────>│ "Данное связанное с Y" │ └─────────────┘ │ ├───────────────────────────┤ Таблица адресов └────────────>│ "Данное связанное с Z" │ переменных └───────────────────────────┘ Таблица локальных переменных Рис.11.11 Обращение к локальным переменным через таблицу адресов Стр. 302 В связи с этим обстоятельством, применяемые схемы буферизации сильно замедляют работу программы при вызове (и особенно при частых последо- вательных перевызовах) внешних процедур (расположенных в иных програм- мах) при исполнении программы. Отметив это, можно порекомендовать стремиться расположить часто вызываемые процедуры в тексте самой прог- раммы. Во время загрузки на исполнение, каждая программа загружается с собственной таблицей адресов переменных, которая на момент загрузки не содержит указателей на таблицу локальных переменных. Когда во время исполнения объектного кода встречается ссылка на локальную переменную, интерпретатор обращается к таблице адресов, для того, чтобы проверить, имеется ли в ней указатель на таблицу локальных переменных. Если он имеется, то обращение производится напрямую, если нет, сканируется таблица локальных переменных, и, если в ней определена встреченная пе- ременная, то для программы используется ее значение, а в таблицу адре- сов заносится указатель на нее. И все последующие обращения к этой пе- ременной производятся уже через таблицу адресов, без просмотра таблицы локальных переменных. Когда производится обращение к процедуре из другой программы, то процесс описанный в предыдущем абзаце повторяется. Более того, он пов- торяется также и при возврате управления назад, в вызывающую програм- му, так как при этом необходимо пере-обновить все элементы таблицы ад- ресов переменных. Это связано с тем, что в таблице локальных перемен- ных могут произойти существенные изменения, и если просто сохранять где-либо таблицу адресов, то после восстановления большинство ее эле- ментов могут уже не соответствовать содержимому таблицы локальных пе- ременных раздела. В программах, где осуществляется много внешних вызо- вов теряются почти все преимущества даваемые описанной технологией об- ращения к локальным переменным через таблицу адресов. ┌─────────────────────────────────────────────────────────────┐ │ Вызов внешних (по отношению к исполняемой программе) │ │ процедур замедляет ее исполнение, в связи с необходимостью │ │ переобновления содержания таблицы адресов при вызове и │ │ возврате управления. │ └─────────────────────────────────────────────────────────────┘ Все вышесказанное не отменяет необходимости создавать внешние библиотечные процедуры, по скольку использование внешних распределяе- мых библиотек одинаково важно как при разработке, так и при сопровож- дении программ. Но в тех случаях, когда требуется максимально повысить скорость исполнения какого-либо модуля, то в нем следует исключить, или, по крайней мере оптимизировать, вызов внешних процедур. Кроме того, можно посоветовать разработчикам MUMPS систем поду- мать над реализацией процессов оптимизации прикладных пакетов программ целиком. В этом случае, после того, как весь пакет написан и отлажен можно было-бы построить общие таблицы связи программ, переменных и ме- ток, в той манере, как, например, это реализовано в таких языках как FORTRAN. /*1/ ------------------------------------------------------------------- *1 Как уже упоминалось ранее, таблицы для переходов между программами, и некоторые другие возможности, реализованы в Data Tree MUMPS начиная с version 4.3. (С 1994 года эта фирма слилась с фирмой Intersystems /Англия/) Стр. 303 К сожалению, процесс компиляции не может быть полностью отделен от фазы исполнения программы. Это связано с тем, что использую меха- низм косвенности и операторы, и их аргументы могут изменяться и созда- ваться в процессе исполнения программ. Поэтому нет возможности пол- ностью преобразовать весь исходный текст программы. ┌──────────────────────────────────────────────────────────────┐ │o|Set X="A=22/7" Do test Set @X |o│ └──────────────────────────────────────────────────────────────┘ Прим. 11.6 Косвенность снижает скорость исполнения объектного кода. В примере 11.6 приведен случай, когда оператор "Set @X" не может быть полностью скомпилирован до тех пор, пока его аргумент не будет определен в ходе исполнения, поскольку значение переменной Х может быть изменено в любом месте программы. (Например, в процедуре test). Следовательно, в таких случаях в объектный код необходимо вставлять команду вызова компилятора, для обработки косвенных выражений во время исполнения программы. Процесс вызова даже упрощенного компилятора, ко- торый требуется для разборки косвенности (компилятор в таких случаях может быть упрощен за счет общих ограничений на косвенность, а также за счет того, что нет необходимости строить таблицу адресов переменных и т.д.) существенно замедляет процесс исполнения программ. ┌─────────────────────────────────────────────────────────────────┐ │ Выражение написанное с использованием косвенности, исполняется │ │ гораздо медленнее, чем эквивалентное, составленное без кос- │ │ венности. │ └─────────────────────────────────────────────────────────────────┘ При оценки степени влияния косвенности на скорость исполнения программ приходится учитывать несколько факторов. А главным образом задаться вопросом - оправдано ли в данном месте использование косвен- ности? В некоторых случаях (как, например, в процедуре копирования глобалей, описанной в примере 10.5) косвенность можно исключить, но вместо этого необходимо будет использовать значительно больше операто- ров, и, в конце концов, даже после такой замены процедура быстрее ра- ботать н будет. Степень замедления исполнения программ с косвенностью сильно зависит от конкретной реализации. Автором производилось прос- тейшее тестирование четырех компилирующих MUMPS систем. В результате, выяснилось, что выражение вида : 'Set a="b" For i=1:1:10000 Set @a=1' исполняется в 2-4 раза медленнее, чем эквивалентное ему, но построен- ное без косвенности: 'For i=1:1:10000 Set b=1' Но хочется заметить, что обычно косвенность используется довольно редко, и не стоит отказываться от тех выгод, которая она дает, во имя достаточно призрачного общего выигрыша в скорости исполнения. При работе компилирующих MUMPS систем исходный текст программ мо- жет отсутствовать. Исходный текст программ может удаляться например при распространении коммерческих пакетов, с целью защиты от внесения несанкционированных изменений, или в каких-либо других целях. Но с ис- чезновением исходного текста программ функция $TEXT перестает рабо- тать. Стр. 304 Поэтому в большинстве компилирующих MUMPS систем поддерживается такое соглашение: комментарий, следующий за двумя точками с запятой (;;) за- носится в объектный код. Кроме этого, несмотря на то, что комментарий после одной точки с запятой (;) не заносится в объектный код, с целью сохранения количества строк, и возможности адресации относительно ме- ток, вместо него в объектный код вставляется пустая строка. Но ! Это соглашение поддерживается не во всех MUMPS системах. Вывод - с $TEXT в компилирующих системах следует быть по-осторожнее. ┌─────────────────────────────────────────────────────────────────┐ │ Функция $TEXT, в тех случаях, когда исходный текст программ от-│ │ сутствует, может перестать работать. /В части MUMPS реализаций, │ │ есть возможность применения особых приемов, позволяющих сохра- │ │ нить особо указанные комментарии в объектном коде/ │ └─────────────────────────────────────────────────────────────────┘ Есть еще одна особенность в "компилирующих" MUMPS системах, свя- занная с сообщениями о возникающих ошибках. В "чисто" интерпретирующих MUMPS системах когда во время исполнения программы происходит ошибка, то интерпретатор может точно указать, в каком месте командной строки (то есть на каком операторе, или на каком аргументе оператора), она произошла. В связи с тем, что объектный код при генерации оптимизиру- ется, а исходный текст программы может быть не доступен, в большинстве компилирующих систем приходится ограничиваться только сообщением об виде ошибки и указанием на строку, в которой она произошла. И, наконец, необходимо заметить, что программы не могут перено- ситься в объектном коде из одной MUMPS системы в другую, так как каж- дая MUMPS реализация имеет собственный компилятор, а разные компилято- ры могут использовать как отличающиеся алгоритмы генерации объектного кода, так и уникальные внутренние команды управления. Программы могут переноситься только в исходном коде, а затем компилироваться. ┌─────────────────────────────────────────────────────────────────┐ │ Программы в объектном коде нельзя переносить из одной MUMPS │ │ реализации в другую. Так как разные компиляторы используют │ │ отличающиеся алгоритмы создания объектного кода. │ └─────────────────────────────────────────────────────────────────┘ Процесс генерации объектного кода обычно производится неявно для программиста в ходе сохранения исходного кода на диск. Поэтому в боль- шинстве MUMSP реализаций каждая программа существует на диске в двух копиях - в исходном и в объектном кодах. Исходный код используется при редактировании программ, объектный - при запуске на исполнение. Конеч- но, при этом требуется дополнительное дисковое пространство, но, во-первых, после полной отладки Вы можете удалить исходный текст, а во-вторых, получаемый выигрыш в скорости исполнения оправдывает увели- чение занимаемого программами дискового пространства, по-скольку оно не столь велико (по сравнению с пространством занимаемом глобальными массивами). Стр. 305 11.3 Общие положения ■ В большинстве MUMPS систем глобальные массивы реализуются с ис- пользованием так называемых "сбалансированных" деревьев, хотя глобали могут быть, в принципе, организованы и посредством дру- гих файловых структур. ■ В-деревья состоят из блоков-указателей и блоков для данных. ■ В блоке указателей содержатся элементы, каждый из которых ука- зывает на блок указателей нижнего уровня, или на блок данных. ■ Каждому узлу массива соответствует элемент в блоке данных. Этот элемент содержит одновременно ключ (индексную ссылку), и дан- ное, связанное с этим узлом. ■ При переполнении блока данных производится его "расщепление". Часть данных из него, или только вновь добавляемый элемент, (в зависимости от значения его ключа), будет записана в новый блок данных. В блок указателей будет добавлен элемент, указывающий на первый ключ нового блока данных. При необходимости будет мо- дифицирован и указатель на прежний блок данных. ■ При переполнении блока указателей он также "расщепляется", но при этом создается новый уровень указателей на блоки данных. ■ Число чтений блоков при доступе к конкретному узлу массива НЕ зависит от числа индексов в ссылке на этот узел (глубины индек- сации). ■ Порядок добавления узлов в массив сказывается на физической структуре В-дерева. Если ключи массива создаются в порядке сов- падающем с упорядочивающей последовательностью, то в результате плотность хранения элементов данных выше, а В-дерево будет иметь меньше уровней физической организации структуры, по срав- нению с тем случаем, когда ключи создаются в случайном порядке. ■ Избыточные связи часто закладываются в структуру В-деревьев с целью обеспечить возможность перемещения внутри структуры без обращения к блокам указателей при проверках целостности. ■ В большинстве MUMPS реализаций используется технология "сжатия" ключа, для уменьшения пространства требуемого для записи сход- ных ключей. ■ В большинстве из MUMPS реализаций при удалении данных из В-де- рева его структура не подвергается "переупаковке". Для оптими- зации структуры требуется сохранение массива на какоенибудь внешнее устройство, скажем на магнитную ленту, удаление его с диска, а затем восстановление с внешнего устройства. Стр. 306 ■ Буферизация операций дискового ввода/вывода в оперативной памя- ти прямо ускоряет доступ к данным, содержащимся на диске. ■ Следует прилагать большие усилия по повышению эффективности за- писи данных в статические справочные массивы, чем в те массивы, в которые данные постоянно добавляются. ■ Лучший способ избежать ошибок связанных с базами данных - про- водить регулярное и исчерпывающее копирование. ■ В части MUMPS систем, для повышения производительности, из ис- ходного кода генерируется объектный, который подается затем на вход упрощенного интерпретатора. ■ Вызов внешних (по отношению к исполняемой программе) процедур замедляет ее исполнение, в связи с необходимостью переобновле- ния содержания таблицы адресов при вызове и возврате управле- ния. ■ Выражение написанное с использованием косвенности, исполняется гораздо медленнее, чем эквивалентное, составленное без косвен- ности. ■ Функция $TEXT, в тех случаях когда исходный текст программ от- сутствует, может перестать работать. /В части MUMPS реализаций, есть возможность применения особых приемов, позволяющих сохра- нить особо указанные комментарии в объектном коде/ ■ Программы в объектном коде нельзя переносить из одной MUMPS ре- ализации в другую. Так как разные компиляторы используют отли- чающиеся алгоритмы создания объектного кода. Стр. 307 Глава 12. Устройства ввода-вывода В этой главе мы будем рассматривать общие принципа Ввода/Вывода информации в MUMPS (Input/Output, или, сокращенно - I/O), а также некоторые типы устройств, наиболее часто используемые в MUMPS систе- мах. Для начала рассмотрим общие концепции Ввода/Вывода и управления устройствами. В разделе 12.4 приведена некоторая специфическая ин- формация, относящаяся к конкретным типам видеотерминалов, принтеров и накопителей на магнитной ленте. В остальных разделах мы более уг- лубленно рассмотрим аспекты относящиеся к вопросам управления видео- терминалами. Большинство из понятий, рассматриваемых в этой главе были уже упомянуты раньше, (так, например, операторы ввода/вывода описаны в разделе 3.4, а системные переменные, содержащие информацию о устройстве в разделе 4.4.4), но в этой главе мы углубим и расширим информацию о них. 12.1 Общие концепции операций ввода/вывода Подобно многим другим языкам программирования, MUMPS поддержи- вает большое количество операций управления вводом/выводом информа- ции с устройств, которые не зависят от конкретного типа устройства. В MUMPS системах поддерживается доступный для прикладных задач внут- ренний список устройств, называемый таблицей устройств. Каждому уст- ройству в этой таблице соответствует логический номер, а в некоторых системах и логическое имя. Устройства обычно идентифицируются своим номером, но некоторые прикладные программы оперируют их мнемоничес- кими именами. Таблица устройств уникальна для каждой MUMPS реализа- ции, но как правило, она имеет вид приведенный в Табл.12.1 (В приме- чании 1 приведена таблица устройств DataTree MUMPS). Номера и мнемо- нические имена устройств в различных MUMPS реализациях обычно никак не коррелируются. Пример таблицы устройств MUMPS системы Табл.12.1 --------------------------------------------------------------- Номер устройства в системе Описание --------------------------------------------------------------- 1 Системная консоль /консольный терминал/ 2 Устройство спулинга печати 3-5 Принтеры 6-9 Накопители на магнитной ленте 10-15 Последовательные файлы 16-31 Терминалы --------------------------------------------------------------- Примечание 1. Таблица устройств системы DataTree MUMPS: --------------------------------------------------------------- Номер Мнемоника Описание --------------------------------------------------------------- 1-8 PCVID "Окна" консольного терминала и клавиатура 9 PCVID "Окно" для вывода сообщений сетевого задания 10-29 BSGEN Каналы доступа к DOS устройствам 30 PCLPT Параллельный принтер 31-39 BSPRE "Переопределенные" каналы для доступа к параллельным принтерам и другим не RS - 232 устройствам.. 40-99 BSGEN DOS - файлы и другие подобные у-ва. (подобно устройствам 10-29) 100-131 PCTERM RS-232 последовательные у-ва 200-231 SMART RS-232 последовательные у-ва через мультиплексор Стр. 308 В самых общих чертах концепция использования устройства в MUMPS выглядит следующим образом: установление собственности процесса над устройством ("захват" устройства), выполнение желаемой операции вво- да/вывода (READ или WRITE), а затем освобождение устройства. Боль- шинство из устройств могут быть использованы в один момент времени только одним процессом, (спулер печати обычно представляет исключе- ние из этого правила). Хотя MUMPS процессом может быть "захвачено" одновременно несколько устройств, но в текущий момент времени может использоваться только одно. Все операции чтения или вывода /READ или WRITE/ могут быть адресованы только на устройство, объявленное теку- щим. В MUMPS существует 5-ть основных команд ввода/вывода: OPEN, USE, WRITE, READ и CLOSE: 1. OPEN: Оператор OPEN представляет собой запрос процесса на "захвата" (или резервирования) устройства. Необязательными аргу- ментами оператора OPEN являются параметры, назначаемые устройству при успешном "захвате". Если устройство, к которому адресуется оператор OPEN уже используется другим процессом, то это приводит к приостановке текущего процесса на неопределенное время - до тех пор, пока устройство не будет освобождено. Для предотвращения по- добной ситуации следует использовать оператор OPEN с установлен- ным временем ожидания, для проверки возможности "захвата" уст- ройства без приостановки процесса на неопределенное время. 2. USE: Оператор USE объявляет "захваченное" устройство текущим устройством процесса. В качестве необязательного аргумента(ов) этого оператора используются параметры, назначаемые устройству. Оператор OPEN производит "захват" или резервирование устройства для процесса, но не объявляет его текущим. В то же время попытка объявить текущим устройство, еще не захваченное процессом, приво- дит к ошибке. Все операции ввода/вывода адресуются только на те- кущее устройство. Системная переменная $IO содержит номер текуще- го устройства процесса, ее значение меняется после успешного за- вершения оператора USE. 3. WRITE: Оператор WRITE используется для вывода строки символов, или специальных кодов управления форматом (#, !, или ?nn), в не- которых MUMPS системах и команд управления, на текущее устройс- тво. При этом в большинстве случаев (за исключением вывода кодо- вых последовательностей непосредственно адресующих курсор) моди- фицируются значения системных переменных $X и $Y для отражения нового положения курсора устройства. 4. READ: Оператор READ используется для ввода символов с текущего устройства в локальную переменную. После завершения чтения моди- фицируются значения системных переменных $X и $Y для отражения нового положения курсора устройства. Варианты завершения операции READ зависят одновременно как от вида текущего устройства, так и от конкретной MUMPS реализации. Например, при вводе с клавиатуры, завершение обычно происходит при нажатии клавиши , но не- которые реализации допускают завершение и по нажатию других кла- виш. В тоже время, при вводе из DOS файла завершение чтения про- изводится по встреченному коду CR (ASCII 13), или по достижению конца файла. Для удобства использования оператор READ может выво- дить перед исполнением чтения коды управления форматом (#, !, или ?nn) или строки символов, также, как и оператор WRITE. Стр. 309 5. CLOSE: Оператор CLOSE освобождает устройство "захваченное" процессом и возвращает его номер в список свободных устройств. После "закрытия" устройства оно становится доступным для других процессов. Оператор CLOSE неявно исполняется на все "захваченные" устройства при закрытии процесса по HALT. На Рис. 12.1 отображен в общих чертах процесс "захвата" уст- ройства, исполнения операций ввода/вывода, а затем освобождения уст- ройства для его использования другими процессами. Операция Действия ----------------------------------------------------------------------- Open 2 Резервирование устройства 2 Open 3 Резервирование устройства 3 Use 2 Объявление устройства 2 текущим Read ... Чтение с устройства 2 Write ... Вывод на устройство 2 Use 3 Объявление устройства 3 текущим Read ... Чтение с устройства 3 Write ... Вывод на устройство 3 Use 2 Объявление устройства 2 текущим повторно Read,Write ... Ввод/Вывод с устройства 2 Close 2 Освобождение устройства 2 Close 3 Освобождение устройства 3 Рис. 12.1 Направление операций ввода/вывода Важно обратить внимание на то, что OPEN резервирует устройство, но не делает его текущим, а оператор USE указывает направление опе- раций ввода/вывода на конкретное устройство. 12.2 Основное устройство При регистрации в MUMPS система автоматически выделяет пользо- вательский раздел (Раздел 2.2.1) и определяет терминал, с которого проводилась регистрация, как основное устройство нового задания. При этом неявно исполняются операторы OPEN и USE на основное устройство, так что все операции ввода/вывода будут направляться на него, до тех пор, пока не будет исполнен оператор USE. ┌─────────────────────────────────────────────────────────────────┐ │ После регистрации с терминала он назначается основным устрой- │ │ством нового задания и на него назначаются все операции ввода/ │ │вывода, до тех пор, пока не будет использован оператор USE. │ └─────────────────────────────────────────────────────────────────┘ Основное устройство задания может в дальнейшем по-особому иден- тифицироваться в операторах OPEN, USE и CLOSE - не своим номером в таблице устройств системы, а 0. Устройство номер 0 - это всегда ос- новное устройство задания, вне зависимости от того, какой номер ему в действительности соответствует. Обычно, когда в программах встре- чается 0, как номер устройства, то он при обращении к таблице уст- ройств переназначается в соответствии с реальным номером устройства. Основное устройство может быть сделано текущим с помощью выражения - 'Use 0' Стр. 310 ┌─────────────────────────────────────────────────────────────────┐ │ Обращение к основному устройству задания может быть сделано │ │ через специальный идентификатор - 0 (символ 0). │ │ Например: Use 0 │ └─────────────────────────────────────────────────────────────────┘ Существует несколько ситуаций, когда основное устройство процес- са становится его текущим устройством без явного использования выра- жения: Use 0 ■ Когда пользователь регистрируется в MUMPS системе, то устройс- тво, с которого производится регистрация автоматически стано- вится текущим устройством нового задания. ■ После CLOSE на текущее активное задание, основное задание ста- новится текущим. ■ Если в программе происходит ошибка, то основное устройство ста- новится текущим активным устройством в системе. То же самое происходит и тогда, когда в программе встречается оператор BRE- AK (вызов отладчика). ■ При работе в непосредственном режиме неявное Use 0 исполняется после завершения каждой командной строки, перед переходом к но- вой строке непосредственного режима. Заданиям, запускаемым с помощью оператора JOB (Раздел 3.5.3) не всегда назначается основное устройство. В этом случае, в некоторых реализациях им назначается то же основное устройство, что и в поро- дившем их процессе. /*1/ Поэтому, в таких реализациях, все операции ввода/вывода на основное устройство могут зависнуть до тех пор, пока все порожденные процессы не освободят его (выполнят CLOSE на него). Другие MUMPS реализации позволяют назначать запускаемому заданию другое основное устройство /с переназначением этому устройству тех или иных параметров/, через необязательный список SPCL PARMS в опе- раторе JOB. В любом случае, проверьте в документации на используемую Вами MUMPS реализацию. Этот вопрос является актуальным еще и потому, что если в запускаемом задании происходит ошибка, то перед сообщени- ем об ошибке MUMPS система выполняет неявное 'Use 0' перед выводом сообщения. И если запущенному заданию не назначено основного уст- ройства, то эта ситуация генерирует новую ошибку, о которой и выдаст информацию / а совсем не о первоначальной, подобный казус может сильно осложнить отладку заданий, запускаемых по JOB/ 12.3 Системные переменные, связанные с операциями ввода/вывода Ранее мы уже описывали три системные переменные ($IO, $X и $Y), связанные с операциями ввода/вывода (Раздел 4.4.4), теперь рассмот- рим аспекты их применения в практике программирования. -------------------------------------------------------------------------- Примечание 1. DataTree MUMPS в этом случае не назначает основного устройства. Поэтому, если в задании, запущенном по JOB Вы используете выражение Use 0 - возникнет ошибка. Стр. 311 Системная переменная $IO содержит логический номер текущего ак- тивного устройства задания, назначенному через последнее исполнение оператора USE. Если текущим устройством является основное устройс- тво, то переменная $IO содержит его действительный номер в MUMPS сис- теме, а не 0. Переменная $IO часто используется в системных утили- тах, для выполнения специальных функций, связанных с определением ти- па устройства, как, например, показано на приведенном ниже примере. ┌──────────────────────────────────────────────────────────────┐ │ |Search ;Просмотр БД, результат выводится на DEV | │ │ | New DEV,sub,rec | │ │o| Set DEV=$$GetDev Quit:DEV="" ;Определение текущ.устр.|o│ │ | Use DEV If $IO>5&($IO<10) Write *-5 ;Перемотка для НМЛ| │ │ | Set sub="" For Do Quit:sub="" | │ │o| . Set rec=$$GetRec(.sub) Quit:sub="" |o│ │ | . Write rec,! | │ │ | Use 0 Close:$IO'=DEV DEV | │ │o| Write !,"Просмотр закончен !" |o│ │ | Quit | │ └──────────────────────────────────────────────────────────────┘ Прим. 12.1 Использование $IO В примере 12.1 системная переменная $IO используется дважды: Первый раз в строке 'Search +3' для проверки, не является ли выбран- ное устройство Накопителем на Магнитной Ленте, и второй раз - в строке 'Search +7' для проверки, не является ли выбранное устройство основным устройством задания. /Если оно не является основным, то устройство закрывается через использование CLOSE/. В первом случае $IO используется в аргументе оператора If, пос- ле того, как устройство DEV назначается текущим устройством задания. Обратите внимание на то, что использование в программах строк кода, связанного с конкретными устройствами не является, так сказать, признаком "хорошего тона" в программировании. Такие фрагменты должны быть строго локализованы в прикладных программах, поскольку, при перенесении в другую реализацию могут представить большие трудности. Вообще желательно такие фрагменты сводить в одно место. В этом при- мере, такой представляется в виде использования внешней функции $$GetDev, которая должна обрабатывать все особые случаи, как напри- мер, накопители на магнитной ленте и им подобные устройства. /*1/ Во втором случае Вы видите часто используемый прием определения того является или нет выбранное устройство основным устройством за- дания. Эта проверка производится для того, чтобы определить, есть ли необходимость закрывать устройство, или нет. Очень часто, при отлад- ке программ, вывод преднамеренно направляется на основное устройс- тво. В этом примере, вывод может быть направлен на экран для провер- ки работы программы. По завершению работы устройство будет --------------------------------------------------------------------- Примечание 1. Автор здесь, наверное, имеет в виду то, что вместо этой внешней функции можно было бы использовать выражение вида: 'Set DEV=$IO', но в эту функцию тогда надо было добавить код, который в этом примере вынесен в строку 'Search +3'. В этой строке производит- ся перемотка ленты на начало, причем код управления НМЛ в этом слу- чае относится к конкретной MUMPS реализации и к конкретному типу НМЛ. Стр. 312 закрыто (CLOSE) только в том случае, если оно не является основным устройством. В примере 12.1 на оператор CLOSE наложено постусловие, которое позволяет выполнить оператор только в том случае, когда DEV не основное устройство. Системные переменные $X и $Y содержат, так сказать, "предпола- гаемые" текущие строку и позицию соответствующие последнему выведен- ному на текущее устройство символу. Эти переменные намеренно предс- тавляются в терминах странично ориентированных устройств подобных принтеру и видеотерминалам. Каждая страница состоит из определенного числа строк (row), а каждый ряд - из определенного колонок или позиций (column). Верхнему левому углу страницы соответствуют координаты 0,0 ($Y (строки)=0, $X (позиции)=0). Указатель строк ($Y) устанавливается каждый раз в 0 при выводе на устройство кодовой последовательности перевода страни- цы (или символа управления форматом - '#'), и увеличивается на еди- ницу при выводе кодовой последовательности для перевода строки (или символа управления форматом - '!'). Указатель позиций ($X) устанав- ливается в 0 при выводе на устройство кодовых последовательностей перевода страницы и перевода строки. Значение $Х увеличивается на единицу при выводе на устройство любого из отображаемых кодов ASCII (Как правило, их коды лежат в диапазоне 32-126 /а также 128-175 и 224-239 для русского алфавита/) Кроме этого, в некоторых реализациях поддерживается возможность согласовать внутренние ограничения после которых значения переменных $X и $Y сбрасываются с физическими характеристиками устройств. Это производится через установку правой границы вывода и длины страницы устройства в параметрах операторов OPEN и USE. Например, Use 10:(RMAR=132), после этого, значение переменной $Х будет сбрасывать- ся в 0 после вывода 132-го символа в строке, и начинается новая строка. Таким образом системные переменные $X и $Y отслеживают текущую позицию курсора в строке. Значения этих переменных могут модифициро- ваться как MUMPS системой, так и прикладными программами. MUМPS сис- темы используют значение переменной $Х для горизонтальной табуляции (то есть для выражений вида '?nn' ). При этом производится перевод курсора в позицию nn, через вывод необходимого количества пробелов определяемого исходя из текущей позиции ($Х) и nn. При этом, если начальное значение $Х меньше nn, то табулирование не производится. В конце операции табулирования значение $Х модифицируется в соответс- твии с новой позицией курсора устройства. И, если $Х содержит значе- ние, не соответствующее позиции курсора то эффект от табуляции будет непредсказуемым. Во многих случаях соответствие между значениями системных пере- менных $X и $Y и реальной позицией курсора теряется. Предположим, производится вывод на видеотерминал, после вывода на 24-ю строку, экран сдвигается, при этом содержание 1-й строки теряется, а вывод производится в 24-ю строку, между тем, как значение $Y=25, и больше не соответствует реальной строке курсора устройства. Так будет про- должаться и дальше, после вывода каждой новой строки значение $Y бу- дет увеличиваться на 1, а физически курсор будет находиться в 24-й строке экрана. Стр. 313 Еще больше недоразумений может произойти после так называемой прямой адресации курсора - вывода на экран кодовой последовательнос- ти, непосредственно адресующей курсор. Терминалом такая кодовая пос- ледовательность интерпретируется как командная, и на экране не отоб- ражается, а курсор перемещается в указанную точку. Так как подобного рода управляющие последовательности не стандартизованы, они не рас- познаются MUMPS системой, и естественно, после прямой адресации кур- сора значения системных переменных $X и $Y не соответствуют позиции курсора устройства. В подобных случаях следует принимать меры для того, чтобы прикладные программы модифицировали значения системных переменных $X и $Y в соответствии с прямой адресацией курсора. MUMPS стандарт не позволяет изменять значения системных переменных через использование оператора SET, но в большинстве реализаций обычно поз- воляется изменять значения системных переменных $X и $Y именно в це- лях синхронизации между их значениями и реальным положением курсора. Значения переменных $X и $Y сохраняются перед тем, как текущим становится новое устройство, и восстанавливаются тогда, когда оно объявляется текущим вновь. Если устройство назначается текущим впер- вые, то переменные $X и $Y устанавливаются в 0. Кроме вышеупомянутых трех системных переменных во многих реали- зациях созданы и другие системные переменные связанных с операциями ввода/вывода, и, в частности, для отслеживания позиции курсора уст- ройства. Эти переменные, как правило, начинаются с $Z и подробнее о них смотрите в документации на свою MUMPS реализацию. Они будут упо- мянуты также в разделе 12.4.3 посвященном управлению Накопителями на Магнитных Лентах (НМЛ). 12.4 Устройства в MUMPS системах Обычно MUMPS система поддерживает разнообразные устройства, в том числе: видеотерминалы, принтеры, НМЛ и другие. Но все устройства используемые в MUMPS системах имеют одну общую черту - они предназ- начаются для передачи строк символов (обычно из набора ASCII между устройством и MUMPS процессом. Операторы READ и WRITE представляют большой выбор дополнительных возможностей при чтении и выводе строк символов, например, чтение строк фиксированной длины, в том числе и с длиной, задаваемой значением в некоторой переменной, чтение с ус- тановленным временем ожидания, а также различные виды управления форматом вывода в WRITE. Кроме этого, в MUMPS содержится достаточное количество функций для обработки строк данных, а также возможностей для обеспечения достаточной степени независимости прикладных прог- рамм от привязки к конкретным устройствам. Физические различия между различными устройствами мешают созда- вать полностью независимые от типа устройства программы. Так, напри- мер, при работе с НМЛ ленту необходимо позиционировать, перематы- вать, пропускать запись, искать маркер ленты, и т.д., последователь- ные дисковые файлы необходимо открывать и закрывать, назначать им атрибуты, и т.д., принтеры иногда не имеют возможностей для измене- ния шрифта, или определения конца бумаги, перечень подобных ситуаций можно сделать очень длинным. Эта проблема в общем иногда именуется "проблемой обеспечения терминальной независимости программ"./При этом термин "терминал" трактуется в своем расширительном значении, - как "терминальное" /переферийное/ устройство, а не только как видео- терминал. Стр. 314 Однако, несмотря даже на большое сходство имеющееся между устройс- твами одного класса, различия, обуславливаемые различными физически- ми характеристиками и возможностями, закладываемыми в них производи- телями, создают наибольшие сложности в практическом программирова- нии. Так, например, у многих видеотерминалов не совпадают командные последовательностей для управления их операциями (такими, скажем, как очистка экрана или прямая адресация курсора). В следующих разде- лах мы рассмотрим основные классы устройств и принципы управлениями ими из MUMPS программ. 12.4.1 Видеотерминалы Видеотерминал является наиболее широко распространенным устройс- твом предназначенным для обмена информацией между человеком и компь- ютером. Его можно представить состоящим из: коммуникационного порта, видео-памяти, которая содержит символы, присланные с компьютера, эк- рана дисплея, отображающего эти символы, и клавиатуры для посылки данных в компьютер. В настоящее время простые терминалы вытесняются микрокомпьютерами, эмулирующими терминалы. В первом приближении принцип работы терминала можно описать так: символы вводимые с клавиатуры, посылаются в компьютер, а прини- маемые от компьютера записываются в видеопамять и отображаются на экране. Каждый символ выводится в позиции курсора, после чего курсор перемещается на одну позицию вправо. Если достигнут предел длины строки, курсор перемещается в первую позицию следующей строки. Если следующая строка находится за пределами экрана, весь экран сдвигает- ся на строку вверх, при этом содержание первой строки теряется м но- вая строка становится последней строкой экрана. Как правило, информация между компьютером и терминалом переда- ется согласно асинхронному аппаратно/программному протоколу обмена для последовательных линий связи именуемому RS232C. (Подробная ин- формация об этом протоколе, вместе со схемой кабельных соединений, приведена в приложении Н). Вкратце можно сказать, что согласно этого протокола информация передается в виде битовых строк /последователь- ная передача/. Специальные устройства внутри терминала и ЭВМ кодиру- ют и декодируют эти битовые строки в соответствующие им символы AS- CII. При последовательной передаче каждому символу соответствует, как правило, блок из 10 бит. Скорость передачи измеряется в Бодах (Baud) и определяет количество бит информации передаваемых в 1 се- кунду. /Таким образом скорость передачи 9600 Бод означает передачу в 1 секунду 960 символов. [Блок из 10 символов содержит - 1 так назы- ваемый "стартовый" бит - отмечающий начало блока, 8 бит информации /байт/ и 2 "стоповых" бита, отмечающих конец блока]) Протокол RS232C называется асинхронным в связи с тем, что в линии связи символы мо- гут передаваться и приниматься одновременно. При обмене информацией с компьютером терминал может работать в режиме так называемого "полного дуплекса", или "полу-дуплекса". /А также и в других режимах обмена/. Когда терминал работает в режиме "полу-дуплекса", то каждый посылаемый в компьютер символ сразу же отображается на его экране. Стр. 315 Если же отрабатывается режим полного "дуплекса", то посылаемые в компьютер символы не отображаются непосредственно на экране термина- ла, и только после того, как компьютер посылает их обратно на терми- нал /так называемое "эхо"/, они отображаются на экране. Это предпри- нимается для того, чтобы удостовериться в том, что символы правильно приняты и интерпретированы компьютером. /Все ошибки в приеме/переда- че сразу же отобразятся на экране, например, в виде несоответствия между нажатой клавишей и появившемся на экране символом/. Режим "полного дуплекса" может применяться также и для того, чтобы иметь возможность "подавить" вывод вводимых символов на экран /запретив "эхо"/, например, при вводе паролей, или в заданиях организующих связь между MUMPS процессами на различных машинах. MUMPS поддержива- ет и другие формы обмена информацией, хотя "полный дуплекс" исполь- зуется чаще прочих. 12.4.1 Управление дисплеем Кроме управления обменом информацией терминалы обычно имеют и другие возможности связанные с управлением отображением информации на экране. Это могут быть: очистка всего экрана и его частей, прямое позиционирование курсора, сдвиг информации вверх/вниз в сторону, вы- вод графической информации, изменение яркости изображения и многие другие возможности. Возможности разных типов терминалов, конечно сильно отличаются друг от друга, но часто вызов одной и той же функ- ции на различных терминалах производится различными кодовыми после- довательностями. Для обращения к той или иной терминальную функции необходимо из программы послать на терминал специальную управляющую последователь- ность кодов. Такая последовательность начинается с одного или нес- кольких неотображаемых кодов, за которыми могут следовать любые дру- гие коды ASCII. Большинство таких последовательностей обычно начина- ется с символа ESC , и потому, такие управляющие последо- вательности часто именуются "ESC" последовательностями. Число симво- лов в управляющей последовательности может быть самым разным. Но да- же тогда когда в корректную управляющую последовательность включены отображаемые обычно символы, они на экран не выводятся. После полу- чения управляющей последовательности терминал отрабатывает необходи- мые действия и переключается обратно в режим отображения - все пос- ледующие символы нормальным образом отображаются на экране. Когда терминал отрабатывает управляющую последовательность, то позиция курсора может измениться, а значения системных переменных $X и $Y - нет. Рассмотрим управляющую последовательность применяемую для очистки экрана на терминалах VT-100. Собственно это две управляющих последовательности, первая переводит курсор в верхний левый угол эк- рана, вторая очищает экран от позиции курсора до конца экрана. В примере 12.2 продемонстрированы два метода очистки экрана на VT-100. Обе приведенных последовательности совершают одно и тоже действие - очищают экран, но по разному оказывают воздействие на изменение зна- чений в системных переменных $X и $Y. Стр. 316 ┌──────────────────────────────────────────────────────────────┐ │ |>Write *27,"[H",*27,"[2J"<- | │ │ | | │ │o| - или - |o│ │ | | │ │ |>Write *27,*91,*72,*27,*91,*50,*74<- | │ └──────────────────────────────────────────────────────────────┘ Прим.12.2 Очистка экрана на VT-100 MUMPS стандарт указывает на необходимость изменения $X и $Y при выводе на терминал отображаемых символов, но в нем ничего не гово- рится об отслеживании "Write *char", - этот вопрос остается на ус- мотрение разработчикам MUMPS реализаций, а в большинстве реализаций 'Write *char' не отслеживается. В примере 12.3 продемонстрирован пример неправильной модифика- ции значения $X при выводе на экран управляющих последовательностей через 'Write *char'. Подобный прием можно применять для ответа на вопрос - модифицируется ли $Х при использовании 'Write *char' ┌──────────────────────────────────────────────────────────────┐ │ |>W !,$C(84,101,115,116,105,110,103) S x=$x W ?9,x<- | │ │ |Testing 7 | │ │o|>W !,*84,*101,*115,*116,*105,*110,*103 S x=$x W ?9,x<- |o│ │ |Testing 0 | │ └──────────────────────────────────────────────────────────────┘ Прим.12.3 Влияние 'Write *char' на $Х Если 'Write *char' не оказывает влияния на системную переменную $X, то вывод будет выглядеть примерно так, как на нижней строке при- мера, в котором строка символов 'Testing' выведена на экран, а зна- чение $Х не изменилось. При табуляции поэтому выводится 9-ть пробе- лов. В первом же случае в $X содержится 7, и, поэтому, курсор пере- мещается в 9-ю позицию строки. Но, вне зависимости от того, влияет или нет 'Write *char' на значения $Х и $Y, после выполнения очистки экрана с использованием любой из последовательностей, приведенных в примере 12.2, они не бу- дут правильно модифицированы. Дело в том, что MUMPS система не может правильно интерпретировать всю последовательность в целом, и поэтому после ее выполнения значения $X и $Y никогда не будут равны 0. Для того, чтобы решить эту проблему можно воспользоваться одним из двух приемов: После операции Write, используя оператор Set явно прирав- нять $X и $Y нулю, [Set ($X,$Y)=0], или включить код управления фор- матом '#' последним аргументом оператора 'Write' (Write *27,"[H",*27,"[2J",#) ┌─────────────────────────────────────────────────────────────────┐ │ Обратите внимание ! MUMPS стандарт не позволяет изменять │ │ значения системных переменных через использование оператора │ │ SET, но в большинстве реализаций обычно позволяется изменять │ │ значения системных переменных $X и $Y в целях синхронизации │ │ между их значениями и реальным положением курсора. │ └─────────────────────────────────────────────────────────────────┘ Стр. 317 Для достижения терминальной независимости прикладных программ многие реализации поддерживают специальные утилиты, которые опреде- ляют и возвращают в прикладные программы основные характеристики терминалов. Это шаг в правильном направлении, но возвращаемая инфор- мация обычно сильно лимитирована, и в нее обычно входит лишь общее описание экрана (количество строк и позиций), и несколько управляю- щих последовательностей (как правило - очистка экрана и прямая адре- сация). Более универсальным подходом является вызов процедуры описа- ния терминального устройства при запуске прикладного программного средства. Такая процедура возвращает список переменных для управле- ния основным устройством ввода/вывода задания, которые будут исполь- зоваться в прикладных программах. Типичный список таких переменных приведен в Табл.12.2. Таблица 12.2 Список переменных для управления видеотерминалом -------------------------------------------------------------------- Переменная Назначение и способ использования -------------------------------------------------------------------- Переменная используется в качестве косвенного аргумента FF оператора Write для очистки экрана и устанавливает системные переменные $X и $Y в 0. (Например Write @FF) RM Содержит правую границу экрана (то есть $X для последнего символа в строке, для большинства терминалов равна 79) SL Размер экрана (Количество строк, 24 для большинства терминалов) XY Переменная используется в качестве аргумента оператора Xecute для позиционирования курсора в произвольную точку экрана и модифицирования значений переменных $X и $Y. Для VT-100 она может выглядеть вот так: 'Write *27,"[",dy+1,";",dx+1,*72 Set $x=dx,$Y=dy' и использоваться следующим образом: 'Set dx=20,dy=10 Xecute XY' -------------------------------------------------------------------- В примере 12.4 мы продемонстрируем пример инициализации списка переменных и их использование. В этом примере переменные FF, RM и XY возвращаются из процедуры CURRENT^%TRM и используются для очистки экрана (Write @FF) и для центрирования двух строк заголовка. Утилита %TRM может обрабатывать любой тип терминала, а процедура центрирова- ния работает корректно вне зависимости от установленной длины строки экрана (например, 40, 80 или 132 символа) и длины выводимой строки. Вид экрана после вызова подпрограммы GetData, описанной в примере 12.4 показан в примере 12.5. Описанный в примере подход является еще одним шагом на пути к достижению терминальной независимости прог- рамм, но до достижения полной независимости еще надо будет преодо- леть много препятствий. В разделе 12.5.1 мы обсудим еще несколько приемов применяемых для управления терминалом. -------------------------------------------------------------------- Примечание: В некоторых MUMPS реализациях, и в частности в DataT- ree MUMPS вместо создания списка переменных для управления экраном можно воспользоваться дополнительными функциями управления экраном вызываемыми как 'Write /fun'. Список этих функций можно дополнять самостоятельно. Стр. 318 ┌──────────────────────────────────────────────────────────────────┐ │ |GetData ;Ввод/редактирование данных пациента | │ │ | Do CURRENT^%TRM ;Получение переменных FF, SL, RM и XY | │ │o| Write @FF,$$Center("Лечебное учреждение XYZ") |o│ │ | Write !,$$Center("Ввод/Редактирование данных пациента"),! | │ │ |GetID Read !,"Пациента ID: ",PID Quit:PID="" | │ │o| . |o│ │ | . | │ │ |Center(txt) ;центрирование "txt" | │ │o| New dx,dy Set dy=$y,dx=RM-$l(txt)\2 |o│ │ | Xecute XY | │ │ | Quit txt | │ └──────────────────────────────────────────────────────────────────┘ Прим. 12.4 Использование FF,RM и XY ┌──────────────────────────────────────────────────────────────────┐ │ | Лечебное учреждение XYZ | │ │ | Ввод/Редактирование данных пациента | │ │o| |o│ │ | | │ │ |Пациента ID: | │ │o| |o│ │ | | │ └──────────────────────────────────────────────────────────────────┘ Прим. 12.5 Экран после исполнения примера 12.14. 12.4.1.2 Обработка ввода с клавиатуры. Во многих отношениях обработка ввода с клавиатуры приносит меньше затруднений, чем управление экраном, особенно тогда, когда ввод ограничен основной частью набора ASCII кодов /32-126/. Вне за- висимости от "раскладки" клавиатуры и типа терминала эти коды гене- рируются всегда. Графические символы и управляющие коды генерируются при нажатии специально обозначенных клавиш или при нажатии одновре- менно двух и более клавиш (например при нажатии и удерживании или Вы должны будете нажать другую клавишу. В тексте такие случат обозначаются так - , то есть одновременное нажатие и ) В приложении А приведен список кодов генерируемых при нажатии клавиш /сочетаний клавиш/. Абстрагируясь от частностей, можно сказать что в любой MUMPS системе поддерживается два основных вида операции READ /чтения с клавиатуры/: многосимвольный READ (например - 'Read Ans' или скажем 'Read Ans#10') и односимвольный READ (например - 'Read *char'). Рассмотрим принципиальные различия между этими формами. Многосимвольное чтение (в том числе и с фиксированной длиной ввода) используется для ввода одного и более символов в переменную. Некоторые из управляющих кодов, вводимых с клавиатуры (Таблица 3.1) имеют специальное назначение в MUMPS системах, поэтому они не вклю- чаются в вводимую строку, а управляют поведением MUMPS системы. Нап- ример, - в большинстве MUMPS систем указывает на необходи- мость очистить всю введенную в данной операции READ строку символов. Обычно, после ввода с клавиатуры все символы сразу отображаются на экране. Стр. 319 Процесс отображения /"эха"/ в некоторых MUMPS системах управляется через параметры операторов OPEN и USE. А после нажатия /ес- ли этот код обрабатывается MUMPS системой/, введенная строка симво- лов очищается и на экране. По завершению READ переменная, используе- мая в аргументе READ, будет содержать введенные с клавиатуры символы /за исключением управляющих, оказывающих воздействие на MUMPS систе- му/. Если READ завершается по нажатию /или другой клавиши, описанной как клавиша завершения ввода/, то код, соответствующий на- жатию этой клавиши не включается во введенную строку. В односимвольном READ, напротив, все вводимые с клавиатуры коды помещаются в переменную, поскольку все управляющие коды, перечислен- ные в Таблице 3.1 в этой форме READ игнорируются. В большинстве MUMPS реализаций коды, вводимые в этой форме READ на экране не отоб- ражаются, а в некоторых их отображением можно управлять. По заверше- нию ввода в переменной будет десятичное значение кода ASCII соот- ветствующего нажатой клавише. Обратите внимание на то обстоятельст- во, что READ с длиной ввода =1 тоже прочитает только один символ, но при этом ряд символов будет про игнорирован, и кроме того в перемен- ной будет символ, а не десятичное значение соответствующего ему кода ASCII. Операция READ может быть завершена одним из трех способов: Нажатием клавиши которая завершает ввод /как правило - , в некоторых реализациях завершение может производиться при нажатии и других клавиш. Описание таких клавиш производится обычно в параметрах операторов OPEN и USE - Подробнее смотрите в документации на используемую Вами MUMPS реализацию/ Достижением указанной длины вводимой строки /односимвольный READ всегда завершается после чтения одного кода/ Истечением установленного времени ожидания /все введенные к этому моменту времени символы помещаются в переменную, одновре- менно в системную переменную $TEST заносится 0, в односимвольном READ с временем ожидания в переменную, используемую в качестве аргумента READ заносится -1/ К сожалению, на клавиатуре иногда имеются и так называемые функциональные клавиши и клавиши управления курсором и клавиши уп- равления. И на различных типах терминалов эти клавиши могут генери- ровать самые различные кодовые последовательности. /*1/ В таблице 12.3 в качестве примера приведены кодовые последовательности генери- руемые при нажатии некоторых специальных клавиш терминала VT-100. ----------------------------------------------------------------------- Примечание 1: К еще большему сожалению генерируемые кодовые последо- вательности иногда зависят и от используемой MUMPS реализации. Так, например, в ISM клавиатура консоли генерирует одни кодовые последо- вательности, а в DTM - другие. Стр. 320 Таблица 12.3 Кодовые последовательности генерируемые функциональными клавишами терминала VT-100. ---------------------------------------------- Клавиша Кодовая последовательность ---------------------------------------------- Курсор вверх [A Курсор вниз [B Курсор вправо [C Курсор влево [D PF1 OP PF2 OQ PF3 OR PF4 OS ---------------------------------------------- Если во время обычного READ (например - 'Read Ans') будет нажа- та одна из таких клавиш /или их сочетание/, то кодовая последова- тельность будет вставлена в вводимую строку, а часть символов из этой последовательности будет отображена на экране. /Также, как если бы эта клавиша была просто нажата в непосредственном режиме./ Так, если во время ввода пользователь нажмет клавишу <Курсор вправо>, то во вводимую строку будут вставлены коды [C, а на экран будут выведены символы '[C'. Использование функциональных и прочих клавиш требует явной об- работки пользовательскими программами, так как ни одной из MUMPS систем они автоматически не обрабатываются. Для того, чтобы выделить эти последовательности существует несколько приемов. Так, в некото- рых реализациях, как уже упоминалось можно указать другие коды за- вершения READ, кроме /ASCII=13/. В подобных реализациях кро- ме этой возможности есть обычно системная переменная которая содер- жит код клавиши по которой завершился последний READ. /*1/ В этом случае можно определить /ASCII=27/, как дополнительный код за- вершения READ /обычно в параметрах OPEN или USE/. После каждого READ надо анализировать значение этой системной переменной, если READ за- вершен по в ней будет код /ASCII=13/, а в том случае, если READ завершен по вводу Esc-последовательности, вся Esc-последова- тельность. Но мало просто прочитать Esc-последовательность, ее необходимо еще правильно интерпретировать и обработать. Представим себе, что при вводе строки символов мы хотим, используя клавиши управления курсором произвести редактирование. Используя только что описанный прием мы сможем прочитать коды нажатых клавиш, применив метод позицио- нирования курсора /См. Раздел 12.4.1.1/ можно переместить курсор в нужную точку. А дальше? Как выделить и сохранить частично введенную строку? Как осуществлять редактирование символов в этой строке? В MUMPS нет возможностей осуществить это непосредственно в операции READ. Вместо этого создают специальные прикладные программы, которые имитируя обычный READ предоставляют расширенные возможности для ре- дактирования вводимых данных. Пример текста внешней функции выполня- ющей подобные действия представлен в разделе 12.5.4 ----------------------------------------------------------------------- Примечание 1: В DTM это переменная $ZDEVR, и параметр TERM Стр. 321 12.4.2 Печатающие устройства /Принтеры/ Принтеры во многом подобны видеотерминалам, за исключением то- го, что вывод информации в них производится не на экран, а на бума- гу. Существует большое разнообразие подобных устройств /*1/, это мо- гут быть и строчно-печатающие, и матричные, и лазерные и многие дру- гие. При этом поддерживается большое разнообразие различных режимов - смена шрифта, плотности печати, размера листа и т.д. и т.п.. А в результате написание терминально независимых программ для принтеров становится более трудным делом, чем для видеотерминалов. Принтеры могут быть подключены как через RS232C (см. раздел 12.4.1 и Приложение А), так и через параллельный интерфейс. Но в MUMPS системах для программиста вид подключения принтера к MUMPS системе обычно не имеет значения. У части принтеров для ускорения вывода информации производится ее буферизация. То есть информация читается блоками с компьютера, после чего принтер производит распе- чатку блока, а тогда, когда буфер данных становится почти пуст, счи- тывается новая порция информации. Для осуществления этого процесса на аппаратном уровне производится управление обменом, подробнее оно описано в Приложении Н. Но, впрочем, все эти процессы исполняются абсолютно неявно и MUMPS программиста не должны интересовать. Так же как и у видеотерминалов вызов тех или иных функций обыч- но посылкой управляющих последовательностей, которые также в основ- ном являются Esc-последовательностями. Но, если пока не рассматри- вать вариаций связанных с управлением дополнительными возможностями принтеров, то большинство приемов описанных в разделе 12.5 применимы и к ним. 12.4.3 Накопители на магнитной ленте /НМЛ/ Накопители на магнитной ленте /НМЛ/ это устройства, предназна- ченные для хранения больших массивов информации, организуемых в виде последовательных файлов. В MUMPS системах они обычно используются для создания архивных копий, для сохранения и восстановления гло- бальных массивов и программ, а также для переноса информации между ЭВМ. Несмотря на то, что стандартизация в области записи информации на магнитные ленты продвинулась дальше, чем стандартизация управления принтерами и видеотерминалами, между различными типами НМЛ существуют достаточно большие отличия в способах записи и логической организации данных на ленте. Наиболее широко распространенный стандарт регламенти- рует 9-ти дорожечную запись и чтение данных на ленту шириной 1/2 дюй- ма. При этом каждому символу соответствует блок из 9-ти бит, /байт ин- формации и так называемый бит паритета/, которые записываются и считы- ваются одновременно блоком из 9-ти головок. Данные записываются в од- ной из трех стандартных плотностей записи, измеряемой в Битах на дюйм - 800, 1600 или 6250 BPI. (Bits Per Inch - BPI) Естественно, что счи- тывание данных должно вестись с той же установленной плотностью, что была при записи. Стр. 322 Обсуждая физические характеристики накопителя мы должны иметь в виду также и логическую организацию данных на ленте. Ленты могут быть с метками и без них, они могут быть записаны с использованием различ- ных наборов символов /а не только в ASCII/, отдельные записи могут быть фиксированной или произвольной длины, и так далее. Физические и логические характеристики накопителя обычно устанавливаются через па- раметры операторов OPEN и USE. Подробнее смотрите в документации на используемую Вами реализацию. При работе с НМЛ все операции ввода/вывода производятся со стро- ками, так как MUMPS системы не поддерживают обработку двоичных данных, кроме этого, MUMPS обычно не способен расшифровывать файловые каталоги некоторых операционных систем. Поэтому, как правило, чтение с ленты интерпретируется как поток символов, и завершается тогда, когда в этом потоке встречается символ CR /ASCII=13/. Можно провести прямую анало- гию с чтением с клавиатуры, которое завершается по нажатию клавиши , также генерирующей символ CR /ASCII=13/. ┌─────────────────────────────────────────────────────────────────┐ │ MUMPS системы не поддерживают операции READ и WRITE для │ │ двоичных данных, ограничиваясь только наборами символов ASII │ │ и ECBDIC. │ └─────────────────────────────────────────────────────────────────┘ В некоторых реализациях поддерживается автоматическая внутренняя перекодировка между наборами кодов ASCII и ESBDIC (Extended Binary Co- ded Decimal Interchange Code), который используется на многих мини- и больших ЭВМ. Управление трансляцией производится через установку пара- метров операторов OPEN или USE и производится автоматически при вводе и выводе, неявно для программиста во время исполнения операций READ и WRITE. Данные записываются на ленту блоками. Размер блока устанавливает- ся в параметрах операторов OPEN и USE и может быть, как правило - 512, 1024 или 2048 байт. Эти блоки в какой-то мере подобны дисковым блокам описанным в главе 2, и представляют собой минимальный объем информа- ции, которыми обмениваются между собой накопитель и буфер оперативной памяти MUMPS системы. При операции READ MUMPS система обращается к бу- феру накопителя и считывает символы из буфера, если буфер пуст, то считывается очередной блок с ленты в буфер накопителя и оттуда продол- жает пересылать данные. Этот процесс повторяется до тех пор, пока READ не закончится. При выводе на магнитную ленту процесс повторяется в об- ратном порядке, информация сначала выводится в буфер накопителя и от- туда сбрасывается блоками на ленту. Но, кроме установки основных параметров и управление накопителями коренным образом отличается от управления видеотерминалами и принтера- ми. Магнитную ленту необходимо перематывать на начало, пропускать бло- ки (вперед или назад), распознавать и специальным образом обрабатывать специфические ошибки записи/чтения с ленты. В качестве примера можно рассмотреть следующую ситуацию. Как правило, отдельные логические фай- лы на ленте разделяются так называемыми ленточными маркерами. Ленточ- ный маркер это не что иное, как блок особого вида и размера, не содер- жащий данных, распознаваемый накопителем как метка на ленте. Стр. 323 Прямое чтение блока метки MUMPS системой вызывает ошибку, поскольку она установлена на другие параметры. Ошибка может возникнуть также и тогда, когда достигнут физический конец ленты, или встретилась метка конца файла. В общем управление и запись/чтение с магнитной ленты со- вершенно отличается от тех, что применяются при работе с видео терми- налами. Управление НМЛ осуществляется через использование WRITE *CHAR, где десятичное значение CHAR интерпретируется совершенно по-другому, чем при выводе на видеотерминал. В этом случае CHAR указывает НМЛ на то, что это команда управления, а не вывод данных на устройство. Очень часто, для того, чтобы подчеркнуть отличие этой формы управления НМЛ от вывода данных используют отрицательные значения CHAR. (Как, напри- мер, показано в таблице 12.4) Таблица 12.4 Примерный список команд управления НМЛ ----------------------------------------------------------------- MUMPS команда Управляет функцией ----------------------------------------------------------------- Write *-1 Перемотка ленты на начало и сброс готовности Write *-2 Перемотка ленты. Если перед этим проводилась запись данных, то сбрасывается весь буфер данных и записываются метки конца блока и конца файла. (В зависимости от параметров установленных в операторах OPEN и USE) Write *-3 Сбросить на ленту содержимое буфера данных НМЛ, и записать маркер ленты Write *-4 Поставить метку конец файла (EOF). Перед этим сбросить буфер данных и поставить метку конца блока. ----------------------------------------------------------------- Хочется подчеркнуть, что, во-первых, таблица 12.4 достаточно ус- ловна, во-вторых, в ней приведена лишь небольшая часть команд, необхо- димых для управления НМЛ, и в-третьих, в каждой реализации имеется свой набор команд. Кроме всего прочего, после выполнения операций ввода/вывода необ- ходимо проверять статус НМЛ. Во всех MUMPS реализациях, поддерживающих работу с НМЛ имеются системные переменные содержащие его статус, как правило он представлен в виде битовой строки которая копирует состоя- ние регистра статуса устройства. В этой строке отражены многие состоя- ния, в том числе и статус происшедших ошибок, которые могут быть нор- мальным образом обработаны в MUMPS. (Обработка ошибок в MUMPS системах обсуждается в разделе 13) В таблице 12.5 представлено интерпретация значений системной пе- ременной $ZA которая отражает статус устройства в одной из MUMPS реа- лизаций. Значение $ZA всегда интерпретируется как целое число, которое может быть обработано в MUMPS программе. Состояние каждого бита в строке может быть опрошено с помощью приема описанного в следующем примере. Этот пример демонстрирует один из приемов выделения состояния би- та из целого числа. MUMPS обычно не имеет операций для работы с двоич- ными числами. Операции AND и OR (И и ИЛИ) возвращают 0 или 1, а не строку бит. Стр. 324 Таблица 12.5 Регистр статуса НМЛ - $ZA ----------------------------------------------------------------- Бит Значение Trap? Значение ----------------------------------------------------------------- 0 1 Yes Логическая ошибка, одновременные READ и WRITE 1 2 No НМЛ перематывает ленту 2 4 No Установлена защита записи 3 8 No Плотность 800 BPI 4 16 No Плотность 1600 BPI 5 32 No Плотность 6250 BPI 6 64 No Метка начала ленты 7 128 No Устройство готово 8 256 Yes Ошибка размера блока 9 512 No Метка конца ленты 10 1024 Yes Ошибка паритета или контрольной суммы 11 2048 Yes Ошибка контроллера НМЛ 12 4096 Yes Маркер ленты 13 8192 Yes Устройство не готово ----------------------------------------------------------------- В общем виде операция выделения значения бита из целого может быть представлена следующей формулой: Bit=integer\(2**(pos))#2 где pos - позиция проверяемого бита 2**(pos) - 2 в степени pos При вычислении значения бита сначала отбрасываются значения всех битов, расположенных "правее" проверяемого (при операции деления наце- ло одновременно отбрасывается и дробная часть числа), а затем опреде- ляется значение бита (если бит установлен, то результирующее значение нечетное, в противном случае - четное). В примере 12.7 приведен текст утилиты общего назначения отображающей на экране статус НМЛ. ┌──────────────────────────────────────────────────────────────┐ │ |Ready(MT) ;Проверка готовности устройства (Да/Нет) | │ │ | New online Use MT | │ │o| Set online=$ZA\128#2 Use 0 |o│ │ | Quit online | │ └──────────────────────────────────────────────────────────────┘ Прим.12.6 Проверка значения бита 6 (Устройство готово) ┌─────────────────────────────────────────────────────────────────┐ │ Хотя в MUMPS системах, как правило, не поддерживается возмож- │ │ ность работы с битовыми строками, Вы всегда сможете определить │ │ значение отдельного бита используя операции деления нацело (\) │ │ и взятия по модулю (#) │ └─────────────────────────────────────────────────────────────────┘ Стр. 325 ┌──────────────────────────────────────────────────────────────┐ │ |MTstatus(MT) ;Вывод на экран статуса НМЛ номер MT | │ │ | New i,status Use MT Set status=$ZA Use 0 | │ │o| Write !,"Слово статуса =",status |o│ │ | For 1=1:1:14 Do Disp | │ │ | Quit | │ │o|Disp New bit Set:i>1 status=status\2 Set bit=status#2 |o│ │ | Write:bit !,$P($T(status+i),";",2,99) | │ │ | Quit | │ │o|Status ;Список значений соответствующих битам статуса |o│ │ | ;Одновременные операции Read/Write | │ │ | ;НМЛ перематывает ленту | │ │o| ;Установлена защита записи |o│ │ | ;Плотность 800 BPI | │ │ | ;Плотность 1600 BPI | │ │o| ;Плотность 6250 BPI |o│ │ | ;Метка начала ленты | │ │ | ;Устройство готово | │ │o| ;Ошибка размера блока |o│ │ | ;Метка конца ленты | │ │ | ;Ошибка паритета или контрольной суммы | │ │o| ;Ошибка контроллера НМЛ |o│ │ | ;Маркер ленты | │ │ | ;Устройство не готово | │ └──────────────────────────────────────────────────────────────┘ Прим.12.7 Вывод на экран статуса НМЛ 12.4.4 Последовательные дисковые файлы Большинство из MUMPS реализаций, в дополнение к возможности запи- сывать программы и массивы осуществляют доступ к стандартным дисковым файлам. В монопольных MUMPS системах (тех, что запускаются на компь- ютерах без другой операционной системы) таким файлам имена обычно не назначаются, они представляют собой просто область дискового прост- ранства для временного хранения информации, организуемой как последо- вательный файл. В MUMPS системах, запускаемых под управлением другой операционной системы общего назначения (например MS-DOS), поддерживается возмож- ность создавать и использовать файлы совместимые с этой операционной системой. В общем случае, для указания обращения к дисковым файлам исполь- зуются параметры оператора OPEN. При этом указывается имя файла, иног- да с указанием диска и директории, и специальный параметр, указывающий на операцию, которую необходимо произвести с файлом (например - чи- тать, записать, назначить, или удалить). При этом попытка исполнить операцию противоречащую установленным параметрам обычно вызывает сос- тояние ошибки (например, попытка чтения из несуществующего файла, или попытка записи в файл открытый только для чтения). Такая ошибка может быть обработана обычным образом, с использованием приемов описанных в главе 13. Так же как и с НМЛ, информация читается из файлов в виде строк символов, а не в виде битовых строк. И хотя возможно открыть любой файл с двоичными данными и читать его, но информация будет считываться только в виде строк из 8-ми битовых символов. Стр. 326 В качестве демонстрации способов использования дисковых файлов мы разберем задачу передачи данных из MUMPS в LOTUS-123 для последующей обработки. LOTUS-123 использует данные организованные в специальном формате, в виде строк фиксированной длины, завершающихся комбинацией кодов $char(13,10) и содержащих данные, выравненные в табличном виде и разделенные пробелами. Таблица 12.6 Данные для передачи в LOTUS-123 ------------------------------------------------- Узел массива Данные ------------------------------------------------- ^Srch(1,1) 10,13,142,1,76,28 ,2) 121,23,4,57,71,47 . . ,25) 41,89,,14,62,101 ------------------------------------------------- В таблице 12.6 приведены данные, собранные специальной программой и подготовленные для передачи в LOTUS-123. Предположим, также, что каждый узел глобального массива представляет собой логическое объеди- нение данных, передаваемых одной строкой. Используя эти предположения напишем утилиту общего назначения, которая будет передавать данные из MUMPS системы в файл для использования его в LOTUS-123. ┌──────────────────────────────────────────────────────────────────┐ │ |Export(gref) ;создание файлов из данных под "gref" | │ │ | New File,sub Set sub="" | │ │o| Read !," Имя файла для вывода: ",File,! Quit:File="" |o│ │ | Open 10:(File=File:Mode="Write") Use 10 | │ │ | For Set sub=$o(@gref@(sub)) Quit:sub="" Do OUT | │ │o| Close 10 Write !,"Выполнено !" |o│ │ | Quit | │ │ |OUT Write @gref@(sub) Use 0 Write "." Use 10 | │ │o| Quit |o│ │ |--------------------------------------------------------------| │ │ |>Do Export("^Srch(1)")<- | │ │o| Имя файла для вывода: A:LDATA.123<- |o│ │ |.............................................. | │ │ |Выполнено ! | │ │o|> |o│ └──────────────────────────────────────────────────────────────────┘ Прим.12.8 Процедура для вывода данных из глобали в файл В некоторых из MUMPS реализаций, работающих под MS-DOS, оператор CLOSE при работе с дисковыми файлами автоматически посылает перед зак- рытием файла в него код /ASCII=26/, который интерпретируется DOS как метка конца файла для текстовых файлов. При чтении из DOS текстовых файлов этот символ может использоваться для завершения чте- ния, как показано в примере 12.9. ┌──────────────────────────────────────────────────────────────┐ │ |Get Open 10:(FILE="TEST.DAT":MODE="READ") | │ │ |Read Use 10 Read data If data=$c(26) Close 10 Quit | │ │o| Do Calc Goto Read |o│ └──────────────────────────────────────────────────────────────┘ Прим. 12.9 Прекращение чтения файла по Стр. 327 Следует особо отметить, что использование в качестве метки конца файла используется не всеми программами даже в DOS. Поэто- му, в MUMPS реализациях обычно поддерживаются системные переменные, которые позволяют определить статус устройства после операции вво- да/вывода. Подробнее смотрите в документации на свою MUMPS систему. /*1/ Вернемся к нашему примеру. Теперь предстоит дописать текст проце- дуры пример из примера 12.8 так, чтобы он удовлетворял условиям работы пакета LOTUS-123 /то есть строки должны быть фиксированной, а не пере- менной длины, и значения должны быть выравнены пробелами в ширину до определенного размера, скажем до 5-ти знаков, и не должны разделяться запятыми/. Необходимые для этого изменения показаны в примере 12.10. ┌──────────────────────────────────────────────────────────────────┐ │ |Export(gref) ;создание файлов из данных под "gref" | │ │ | New File,sub Set sub="" | │ │o| Read !," Имя файла для вывода: ",File,! Quit:File="" |o│ │ | Open 10:(File=File:Mode="Write") Use 10 | │ │ | For Set sub=$o(@gref@(sub)) Quit:sub="" Do OUT | │ │o| Close 10 Write !,"Выполнено !" |o│ │ | Quit | │ │ |OUT New i,line,x Set lene="",x=@gref@(sub) | │ │o| For i=1:1:$l(x,",") Set line=line_$J($P(x,",",i),5) |o│ │ | Write line,! Use 0 Write "." Use 10 | │ │ | Quit | │ │o|--------------------------------------------------------------|o│ │ |>Do Export("^Srch(1)")<- | │ │ | Имя файла для вывода: A:LDATA.EXP<- | │ │o|.............................................. |o│ │ |Выполнено ! | │ │ |> | │ └──────────────────────────────────────────────────────────────────┘ Прим.12.10 Процедура для создания файла с фиксированными полями Единственное отличие этой процедуры от примера 12.8 содержится после метки OUT, где мы используем функцию $JUSTIFY для выравнивания значений в строке указанного размера (здесь - 5 символов). В примере 12.11 продемонстрирован простейший способ просмотреть созданный файл. ┌──────────────────────────────────────────────────────────────┐ │ |>Open 10:(FILE="A:LDATA.EXP":MODE="READ")<- | │ │ |>For Use 10 Read L Quit:l=$C(26) Use 0 Write!,L<- | │ │o| |o│ │ | 10 13 142 1 76 28 | │ │ | 121 23 4 57 71 47 | │ │o| . |o│ │ | . | │ │ | . | │ │o| 41 89 14 62 101 |o│ └──────────────────────────────────────────────────────────────┘ Прим.12.11 Просмотр файла, созданного в примере 12.10 --------------------------------------------------------------- *1 В DataTree MUMPS, например, это переменная $ZDEVR. Она содержит значение из трех полей, если достигнут конец файла, то в первом поле - 3. Стр. 328 12.4.4 Устройства спулинга печати Устройства спулинга печати часто используется в MUMPS системах, как альтернатива непосредственного вывода на принтер. Это вызвано тем обстоятельством, что в многопользовательских системах возникает "кон- куренция" пользователей при захвате принтера. /Так как устройство мо- жет использоваться только одним пользователем в каждый момент времени, и до тех пор, пока он не освободит его, другие пользователи не смогут получить к нему доступ./ Единственное исключение из этого правила - устройства спулинга печати, которые могут одновременно использоваться несколькими MUMPS процессами. При этом вывод на устройство спулинга отличается от непосредственного вывода на принтер тем, что выводимая информация не распечатывается тут же, а хранится в некоем глобальном массиве, который выводит на принтер специальная утилита общего назна- чения, называемая "спулером" печати, и запускаемая, в основном, как "фоновое" задание MUMPS системы. ┌─────────────────────────────────────────────────────────────────┐ │ Устройство спулинга печати, в отличии от всех остальных ус- │ │ тройств, может быть одновременно доступно нескольким процессам. │ └─────────────────────────────────────────────────────────────────┘ "Спулер" печати постоянно просматривает массив спулинга, для то- го, и, тогда, когда находит в нем завершенный массив для печати (мас- сив открывается тогда, когда MUMPS процесс открывает доступ к устройс- тву спулинга (OPEN) и закрывается по CLOSE в процессе). После этого, спулер печати определяет доступность принтера, и, если тот свободен, начинает вывод текста. При этом процесс обращавшийся к спулингу может продолжать работу, в том числе и начать выводит новый текст в массив спулинга. Такой подход имеет определенные преимущества, по сравнению с не- посредственным выводом информации на принтер. Кроме опасности "конку- ренции" за захват принтера, следует учесть, что принтер достаточно медленно выводит текст, и потому, особенно при выводе больших докумен- тов может сильно замедлить работу пользователя. Необходимо также отме- тить, что "спулер" печати может упростить вывод на печать нескольких копий одного документа, и наконец, он корректно обрабатывает все оши- бочные ситуации связанные с принтером (отсутствие бумаги, сбой, него- товность принтера, и т.д.), избавляя программиста от необходимости де- лать эти проверки в прикладных программах. 12.4.6 Прочие устройства В отличии от четко классифицируемых устройств, описанных выше, в этой главе мы кратко рассмотрим только две очень большие и "расплывча- тые" группы устройств - сетевое оборудование и устройства, подсоединя- емые через интерфейс RS-232C. /Именно поэтому последние часто называ- ются просто "RS-232 устройствами"/ Стр. 329 12.4.7 RS-232 устройства Интерфейс RS-232 является одним из самых широко распространенных интерфейсов для подключения переферийных устройств к компьютеру. Мы уже упоминали о видеотерминалах и принтерах, (Разделя 12.4.4, 12.4.2 и приложение Н), теперь поговорим о других устройствах. В число таких устройств входят и плоттеры, и устройства записи/считывания с магнит- ных карт, световые перья, оборудование для измерений и автоматизации, другие компьютеры и многое другое. MUMPS системы, с их гибкой средой ввода/вывода и мощными строко- выми примитивами являются идеальным средством для работы со всеми строчно-ориентированными устройствами. Работа со всем набором ASCII кодов (как с блоками данных так и с отдельными символами), READ со временем ожидания и многие другие возможности, позволяют строить прог- раммы для управления и сбора информации с любых RS-232C устройств. 12.4.6.2 Сетевое оборудование Работа MUMPS системы в среде локальных сетей может представить большое количество дополнительных возможностей. В общем, можно ска- зать, что локальные сети используются либо для передачи данных между компьютерами-узлами сети, либо для обеспечения доступа нескольких компьютеров к одному какому-либо устройству /скажем, к лазерному прин- теру/. Локальные сети могут быть реализованы и как на обычных линиях связи, так и с использованием стекловолоконного или коаксиального /ра- диочастотного/ кабеля и т.д. и т.п.. В зависимости от использованной линии связи скорость обмена данными в сети может быть от единиц Кбайт до десятков Мегобайт в секунду. Топология сети также может быть самой разной - кольцо, шина, звезда, цепочка и т.д. и т.п.. Практически все MUMPS системы в той или иной мере совместимы с локальными сетями или передачей информации по каналам связи этих се- тей. В основном, работа в сетевой среде абсолютно "прозрачна" для программиста, и, поэтому, нет необходимости разбираться в деталях вза- имодействия между сетевым аппаратным обеспечением и MUMPS системой, или в построении сетевых протоколов обмена информацией. Пример исполь- зования локальной сети для организации распределенной базы данных и "расширенного" синтаксиса глобальных ссылок рассматривался в разделе 10.5. MUMPS системы, поддерживающие работу в среде локальных сетей обычно допускают размещение программ и массивов на различных компьюте- рах - узлах сети. При этом данные могут быть доступны всем узлам сети либо при конкретном переназначении /с явным указанием идентификатора узла сети к которому производится обращение/, либо через использование "расширенного" синтаксиса глобальных ссылок (указание в квадратных скобках перед именем глобального массива идентификатора узла сети, где он находится). К сожалению, до сих пор не решен вопрос стандартизации сетевых протоколов у различных фирм-производителей сетевого оборудования. В связи с этим, как правило нет возможности доступа из MUMPS системы од- новременно к нескольким сетям. Комитет по развитию MUMPS прилагает усилия по решению этой проблемы, но вряд ли стоит ожидать успехов в ближайшее время. Стр. 330 12.5 Дополнительные возможности управлением вводом/выводом В предыдущих разделах этой главы мы вкратце рассмотрели различные классы устройств и возможности управления ими из MUMPS систем. И, как уже было отмечено, устройства входящие в один класс (например, видео- терминалы) могут иметь сходные возможности, но сильно отличающиеся ко- манды управления ими. В этом разделе мы рассмотрим как методы управле- ния терминалами так и способы достижения высокой степени терминальной независимости. Мы намеренно сконцентрируем свое внимание на видеотер- миналах, являющимися для пользователя окном в MUMPS систему. Но прак- тически все основные приемы, используемые для управления терминалами, могут быть применены и к другим устройствам. При написании сложных прикладных программ с развитым интерфейсом требуется использовать все возможности современных видеотерминалов. К сожалению, как уже упоминалось ранее, способы обращения к этим возмож- ностям отличаются как для терминалов разных типов, так и для различных MUMPS систем. Наверное, самыми неприятными для программистов являются проблемы вызова одних и тех же функций, и различие в кодовых последо- вательностях, генерируемые дополнительной клавиатурой на терминалах разных типов. Встречается много прекрасных прикладных программных комплексов впечатление от которых портится реализованным в них интерфейсом поль- зователя. Как правило, этот интерфейс подобен выводу на принтер, зап- росы и ответы последовательно выводятся на экран, при заполнении экра- на исполняется скролинг вверх, с затиранием прежнего состояния. И тот консерватизм, с которым программисты отстаивают строчно-ориентирован- ный интерфейс, вместо экранно-ориентированного, просто удивляет. Кроме того, часто для поддержания терминальной независимости используется самое медленное и бедное функциональными возможностями устройство. В нашем случае этого следует избежать. В среде MUMPS систем вполне воз- можно создание программ с экранно-ориентированным интерфейсом, которые будут обладать малым временем реакции на действия пользователя и од- новременно обладать достаточной степенью переносимости между MUMPS ре- ализациями и различными типами терминалов. Мы начнем обсуждение с разработки набора терминально независимых внешних функций для управления экраном видео-терминалов (Раздел 12.5.1). В разделе 12.5.2 рассмотрим набор функций для работы с клави- атуры, а также преобразование кодовых последовательностей генерируемых функциональными клавишами в общий вид для использования в прикладных программах. Раздел 12.5.3 демонстрирует использование функций управле- ния экраном, клавиатурой и односимвольного READ (которые обсуждались в разделах 12.5.1 и 12.5.2). Более углубленное использование функций ре- дактирования будет обсуждаться в разделе 12.5.4. Раздел 12.5.4 будет посвящен проблеме создание экранно-ориентированного драйвера запросов, в котором будут использованы все возможности обсуждаемые в предыдущих разделах. Все использованные в разделах примеры, конечно не охватывают всех проблем возникающих при работе с терминалами, но могут послужить отправной точкой для создания утилит обслуживающих все Ваши нужды. Полный список функций, упоминаемых в этом разделе, вместе с крат- кими пояснениями, приведен в приложении I. Стр. 331 12.5.1 Утилиты управления экраном В этом разделе мы будем обсуждать проблемы управления вводом/вы- водом с терминала. И закончим построением внешних функций вызываемых для отработки той или иной операции с экраном. Сначала мы определим перечень необходимых функций и присвоим им мнемонические имена. В таб- лице 12.7 приведен перечень мнемонических имен, вместе с краткими по- яснениями и указана степень влияния на значения системных переменных $X и $Y. Таблица 12.7 Мнемонические имена функций управления устройствами ---------------------------------------------------------------------- Имя Описание ---------------------------------------------------------------------- Clr Очистка экрана, установка курсора в верхний левый угол. $X и $Y устанавливаются в 0. Cup(X,Y) Позиционирование курсора в точку X,Y экрана, модификация значений переменных $X и $Y. Eol Очистка от курсора до конца строки. $X и $Y не изменяются. Eos Очистка от курсора до конца экрана. $X и $Y не изменяются. Hoff Установка нормальной интенсивности символов. /Отмена повышенной яркости/ $X и $Y не изменяются. Hon Установка повышенной интенсивности символов. $X и $Y не изменяются. Len Содержит максимальное количество строк в экране. $X и $Y не изменяются. Wid Содержит максимальное количество символов в строке $X и $Y не изменяются. Reset Инициализация терминала. При этом очищается экран (Clr), устанавливается нормальная яркость символов (Hoff), прямой контраст (Roff), отменяется подчеркивание (Uoff). $X и $Y устанавливаются в 0. Roff Отмена реверса изображения экрана /Установка темного фона и ярких символов/. $X и $Y не изменяются. Ron Установка реверса изображения экрана /Светлый фон и темные символы/. $X и $Y не изменяются. Uoff Отмена подчеркивания символов. $X и $Y не изменяются. Uon Установка подчеркивания у выводимых символов. $X и $Y не изменяются. ---------------------------------------------------------------------- Стр. 332 Как Вы заметили, существует две группы функций: одни используются для управления изображением на экране, вторые лишь содержат основные физические параметры экрана /его размер/. Управляющие функции требуют посылки на терминал определенной кодовой последовательности. Функции же описания используются только в прикладных программ в расчетах, свя- занных с управлением экраном (скажем при "центрировании" текста). В таблице 12.7 представлен более расширенный (по сравнению с таб- лицей 12.2) список функций управления экраном, который, тем не менее, представляет собой лишь минимальный список наших утилит. Для начала мы не будем рассматривать функцию Cup, предполагая, что ввод/вывод будет производиться без позиционирования курсора. Конечно, желательно работать с терминалами отрабатывающими функ- ции Eol и Eos, но все же действие этих функций можно программно эмули- ровать. (В этом случае понадобиться обращение к прямому позиционирова- нию курсора.) Остальные функции (Hoff, Hon, Roff, Ron, Uoff и Uon) ис- пользуются, так сказать, лишь в "оформительских" целях, и, потому их использование не оказывает особого влияния на прикладные программы. Список, приведенный в таблице 12.7 может быть расширен за счет включе- ния в него функций управления цветом (фоном) и мерцанием. Реализуя вышеперечисленные функции следует определиться с метода- ми их использования в прикладных программах. В общем случае мы будем придерживаться подхода описанного в разделе 12.4.1, где применяется только единственное обращение (из головной программы прикладной зада- чи) к утилите создающей необходимые для управления устройством пере- менные. Конечно, при использовании такого подхода возникают и опреде- ленные сложности. Имена управляющих переменных фиксированы и могут пе- ресекаться с именами переменных в прикладных модулях. Кроме того, пос- тоянное наличие в разделе набора управляющих переменных уменьшает сво- бодное пространство. (А управляющие переменные могут иметь достаточно большой размер). И наконец, самое главное - управляющие переменные должны являться (в зависимости от типа терминала и способа модификации $X и $Y поддерживаемого MUMPS системой) аргументами либо оператора Write, либо аргументами оператора Xecute. Поэтому, прикладная програм- ма должна определить способ использования вызываемой функции. Впрочем, Вы можете выполнить все функции управления как внешние функции и всегда использовать их как аргумент WRITE. При этом все опе- рации по определению какую именно кодовую последовательность надо пос- лать на терминал, и способ ее посылки будут определяться именно во внешней функции, а не в прикладной программе. В примере 12.2 демонс- трирует то, как изменяется текст примера 12.4 переписанный с примене- нием данного подхода. Использование внешних функций упрощает управление терминалом из прикладной программы и повышает "прозрачность" ее текста. Каждая функ- ция вызывается как аргумент оператора WRITE, причем их имена четко указывают на исполняемые ими функции. Кроме того, больше нет необходи- мости использовать косвенное задание аргументов (Как например, Write @FF в примере 12.4), поскольку всю необходимую косвенность можно вы- нести во внешнюю функцию. Стр. 333 ┌──────────────────────────────────────────────────────────────────┐ │ |GetData ;Ввод/редактирование данных пациента | │ │ | Write $$Clr^%Tf,$$Center("Лечебное учреждение XYZ") | │ │o| Write !,$$Center("Ввод/Редактирование данных пациента"),! |o│ │ |GetID Read !,"Пациента ID: ",PID Quit:PID="" | │ │ | . | │ │o| . |o│ │ |Center(txt) ;центрирование "txt" | │ │ | Write $$Cup^%Tf($$Wid^%Tf-$l(txt)\2,$Y) | │ │o| Quit txt |o│ └──────────────────────────────────────────────────────────────────┘ Прим. 12.12 Управление терминалом с использованием внешних функций Каждый тип терминала для идентификации обозначается неким уни- кальным именем, по которому вызывается описание его набора управляющих последовательностей. Можно конечно организовать вызов каждой функции с указанием типа терминала (например, 'Write $$Clr^%T("VT-100")' ), но это громоздко, и кроме того, занимает лишнее пространство в програм- мах. Можно предложить указывать имя терминала (TN - Terminal Name) лишь для функции Reset. Все остальные функции должны будут использо- вать имя установленное последней выполненной функцией Reset и связан- ное с конкретным номером устройства в MUMPS системе ($IO). Функция Reset должна для корректной работы всех остальных функций управления инициализировать экран и записывать в массиве описания тер- минала имя терминала по его номеру ($IO). Инициализация включает в се- бя: назначение видеоатрибутов (темный фон, светлые символы, без повы- шенной яркости и мерцания), очистку экрана, перевод курсора в верхний левый угол экрана и установка значений системных переменных $X и $Y в соответствии с положением курсора (0,0). Для определения имени терминала при вызове функции Reset можно использовать два альтернативных метода. Первый из них состоит в пере- даче имени терминала в функцию в виде параметра, второй заключается в создании глобального массива ^%Ti содержащего описания терминалов, где под индексами, соответствующими номерам устройств в системе ($IO) за- писываются их имена. При этом, каждый вызов функции Reset записывает имя терминала в ^%Ti, по номеру устройства в системе ($IO). Рисунок 12.2 описывает структуру ^%Ti. ┌───────┐ ^%Ti └┬──┬──┬┘ │ │ │ │ │ ($IO) ┌───────┴─────────────┐ │ Имя терминала (TN) │ /например, "VT-100"/ └─────────────────────┘ Рис.12.2 Массив идентификации терминалов Но, кроме определения имени терминала, нам необходимо поддержи- вать набор управляющих им и описывающих его переменных. Для этого, можно использовать другой массив, который по первому уровню индексиро- ван по имени терминала, и содержит при узлах произвольный текст соот- ветствующий этому типу терминалов. Стр. 334 По второму уровню такой массив будет содержать мнемонические имена функций управления устройствами (DFM -Device Function Mnemonic), а в данных при них значения связанные с этими функциями. (Которые будут использоваться как косвенные аргументы WRITE, или как аргументы XECU- TE, или содержать физические характеристики терминала). ^%Td │ │ $ TN │ (Description) │ │ $ DFM (T\Arg) Где : TN - имя терминала, например, "VT-100"/ Description - текстовое описание, соответствующее конкретному имени терминала, например, "Wyse-120 в режиме VT-100" DFM - мнемоническое имя функции управления, например, "Cup", "Clr" или "Reset" T\Arg - Строка из двух полей, разделенных символом '\'. Первое поле - 'T', содержит Тип аргумента функции. (Если он должен использоваться в качестве косвенно- го аргумента Write, то Т='W', если как аргумент XE- CUTE - 'X', если содержит описание - 'D'). Второе поле - 'Arg' - содержит аргумент функции управления терминалом. Рис.12.3 Массив описания терминалов /*1/ В таблице 12.8 приведено, в качестве примера, содержимое массива ^%Td для терминала "VT-100". Все значения, приведенные в Таблице 12.8 построены с учетом пред- положения, что MUMPS система при выполнении 'Write *' не изменяет зна- чение системной переменной $X (См. обсуждение этой проблемы в разделах 3.4.4.3 и 12.4.1.1). Если используемая Вами MUMPS система модифицирует значения переменных при исполнении 'Write *', то строки исполняемые при вызове функций Eol, Eos, Hoff, Hon, Roff, Ron, Uoff и Uon, необхо- димо дополнить так, чтобы после посылки на терминал управляющей после- довательности восстановить значение переменных $Х и $Y. ------------------------------------------------------------------------- *1 Начиная с этой схемы я немного отступаю от предлагаемого автором способа отражения структуры массивов, в виду его громоздкости при опи- сании сложных структур. Предлагаемый метод опробован на практике и достаточно часто применяется MUMPS программистами при документировании схем Баз Данных. Символ '$' отражает уровень индексации, значение при нем - либо имя переменной по которой производится индексация, либо текстовый литерал /В этом случае заключается в кавычки/. Значение, указываемое ниже символа '$' описывает данное, записываемое по этому уровню индексации. Стр. 335 Предположим, для очистки до конца экрана (Eol) терминала VT-100 на не- го необходимо послать последовательность '[0K'. В этой последова- тельности три символа из четырех являются отображаемыми и могут изме- нить значение $Х. Для того, чтобы после исполнения функции Eol значе- ние $Х оставалось неизменным, строку исполняемую при вызове этой функ- ции следует дополнить: ^%Td("VT-100","Eol")="X\New x,y Set x=$X,y=$Y Write *27,*91,*48,*75 Set $X=x,$Y=y" Функция Eol не изменяет положения курсора, и после выполнения по XECUTE строки, приведенной выше, значения переменных $X и $Y будут со- ответствовать положению курсора. Все остальные функции должны быть до- полнены аналогичным образом. Не советую пытаться упростить приведенную выше строку исключая из нее 'New x,y Set x=$X,y=$Y ... Set $X=x,$Y=y', заменив ее на 'Set $X=$X-3'. Подобная замена не всегда будет корректно работать. Надо учесть то, что некоторые MUMPS системы автоматически выводят последо- вательность символов CR+LF после последнего символа строки. И в этом случае $Y увеличивается на 1, а $X устанавливается в 0. Таблица 12.8 Функции управления для терминала VT-100 ------------------------------------------------------------------- Узел массива ^%Td Данное при узле ------------------------------------------------------------------- ^%Td("VT-100") Терминал DEC VT-100 ^%Td("VT-100","Clr") W\#,*27,*91,*72,*27,*91,*50,*74 ^%Td("VT-100","Cup") X\W *27,*91,y+1,*59,x+1,*72 S $X=x,$Y=y ^%Td("VT-100","Eol") W\*27,*91,*48,*75 ^%Td("VT-100","Eos") W\*27,*91,*48,*74 ^%Td("VT-100","Hoff") W\*27,*91,*48,*109 ^%Td("VT-100","Hon") W\*27,*91,*49,*109 ^%Td("VT-100","Len") D\23 ^%Td("VT-100","Roff") W\*27,*91,*48,*109 ^%Td("VT-100","Ron") W\*27,*91,*55,*109 ^%Td("VT-100","Uoff") W\*27,*91,*48,*109 ^%Td("VT-100","Uon") W\*27,*91,*52,*109 ^%Td("VT-100","Wid") D\79 ------------------------------------------------------------------- Некоторые MUMPS системы специальным образом отслеживают последовательности, выводимые на терминал. В таких системах модерниза- ция значений $X и $Y приостанавливается от того момента, когда при вы- воде встречается символ и до конца оператора Write. Поэтому, символы, следующие за символом не изменяют $Х и $Y. В системах, поддерживающих эту возможность одинаково исполняется и 'Write $C(27,91,49,109)' и 'Write *27,*91,*49,*109' - очищая экран от курсора до конца строки (Eol), но вторая последовательность некорректно изме- няет значение $Х. Дело в том, что в первой последовательности все сим- волы, следующие за рассматриваются как часть одного аргумента оператора Write, а во втором случае каждый символ управляющей последо- вательности является отдельным аргументом оператора Write. В силу этих причин создание функций управления терминалом зависит одновременно от того как терминал отрабатывает управляющие последова- тельности, а MUMPS система модифицирует $X и $Y. Подробнее смотрите в документации на используемую Вами MUMPS систему. Стр. 336 Определив мнемонические имена функций управления давайте посмот- рим на их реализацию в виде внешних функций. Пример 12.13 демонстриру- ет текст двух функций - Reset и Clr. ┌──────────────────────────────────────────────────────────────┐ │ |^%Tf ;JML-NYSCVM | │ │ | ;Внутренние функции для управления терминалом | │ │o| ; |o│ │ |Reset(TN) ;Инициализация терминала "TN" | │ │ | Set TN=$G(TN),TN=$S(TN="":$G(^%Tf($I)),1:TN) | │ │o| If TN="" Quit 0 |o│ │ | Set ^%Ti($I)=TN Write $$Clr,$$Hoff,$$Roff,$$Uoff | │ │ | Quit 1 | │ │o| ; |o│ │ |Clr() Quit $$doit("Clr") ;Очистка экрана, Set ($X,$Y)=0 | │ │ | ; | │ │o|doit(mne) ;Общая точка выхода |o│ │ | New value Set value=$G(^%Td(^%Ti($I),mne)) | │ │ | If value?1"W".E Write @$P(value,"\",2,999) | │ │o| If value?1"X".E Xecute $P(value,"\",2,999) |o│ │ | Quit $S(value?1"D".E:$P(value,"\",2,999),1:"") | │ └──────────────────────────────────────────────────────────────┘ Прим. 12.13 Функции Reset и Clr Функция Reset обычно вызывается один раз в головной программе прикладной задачи для выполнения двух задач. Во-первых, - определить тип терминала соответствующий текущему устройству и записать его в ^%Ti($IO) для использования другими функциями. Во-вторых, очистить эк- ран и установить нормальное состояние видеоатрибутов. (Отменить повы- шенную яркость, реверс изображения, мерцание и подчеркивание). Если в процедуру Reset имя терминала не передается, оно считывается из ^%Ti($IO). Если имя терминала некорректно, функция Reset возвращает 0. Если имя корректно, то функция исполняется и возвращает 1. Функция Clr предназначена для очистки экрана текущего устройства ($IO) и использует имя терминала, установленное последней исполненной функцией Reset. Управляющая последовательность, используемая для очистки экрана считывается из массива описания терминалов - ^%Td. Все функции управления, за исключением Reset используют для вы- полнения необходимых действий общую внешнюю функцию doit. Обратите внимание на способ вызова внешней функции doit. Эта функция использу- ется в качестве аргумента оператора Quit в вызываемой функции управле- ния, и в нее передается имя функции управления. [Например, 'Quit $$do- it("Clr")'] Внутри функции doit переменная mne содержит мнемоническое имя функции управления, в переменной value содержится строка MUMPS ко- да и указание на то, как эта строка кода должна исполняться. Если пе- ременная value начинается с символа "W", то строка должна исполняться как косвенный аргумент оператора Write. Если она начинается с "X", то строка исполняется как аргумент Xecute, а если с "D", то никаких дейс- твий не выполняется и функция возвращает значение (В данном случае один из размеров экрана). Если устройство или имя функции не определе- ны, то внешняя функция doit возвращает пустую строку и не производит никаких действий с экраном. Стр. 337 Остальные функции из таблицы 12.7 реализованы аналогичным обра- зом. Текст утилиты ^%Ti, содержащий их, приведен в приложении I. В примере 12.14, который представляет собой дальнейшее развитие примеров 12.4 и 12.12, показано использование некоторых внешних функ- ций управления терминалом. ┌──────────────────────────────────────────────────────────────────┐ │ |GetData ;Ввод/редактирование данных пациента | │ │ | Set ok=$$Reset^%Tf Quit:'ok | │ │o| Write $$Hon^%Tf,$$Center("Лечебное учреждение XYZ"),! |o│ │ | Write $$Center("Ввод/Редактирование данных пациента"),! | │ │ | Write $$Hoff^%Tf | │ │o|GetID Read !,"Пациент : ",PID Quit:PID="" |o│ │ | . | │ │ | . | │ │o|Center(txt) ;центрирование "txt" |o│ │ | Write $$Cup^%Tf($$Wid^%Tf-$L(txt)\2,$Y) | │ │ | Quit txt | │ │o|--------------------------------------------------------------|o│ │ | Лечебное учреждение XYZ | │ │ | Ввод/Редактирование данных пациента | │ │o| |o│ │ |Пациент : | │ │ | | │ └──────────────────────────────────────────────────────────────────┘ Прим.12.14 Управление экраном с использованием внешних функций /В тексте не видно, но на экран две первые строки выводятся с повышенной яркостью/ Внешние функции для управления терминалом, описанные в этом раз- деле дают программисту высокую степень терминальной независимости, вместе с легкостью применения. Но, есть к сожалению один неприятный аспект. Вызов некоторых функций управления может оказывать воздействие одновременно на несколько параметров экрана терминала. Обратите внима- ние на приведенные в таблице 12.8 управляющие последовательности для терминала VT-100, исполняющие функции "Отмена повышенной яркости" (Hoff), "Отмена мерцания" (Boff) и "Отмена подчеркивания" (Uoff). Они полностью идентичны. То, что одна последовательность используется для управления нес- колькими возможностями видеотерминала, серьезно осложняет его програм- мирование. Отдельный видеоатрибут не может быть отменен без отмены всех видеоатрибутов. Но такая взаимосвязь между атрибутами существует не для всех типов терминалов, некоторые из них допускают отмену от- дельных видеоатрибутов, не изменяя все остальные. Но в общем случае прикладные программы должны отслеживать текущее состояние атрибутов экрана, чтобы для отмены одного из них восстановить все остальные, после общего сброса видеоатрибутов. Ранее в этом разделе мы уже упоминали, что в принципе, все необ- ходимые функции управления можно реализовать программно, если только терминал поддерживает прямую адресацию курсора. А так как такие воз- можности терминала, как установка повышенной яркости, мерцания и под- черкивания используются лишь для "оформления" экрана, то можно обой- тись и без них. В таблице 12.9 приведены коды для функций управления для так называемого "немого" терминала, который аппаратно поддерживает только прямую адресацию курсора. Стр. 338 Следует отметить, что терминал, аппаратно поддерживающий функции Eol и Eos, конечно будет работать быстрее, чем тот, что реализован через функции описанные в таблице 12.9. Таблица 12.9 Функции управления для "немого" терминала ------------------------------------------------------------------- Узел массива ^%Td Данное при узле ------------------------------------------------------------------- ^%Td("DUMB") Aardvark glass teletype ^%Td("DUMB","Clr") W\Write $$Cup(0,0),$$Eos ^%Td("DUMB","Cup") X\Write *3,*x,*y Set $X=x,$Y=y ^%Td("DUMB","Eol") X\New x,y Set x=$X,y=$Y Write ?$$Wid, $$Cup(x,y) ^%Td("DUMB","Eos") X\New x,y,i Set x=$X,y=$Y Write $$Eol For i=$Y+1:1:$$Len Write $$Cup(0,i),$$Eol If i=$$Len Write $$Cup(x,y) Quit ^%Td("DUMB","Hoff") [пустая строка] ^%Td("DUMB","Hon") [пустая строка] ^%Td("DUMB","Len") D\23 ^%Td("DUMB","Roff") [пустая строка] ^%Td("DUMB","Ron") [пустая строка] ^%Td("DUMB","Uoff") [пустая строка] ^%Td("DUMB","Uon") [пустая строка] ^%Td("DUMB","Wid") D\79 ------------------------------------------------------------------- 12.5.2 Ввод с клавиатуры; Односимвольный READ А теперь мы перейдем к проблемам, связанным с обработкой ввода данных с клавиатуры, особенно заостряя внимание на распознавании нажа- тия специальных клавиш, таких, как например, клавиши управления курсо- ром и функциональные клавиши. В разделе 12.4.1.2 мы уже вкратце упомя- нули о проблемах, связанных с чтением таких клавиш. На кодовые после- довательности, посылаемые такими клавишами в компьютер стандарт не ус- тановлен, в некоторых случаях они могут посылать один код (как правило один из управляющих кодов ASCII), в других - многосимвольные Esc - последовательности. Для использования таких клавиш следует разработать метод преобра- зования кодовых последовательностей в некий общий формат. Для этого мы создадим внешнюю функцию, которая будет через использование односим- вольного READ читать кодовые последовательности, (как одно, так и мно- го символьные) и преобразовать их в какой-то общий формат. Какая бы клавиша не была нажата, функция будет возвращать ее мнемоническое имя, приведенное в таблице 12.10. Все отображаемые ASCII коды (с кодами от 32 до 126) / *1/ отображаются как одиночный символ. Все остальные символы /за исключением отображаемых и перечислен- ных в таблице 12.10/, игнорируются. В таблице 12.10 элемент обозначен- ный [timeout] соответствует тому состоянию, когда в ожидаемый интервал времени не нажата ни одна из клавиш. /На одно символьный READ может быть наложено время ожидания, так же как и на обычную операцию READ/. --------------------------------------------------------------------------- *1 А также обычно и 128 -255 /если Ваша ЭВМ работает с ASCII - альтернативной таблицей./ Стр. 339 Таблица 12.10 Строки, возвращаемые при нажатии специальных клавиш. ───────────────────────────────────┬───────────────────────────────── Клавиша Возвращаемая строка │ Клавиша Возвращаемая строка ───────────────────────────────────┼───────────────────────────────── BACKSPACE "" │ Курсор вверх "" TAB "" │ Курсор вниз "" ENTER "" │ Курсор вправо "" +U "" │ Курсор влево "" ESC "" │ Function Key 1 "" DELETE "" │ Function Key 2 "" [timeout] "" │ Function Key 3 "" │ Function Key 4 "" ───────────────────────────────────┴───────────────────────────────── Дополним массив описания терминалов ^%Td описанием специальных клавиш, записав в него как кодовые последовательности, генерируемые ими, так и мнемонические имена, в которые эти последовательности будут преобразовываться. В этом массиве код терминала (TN) записывает по первому уровню индексации, литерал "keys" - по второму, а мнемоничес- кие имена ("AU", "AD", "AR" и т.д.), по третьему. В качестве данного по третьему уровню индексации (при мнемоническом имени клавиши - KM [Keys Mnemonic]) записывается кодовая последовательность, генерируемая при нажатии клавиши. Мнемонические имена совпадают со строками в кото- рые преобразуются кодовые последовательности при передачи в прикладную программу. Дополнения к структуре массива ^%Td отражены на Рис.12.4. ^%Td | $ TN | ("Description") | -------------------------------- | | | | $ "funct" $ "keys" | | (To;From) | | $ DFM $ KM (T\Arg) (Sequence) Где : To;From - список мнемоник и соответствующий ему список кодовых последовательностей /см.ниже/ KM - мнемоническое имя /например, - "AU"/ Sequence - последовательность, генерируемая клавишей, /например, - $C(27,91,65) ([A)/ Рис. 12.8 Дополнения в структуре массива ^%Td необходимые для работы с клавиатурой Элементы дополнительной ветви массива ^%Td будут выглядеть следующим образом: ^%Td("VT100","keys","AU")=$C(27,91,65) ^%Td("VT100","keys","F1")=$C(27,79,83) Стр. 340 Следует только принять во внимание, что не все терминалы, совмес- тимые с VT-100, генерируют одинаковые кодовые последовательности при нажатии одних и тех же специальных клавиш. Поэтому, каждый тип терми- налов должен быть отдельно описан в массиве ^%Td. Теперь мы имеем информацию, необходимую для преобразования кодо- вых посылок соответствующих нажатым клавишам, но она достаточно неу- добно записана в массиве, поскольку для обработки чтения клавиши тре- бовалось бы каждый раз просматривать ветку "keys" массива ^%Td. Такой подход сильно замедлил бы работу программ. Для того, чтобы этого избежать под индексом "keys" мы запишем две подстроки, разделенные символом ';' - (To;From). Первая из них - (To), список мнемоник, вторая (From) - список кодовых посылок. Мнемоники разделены запятой, кодовые посылки символом DELETE [$C(127)]. Порядок следования мнемонических имен в подстроке 'To' соответствует порядку записи кодовых последовательностей в подстроке 'From'. После извлече- ния из строки 'To' мнемоническое имя заключается в угловые скобки ("<" и ">") и передается в программу. Принцип построения этих строк предс- тавлен на Рис.12.5 FROM=[A[B[D[C ┌───────────┘ │ │ │ │ ┌───────────────────────┘ │ │ │ │ ┌──────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┘ │ │ │ │ TO="AU,AD,AR,AL,... " Рис. 12.5 Взаимосвязь между кодовыми последовательностями и соответствующими им мнемоническими именами. Подстроки 'To' и 'From' строятся на основе записанных ниже индек- са "keys" [^%Td(TN,"keys")] в массиве ^%Td описаний специальных клавиш терминала следующей процедурой: ┌──────────────────────────────────────────────────────────────┐ │ |^%Tf ;JML-NYSCVM | │ │ | ;Внешние функции для управления терминалом | │ │o| ; |o│ │ | . | │ │ | . | │ │o|BuildTF(TN) ;Создание строк To;From из ^%Td(TN,"keys") |o│ │ | Set TN=$G(TN) Quit:TN="" | │ │ | New from,km,to Set (km,to)="",from=$c(127) | │ │o| For Do Quit:km="" |o│ │ | . Set km=$O(^%Td(TN,"keys",km)) Quit:km="" | │ │ | . Set from=from_^(km)_$C(127),to=to_km_"," | │ │o| . Quit |o│ │ | Set ^%Td(TN,"keys")=to_";"_from | │ │ | Write !,"Описание клавиш для "_TN_" выполнено." | │ │o| Quit |o│ └──────────────────────────────────────────────────────────────┘ Прим. 12.15 Создание строк для преобразования кодовых последовательностей Стр. 341 Строки To и From должны создаваться всякий раз заново при внесе- нии изменений в описание клавиатуры терминала. /То есть после каждого внесения изменений в описание терминала TN необходимо запускать проце- дуру BuildTF(TN)/. Использование списка записанного в одной строке позволяет осу- ществить проверку на присутствие в списке кодовой последовательности с помощью одного оператора СОДЕРЖИТ - '['. /Вместо поиска в глобальном массиве/. На этом подготовительная работа закончена и можно приступить к анализу работы функции. ┌──────────────────────────────────────────────────────────────┐ │ |^%Tf ;JML-NYSCVM | │ │ | ;Внешние функции для управления терминалом | │ │o| . |o│ │ | . | │ │ |ReadS(Timeout,Echo) ;Односимвольный READ ;Do $$EchoOFF | │ │o| New c,chars,d,find,from,n,to Set d=$c(127) |o│ │ | Set from=$G(^%Td(^%Ti($I),"keys")),to=$P(from,";",),| │ │ | ---from=$P(from,";",2,999) | │ │o| Set Timeout=$G(Timeout) Set:'Timeout="" Timeout=1E25|o│ │ | Set Echo=$G(Echo) Set:Echo="" Echo=1 | │ │ |gchar Set chars="" Read *c:Timeout Goto readsx:c>31&(c<127| │ │o| ---)!(c<0) |o│ │ |cchar Set chars=chars_$C(c) | │ │ | Goto readsx:from'[(d_chars),more:from'[(d_chars_d) | │ │o| Set find=$L($E(from,1,$F(from,d_chars_d)),d)-2 |o│ │ | Set c=$P(to,",",find) Goto readsq | │ │ |more For n=1:1:$$RPT Read *c:0 Quit:c'<0 | │ │o| Goto cchar:c'<0 Set c=$A(chars,$L(chars)) |o│ │ |readsx If c>31&(c<127) Write:Echo *c Quit $C(c) | │ │ | Set c=$S(c<0:"TO",c=13:"RET",c=21:"CAN",c=27:"ESC",c| │ │o| ---=8:"BS",c=9:"TAB",c=127:"DEL",1:"") |o│ │ | If c="" Write *7 Goto gchar | │ │ |readsq Quit "<"_c_">" | │ └──────────────────────────────────────────────────────────────┘ Прим.12.16 Функция ReadS для эмуляции односимвольного READ Необязательный параметр Timeout в функции ReadS выполняет функ- ции, аналогичные параметру timeout накладываемому на оператор READ, - он определяет промежуток времени (в секундах) в течении которого внеш- няя функция ждет нажатия клавиши. Если до истечения времени ожидания клавиша не нажата, функция возвращает строку ''. Если при вызове функции параметр Timeout не определяется, он приравнивается произволь- но выбранному большому числу /здесь 1Е25 секунд/. Необязательный параметр Echo управляет выводом на экран отобража- емых символов. Если он принимает значение ИСТИНА /не равен 0/, или не определен при вызове функции, то символ отображается, если же Еcho=0, то введенный символ не высвечивается на экране. Этот параметр будет использоваться в примерах рассматриваемых в следующей главе /При опи- сании "драйвера" запросов и функций выбора $$MC и $$Opt./ Основные операции, выполняемые этой функцией очевидны. Выполняет- ся односимвольный READ и, если введен графический символ, то он отоб- ражается на экране (Если параметр Echo - ИСТИНА /не равен 0/), и возв- ращается в вызывающую программу. Стр. 342 Если определен параметр Timeout, и за период времени, определяемый его значением ни одна из клавиш не нажата, в вызывающую программу возвра- щается строка "". Если же первым символом является НЕ графический символ, то снача- ла производится проверка: - не является ли этот символ первым символом управляющей последовательности [то есть не содержится ли он в строке from, - from[$c(127_chars]. Если нет, то управление передается дальше, где производится дополнительная проверка для не-графических символов, где распознаваемые символы (например, , или ), преобразу- ются в соответствующие им строки (например, "" или "") и возвращаются в вызывающую программу. Если управляющий символ не рас- познается, то он игнорируется и READ повторяется. Если же введенный символ является первым символом управляющей последовательности, то производится дополнительное чтение (метка more) до тех пор, пока она не завершится, или же перестанет совпадать с шаб- лоном из строки To. Если последовательность завершилась и полностью совпадает с шаблоном, то функция возвращает в вызывающую программу символическое имя, закрепленное за этой клавишей. (Например, "", "" ...) Если же последовательность завершилась и не совпала с шаблоном, то в вызывающую программу возвращается символ, на котором последовательность перестала совпадать с шаблоном. Обратите внимание на дополнительную функцию $$RPT используемую в строке more. Вы можете столкнуться с проблемой при чтении многосим- вольных последовательностей, особенно при работе с терминалам, которые работают через линию связи с низкой скоростью (например, 1200 бод). Если прочитанный символ является первым символом управляющей последо- вательности, то функция должна произвести дополнительные READ, чтобы прочитать все остальные символы этой последовательности. Это чтение необходимо производить в такой интервал времени, чтобы, с одной сторо- ны гарантированно прочитать всю последовательности, а с другой стороны не приостанавливать основной процесс, если следующие символы управляю- щей последовательности не появляются. Если, например, мы используем клавишу для сигнализации отказа от продолжения работы, то необ- ходимо обеспечить возврат в управляющую программу как можно быстрее. Для этого функция должна определить, что это нажатие одиночной клави- ши, а не начало управляющей последовательности, так как дополнительные символы не появляются следом за кодом . Обычно наименьшей скоростью обмена, поддерживаемой MUMPS система- ми, является 110 Бод /около 10 символов в секунду/. То есть, если функция будет ожидать появление следующего символа 1/10 секунды, то мы сможем быть уверенными, что соберем все символы многосимвольной управ- ляющей последовательности. К сожалению, MUMPS системы обычно позволяют установить время ожидания в операторе READ только с точностью +/- одна секунда. /*1/, и, для того, чтобы не потерять ни одного символа, мы будем вынуждены установить время ожидания равное 2 секундам. ------------------------------------------------------------------------- *1 Не все MUMPS системы. Так, например, DataTree MUMPS позволяет зада- вать время ожидания с точностью +/- 1/10 секунды, и, поэтому используя DataTree MUMPS Вы можете реализовать задержку более простым способом, чем предлагаемый автором. Стр. 343 Поэтому, мы организуем задержку на 1/10 секунды, необходимую для рабо- ты функции программным путем. Так как скорость исполнения сильно зави- сят и от используемой ЭВМ, и от конфигурации программных средств /*1/, то необходимо будет опытным путем подобрать число исполняемых циклов для создания задержки в 1/10 секунды. В примере 12.17 приведен текст процедуры SetRPT, которая определяет значение для внешней функции $$RPT. ┌──────────────────────────────────────────────────────────────────┐ │ |^%Tf ;JML-NYSCVM | │ │ | ;Внешние функции для управления терминалом | │ │o| . |o│ │ |SetRPT ;Определение значения для задержки 1/10 сек. | │ │ | New i,h,n | │ │o| Write !,"Определение значения возвращ.$$RPT для 1/10 c."|o│ │ | Set h=$P($H,",",2) For i=1:1 Quit:$P($H,",",2)'=h | │ │ | Set h=$P($H,",",2) For i=1:1 Read *n:0 Quit:$P($H,",",2)| │ │o| ---'=h |o│ │ | Write "Вставьте в строку RPT+1 QUIT ",i\10," для 1/10 се| │ │ | ---к. задержки Read",! | │ │o| Quit |o│ │ | ; | │ │ |RPT() ;Кол-во циклов для задержки 1/10 сек. | │ │o| QUIT 10 ;Замените 10 на значение выданное SetRPT |o│ └──────────────────────────────────────────────────────────────────┘ Прим.12.17 Вычисление значения возвращаемого $$RPT Значение для $$RPT должно вычисляться для каждой MUMPS системы (аппаратной и программной конфигурации). Подпрограмма SetRPT должна запускаться тогда, когда в системе не работают другие пользователи, чтобы они не влияли на вычисленное значение. ┌────────────────────────────────────────────────────────────────┐ │ |>Do SetRPT^%Tf | │ │ |Определение значения возвращ.$$RPT для 1/10 c. | │ │o|Вставьте в строку RPT+1 QUIT 14 для 1/10 сек. задержки Read |o│ │ |> | │ └────────────────────────────────────────────────────────────────┘ Прим.12.18 Вычисление значения для функции $$RPT Возвращаемое процедурой SetRPT значение вставьте в строку RPT+1 как аргумент оператора QUIT, для того, чтобы получить задержку в 1/10 секунды. Эта задержка не сказывается на процессе ввода с клавиатуры, а имеет значение только при чтении многосимвольных последовательностей. В большинстве случаев терминалы используют каналы связи со скоростью обмена горазда большей, чем 110 Бод, и, потому, количество циклов ис- полненных перед чтением следующего символа будет небольшим. Перед использованием функции $$ReadS необходимо создать еще две внешних функции, текст которых будет зависеть от используемой MUMPS реализации. --------------------------------------------------------------------- *1 Необходимо добавить, что время задержки также будет в многопользо- вательских системах зависеть и от степени загрузки ЭВМ. Стр. 344 Текст внешней функции ReadS построен исходя из предположения, что MUMPS система НЕ отображает на экране символы вводимые при использова- нии оператора READ в форме 'READ *char'. /*1/ Для тех систем, у кото- рых нельзя отменить "эхо" отдельно для 'READ *char' необходимо отме- нить "эхо" вообще, перед вызовом $$ReadS. Для включения и отмены "эха" нами и будут использоваться две дополнительные функции $$EchoON и $$EchoOFF. ┌──────────────────────────────────────────────────────────────┐ │ |^%Tf ;JML-NYSCVM | │ │ | ;Внешние функции для управления терминалом | │ │o| . |o│ │ |EchoON() ;Системно-зависимая функция для ECHO ON | │ │ | USE 0:(0:"") ;Вставьте здесь строку для ECHO ON | │ │o| QUIT "" |o│ │ |EchoOFF() ;Системно-зависимая функция для ECHO OFF | │ │ | USE 0:(0:"SI") ;Вставьте здесь строку для ECHO OFF | │ │o| QUIT "" |o│ └──────────────────────────────────────────────────────────────┘ Прим.12.19 Внешние функции для управления "эхом" Текст этих внешних функций необходимо редактировать в зависимости от используемой MUMPS реализации, поскольку аргумент операторов USE демонстрирует способ управления "эхом" применимый лишь в некой конк- ретной MUMPS реализации. А если Ваша система вообще не отображает сим- волы, вводимые в 'Read *char', то текст обоих функций должен состоять из одного оператора - 'Quit ""'. /*2/ В примере 12.20 приведен фрагмент программы, в котором можно явно увидеть основной способ использования функции $$ReadS. ┌─────────────────────────────────────────────────────────────────┐ │ |Test ;Головной модуль пакета TEST MANAGEMENT | │ │ | ; | │ │o| Kill If '$$Reset^%Tf Write "Терминал НЕ описан" Quit |o│ │ | Set opt=$$GetAns Quit:opt="" | │ │ | . | │ │o| . |o│ │ |GetAns() ;Чтение с клавиатуры, возвращает Номер раздела | │ │ | New char,pn | │ │o| Set pn="" Write !,"Номер раздела: ",$$EchoOFF^%Tf |o│ │ | For Do If $L(char)>1 Quit:""[char | │ │ | . Set char=$$ReadS^%Tf(600) | │ │o| . If char?1E Set pn=pn_char Quit |o│ │ | . If ""[char Set char="" Quit | │ │ | . Quit | │ │o| If char="" Write *7," Нет ввода ..." Set pn="" |o│ │ | Write $$EchoON^%Tf | │ │ | Quit pn | │ └─────────────────────────────────────────────────────────────────┘ Прим.12.20 Простая программа использующая функцию $$ReadS --------------------------------------------------------------------- *1 В DataTree MUMPS "эхом" для Read *char можно управлять через пара- метр "ECHOA". 'USE $I:ECHOA=1' - разрешить, а =0 - отменить. *2 Для DataTree MUMPS система по-умолчанию отменяет "эхо" для 'Read *char', и потому его дополнительно отменять не надо. Стр. 345 Другие примеры использования этой функции Вы найдете в следующих раз- делах этой главы. В примере 12.20 видно, как в головном модуле прикладной задачи один раз вызывается функция Reset для определения типа терминала. Пе- ред началом чтения с клавиатуры отменяется "эхо" для вводимых симво- лов, после завершения чтения, "эхо" устанавливается снова и управление передается дальше. Во многих MUMPS системах реализованы дополнительные возможности для чтения многосимвольных последовательностей. /*1/ Так, например, в некоторых имеется специальная системная переменная в которую помещает- ся символ, которым завершилась операция чтения. И, если в такой систе- ме можно назначить символ кодом для завершения чтения /а не только /, то в этой переменной будет размещена вся управляющая последовательность. В тексте функции $$ReadS подобные возможности не используются, и она без каких либо изменений /за исключением функций $$EchoON и $$EchoOFF/ может использоваться в большинстве MUMPS систем. 12.5.3 Запросы с фиксированными вариантами ответа. /Выбор из списка/ Во многих случаях в прикладных программах пользователю необходимо представить возможность выбора одного из нескольких фиксированных ва- риантов ответа на запрос. Ранее мы уже обсуждали некоторые простые приемы, которые можно применить для вывода на экран перечня вариантов ответа. (См. примеры 7.11 и 8.13) Теперь, поддерживая концепцию экран- но-ориентированного ввода/вывода обсудим создание утилиты общего наз- начения для вывода на экран перечня вариантов ответа, которая позволит пользователю выбрать один из них, пользуясь клавишами управления кур- сором. Для начала, обсудим проблему создания простейшей утилиты, которая должна обрабатывать ответ на простейший запрос с двумя вариантами от- вета. Представим, в качестве примера, что необходимо обработать следу- ющий запрос: Читать данные из ФАЙЛА ? [Да] Нет Допустимые варианты ответа ("Да" или "Нет") выводятся на экран следом за запросом, причем ответ, принимаемый по умолчанию (при нажа- тии клавиши ) выделяется повышенной яркостью. Пользователь мо- жет произвести выбор следующим образом: - используя клавиши управления курсором - введя первую букву выбранного варианта ("Д" или "Н") - нажав клавишу для выбора ответа, предлагаемого по умолчанию. Если пользователь введет "?", то должен выводиться текст подсказ- ки, связанной с этим запросом. --------------------------------------------------------------------- *1 В DataTree MUMPS реализован механизм преобразования многосимвольных последовательностей в одиночный код, через создание специальных таблиц трансляции /ixxlate/, которые могут быть созданы для различных терми- налов. Стр. 346 Для вывода на экран подсказок и запросов мы воспользуемся следую- щим положением: первые 19 строк экрана (с 0 до 18), используются для выводов запросов и ответов, а нижние 5 строк (с 19 до 23) резервируют- ся для вывода подсказок и сообщений об ошибках. В утилиту запроса с перечнем фиксированных отчетов необходимо пе- редавать следующую информацию: 1. Список вариантов для выбора 2. Текст подсказки, связанный с запросом. 3. Ответ предлагаемый по умолчанию (тот, который будет подсвечен при обращении к утилите) Если пользователь вводит несколько букв, или букву, с которой на- чинается несколько вариантов, то селектор передвигается на следующий вариант. Вариант предлагаемый по умолчанию указывать не обязательно, если он отсутствует, то по умолчанию предлагается первый в списке ва- риант. Более общим вариантом является передача списка вариантов и текста подсказки в утилиту через массив. Головной индекс массива содержит список вариантов, отдельные варианты в списке разделены двоеточиями. Текст подсказки записывается отдельными строками ниже списка вариан- тов. Для примера см.таблицу 12.11 Таблица 12 Массив, используемый для передачи в утилиту выбора из нескольких вариантов. ------------------------------------------------------------------- ^Opts(1)="Мужской:Женский:Неопределен" ^Opts(1,1)="Введите ПОЛ пациента. Используйте клавиши <ВЛЕВО>/<ВПРАВО>" ^Opts(1,2)=" или буквы 'М', 'Ж' или 'Н' для выбора. После выбора" ^Opts(1,3)=" нажмите клавишу " ^Opts(1,4)="Пожалуйста, не используйте часто вариант 'Неопределен'," ^Opts(1,5)=" он предназначен для управления общим запросом" ^Opts(1,6)=" при поисках в базе данных" ------------------------------------------------------------------- Для передачи информации в утилиту, как правило, используется гло- бальный массив. При этом имя массива (или глобальной ссылки), переда- ется в утилиту как строковый литерал. Для доступа к узлам массива в утилите будет использоваться косвенное задание индексной ссылки на узел массива. Вариант, предлагаемый по умолчанию, передается отдельным параметром, в виде номера поля внутри списка вариантов. Утилита также будет возвращать номер, соответствующий выбранному варианту. (Напри- мер, 2 для "Женский"). В общем виде вызов утилиты будет производиться как обращение к внешней функции и выглядеть следующим образом: Write !," ПОЛ пациента: " Set opt=$$MC^%To("^Opts(1)",2,.term,600) Первый параметр содержит ссылку на массив, где содержатся необхо- димые для работы утилиты данные, и только он является обязательным. Второй параметр указывает на номер варианта, предлагаемый для выбора по умолчанию при вызове утилиты /он не является обязательным, и если он не указывается, то предлагается для выбора по умолчанию первый ва- риант из списка./ Стр. 347 Третий параметр передается ссылкой и возвращает код процесса за- вершения выбора варианта из списка. (то есть, анализируя его можно оп- ределить, какую клавишу нажал пользователь - , и т.д.). Определение того, чем был завершен выбор очень важно для экранно-ори- ентированного интерфейса прикладных задач. Так, например, если пользо- ватель нажал клавишу <КУРСОР ВВЕРХ>, то необходимо обеспечить возвра- щение к предыдущему запросу на экране. Код клавиши, по нажатию которой завершилось чтение, возвращается заключенным в угловые скобки ("<" и ">" ). Список таких клавиш, и соответствующих им кодов приведен в таб- лице 12.12. Четвертый параметр представляет собой время ожидания ввода. Если он не передается, то внутри утилиты он принимается равным произвольно- му большому числу, скажем 1Е20 секунд. Если же он установлен, и время ожидания истекло раньше, чем пользователь произвел выбор, то необяза- тельный третий параметр возвратит строку "". Если пользователь введет "?", то функция $$МС выводит на экран текст подсказки, связанный с запросом. /Текст подсказки, как мы уже говорили, выводится в последних 5-ти строках экрана./ Если строк подсказки больше, чем 5-ть, то они выводятся порциями по 5-ть, каждая последующая после нажатия клавиши . После того, как выведена последняя порция текста, нажатие любой клавиши очищает выведенный текст подсказки. Обратите внимание также на способ выделения варианта предлагаемо- го для выбора, он одновременно и выделен повышенной яркостью, и заклю- чен в квадратные скобки - ([...]). Это сделано для того, чтобы можно было использовать терминалы, которые не способны выделить часть текста повышенной яркостью. В примере 12.21 демонстрируется использование функции $$МС с дан- ными, передаваемыми в нее из массива ^Opts(1). Таблица 12.12 Клавиши, завершающие ввод ------------------------------------------------------------------- Возвращаемая Нажатая Описание строка клавиша ------------------------------------------------------------------- Нормальное завершение, можно переходить к обработке следующего запроса Пользователь отказался от выбора и желает прекратить работу. Может потребоваться дополнительный запрос, типа - "Продолжать работу, или произвести выход из задачи?", а можно выдавать. скажем, подсказку по управляющим клавишам. <КУРСОР ВВЕРХ> После проверки ответа на запрос необходи- мо вернуться к предыдущему запросу. <КУРСОР ВНИЗ> Действия подобны тем, что исполняются при нажатии клавиши , производится проверка ответа и можно переходить к сле- дующему запросу. Время ожидания истекло раньше, чем поль- зователь произвел выбор. ------------------------------------------------------------------- Стр. 348 ┌──────────────────────────────────────────────────────────────┐ │ |Test Quit:''$$Reset^%Tf | │ │ | Write $$Clr^%Tf,!,"ПОЛ пациента: " | │ │o| Set sex=$$MC^%To("^Opts(1)","",.trm) |o│ │ | Goto Escape:trm="Esc",Prev:trm="" | │ │ | . | │ │o| . |o│ │ |----------------------------------------------------------| │ │ |ПОЛ пациента: [Мужской] Женский Неопределен | │ │o| |o│ │ | | │ │ | | │ │o| |o│ │ | | │ │ | | │ │o| |o│ │ | | │ │ | | │ │o| |o│ │ | | │ │ |Введите ПОЛ пациента. Используйте клавиши <ВЛЕВО>/<ВПРАВО>| │ │o| или буквы 'М', 'Ж' или 'Н' для выбора. После выбора |o│ │ | нажмите клавишу " | │ │ | | │ │o|Для продолжения - |o│ └──────────────────────────────────────────────────────────────┘ Прим.12.21 Использование функции $$МС Таблица 12.13 Массив, используемый функцией $$ОРТ --------------------------------------------------------------- Узел массива Значение --------------------------------------------------------------- ^Opt("Main") "Aardvark Terminals!Financial System Options" ^Opt("Main",1) "1) Счета " ^Opt("Main",1,1) "Это меню включает в себя режимы:" ^Opt("Main",1,2) " 1) Выписка счета 2) Печать счетов 3) Печать" ^Opt("Main",1,3) " месячного отчета" ^Opt("Main",2) "2) Полученные" ^Opt("Main",2,1) "Счета полученные к оплате" ^Opt("Main",3) "3) Оплаченные" ^Opt("Main",3,4) "Оплаченные счета" ^Opt("Main",4) "4) Движение средств" ^Opt("Main",4,1) "Доступ к режимам меню 'Движения средств'" ^Opt("Main",9) "0) Выход" ^Opt("Main",9,1) "Завершение работы с компьютером" --------------------------------------------------------------- В примере 12.21 показан также примерный вид экрана с выведенным на него запросом и первой частью текста подсказки. Пользователь может выбирать режим из списка либо клавишами управления курсором (<КУРСОР ВЛЕВО> и <КУРСОР ВПРАВО>), либо введя первую букву (как большую, так и малую) из наименования режима. Подобная функция может быть доработана и для обработки большого списка режимов, располагаемого на экране вертикально, а не горизон- тально. Такого рода утилиты часто используются для управления режимами прикладных программных комплексов. Мы только внесем некоторые измене- ния в подходе к управлению подобными списками. Стр. 349 При построении вертикальными списками можно к каждому режиму дать ко- роткое (от 1-й до 4-х строк) описание, это описание будет выводиться на экран в нижней его области (там, где обычно выводятся подсказки). Также, как и в предыдущем примере, список режимов для выбора передает- ся в утилиту через массив. При головном индексе ссылки содержится за- головок экрана и необязательный подзаголовок, в следующих уровнях мас- сива наименования режимов меню и ниже, свободным текстом - пояснения к этим режимам. Узлы могут записываться под произвольными индексами, но следует учитывать, что порядок упорядочивания индексов определяет пос- ледовательность вывода списка вариантов на экране. Таблица 12.13 де- монстрирует пример массива, который содержит список вариантов и со- путствующий им текст, для работы функции $$Орt. Заголовок экрана - строка свободного текста, выводимая в середине первой строки, все прочие строки подзаголовка также центруются при вы- воде на экран и выводятся ниже. Строка заголовка и строки подзаголовка отделяются друг от друга восклицательным знаком (!). Также, как и при использовании функции $$МС пользователь может произвести выбор, либо используя клавиши управления курсором (<КУРСОР ВВЕРХ> или <КУРСОР ВНИЗ>), либо введя первую букву из наименования ре- жима. Если больше чем один режим начинается с введенной буквы, то ука- затель устанавливается на первый из них, а если он уже находился на таком режиме, то передвигается на один дальше. Когда пользователь работает со списком, то вдоль этого списка на экране двигается строка '==>' указывая на выбираемый в данный момент вариант. Кроме того, в нижней части экрана отображается текст, связан- ный с текущим режимом. Функция $$Opt вызывается следующим образом: Set opt=$$Opt^%To("^Opt(""Main"")",3,.term,600) Первый параметр - ссылка на узел массива, содержащий список вари- антов для выбора. (Этот параметр является обязательным.) Второй пара- метр указывает на номер варианта, предлагаемый для выбора по умолчанию при вызове утилиты /он не является обязательным, и если он не указыва- ется, то предлагается для выбора по умолчанию первый вариант из спис- ка./ И, как вы заметили, этот номер не коррелируется с индексом под которым записан этот вариант. Третий параметр передается ссылкой и возвращает код процесса завершения выбора варианта из списка. (то есть, анализируя его можно определить, какую клавишу нажал пользова- тель - , и т.д.). Определение того, чем был завершен вы- бор очень важно для экранно-ориентированного интерфейса прикладных за- дач. Код клавиши, по нажатию которой завершилось чтение, возвращается заключенным в угловые скобки ("<" и ">" ). Список таких клавиш, и со- ответствующих им кодов приведен в таблице 12.12 (За исключением того, что и не возвращаются, так как клавиши <КУРСОР ВВЕРХ> и <КУРСОР ВНИЗ> используются функцией $$Opt, но возвращаются строки и соответствующие клавишам <КУРСОР ВЛЕВО> и <КУРСОР ВПРАВО>. Чет- вертый параметр представляет собой время ожидания ввода. Если он не передается, то внутри утилиты он принимается равным произвольному большому числу, скажем 1Е20 секунд. Если же он установлен, и время ожидания истекло раньше, чем пользователь произвел выбор, то необяза- тельный третий параметр возвратит строку "". Функция $$Opt возвращает индекс, соответствующий в массиве выб- ранному режиму. (Например '4' - для "Движение средств"). Пример 12.22 демонстрирует использование функции $$Oрt с данными представленными в таблице 12.13 Стр. 350 ┌──────────────────────────────────────────────────────────────┐ │ | Aardvark Terminals | │ │ | Financial System Options | │ │o| |o│ │ | ==> 1) Счета | │ │ | 2) Полученные | │ │o| 3) Оплаченные |o│ │ | 4) Движение средств | │ │ | 0) Выход | │ │o| |o│ │ | | │ │ | | │ │o| |o│ │ | | │ │ | | │ │o| |o│ │ | | │ │ |Это меню включает в себя режимы: | │ │o| 1) Выписка счета 2) Печать счетов 3) Печать |o│ │ | месячного отчета" | │ └──────────────────────────────────────────────────────────────┘ Прим. 12.22 Использование функции $$Opt Внешние функции ($$MC и $$Opt) демонстрируют два простых метода, которые могут быть использованы для предоставления пользователю воз- можности выбора одного варианта из списка. Эти функции, вместе с теми, что описаны в разделе 12.5.1 и функцией для чтения кода нажатой клави- ши описанной в разделе 12.5.2 могут быть использованы при построении диалогового экранно-ориентированного интерфейса прикладных задач. Также как и для всех остальных примеров этой главы, полный текст внешних функций $$MC и $$Opt вместе с описанием их использования Вы найдете в приложении I. 12.5.4 Редактирование строки, вводимой с клавиатуры. Оператор READ в MUMPS обеспечивает возможность ввода с устройств (в том числе и с видеотерминалов) строк символов для их дальнейшего использования в прикладных программах. Несмотря на различные возмож- ности, которые допускает этот оператор, с точки зрения конечного поль- зователя он имеет очень серьезный недостаток - не позволяет редактиро- вать уже существующую строку. Для того, чтобы исправить введенную строку, если не применять специальных приемов, пользователь должен пе- ре-ввести ее полностью заново. В этом разделе мы обсудим использование процедуры, которая позволяет вводить строку с клавиатуры, а также ре- дактировать ее, с использованием специальных клавиш (клавиши управле- ния курсором, , и так далее), которые имеются на большинстве видео терминалов. Внешняя функция $$Read эмулирует работу оператора READ, но пре- доставляет дополнительный сервис, в том числе и редактирование ранее введенного ответа. Полный текст этой функции Вы найдете в приложении I. Функция $$Read вызывается следующим образом: Set ans=$$Read^%Tf(oldans,width,.term,timeout) Стр. 351 Все параметры, используемые при вызове функции - необязательные, они имеют следующее значение: oldans Прежде введенный ответ. Функция $$Read отображает его на эк- ране, для того, чтобы пользователь мог отредактировать его. width Этот параметр определяет максимальную ширину ответа. Если он не указан, то принимается равным длине остатка строки. (Разнице между $$Wid и $X) .term Если этот параметр определен, (Обратите внимание, он передает- ся ссылкой), то в нем возвращается строка, определяющая клавишу, по нажатию которой завершилось чтение. Список та- ких клавиш и соответствующих им строк смотри в таблице 12.12. timeout Этот параметр определяет промежуток времени (в секундах), в течении которого функция ожидает завершения ввода. Если этот параметр не определен, то он принимается равным про- извольному большому числу, скажем, 1Е25 секунд. Если время ожидания истекает до завершения ввода, то третий параметр (term) возвращает "". Функция $$Read отображает в поле шириной width, выделенном инвер- сией видеоатрибутов значение oldans (если оно не передается, или равно пустой строке, то просто "зачищает" поле указанной ширины). Если ol- dans существует, и не равно пустой строке, курсор устанавливается под его последним символом. Для перемещения по строке oldans пользователь может использовать клавиши <КУРСОР ВПРАВО> и <КУРСОР ВЛЕВО>. Вводимые символы вставляются слева от курсора, причем остаток строки сдвигается вправо. Клавиша удаляет символ в позиции курсора, а - слева от позиции курсора. Попытка ввести строку, длиннее, чем width игнорируется, при этом выдается звуковой сигнал. При нажатии +U очищается все поле, но при этом действует следующее соглаше- ние. Если значение в поле совпадает с первоначальным значением oldans, - очищается все поле. Если же нет, то текущее значение заменяется на первоначальное. Работа функции $$Read завершается при нажатии: , , <КУРСОР ВВЕРХ>, <КУРСОР ВНИЗ> и других функциональных клавиш, а также при истечении установленного времени ожидания. Код завершения возвра- щается через параметр term. После того, как функция завершит свою ра- боту, введенное значение будет отображено в поле width повышенной яр- костью, а не инверсией. В примере 12.23 демонстрируется использование функции $$Read для ввода ответов на запросы с клавиатуры. Причем здесь используется эк- ранно-ориентированный интерфейс, а также приняты меры для снижения ко- личества проверок. В следующем разделе мы разберем приемы используемые для облегчения процесса обработки запросов, в том числе: - передвиже- ние между запросами, управление проверками ответов и выводом на экран текста подсказок. Стр. 352 ┌──────────────────────────────────────────────────────────────┐ │ |GetClient ;JML-NYSCVM | │ │ | ;ввод паспортных данных пациента | │ │o| Kill Quit:'$$Reset^%Tf |o│ │ | Write ?15,"Ввод/редактирование данных пациента",! | │ │ | Set ID="" | │ │o|GetID Write !,"Идентификационный номер: " |o│ │ | Set ID=$$Read^%Tf(ID,10,.trm,600) | │ │ | Quit:trm=""!(ID="") | │ │o| Goto Fk:$E(trm,2)="F",Esc:trm="",TO:trm=""|o│ │ | Set x=$G(^Client(ID)) | │ │ | For i=1:1:6 Set @$P("name,street,city,state,zip,pho| │ │o| ---ne",",",i)=$P(x,"\",i) |o│ │ |Name Write !,"Фамилия и имя пациента: " | │ │ | Set new=$$Read^%Tf(name,30,.trm,600) | │ │o| Goto Fk:$E(trm,2)="F",Esc:trm="",TO:trm=""|o│ │ | If new="" Write *7," Нельзя пропускать !" Goto Name| │ │ | Goto Street:new=name Set name=new | │ │o| If name'?1B.R1","1B.R Write *7,"Ведите ответ в форм|o│ │ | ---ате - ФАМИЛИЯ,ИМЯ" Goto Name | │ │ |Street Write !,"Улица: " | │ │o| . |o│ │ | . | │ └──────────────────────────────────────────────────────────────┘ Прим.12.23 Фрагмент программы с использованием функции /*1/ $$Read, эмулирующей оператор READ 12.5.5. Драйвер запросов В предыдущих разделах мы обсуждали вопросы создания и использова- ния утилит нижнего уровня предназначенных для управления экраном и вводом с клавиатуры. Они могут дать нам необходимый инструмент для поддержания экранно-ориентированного пользовательского интерфейса прикладных задач. Однако, как Вы могли заметить из примера 12.23 их явно недостаточно для организации серии простых вопросов-ответов. В этом разделе мы будем разбирать приемы создания инструментальных средств более высокого уровня для управления диалога с пользователем. Перед тем, как начать обсуждать специальные вопросы, давайте поп- робуем перечислить основные факторы, которые должны быть приняты во внимание при организации диалога между компьютерной программой и ко- нечным пользователем. Приведенный ниже перечень, в зависимости от тре- бований пользователя, может быть как расширен, так и показаться немно- го избыточным: ■ Стремитесь к тому, чтобы экран был простым. Чем меньше условнос- тей, тем он легче для понимания. Используйте видеоатрибуты для повышения наглядности, но, в тоже время не увлекайтесь ими, так как можете внести больше затруднений, чем улучшить понимание. ------------------------------------------------------------------- *1 В примере 12.23, в строке Name+5, вставлена проверка на русские буквы, в оригинале - If name'?1A.E1","1A.E Стр. 353 ■ Организуйте запросы в логические группы, и не размещайте вперемеш- ку запросы относящиеся к различным группам. Для ускорения работы и облегчения ввода данных лучше разделить запросы на несколько экранов, чем пытаться всю информацию разместить на одном экране. ■ Логические группы запросов всегда должны иметь конкретный заголо- вок, который ясно указывает пользователю с какого рода информаци- ей он будет работать, и, желательно, в экране необходимо помещать и поясняющий текст. ■ Текст запросов должен быть ясным и кратким. Избегайте лишней мно- гословности и при организации списков вариантов для выбора. До- полнительную информацию пользователь должен получать из текста подсказок. ■ Всегда обеспечивайте возможность получения дополнительной информа- ции на каждый запрос. Для вызова подсказок обычно используется символ '?'. ■ Для запросов, ответы на которые могут быть только фиксированными вариантами из списка, необходимо, при необходимости, организовы- вать вывод на экран всего списка. Желательно при этом избегать обращений к оглавлениям и индексным ссылкам, они довольно часто теряются или не соответствуют истине. Если список небольшой, то его просто можно вывести на экран целиком, в противном случае ор- ганизовать выборку по ключевым словам (фрагментам ключевых слов). ■ Выводите на экран существующий, или предлагаемый по умолчанию от- вет, и, обеспечивайте возможность быстро выбрать такой ответ. (Например, по нажатию клавиши ) ■ Обеспечьте возможность перемещаться вперед и назад внутри логичес- кой группы запросов, поскольку порою необходимо вернуться назад, к ранее введенным значениям и отредактировать их. Кроме того, же- лательно иметь возможность вернуться к первому вопросу запросу в группе, или перепрыгнуть на последний (при этом обязательные зап- росы не должны пропускаться). Если в группе много запросов, то необходимо обеспечить возможность переходить к конкретному запро- су, особенно, если присутствует много запросов с необязательным вводом, или с ответами, предлагаемыми по умолчанию. ■ Обеспечьте возможность гибкого ввода данных, обеспечивая перевод введенных значений внутри программы в общий формат. Так, напри- мер, в запросе "ПОЛ :", можно кодировать данные следующим обра- зом: 1 - Мужской, 2 - Женский, 3 - Неопределенный. А вводить так: '2', 'Ж', 'ж', или 'Женский', и преобразовывать внутри программы все эти значения в код - '2'. ■ Уменьшайте, где возможно чувствительность программ к размеру букв в ответе. (Так, например, надо разрешать ввод 'Yes', 'YES', 'Y' или 'yes' в ответ на запрос 'Yes/No'.) ■ Предлагайте пользователю ответы на запросы по умолчанию. Так, нап- ример, в серии запросов при сборе паспортных данных пациентов Вы запрашиваете : ФИО, Улицу, Город, Область, Почтовый индекс и Те- лефоны. После того, как пользователь введет город и область, то можно подсказать примерный индекс, и если пользователь соглашает- ся, сразу перейти к вводу телефонов. Стр. 354 ■ Обеспечьте для пользователя возможность аварийно прервать работу любой программы. Лучше всего для этого подходит клавиша , так как ее наименование в этом случае совпадает с выполняемым действием. /"Escape" - по английски - "Бежать, избавляться, спа- саться, и т.п."/ ■ Будьте последовательны. Вне зависимости от того, какой набор до- полнительных возможностей Вы предоставляете пользователю при зап- росах, эти возможности должны поддерживаться в КАЖДОМ запросе. Если, скажем, в одном случае символ '?' вызывает подсказку, а в других случаях вводится как данное, то пользователи просто прек- ратят запрашивать подсказки во всех запросах. Даже если на запрос не предусмотрена подсказка, должно выводиться сообщение об этом. Будьте последовательны ! После краткого обзора факторов, которые должны присутствовать в хорошо разработанном интерфейсе диалога с пользователем, давайте вер- немся к проблеме создания ДРАЙВЕРА ЗАПРОСОВ и попробуем рассмотреть общие концепции, которые будут положены в основу его работы. ДРАЙВЕР ЗАПРОСОВ это утилита общего назначения, обрабатывающая заранее создан- ный набор запросов. При этом производится управление: -выводимой на экран информацией, -вводом и редактированием ответов на запросы -проверкой ответов -перемещением между запросами -выводом на экран подсказок. Текст этой утилиты приведен в приложении I. При работе драйвера запросов предполагается, что описания запро- сов хранятся в глобальном массиве, организованном аналогично тому, что показан на Рис.12.6 и относятся к одному логическому блоку информации (frame). ^Qst | | $ FRAME - Код экрана | (Qlist;Titles) | $ Qmne - Код запроса | (Qtxt;Qx;Qy;Ax;Ay;Al;Req;Ver) | $ Нn - строки подсказки ("Первая строка подсказки") Рис.12.6 Структура массива используемого в драйвере запросов Где : FRAME Код экрана, содержащего логический блок запросов. Может быть любым корректным индексом, лучше всего, если при этом он будет мнемонически отражать вид информации, вводимой в экране. Например, код экран для ввода регистрационных дан- ных пациента - "Registration". Стр. 355 Qmne Мнемоническое имя запроса. Qmne должно отвечать двум усло- виям: 1) является индексом массива, под которым записано описание конкретного запроса, 2) используется драйвером запросов, как имя локальной переменной, в которую помещает- ся ответ на запрос. Hn Под индексом Hn записываются строки подсказки, связанной с конкретным запросом. Hn может принимать любые значения, с учетом того, что он определяет порядок вывода на экран строк подсказки. Qlist Содержит список мнемонических имен запросов (Qmne), разде- ленный запятыми, расположенных в порядке обхода запросов на экране. Titles Одна, или несколько строк заголовка экрана. Строки между собой разделяются знаком -'!'. При выводе на экран строки заголовка центруются. Qtxt Текст конкретного запроса Qx Значение $Х для вывода на экран текста запроса (Qtxt) Qy Значение $Y для вывода на экран текста запроса (Qtxt) Ax Значение $X для начала на экране поля ответа Ay Значение $Y для начала на экране поля ответа Al Длина (в символах) поля ответа, если Al=0, то драйвер запросов не исполняет чтение ответа. Req Указание на то, является ли запрос обязательным. Если в этом поле содержится ИСТИНА (не 0), то запрос не может быть пропущен, если ЛОЖЬ (0 или строка, которая после числовой интерпретации = 0) то запрос может быть пропущен. (В поле ответа на запрос можно нажать , вместо ответа) Ver Ссылка на процедуру (LABEL^ROUTINE), в которой производится проверка введенного ответа. Если это поле опускается, про- верка ответа не производится, это означает, что в поле мо- жет вводиться свободный текст. --------------------------------------------------------------------- *1 Хочется отметить, что в предлагаемом автором книги драйвере запро- сов имеется по меньшей мере два серьезных недостатка. Первый состоит в том, что при описании экрана Вы не сможете видеть взаимное расположе- ние запросов и текста на экране, и потому, для отладки потребуется много прогонов. А во-вторых, достаточно неудобно отсчитывать длину текста запроса для того, чтобы определить место для поля ответа, и еще, с появлением сетевых приложений очень часто требуется использо- вать еще и дополнительные возможности - выделение цветом, или более длинное поле ответа /в несколько строк/ и т.п.. Впрочем, здесь мы вы- ходим за рамки предмета рассмотрения и касаемся более общей проблемы, связанной с разработкой инструментальных программных средств. Как практический программист я могу предложить рассмотреть разработанный мною Комплекс Инструментальной Поддержки Программирования (КИПП) в среде MUMPS - 'Прототип'. Описание и сам комплекс могут быть предложе- ны всем желающим. /Примечание переводчика - Топалова И.К. СТ "Сингл" Петропавловск-Камчатский тел.2-87-61/ Стр. 356 В таблице 12.14 приведен пример глобального массива, содержащий описание запросов в соответствии с описанной выше схемой. Для краткости мы опустили все узлы, содержащие текст подсказки по запросам, за исключением запроса Name. После того, как создан глобаль- ный массив, содержащий описания запросов, драйвер запросов может быть вызван следующим образом: Do Drv^%Qd("^CQst(""REG"")",.term,exclude) Первый параметр [^CQst("REG")] представляет собой ссылку на мас- сив описания запросов, и является обязательным. Второй и третий пара- метр не обязательно указывать при вызове драйвера запросов. Второй па- раметр указывает на использованный пользователь способ для завершения работы с драйвером запросов. Так, например, если term= - нормаль- ное завершение, - пользователь хочет вернуться назад, к предыду- щему экрану, и т.д.. Параметр Exclude определяет символ, который будет запрещаться при вводе ответов. В параметре Exclude указывается символ, который используется как разделитель полей в базе данных. Если его не запрещать при вводе, то случайный ввод этого символа может привести к ошибкам в базе данных. Пример экрана, созданного драйвером запросов по массиву, описанному в таблице 12.14 показан в примере 12.24. Таблица 12.14 Пример массива для работы с драйвером запросов -------------------------------------------------------------- ^CQst("CREG") "ID,Name,Str,City,State,ZIP,Phone,Save; Мед. учреждение АБВ!Регистрация пациента" ^CQst("CREG","ID") "Идентификатор:;0;3;12;3;6;1;ID^CREG" ^CQst("CREG","Name") "ФИО:;30;3;36;3;25;1;Name^CREG" ^CQst("CREG","Name",1) "ФИО пациента, например - ИВАНОВ И.И." ^CQst("CREG","Str") "Улица:;4;4;12;4;30;1" ^CQst("CREG","City") "Город:;45;4;51;12;1" ^CQst("CREG","State") "Штат:;5;5;12;5;2;1" ^CQst("CREG","ZIP") "Индекс:;30;5;36;5;5;1" ^CQst("CREG","Phone") "Телефон(ы):;2;7;12;7;40;0" ^CQst("CREG","Save") "Сохранять данные:;0;9;15;9;0;1;Save^CREG" -------------------------------------------------------------- Драйвер запросов очищает экран, выводит строки заголовка, затем тексты запросов и ответы, предлагаемые по умолчанию. Текст запроса для каждого Qmne выводится согласно его координатам (Qx, Qy). Если в раз- деле существует переменная с именем, которое совпадает с Qmne, то ее значение предлагается в качестве ответа. Если же такой переменной нет, то она создается со значением, равным пустой строке. Поле для ответа начинается с координат Ax, Ay, и выделяется повышенной яркостью текста ответа. Для упрощения работы и рассмотрения примеров, драйвер запросов резервирует нижние 5-ть строк экрана под вывод текстов подсказок и со- общений об ошибках. Заголовок, запросы и ответы располагаются в стро- ках с 0 по 18-ю. В драйвере запросов для ввода ответов используется функция эмули- рующая оператор READ, которая описана ранее. После того, как пользова- тель завершает ввод ответа, драйвер исполняет несколько проверок вве- денных данных. Стр. 357 ┌──────────────────────────────────────────────────────────────┐ │ | Мед. учреждение АБВ | │ │ | Регистрация пациента | │ │o| |o│ │ |Идентификатор: ________________ ФИО: | │ │ | Улица: | │ │o| Город: Штат: Индекс: |o│ │ | | │ │ | Телефон(ы): | │ │o| |o│ │ | | │ │ |Сохранять данные: | │ │o| |o│ │ | | │ │ | | │ └──────────────────────────────────────────────────────────────┘ Прим.12.24 Экран после вызова драйвера запросов ■ Если в качестве ответа введена пустая строка, а ответ является обязательным (Req=TRUE), драйвер выводит соответствующее сообще- ние внизу экрана, и предлагает повторить ввод ответа. ■ Если вновь введенный ответ совпадает с прежним /предлагаемым по умолчанию/, драйвер переходит к обработке следующего запроса /из Qlist/ ■ Если ответ содержит знак вопроса - '?', то драйвер выводит в ниж- ней части экрана текст подсказки, связанной с данным запросом. После того, как пользователь завершит чтение текста подсказки и нажмет любую клавишу, драйвер очищает текст подсказки и повторяет запрос ответа. ■ Если ответ содержит символ, указанный в параметре Exclude, то вни- зу экрана высвечивается соответствующее сообщение и запрос повто- ряется. ■ Если в запросе указана проверка (Ver не равно пустой строке), драйвер передает управление на указанную в этом поле процедуру, для проверки ответа на запрос. Указанная процедура производит проверку ответа, и, если ответ некорректен, то процедура возвра- щает код обнаруженной ошибки. Драйвер выводит внизу экрана сооб- щение об ошибке, и предлагает повторить ответ. ■ Если ошибки не обнаружено, драйвер присваивает переменной с име- нем, совпадающим с Qmne значение, введенное пользователем в ответ на запрос. ■ Драйвер запросов переходит к обработке следующего (или предыдуще- го, если запрос завершен по клавише <КУРСОР ВВЕРХ>) запроса из списка Qlist. Если в списке больше нет запросов, драйвер заверша- ет работу. Все переменные, созданные драйвером (из списка Qlist) доступны вызвавшей драйвер программе. Драйвер управляет отображением запросов и редактированием отве- тов, а также перемещением внутри блока запросов. Программисту необхо- димо только обеспечить проверку ответов, оставив все остальные аспек- ты, в том числе обработку "пустых" ответов, или вывод подсказок или сообщений об ошибках драйверу запросов. Стр. 358 Как уже упоминалось, проверка ответа на запрос производится от- дельной процедурой, указываемой в поле Ver. Причем проверку указывать необязательно, если поле Ver равно "пустой" строке, проверка не произ- водится, и в ответе на запрос можно вводить "свободный" текст. Проце- дура проверки выполняет две важных функции: (1) производит проверку ответа на конкретный запрос, и (2) возвращает драйверу сообщение об обнаруженной ошибке. Драйвер вызывает процедуры проверки следующим об- разом: Do @(Ver(.ans,.err,.redisplay,.goto)) Параметр Описание ans В этом параметре передается ответ на запрос, введенный с клавиатуры. Если ответ преобразуется в процедуре про- верки, и в то же время, не устанавливается флаг ошибки, (см. описание параметра err), то модифицированное зна- чение ответа сохраняется в переменной с именем Qmne, когда управление передается драйверу. Модифицированное значение ответа отображается на экране в зависимости от параметра redisplay (см. соответствующее описание) Про- цедура проверки имеет доступ ко всем переменным, соз- данным драйвером (из списка Qlist). Если какая-нибудь из этих переменных изменяется внутри процедуры провер- ки, то ее значение на экране можно пере-отобразить, уп- равляя этим процессом через параметр redisplay. err Через параметр err информация об ошибке, обнаруженной в процедуре проверки возвращается драйверу. Перед переда- чей управления в процедуру проверки этот параметр уста- навливается равным пустой строке. Если при возврате уп- равления назад в драйвер запросов этот параметр не ра- вен пустой строке, то его значение выводится в нижней части экрана. При этом переменная связанная с запросом не модифицируется, и после вывода сообщения запрос пов- торяется. redisplay Если процедура проверки изменяет значения одной и более переменных, связанных с запросами (из списка Qlist), то через этот параметр возвращается список имен перемен- ных, чьи значения изменены. После возврата управления в драйвер запросов, значения этих переменных на экране будут пере-обновлены. Мнемонические имена (Qmne) в этом списке разделяются запятыми. goto Через этот параметр можно изменить порядок "обхода" запросов на экране. Если его значение не изменяется в процедуре проверки, то производится обработка следующе- го (из Qlist) запроса. Если же необходимо изменить по- рядок обхода, то в параметре goto указывается имя зап- роса (Qmne), к которому необходимо произвести переход. Стр. 359 В примере 12.25 демонстрируется использование в прикладной прог- рамме драйвера запросов, на основе данных представленных в таблице 12.14. ┌──────────────────────────────────────────────────────────────┐ │ |Client ;JML-NYSCVM | │ │ | ;Модуль регистрации пациента | │ │o| Do Drv^%Qd("^CQst(""CREG"")",.term,"\") |o│ │ | If term="" Write *7,"Ошибка в описании терминала" | │ │ | Quit | │ │o| ; |o│ │ | ;Процедуры проверки | │ │ | ; | │ │o|ID(ans,err,redisp,goto) ;Идентификатор пациента |o│ │ | If ans'?1.5N Set err="1-5 цифр" Quit | │ │ | New i,y Set redisp="Name,Str,City,State,ZIP,Phone"| │ │o| Set y=$G(^Client(ans)) |o│ │ | For i=1:1:6 Set @$P(redisp,",",i)=$P(y,"\",i) | │ │ | Set:y'="" goto="Save" | │ │o| Quit |o│ │ |Name(ans,err,redisp,goto) ;ФИО пациента | │ │ | Quit:ans?1.B1" "1B1"."1B1"." | │ │o| Set err="ФАМИЛИЯ Имя.Отчество. Например - ИВАНОВ|o│ │ | ---> И.И. Проверьте ответ" | │ │ | Quit | │ │o|Save(ans,err,redisp,goto) ;Готовность к сохранению инф. |o│ │ | New yn,ans,trm Set yn="Да;Нет" | │ │ | Set ans=$$MC^%To("yn",1) | │ │o| If trm="" Set goto="Phone" Quit |o│ │ | If ans=2 Set goto="Name" Quit | │ │ | New i,x Set x="" | │ │o| For i=1:1:6 Set x=x_"\"_@$P("Name,Str,City,State,Z|o│ │ | --->IP,Phone",",",i) | │ │ | Set ^Client(ID)=$e(x,2,999) | │ │o| Quit |o│ └──────────────────────────────────────────────────────────────┘ Прим. 12.25 Использование драйвера запросов /*1 В этом примере содержится все необходимое для того, чтобы обеспе- чить вывод на экран заголовка и запросов, обработать вводимые ответы, управлять перемещением между запросами, читать и записывать информа- цию. ------------------------------------------------------------------- *1 К этому примеру есть несколько замечаний: - В строке примера Name+1 в связи с русификацией примеров изменена проверка по шаблону, в оригинале она выглядит так - 'Qu- it:ans?1A.E1","1A.E', так как американцы не вводят Отчество. Шаблон 'B' в большинстве русифицированных MUMPS систем соответствует боль- шим русским буквам. - В современных MUMPS системах функция $PIECE работает и в левой час- ти оператора SET, поэтому строки 'Save+6' и 'Save+7' можно перепи- сать следующим образом: For i=1:1:6 Set $P(x,"\",i)=@$P("Name,Str,City,State,ZIP,Phone",",",i) Set ^Client(ID)=x Стр. 360 Три запроса (ID, Name и Save) требуют проверки, остальные вводятся свободным текстом. Процедура ID используется для проверки ответа на запрос "Иденти- фикатор пациента". Если ответ правильный, (то есть номер из 1-5 цифр), то одновременно производится чтение существующих данных и указание на то, чтобы пере-обновить значения всех остальных данных на экране. И, если данные существуют, производится переход к запросу "Сохранить дан- ные" /то есть Set goto="Save"/, исходя из предположения, что данные существующего в базе данных пациента нет необходимости корректировать. Процедура Name предназначена для проверки запроса "ФИО пациента". Если ответ на запрос некорректен, например, пропущена точка при имени или отчестве, то процедура возвращает сообщение об этом. Процедура "Save" использует внешнюю функцию $$МС для обработки ответа на запрос "Да/Нет". Так как эта внешняя функция сама управляет вводом/выводом (поле Al=0), то после ее вызова необходимо явно обрабо- тать ее завершение по клавише <КУРСОР ВВЕРХ> для изменения направления обхода запросов. Если пользователь отвечает "Нет", то производится возвращение к запросу "ФИО пациента", а если "Да", то ответы на все запросы этого экрана записываются в базу данных. Как уже говорилось в начале этого раздела, мы только слегка расс- мотрели работу драйвера запросов. Более расширенные пояснения к его работе Вы сможете найти в приложениях, где приведен его текст. Драйвер может быть впоследствии дополнен процедурами автоматического преобра- зования ответов, списками возможных ответов, и многими другими возмож- ностями. 12.5.6 Заключение к обзору управлением дополнительными возможностями ввода/вывода Переносимость утилит, описанных в этом разделе, несколько ограни- чена, они прежде всего предназначены для демонстрации применения прие- мов программирования более высокого уровня в прикладных программах, и могут послужить введением в проблему разработки инструментальных средств поддержки прикладного программирования. Используя описанные приемы, Вы будете способны самостоятельно разрабатывать прикладные программные средства, которые не будут привязаны к конкретным типам терминалов или MUMPS системам. Прикладные программные средства выполненные подобным образом бу- дет иметь единый пользовательский интерфейс, а использование инстру- ментальных программных средств, подобных драйверу запросов, сильно уп- ростит написание прикладных программ, оставив программисту необходи- мость написать только процедуры проверки. Имея ввиду все вышеописанные факторы, мы уже вряд-ли сможем по- нять программистов, которые пишут диалоговые прикладные программы в прежнем строчно-ориентированном стиле. Широкое распространение персо- нальных компьютеров и программных средств с великолепно разработанным и высокоэффективным пользовательским интерфейсом, позволяет пользова- телю предъявлять повышенные требования и к интерфейсу прикладных за- дач, написанных в MUMPS. Не разочаровывайте пользователей !