Данному образовательному сайту пришлось несколько раз менять свое имя. С 2022 года доступ к нему обеспечивается по URL
emc.orgfree.com

emc.km.ru (2001-2007) ==> educomp.org.ru (2007-2011) ==> educomp.runnet.ru (2011-2021) ==> emc.orgfree.com (2022-...)
Более подробно об истории сайта можно прочитать здесь.


Учебные модели компьютера



Модели (software):

"Е14" (parallel !!!)
"S9PU" (parallel)

Модели (hardware):






Награды сайта
Награды сайта 2005

Компилятор?.. Это очень просто

В настоящее время большое распространение получил так называемый пользовательский подход, когда сидящий за компьютером человек не вникает (а часто и не хочет вникать!) в то, что происходит внутри компьютера. Работа при этом, если несколько утрировать, протекает по принципу "нажмешь сюда - получишь вот это". Любое отклонение в поведении компьютера от ожидаемого приводит такого пользователя в состояние если не паники, то уж в легкое замешательство точно.

Можно до хрипоты спорить, сколько должен знать о работе компьютера использующий его человек, но очевидно, что чем лучше он понимает своего электронного помощника, тем успешнее будет его компьютерная деятельность. Особую актуальность приобретает знание "внутренней логики" компьютера для программиста, т.к. он пытается получить от машины значительно больше различных и сложных реакций, чем человек, работающий в "Лексиконе". И вот здесь, по-моему, нас подстерегает некоторая незаметная на первый взгляд опасность. Дело в том, что современный начинающий программист, как правило, не хочет обременять себя знакомством с языком низкого уровня, а сразу берется за Паскаль или Бейсик. В результате на вопрос "Во что преобразуется текст твоей программы и как она работает?", такой человек в лучшем случае может ответить кратко: "Текст генерирует исполнимый код". На этом его познания и заканчиваются.

Мне кажется, что когда наши школьники и студенты бодро рассуждают об особенностях последних версий "Борландовского" Турбо-Паскаля и даже могут писать на нем какие-то программы, это еще не все. Надо, чтобы они хотя бы в самых общих чертах представляли, что такое переменная с точки зрения компьютера, как хранятся те или иные данные в памяти, как реализуются различные алгоритмические структуры и т.п. И вот тут мы, преподаватели, неожиданно осознаем, что в действительности и сами до сих пор как-то не очень задумывались об этом. Так что же все-таки происходит с нашей программой? Ответ на этот вопрос в популярной литературе найти не удается, а "вгрызаться" в объемные труды системных программистов для понимания основ вряд ли целесообразно. Автору этого материала захотелось создать более простой путь и написать демонстрационную программу, поработав с которой обучаемый получил бы какое-то представление о том, что такое компиляция. Отметим, что в опубликованной недавно статье [1] при выборе алгоритмического языка для обучения высказываются аналогичные мысли по поводу языков типа Бейсика или Паскаля:

"Самым катастрофическим изъяном таких систем с точки зрения использования их для изучения программирования является огромное ядро интерпретатора или компилятора, закрытое не только от вмешательства программиста, но и вообще от его взгляда".

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

Сразу оговоримся, что мы будем МОДЕЛИРОВАТЬ работу компилятора, т.е. нас будет сейчас интересовать не внутренняя структура самого компилятора, а результаты его деятельности. Иными словами, мы не будем выяснять, КАК именно компилятор выделяет отдельные символы текста программы и их анализирует, а сосредоточим свое внимание на том, ЧТО при продвижении по тексту программы формируется в ОЗУ в качестве эквивалентного исполнимого машинного кода.

Прежде всего определимся с алгоритмическим языком, на котором будет написан текст исходной программы. Видимо, лучше всего для этой цели подойдет Паскаль - наиболее простой и наглядный язык, с которого программа может компилироваться. (Бейсик с его нумерованными строками хотя и может быть реализован в виде компилятора, все же содержит в себе целый ряд "некомпиляторных" черт).

