Н. Вирт - Программирование на языке Модула-2 (1160777), страница 19
Текст из файла (страница 19)
Расширение области видимости из внутреннего модулянаружу достигается экспортом, снаружи вовнутрь - импортом. Правила полностью симметричны.Рассмотрим теперь следующую структуру модулей N1, N2 и N3, вложенных в модуль М:MODULE M;VAR a: CARDINAL;MODULE N1;EXPORT b;95VAR b: CARDINAL;(* здесь видим только b *)END N1;MODULE N2;EXPORT c;VAR c: CARDINAL;(* здесь видим только с *)END N2;MODULE N3;IMPORT b,с; (* здесь видимы b и с *)END N3;(* здесь видимы а,Ь и с *)END MN3 импортирует идентификаторы b и с, описанные соответственно в модулях N1 и N2.
Этиидентификаторы экспортированы в окружение М из локальных модулей. Если заменить модуль М"внешней средой" (в которой нельзя описывать локальные объекты), то модули N1, N2 и N3превратятся в глобальные модули, обсуждавшиеся в предшествующем разделе (*небольшоеразличие имеется,поскольку экспорт из глобальных модулей может быть толькоквалифицируемым. Кроме того, следует иметь в виду, что у глобальных модулей существуютраздел описаний и раздел реализации. - Прим. перев.*).
Фактически правила видимости длялокальных и глобальных модулей совпадают. Глобальный модуль, т.е. единицу компиляции,можно назвать локальным во внешней среде.Предположим теперь, что переменная с, экспортируемая из N2, тоже называется b. Этопривело бы к коллизии имен, потому что идентификатор Ь уже известен в М (Ь экспортирован изN1).
Эту проблему можно обойти, применяя квалифицируемый экспорт точно так же, как дляглобальных модулей. Теперь объекты с именем Ь, принадлежащие N1 и N2, могут именоватьсякак N1.b и N2.b соответственно.Квалифицируемый экспорт обязателен для глобальных модулей, потому что разработчикглобального модуля не знает, существует ли выбранный им идентификатор в окружающейпрограммной среде. Для локальных модулей квалифицируемый экспорт - скорее исключение, чемправило, поскольку программист знает их окружение и, следовательно, может выбратьидентификаторы так, чтобы избежать конфликта имен.Последнее замечание прямо касается различий модулей и процедур.
И те и другиеобразуют некоторую область видимости (вложенную в их окружение). Но если единственнойФункцией модуля является создание новой области видимости, то процедура, кроме того, образуетновую область существования ее локальных объектов: они исчезают при завершении процедуры.В случае модуля его локальные объекты возникают в' момент создания окружающей его областисуществования и продолжают существовать, пока эта область не исчезнет. Однако случаймодулей, локальных в процедуре, на практике встречается редко (если только не рассматриватьвсю программу как процедуру). Синтаксис локального модуля подобен синтаксису программногомодуля:96$ОписаниеМодуля =$"MODULE" Идентификатор [Приоритет] ";"${Импорт} [Экспорт] Блок Идентификатор.$Приоритет = "[" КонстВыражение "]".(Назначение параметра "приоритет" будет обсуждаться в разделе, посвященномпараллельному исполнению.)Следующий далее пример программы демонстрирует применение локального модуля.Цель программы - читать текст, являющийся описанием синтаксиса с помощью РБНФ, проверятьправильность записи правил РБНФ и генерировать таблицу перекрестных ссылок для вводимоготекста.
Должны быть напечатаны две таблицы: в одной будут содержаться терминалы, т.е. строки,заключенные в кавычки, и идентификаторы, состоящие только из прописных букв, а в другой нетерминалы, т.е. остальные идентификаторы.Приведенная спецификация предполагает разбиение программы, подобное тому, что былоу программы XREF в предшествующем разделе. Мы проведем дальнейшее разбиение задачипросмотра текста на чтение отдельных символов РБНФ, т.е. лексический анализ текста, и проверкуправильности правил РБНФ, т.е. синтаксический анализ. Программа тогда будет состоять изглавного модуля, называемого РБНФ, который импортирует РБНФСканер (производящийлексический анализ) и модуль РаботаСТаблицей (запоминающий и печатающий данные). МодульРаботаСТаблицей взят без изменений из предыдущего раздела.
Все три модуля, кроме того,импортируют модуль InOut.Главная программа работает в соответствии с нисходящей стратегией разбора, подобнойстратегии, которая использована в разделе, посвященном рекурсии. Разница в том, что элементамитекста считаются не литеры, а символы РБНФ, получаемые по одному с помощью вызовапроцедуры ВзятьЛексему из модуля РБНФСканер. Кроме самой процедуры ВзятьЛексемуимпортируются ее результаты: переменные лекс, ид, номстр. Переменной ид присваивается строкалитер, обозначающая лексический элемент, если введенный элемент - идентификатор или строкалитер. Отметим, что лекс имеет тип Лексема, который также определен в модуле РБНФСканер.DEFINITION MODULE РБНФСканер;ТУРЕ Лексема = (идент,литерал,лкрск,лквск,лфск,верт,равно,точка,пкрск,пквск,пфск,другая);CONST ИдентДлина = 24;VAR лекc: Лексема; (* следующая лексема *)ид: ARRAY [0..ИдентДлина] OF CHAR;номстр: CARDINAL;PROCEDURE ВзятьЛексему;PROCEDURE ОтметитьОшибку(n: CARDINAL);PROCEDURE ПропускСтроки;END РБНФСканер.97Этот пример еще раз иллюстрирует тот Факт, что знание раздела описанийимпортируемого модуля как необходимо, так и достаточно для написания импортирующегомодуля.MODULE РБНФ;FROM InOut IMPORTDone,EOL,OpenInput,OpenOutput,Read,Write,WriteLn,WriteCard,WriteString,CloseInput,CloseOutput;FROM РБНФСканер IMPORTЛексема,лекc,ид,номстр,ВзятьЛексему,ОтметитьОшибку,ПропускСтроки;FROM РаботаСТаблицей IMPORT ДлинаСлова,Таблица,переполнение,Инициализировать,Записать,Распечатать;(* Коды синтаксических ошибок:2 = ожидается ")", 6 = ожидается идентификатор3 = ожидается "]", 7 = ожидается "="4 = ожидается "}", 8 = ожидается "."5 = ожидается литерал,идентификатор,"(","[" или "{" *) .VAR T0,T1: Таблица;PROCEDURE пропуск(n: CARDINAL);(* пропустить текст до символа, начинающего выражение *)BEGIN ОтметитьОшибку(n);WHILE (лекс<лкрск)ОR(лекc>точка) DO ВзятьЛексему ENDEND пропуск;PROCEDURE СинтВыражение;PROCEDURE СинТерм;PROCEDURE СинтМножитель;98IF лекc = идент THENЗаписать(Т0,ид,номстр); ВзятьЛексемуELSIF лекc = литерал THENЗаписать(Т1,ид,номстр); ВзятьЛексемуELSIF лекc = лкрcк THENВзятьЛексему; СинтВыражение;IF лекc = пкрcк THEN ВзятьЛексемуELSE пропуск(2) ENDELSIF лекc = лквcк THENВзятьЛексему; СинтВыражение;IF лекc = пквcк THEN ВзятьЛексемуELSE пропуск(З) ENDELSIF лекc = лфcк THENВзятьЛексему; СинтВыражение;IF лекc = пфск THEN ВзятьЛексемуELSE пропуск(4) ENDELSE пропуск(5) ENDEND СинтМножитель;BEGIN (*СинТерм*) СинтМножитель;WHILE лекc < верт DO СинтМножитель ENDEND СинТерм;BEGIN (*СинтВыражение*) СинТерм;WHILE леке = верт DO ВзятьЛексему; СинТерм ENDEND СинтВыражение;PROCEDURE СинтПравило;BEGIN (*лекс = идент*)Записать(Т0,ид,-INTEGER(номстр)); ВзятьЛексему;IF лекc = равно THEN ВзятьЛексемуELSE пропуск(7) END;99СинтВыражение;IF лекc # точка THENОтметитьОшибку(8); ПропускСтроки END;ВзятьЛексемуENDСинтПравило;BEGIN (*Главная программа*)OpenInput("РБНФ");IF Done THENOpenOutput("XREF");Инициализировать(T0); Инициализировать(Т1);ВзятьЛексему;WHILE (лекс=идент)&(переполнение=0) DOСинтПравилоEND;IF переполнение > 0 THENWriteLn; WrlteString("Переполнение таблицы");WriteCard(переполнение,6)END;Write(35C); Распечатать(Т0); Распечатать(Т1);СloseInput; CloseOutputENDEND РБНФ.Заслуживает внимания то, что требование раздельной печати терминалов и нетерминаловотражено в Факте описания двух переменных типа Таблица.
Структура программы отражаетструктуру синтаксиса РБНФ. Отсылаем читателя к разделу, в котором определяется РБНФ.Задача сканера - распознавать отдельные лексические элементы, сохранять информацию ономерах строк и распечатывать вводимый текст. Дополнительная сложность возникает в связи стем, что нужно сообщать об обнаруженных ошибках, т.е. расхождении с правилами записисинтаксиса РБНФ. В сканере хранится информация о позиции последнего прочитанного символа,и если выдается сообщение об ошибке, то вставляется строка, отмечающая место ошибки.Имеется в виду, что входная строка должна быть прочитана вся целиком, до того как начнется ееобработка, т.е. потребуется буфер строки.
Все перечисленные операции ориентированы на работусо строками текста и, следовательно, заключены в локальном модуле ОбработкаСтрок.IMPLEMENTATION MODULE РБНФСканер;100FROM InOut IMPORT EOL,Read,Write,WriteLn,WriteCard;VAR ch: CHAR;MODULE ОбработкаСтрок;IMPORT EOL,ch,HOMCTP,Read,Write,WriteLn,WriteCard;EXPORT ВзятьЛитеру,ОтметитьОшибку,ПропускСтроки;CONST ШиринаСтроки =100;VAR сс: CARDINAL: (* индекс текущей литеры *)сc1: CARDINAL; (* число литер в текущей строке *)сс2: CARDINAL; (* счетчик литер в строке ошибки *)строка: ARRAY [0..ШиринаСтроки-1] OF CHAR;PROCEDURE ВзятьСтроку;BEGIN IF cc2 > 0 THENWriteLn; cc2 := 0 (* ошибочная строка *)END;Read(ch);IF ch = 0C THEN (*конец файла*)строка[0] i= 177C; cc1 := 1ELSEномстр := номстр + 1;WriteCard(номстр,5);Write(" "): cc1 := 0;LOOPWrite(ch);строка[сс1] := ch; cc1 := ccc1 + 1;IF (ch = EOL) OR (ch = 0C) THEN EXIT END;Read(ch)ENDEND101END ВзятьСтроку;PROCEDURE ВзятьЛитеру;BEGINWHILE cc = ccl DOcc := 0; ВзятьСтрокуEND;ch := строка[сс]; cc : = cc + 1END ВзятьЛитеру;PROCEDURE ОтметитьОшибку(n: CARDINAL);BEGIN IF cc2 - 0 THENWrite("*"); cc2 := 3;REPEAT Write(" "); cc2 := cc2 - 1UNTIL cc2 = 0 END;WHILE cc2 < cc DOWrite(" "); cc2 := cc2 + 1END;Write("^"); WriteCard(n,1); cc2 : = cc2 + 2END ОтметитьОшибку;PROCEDURE ПропускСтроки;BEGINWHILE ch # EOL DO ВзятьЛитеру END;ВзятьЛитеруEND ПропускСтроки;BEGIN cc := 0; ccl := 0; cc2 := 0END ОбработкаСтрок;PROCEDURE ВзятьЛексему;VAR i: CARDINAL;BEGIN102WHILE ch <= " " DO ВзятьЛитеру END;IF ch = "/" THENПропускСтроки;WHILE ch <= " " DO ВзятьЛитеру ENDEND;IF (CAP(ch) <= "Z")&(CAP(ch) >= "A") THENi := 0; лекc := литерал;REPEATIF i < ИдентДлина THENид[i] := ch; i := i + 1END;IF ch > "Z" THEN лекc := идент END;ВзятьЛитеруUNTIL (CAP(ch) < "A") OR (CAP(ch) > "Z");ид[i] := " "ELSIF ch = "'" THENi := 0; ВзятьЛитеру: лекc := литерал;WHILE ch # "'" DOIF i < ИдентДлина THEN,ид[i] := ch; i := i + 1END;ВзятьЛитеруEND;ВзятьЛитеру; ид[i] := " "ELSIF ch - '"' THENi : = 0; ВзятьЛитеру: лекc := литерал;WHILE ch # '"' DOIF i < ИдентДлина THENид[i] := ch; i := i + 1END;ВзятьЛитеруEND;103ВзятьЛитеру: ид[i] := " "ELSIF ch = "=" THEN лекc := равно; ВзятьЛитеруELSIF ch - "(" THEN лекc := лкрcк; ВзятьЛитеруELSIF ch = ")" THEN лекc := пкрcк; ВзятьЛитеруELSIF ch - "[" THEN лекc := лквcк; ВзятьЛитеруELSIF ch = "]" THEN лекc := пквcк; ВзятьЛитеруELSIF ch = "{" THEN лекc := лФск; ВзятьЛитеруELSIF ch = "}" THEN лекc := пфcк; ВзятьЛитеруELSIF ch - "|" THEN лекc := верт; ВзятьЛитеруELSIF ch = "." THEN лекc := точка; ВзятьЛитеруELSIF ch = "177C" THEN лекc := другая; ВзятьЛитеруELSE лекc := другая; ВзятьЛитеруENDEND ВзятьЛексему;BEGIN номстр := 0; ch := " "END РБНФСканер.Результат работы этой программы, примененной к синтаксису языка Модула-2, приведен вприложении 1.