Несколько сложнее осуществить выбор машины, для которой будет разрабатываться демо-компилятор. Взять какой-нибудь реальный процессор? Не очень удобно, т.к. слишком много потребуется предварительно рассказывать ученику о принципах его работы. Поэтому мне показался более подходящим другой путь - выбрать условную вычислительную машину с минимальной системой команд. Наиболее простой и распространенной учебной моделью является ЭВМ "Кроха", разработанная и подробно описанная в школьном учебнике [2]. В ее системе команд всего 8 операций:

ТАБЛИЦА 1
КОДСОДЕРЖАНИЕ ОПЕРАЦИИ
000A1 ==> A3
001A1 + A2 ==> A3
010A1 / A2 ==> A3
011ABS(A1 - A2) ==> A3
100при A1 = A2 переход к A3
101A1 * A2 ==> A3
110при A1 > A2 переход к A3
111вывод A1, A2, A3; стоп.

Объем памяти в авторском варианте ЭВМ "Кроха" [2] был всего 8 ячеек, что слишком мало даже для реализации самого примитивного демонстрационного компилятора. Поэтому ОЗУ придется увеличить хотя бы до 16 ячеек, что в воображаемом компьютере сделать заметно проще, чем в реальном. Дальнейшее увеличение количества ячеек приведет к некоторой потере наглядности - для 32 ячеек уже невозможно отобразить их все на экране одновременно. Следовательно для учебных целей придется ограничиться именно шестнадцатью ячейками ОЗУ.

Указанная мера приводит к увеличению размера адреса ячеек памяти: если раньше он умещался в трех битах, то теперь потребуется уже четыре. Учитывая, что ЭВМ "Кроха" является трехадресной, общая длина команды возрастет на 3 бита и станет равной 15 битам.

Таким образом, в качестве ЭВМ для демо-компилятора была выбрана условная машина (назовем ее для определенности "Кроха-М") с полностью идентичной [2] системой команд, но с расширенным объемом ОЗУ.

Практически реализован учебный компилятор на компьютере УКНЦ, что объясняется "местными условиями" Пермского педагогического университета, где работает автор. Впрочем, для тех, кто не умеет пользоваться никакими другими машинами кроме IBM, имеется полный аналог программного обеспечения и для этой машины.

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

Перейдем теперь непосредственно к компилятору.

Как известно, программа на Паскале состоит из трех частей: заголовка, описаний и операторов, т.е. собственно программы.

Наиболее просто разбирается заголовок. В нем распознается слово PROGRAM и пропускается имя программы, т.к. оно носит "декоративный" характер. При желании можно контролировать, чтобы в имени не было недопустимых символов. Наконец, последнюю часть заголовка, перечисляющую используемые программой файлы, в демо-компиляторе можно смело опустить. Как известно, при отсутствии работы с диском используются только стандартные файлы INPUT и OUTPUT, а их разрешается не указывать практически во всех версиях Паскаля. Таким образом, заголовок только контролируется на корректность синтаксиса, не порождая в памяти ЭВМ никаких конструкций.

Далее следуют описания. Учитывая, что наша ЭВМ "Кроха" может работать только с целыми числами, в описании переменных после слова VAR оставим допустимым только тип INTEGER. По этой же причине не будем рассматривать и описания типов. Метки удалим за ненадобностью: незачем учить использовать GOTO в структурном языке! С огромным сожалением придется исключить описание процедур и функций, ибо в системе команд ЭВМ "Кроха" не предусмотрен переход с возвратом, без которого эти конструкции реализовать очень трудно. Наконец, описание констант можно было бы и сохранить, но для упрощения компилятора я пожертвовал и ими. Таким образом, "когда дым рассеялся", всевозможные описания для нашего учебного компилятора выродились в единственную строку:

VAR <список переменных>: INTEGER;

При разборе этой строки помимо контроля синтаксисатребуется произвести резервирование ячеек ОЗУ для упоминающихся в списке переменных. Желательно, чтобы на экране дисплея было видно, что по описанию лишь отводится место в памяти под переменную, а не задается ее значение. Отметим еще, что при обработке списка необходим дополнительный контроль на отсутствие повторного описания переменных и (на всякий случай) на наличие свободной памяти.

Остановимся подробнее на вопросе, в какой части ОЗУ размещать переменные. Согласно [2], исполнение программы ЭВМ "Кроха" всегда начинает с нулевой ячейки. Следовательно, программу удобно располагать в младших адресах, заполняя память в сторону их возрастания. Поскольку длина программы заранее неизвестна, то наиболее разумным и простым решением будет выделять место под переменные в старших адресах в сторону уменьшения, т.е. "навстречу" программе (рис.1).

1111
ПЕРЕМЕННЫЕ

AV
КОНСТАНТЫ

AC
СВОБОДНАЯ
ОБЛАСТЬ

AP

0000
ПРОГРАММА
Рис.1. Распределение памяти при компиляции

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

Теперь перейдем, наконец, к трансляции исполнимой части программы на Паскале. Прежде всего, определим список операторов, подлежащих распознаванию в нашем компиляторе. Это, конечно, присвоение (стандартное обозначение ":="), условный оператор IF/THEN/ELSE и все циклы: WHILE/DO, REPEAT/UNTIL, FOR/TO (DOWNTO) /DO. Конструкцию CASE в ОЗУ объемом 16 ячеек реализовывать нецелесоообразно, т.к. по самым простым прикидкам в него вряд ли поместится более трех ветвей.

Еще раз отметим отсутствие операторов вызова процедур и функций, что, по мнению автора, является главным недостатком проекта. Впрочем, без одной стандартной процедуры - WRITELN - все же не обойтись, поэтому ее в порядке исключения придется разрешить, увеличив список допустимых служебных слов еще на одно.

Таким образом, при расшифровке очередного оператора необходимо прежде всего проверять ключевые слова IF, WHILE, REPEAT, FOR, WRITELN и при совпадении с одним из них перейти к расшифровке соответствующей конструкции. В противном случае оператор может быть только присвоением; к его более подробному рассмотрению мы сейчас и переходим.

Оператор присвоения содержит в левой части имя переменной, в которую помещается результат вычислений, затем символы присвоения := и после этого выражение, определяющее значение переменной. Неоднозначность может возникать только в правой части, поскольку выражения могут быть самыми разнообразными. Учитывая учебный характер нашего компилятора, ограничимся наиболее простыми выражениями: будем предполагать, что они содержат не более одного знака арифметической операции. Конечно, такое ограничение сильно снизит универсальность компилятора, зато резко упростит результирующую исполняемую программу: каждый оператор всегда будет компилироваться в одну машинную команду и не возникнет необходимости в рабочих ячейках под промежуточные результаты. Подобное упрощение выражений придаст еще один недостаток проекту, но, думается, с ним можно смириться, заменяя, например, A := 2 * (B - C) на A := B - C; A := 2 * A; .

Рассмотрим основные типы допустимых выражений в операторе присвоения. Если в правой части есть (единственное!) арифметическое действие, то код операции определяется этим действием (+, -, *, div - цельночисленное деление). Если же оператор имеет вид A := B, то он транслируется в команду переписи информации из одной ячейки в другую.

Имена переменных обязательно проверяются по составленной при расшифровке конструкции VAR таблице. В случае, когда переменная в ней отсутствует, выдается сообщение об ошибке; при успешном поиске имя переменной заменяется ее адресом в ОЗУ.

В выражение могут входить не только имена переменных, но и некоторые непосредственно написанные константы, например, A := 2 или B := C + 30. Распознавать их будем по первому символу: если он цифра, значит операндом в выражении служит число. В этом случае придется указанную в программе константу перевести из текстовой формы в числовую (не забыв проверить, чтобы она не превышала максимально допустимого значения!). При отсутствии синтаксических ошибок полученное число поместим в ячейку ОЗУ "ниже" области, где зарезервировано место под переменные (см. рис.1). Особенно важно для экономии памяти предварительно проверить, не встречалась ли такая константа ранее. Например, при трансляции операторов A := A + 1; B := B + 1 константа 1 должна быть общей для обоих операторов.

Приведем примеры трансляции выражений, в предположении, что описание имеет вид VAR A,B,C:INTEGER; а приведенные операторы следуют сразу за открывающим программу словом BEGIN:

1) A := B + C
 
001
сложить
1110
B
1101
C
1111
A
2) A :=B
 
000
переписать
1110
B
0000
 
1111
A
3) A := 2 * B
 
101
умножить
1100
2
1110
B
1111
A

Напомним, что переменные создаются в следующем порядке: A - ячейка 1111, B - 1110, C - 1101. В ячейку 1100 будет помещена константа 2. Кодовая часть команд берется из таблицы 1.

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

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

     ПРОГРАММА                 РЕЗУЛЬТАТ КОМПИЛЯЦИИ
PROGRAM proba1;
VAR a:INTEGER;                 адрес     содержимое
BEGIN a:=1;                    0000  000 1110 0000 1111
      WRITELN(a,a,a);HALT      0001  111 1111 1111 1111
END.                           ........................
                               1110      1
                               1111      A

Перейдем теперь к рассмотрению алгоритмических структур. Начнем с условного оператора IF/THEN/ELSE. В Паскале он записывается в виде:

IF <условие> THEN <оператор 1> ELSE <оператор 2>
где <условие> - это <левая часть> <знак неравенства> <правая часть>
В приведенной записи <левая часть> и <правая часть> - арифметические выражения; <знак неравенства> - одна из 6 следующих комбинаций: =, <>, >, >=, <, <=; <оператор 1> и <оператор 2> - операторы Паскаля, в том числе составные (несколько операторов, заключенных между словами BEGIN/END).

На выражения в левой и правой части уместно наложить те же самые ограничения, что и на выражения в операторе присвоения. С учетом всего сказанного, полная условная конструкция в памяти ЭВМ будет иметь вид, представленный на рис.2 (на нем знаком * справа отмечены необязательные элементы).

 команда вычисления левой части*
 команда вычисления правой части*
 сравнить левую и правую часть; если
"НЕТ", переход к ADR2, иначе к ADR1
 
ADR1оператор 1 
 переход к ADR3 (обход ELSE)*
ADR2оператор 2*
ADR3 

Рис.2. Структура IF/THEN/ELSE

Приведем в качестве иллюстрации конкретный пример разветвляющегося участка программы. Пусть переменная A должна принимать значение переменной B, если B/2 >= C, и значение 2B+1 в противном случае.

     ПРОГРАММА                 РЕЗУЛЬТАТ КОМПИЛЯЦИИ
PROGRAM proba2;                адрес     содержимое
VAR a,b,c:INTEGER;             0000  010 1110 1100 1011
BEGIN IF b div 2 >= c          0001  110 1101 1011 0100
         THEN a:=b             0010  000 1110 0000 1111
                               0011  100 0000 0000 0110
         ELSE BEGIN a:=2*b;    0100  101 1100 1110 1111
                    a:=a+1     0101  001 1111 1010 1111
              END              ........................
END.                           1010      1
                               1011  рабочая ячейка (B div 2)
                               1100      2
                               1101      C
                               1110      B
                               1111      A

Новой чертой оттранслированной программы является потребность в рабочей ячейке для хранения промежуточного результата B div 2. Т.к. правая часть условия вычислений не требует, в данной программе удается обойтись только одной рабочей ячейкой с адресом 1011.

Особо отметим, что в системе команд ЭВМ "Кроха" всего два условных перехода и совсем нет безусловного. Последний несложно имитировать командой 100 0000 0000 ADR: если содержимое ячейки 0000 равно самому себе, что всегда справедливо, следующая исполняемая команда будет иметь адрес ADR. Сложнее обстоит дело с кодированием условий - для реализации некоторых приходится использовать комбинацию условного и безусловного переходов. Все 6 возможных вариантов отношений сведены в таблицу 2.

ТАБЛИЦА 2
УСЛОВИЕ
В ТЕКСТЕ
ОТРИЦАНИЕ
УСЛОВИЯ
КОДИРОВАНИЕ ПЕРЕХОДА
ПО "НЕТ"
=<>
100ALARADRP+2
10000000000ADRS
<>= 100 AL AR ADRS
><=
110ALARADRP+2
10000000000ADRS
<=> 110 AL AR ADRS
<>=
110ARALADRP+2
10000000000ADRS
>=< 110 AR AL ADRS
безусловный переход 100 0000 0000 ADRS

ОБОЗНАЧЕНИЯ таблицы 2.
ADRP - адрес ячейки, в которую помещается первая
команда проверки условия
ARRS - адрес перехода, требуемый в данной структуре
AL - адрес результата вычисления левой части
AR - адрес результата вычисления правой части

ПРИМЕЧАНИЕ. Использование условий <>, <= и >= является для нашего компилятора предпочтительнее, т.к. создается более компактный код.

Перейдем теперь к вопросу о компиляции циклов.

Конструкция WHILE/DO очень похожа на IF/THEN без ELSE (сравните рис.2 и 3). Единственным ее отличием является наличие безусловного перехода, возвращающего выполнение программы на начало цикла, адрес которого ADR0 при обнаружении слова WHILE необходимо запоминать. Учитывая, что рассмотренные алгоритмические структуры очень похожи, более подробное описание трансляции цикла WHILE приводить не будем.

ADR0команда вычисления левой части*
 команда вычисления правой части*
 сравнить левую и правую часть; если
"НЕТ", переход к ADR2, иначе к ADR1
 
ADR1тело цикла 
 переход к ADR0 (зацикливание) 
ADR2 

Рис.3. Структура WHILE/DO

Второй тип цикла - REPEAT/UNTIL имеет следующий вид:

REPEAT <операторы> UNTIL <условие>.
Он отличается от WHILE прежде всего расположением тела цикла и условий. Есть еще и синтаксическое различие: служебные слова REPEAT/UNTIL одновременно "по совместительству" выполняют роль ограничителей составного оператора BEGIN/END. Следовательно, REPEAT - это единственная конструкция, которая без особых указаний может распространяться на несколько операторов, размещенных после служебного слова. Компиляция данного цикла еще более проста, чем в случае WHILE. Встретив ключевое слово REPEAT, запоминаем адрес ячейки, где начинается тело цикла (ADR0 на рис.4), а затем транслируем его. Обнаружив слово UNTIL, остается только перевести в машинные команды проверку стоящего после него условия. Как и для конструкций IF и WHILE, при этом используется таблица 2.

ADR0тело цикла 
 команда вычисления левой части*
 команда вычисления правой части*
 сравнить левую и правую часть; если
"НЕТ", переход к ADR0, иначе к ADR1
 
ADR1 

Рис.4. Структура REPEAT/UNTIL

Приведем пример компиляции фрагмента, содержащего REPEAT.

     ПРОГРАММА       РЕЗУЛЬТАТ КОМПИЛЯЦИИ
PROGRAM proba3;
VAR i,s:INTEGER;     адрес     содержимое
BEGIN REPEAT
      s:=s+i;        0000  001 1110 1111 1110
      i:=i+1         0001  001 1111 1101 1111
      until i=10     0010  100 1111 1100 0100
END.                 0011  100 0000 0000 0000
                     ........................
                     1100      10
                     1101      1
                     1110      S
                     1111      I

Наконец, наиболее сложным для компиляции оказывается цикл FOR/TO(DOWNTO)/DO (рис.5). Напомним, что он имеет следующую структуру:

FOR <пременная цикла>:=<выражение> TO(DOWNTO) <выражение> DO <оператор>

 инициализация переменной цикла 
 команда вычисления выражения после TO*
 начальное и конечное значения корректны?
"нет" - к ADR1, иначе - к ADR0
 
ADR0тело цикла 
 переход к ADR1 при равенстве переменной
и конечного значения
 
 команда приращения переменной цикла 
 переход к ADR0 (зацикливание) 
ADR1 

Рис.5. Структура FOR/TO(DOWNTO)/DO

При трансляции этой конструкции будем руководствоваться идеями автора Паскаля Н.Вирта, изложенными, например, на стр.185-186 в [3].

После слова FOR стоит оператор присвоения переменной цикла начального значения. Согласно [3] он выполняется в два этапа: сначала вычисляется значение правой части и результат помещается в некоторую рабочую ячейку, а затем, при корректности соотношения между начальным и конечным значениями, он переписывается в переменную цикла. Такой алгоритм обеспечивает "неприкосновенность" переменной в случае, когда цикл не выполняется ни разу. Платой за такую осторожную стратегию является заметное увеличение длины результирующего кода, хотя при нормальном обращении к циклу описанные меры оказываются излишними. Поэтому после некоторых размышлений ради экономии памяти я пожертвовал этой тонкостью трансляции и разрешил демо-компилятору раскрывать выражение после FOR обычным образом: результат вычисления правой части сразу заносится в переменную цикла, так что при ошибочно заданных параметрах ее значение меняется. Во всех остальных случаях цикл работает в точности как описано в [3]. Забегая вперед, скажу, что это единственное несоответствие классической идеологии конструкции FOR.

Далее следует служебное слово TO или DOWNTO, определяющее шаг цикла (1 или -1). Учитывая, что у ЭВМ "Кроха" отсутствуют отрицательные числа, при компиляции шаг в обоих случаях принимается равным 1, но при TO он прибавляется к переменной цикла, а при DOWNTO - вычитается. Запомнив код необходимой операции, продолжаем разбор дальше. Компилируем выражение, стоящее после TO (DOWNTO), а под результат вычислений заводим специальную рабочую ячейку, с которой в дальнейшем будем сравнивать переменную цикла. Отметим очень распространенное исключение: если правой частью служит константа, занимать рабочую ячейку не стоит - можно ссылаться непосредственно на ячейку с константой.

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

Переходим к компиляции основной части цикла, соответствующей нормальным значениям параметров в заголовке. Запоминаем адрес ОЗУ ADR0, соответствующий началу тела цикла и обычным образом транслируем операторы, предназначенные для циклического повторения. Затем помещаем проверку на равенство между текущим значением переменной цикла и требуемым конечным значением: при их совпадении цикл завершается (подчеркнем, что это условие одинаково для TO и DOWNTO). Для противоположной ветви остается сформировать команду приращения переменной цикла в соответствии с требуемым шагом и поместить ее в ОЗУ вместе с командой перехода на начало цикла, т.е. к адресу ADR0.

Пример трансляции фрагмента с циклом FOR выглядит так:

     ПРОГРАММА                 РЕЗУЛЬТАТ КОМПИЛЯЦИИ
PROGRAM proba4;
VAR i,s:INTEGER;               адрес     содержимое
BEGIN FOR i:=1                 0000  000 1101 0000 1111
          TO 100               0001  110 1111 1100 0110
             DO s:=s+i         0010  001 1110 1111 1110
                               0011  100 1111 1100 0110
                               0100  001 1111 1101 1111
                               0101  100 0000 0000 0010
END.                           ........................
                               1100      100
                               1101      1
                               1110      S
                               1111      I

Завершая обсуждение принципов компиляции Паскаль-программы, отметим необходимость постоянного контроля наличия памяти под результирующую программу. Для этого необходимо перед каждой записью машинной команды сравнивать адреса границ программы и констант (AP и AC на рис.1): переполнение памяти наступает, когда AP > AC.

Наконец, приведем примеры трансляции полных программ, в которых задаются значения переменных и результат выводится на экран. В отличие от описанных ранее фрагментов, это уже вполне реальные задачи.

Пример 1. Заданы числа A и B. Большее из них разделить
на меньшее и найти остаток.
     ПРОГРАММА                 РЕЗУЛЬТАТ КОМПИЛЯЦИИ
PROGRAM primer1;
VAR a,b,d,o:INTEGER;           адрес     содержимое
BEGIN a:=5;                    0000  000 1011 0000 1111
      b:=14;                   0001  000 1010 0000 1110
      if b>=a                  0010  110 1111 1110 0110
         then begin d:=a;      0011  000 1111 0000 1101
    {меняем A и B}  a:=b;      0100  000 1110 0000 1111
                    b:=d       0101  000 1101 0000 1110
              end;
      d:=a div b;              0110  010 1111 1110 1101
      o:=b*d;                  0111  101 1110 1101 1100
      o:=a-o; {остаток}        1000  011 1111 1100 1100
      WRITELN(a,d,o);halt      1001  111 1111 1101 1100
END.
                               1010      14
                               1011      5
                               1100      O
                               1101      D
                               1110      B
                               1111      A

Пример 2.      N
Вычислить Y = 3  .
     ПРОГРАММА                 РЕЗУЛЬТАТ КОМПИЛЯЦИИ
PROGRAM primer2;
VAR n,y,w:INTEGER;             адрес     содержимое
BEGIN n:=4;                    0000  000 1100 0000 1111
      w:=1;                    0001  000 1011 0000 1101
      y:=1;                    0010  000 1011 0000 1110
      while w<=n do            0011  110 1101 1111 0111
            begin y:=3*y;      0100  101 1010 1110 1110
                  w:=w+1       0101  001 1101 1011 1101
            end;               0110  100 0000 0000 0011
      writeln(n,y,y);halt      0111  111 1111 1110 1110
END.                           ........................
                               1010      3
                               1011      1
                               1100      4
                               1101      W
                               1110      Y
                               1111      N

Пример 3. Вычислить s = 1+x(2+x(3+x(4+x*5))) (схема Горнера).
     ПРОГРАММА                 РЕЗУЛЬТАТ КОМПИЛЯЦИИ
PROGRAM primer3;
VAR x,s,k:INTEGER;             адрес     содержимое
BEGIN x:=2;                    0000  000 1100 0000 1111
      s:=5;                    0001  000 1011 0000 1110
      for k:=s-1               0010  011 1110 1010 1101
          downto 1 do          0011  110 1010 1101 1001
          begin s:=s*x;        0100  101 1110 1111 1110
                s:=s+k         0101  001 1110 1101 1110
          end;                 0110  100 1101 1010 1001
                               0111  011 1101 1010 1101
                               1000  100 0000 0000 0100
      writeln(k,s,s);halt      1001  111 1101 1110 1110
END.
                               1010      1
                               1011      5
                               1100      2
                               1101      K
                               1110      S
                               1111      X

Наступило время подвести итоги. Основные принципы преобразования текста Паскаль-программы в машинный код выработаны, практическая реализация демонстрационного учебного компилятора, как уже отмечалось ранее, существует. Написан демо-компилятор на языке Паскаль и откомпилирован на школьный компьютер УКНЦ, а также на IBM. Разумеется, в последнем случае черно-белый вариант программы, созданный в расчете на компьютер с объемом ОЗУ 64 К, смотрится довольно скромно, но функции свои исправно выполняет и для обучения вполне пригоден.

В настоящее время ведется работа по адаптации программы демо-компилятора на "Ямаху".

Надеюсь, что подробное изложение материалов статьи будет представлять интерес для широкого круга работников образования.

Л И Т Е Р А Т У Р А
  1. К.Г.Бурков. Для тех, кто знает: FORTH! Персональный компьютер УКНЦ (приложение к журналу "Информатика и образование"), 1994, N 3, с.57-60.
  2. А.Г.Гейн, В.Г.Житомирский, Е.В.Линецкий, М.В.Сапир, В.Ф.Шолохович. Основы информатики и вычислительной техники. М.: Просвещение, 1991.
  3. К.Йенсен, Н.Вирт. Паскаль. Руководство пользователя. М.: Финансы и статистика, 1989.


© Е.А.Еремин, 1995
Статья:
Еремин Е.А. Компилятор? Это очень просто. Компьютер УКНЦ. М.: Компьютика, 1995, N 3, c.25-33.


Автор сайта - Евгений Александрович Еремин (Пермский государственный педагогический университет). e_eremin@yahoo.com


Free Web Hosting