Министерство образования Российской Федерации Государственное образовательное учреждение высшего профессионального образования
СЕВЕРО-ЗАПАДНЫЙ ГОСУДАРСТВЕННЫЙ ЗАОЧНЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ Кафедра компьютерных технологий и программного обеспечения
ТЕОРИЯ ЯЗЫКОВ ПРОГРАММИРОВАНИЯ И МЕТОДЫ ТРАНСЛЯЦИИ
Методические указания к выполнению практических занятий
Факультет информатики и систем управления Направление и специальность подготовки дипломированного специалиста: 654600 - информатика и вычислительная техника 220100 - вычислительные машины, комплексы, системы и сети
Санкт-Петербург 2003
Утверждено редакционно-издательским советом университета УДК 681.3.06 Теория языков программирования и методы трансляции: Методические указания к выполнению практических занятий. – СПб.: СЗТУ, 2003 - 36 с. Методические указания составлены в соответствии с требованиями государственного образовательного стандарта высшего профессионального образования по направлению подготовки дипломированного специалиста 654600 (специальность 220100 – «Вычислительные машины, комплексы, системы и сети») для дисциплины специализации (ДС) «Теория языков программирования и методы трансляции».
Практические занятия предназначены для углубленного изучения приемов программирования на языке С++ и приобретения практических навыков в разработке интерпретаторов. В задании на проектирование интерпретатора предложен простейший язык Small BASIC. В процессе выполнения практических заданий студенты должны познакомиться с алгоритмизацией построения анализатора выражений и интерпретатора команд. Рассмотрено на заседании кафедры компьютерных технологий и программного обеспечения (КТ и ПО) 9.06.2003 г., одобрено методической комиссией факультета информатики и систем управления (ФИСУ) 17.11.2003.
Рецензенты: кафедра прикладной и компьютерной оптики СПбГИТМО (О.В.Багдасарова, канд.техн.наук., доц.); кафедра компьютерных технологий и программного обеспечения (Г.И.Анкудинов, д-р.техн.наук, проф.) Составители: М.В. Копейкин, канд.техн.наук, доц.; Н.В. Рачева, доц.,; Е.О. Шумова, доц. © Северо-Западный государственный заочный технический университет, 2003
ВВЕДЕНИЕ Цель практических занятий - углубление знаний в алгоритмизации и приемах программирования на языке С++, получение практических навыков в проектировании сложнейших программных продуктов. Основные требования данной специальной дисциплины – ознакомиться на практике со следующими вопросами: Профессиональное программирование на языке С++. Создание файла проекта в интегрированной среде Borland С++. Изучить правила описания внешних переменных и использования рекурсивных функций. Синтаксический анализ выражений. Изучить принципы построения рекурсивно-нисходящего алгоритма, ознакомиться с продукционными правилами разбора выражений и получить навыки в программировании процедур для простых алгебраических выражений. Интерпретатор языковых команд. Изучить возможности и принципы построения интерпретирующей системы и получить навыки в программировании простейших команд интерпретатора. 1. Задания на выполнение практических занятий 1.1. Общие указания к выполнению заданий на практических занятиях
Методические указания предназначены для студентов заочной и очно-заочной форм обучения как в дисплейном классе, так и в домашних условиях. Результаты выполненных заданий должны быть продемонстрированы преподавателю в дисплейном классе во время, выделенное на практические занятия. В случае самостоятельного выполнения обязательно представить отчеты в виде печатанного текста, где должны быть задание, выбранное по последней цифре студенческого шифра, исходные тексты программных модулей на языке С++ с соответствующими комментариями и приведены протоколы тестирования. Работа № 1 Создание файла проекта в интегрированной среде Borland С++. I. Цель работы. Изучить принципы многофайловой компиляции. Создать файлы с расширением *.prj и *.exe. II. Порядок выполнения работы.
В главном верхнем меню интегрированной среды BC++ найти надпись project. Выбрать имя проекта, установить режим add и занести для компиляции имена двух файлов (исходные тексты анализатора выражений и интерпретатора команд взять с сайта http://www.de.nwpi.ru/). Запустить на трансляцию. Полученный файл с расширением *.exe проверить на выполнение. Для проверки составить исходный текст на Small BASIC в 7-10 строк. Вариант задания может быть составлен индивидуально, либо выбрать по последней цифре шифра из таблиц 1 и 2. Убедиться в правильности исполнения программы на Small BASIC и зафиксировать протокол. Работа № 2 Расширение списка команд Small BASIC. I. Цель работы. Добавить два простейших оператора: очистка экрана и пауза. II. Порядок выполнения работы. Разобраться с заданием №2. Исходный текст интерпретатора изучить по исполняемым функциям. Предложить самостоятельно имена (аббревиатуры) операторов. Задать синтаксис конструкций по примеру уже имеющихся семи операторов. Написать для реализации предложенных команд функции на языке BC++. Для общих занятий в дисплейном классе вариант задания выбирается наиболее простой. Рекомендуется реализовать команды очистки экрана и установки паузы. Для самостоятельной работы можно взять и более сложные команды, например, реализовать оператор цикла с неизвестным количеством повторов. После внесения изменений в текст интерпретатора команд повторить процесс отладки и тестирования. Работа № 3 Построение рекурсивных функций на языке С++. I. Цель работы. Изучить принципы построения рекурсивных функций на примерах, работающих программ. Написать самостоятельно функцию для построения двоичного дерева или быстрой сортировки одномерного массива. II. Порядок выполнения работы. Изучить раздел «алгоритмы построения рекурсивных функций». Ознакомиться с примером программы, реализующей работу калькулятора. Написать предложенные алгоритмы на языке С++. Провести тестирование
на двух-трех примерах. Протокол преподавателю по окончанию занятий.
зафиксировать
и
предъявить
1.2. Задание на практические занятия Задание 1: составить примеры исходных текстов для языка Small BASIC для решения простой задачи. Задача: вычислить А для всех Х от 1 до 5, ⎧ <Выражение 1> , Х < 3 А= ⎨ ⎩ <Выражение 2> , Х ≥ 3 Таблица 1 Последняя цифра шифра 0 1 2 3 4 5 6 7 8 9
Выражение 1 Х+5 Х-1 Х*2 10-Х Х/2+5 20-Х*2 Х+7 Х/2+10 Х*2-8 11-Х*2 Таблица 2
Последняя цифра шифра 0 1 2 3 4 5 6 7 8 9
Выражение 2 Х*2 Х*3 Х-2 10-Х 15-Х 20-Х/2 Х*2+5 9-Х*2 8+Х 11-Х
Задание 2: в исходном тексте интерпретатора в функцию под именем tokens добавить к перечисленным ранее командам аббревиатуры (идентификаторы) команд очистки экрана и паузы, а также указать эти команды в таблице commands и в операторе switch главного цикла программы. Не забыть, главное, написать соответствующие функции для обработки данных команд. Задание 3: изучить исходные тексты (размещены ниже) программных модулей, написанных на языке Паскаль, понять алгоритмизацию предложенных задач и реализовать заданные задачи на С++ для закрепления знаний по рекурсивным функциям. { Метод быстpой сортировки } procedure qwick(var b:vec); procedure sortqwick(kl,kr:integer; var u:vec); { sortqwick - рекурсивная процедура } Var { раздел описания переменных} r,v:real; ir,il:integer; { kl,kr - первый и последний элемент текущей группы } Begin ir:=kr; il:=kl; r:=u[(kr+kl) div 2]; repeat while u[il] < r do il:=il+1; while r < u[ir] do ir:=ir-1; if il<=ir then begin v:=u[il]; u[il]:=u[ir]; u[ir]:=v; il:=il+1; ir:=ir-1; end; until il>ir; { repeat – until подобен оператору цикла на С++ do-while } if kl
f=^nd; {определение адреса } nd=RECORD k:integer; { значение, размещаемое в узле } lf,rf:f; { адреса порождаемых узлов слева и справа} END; VAR n:integer; rt:f; { построение сбалансированного двоичного дерева с n узлами } function tree(n:integer):f; VAR newnd:f; x,nl,nr:integer; { NIL – означает адрес пуст, т.е. адреса нет } BEGIN if n=0 then tree:=NIL else begin nl:=n div 2; nr:=n-nl-1; write(' Введите значение X='); readln(x); new(newnd); { запрос на текущий адрес} with newnd^ do begin k:=x; lf:=tree(nl); rf:=tree(nr); end; tree:=newnd; end; END; { результат выполнения - адрес последнего узла } { вывод дерева в горизонтальном положении } procedure printtree(T:f; H:integer); VAR i:integer; begin { печать дерева со сдвигом } if T<>NIL then with T^ do begin printtree(lf,h+1); for i:=1 to h do write(' '); writeln(k:3); { вывод значения узла } printtree(rf,h+1); end; end; { основная программа } Begin write(' Введите число узлов='); readln(n); rt:=tree(n); writeln(' Вывод дерева '); printtree(rt,0); readln; {пауза для фиксирования состояния экрана} end.
2. Принципы проектирования интерпретатора 2.1. Синтаксический разбор выражений 2.1.1. Выражения Выражения могут быть составлены из любых типов данных, но для учебных целей (доходчивого понимания) предлагается ограничиться разбором только числовых выражений[1]. В качестве элементов выражения предлагается рассмотреть следующие: числа 0 |1 | 2| 3| 4 | 5 | 6 | 7| 8| 9 операции + - * / ^ % = () <> ; . латинские буквы от А до Z. Операция ^ введена для возведения в степень. Операция % предусмотрена как деление нацело. Операции выполняются в соответствии с приведенным приоритетом: Высший () ^ / * % + Низший = Операции равного приоритета выполняются слева направо. Для переменных желательно ввести правило распознавания одного символа разных регистров равнозначное, т.е. "а" и "А" воспринимались одинаково. 2.1.2. Лексемы Выражения разбиваются на составные части (лексемы). Функция, выполняющая разбор выражений, должна решать следующие действия: - игнорировать пробелы и символы табуляции; - извлекать каждую лексему из исходного текста; - преобразовывать лексему во внутренний формат; - определять тип лексемы. Например, выражение А*В-( Х+10) содержит элементы "А", "*", "В", "-", "(", "Х", "+", "10", ")". Каждая лексема имеет внутренний и внешний формат (см. табл. 3). Внешний формат отражает содержательно действие, которое выполняет данная лексема. Внутренний формат необходим для реализации эффективного транслятора. Преимущество внутреннего формата заключается в том, что выполнение любых операций над целыми числами
происходит более быстро, чем над символьными строками. Каждому элементу выражения в результате разбора присваивается тип лексемы. Таблица 3 Тип лексем
Внешний формат
Разделитель Переменная Число Команда Строка Кавычки
DELIMITER VARIABABLE NUMBER COMMAND STRING QUITE
Внутренний формат 1 2 3 4 5 6
Пример разбора выражения: PRINT A+ 10- (B*C)/2 PRINT COMMAND A VARIABABLE + DELIMITER 10 NUMBER DELIMITER ( DELIMITER B VARIABABLE * DELIMITER C VARIABABLE ) DELIMITER / DELIMITER 2 NUMBER Программная реализация функции разбора выражений выполнена на языке Borland C++. #define DELIMITER #define VARIABLE #define NUMBER #define COMMAND #define STRING #define QUOTE #define EOL #define FINISHED
1 /* разделитель */ 2 /* переменная */ 3 /* число */ 4 /* команда */ 5 /* строка */ 6 /* кавычки */ 9 10
char *prog_W; /* буфер для анализируемого выражения */
extern char *prog; /* *prog указывает на входной поток лексем */ char *tok_a; get_token() /* получить */ { int ch; register char *temp; token_type=0; tok=0; prog_W=prog; temp=token; if (*prog=='\0') /* проверка на конец */ { *token=0; tok=FINISHED; tok_a=tok; return(token_type=DELIMITER); } while(iswhite(*prog))++prog; /* iswhite - */ if (*prog=='\r') /* Ctrl */ { ++prog; ++prog; tok=EOL; *token='\r'; token[1]='\n'; token[2]=0; return(token_type=DELIMITER); } if (strchr("+-*^/%=';().><",*prog)) /* тип - разделитель */ { *temp=*prog; ++prog; temp++; *temp=0; return(token_type=DELIMITER); } if (*prog=='"') /* тип - кавычки */ { prog++; while(*prog!='"' && *prog!='\r') *temp++=*prog++; if(*prog=='\r') serror(1); prog++; *temp=0; return(token_type=QUOTE); } if (isdigit(*prog)) /* тип - цифра */
{ while(!isdelim(*prog) && !iswhite(*prog)) *temp++=*prog++; *temp='\0'; return(token_type=NUMBER); } if (isalpha(*prog)) /* тип: переменная или команда */ { while(!isdelim(*prog) && !iswhite(*prog)) {*temp++=*prog++; }; token_type=STRING; } *temp='\0'; if (token_type==STRING) { tok=look_up(token); if(!tok) token_type=VARIABLE; else token_type=COMMAND; } return token_type; } Предложенная функция распознает шесть типов лексем (см. табл. 3). Глобальная ( extern ) переменная token_type хранит результат работы функции - тип лексемы. Внутренне представление лексемы помещается в переменную tok, имеющую глобальный статус extern. Конструкция extern (глобальная) открывает доступ к содержимому переменных для других функций. Для повторного просмотра лексемы, если это необходимо предлагается функция возврата putback(). /* Возврат лексемы во входной поток */ void putback() { char *t; t=token; for(; *t; t++)prog--; }
2.1.3. Построение выражений Для синтаксического анализа выражений был выбран рекурсивнонисходящий алгоритм. Для разбора выражений это означает ссылку на определения в терминах самого себя. Все выражения строятся по следующим правилам: Выражение ⇒ Терм[+ Терм][- Терм] Терм ⇒ Фактор[* Фактор][/ Фактор] Фактор ⇒ Переменная, Число или [Выражение] Квадратные обозначают возможное отсутствие элементов выражения. Символ ⇒ введен вместо слова "продуцирует". Перечисленные правила можно рассматривать как правила вывода выражения. Следует читать, например, вторую строку: "Терм является произведением или отношением факторов". Правила диктуют безусловный приоритет выполнения операций. Приоритет устанавливается по вложенности изложенных правил, начиная от фактора к выражению. Пример. Выражение 15/5 - (10 + 5). Анализ выполнится по следующей схеме: 1. Первый терм: 15/5. 2. Факторы 15 и 5, выполняется деление, результат 3. 3. Второй терм: (10 + 5). Стартует рекурсивный анализ второго выражения. 4. Факторы выбираются и суммируются, результат 15. 5. Число, возвращенное из рекурсии, вычитаем из первого результата. Итоговый результат: -12. Предложенный рекурсивный разбор выражений устанавливает следующее: - приоритет операций определяется в продукционных правилах и является безусловным; - вычисления выражений соответствуют правилам обычной математики. 2.1.4. Анализатор выражений Простейший анализатор выражений поддерживает шесть арифметических операций и унарный минус. Все операции могут использоваться в сочетании с круглыми скобками, приоритет выполнение вычислений, в которых является наивысшим. Функция SERROR (error) предназначена для выдачи сообщений при обнаружении ошибки.
Выполнение программных модулей анализатора предусмотрено только при объединении с интерпретатором через файл проекта. Для выбора имен переменных от "А" до "Z" предусмотрен variables массив из 26 элементов, заполненный изначально нулевыми значениями. Обращение к элементам массива построено на числовой значимости кодов АSCII латыни. int variables[26]= { /*26 возможных имен переменных, A-Z */ 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0 }; Функция допускает более длинные имена, но определение имени выполняется только по первой букве. /* Поиск значения переменной */ int find_var(s) char *s; { if(!isalpha(*s)) { serror(4); return 0; } return variables[toupper(*token)-'A']; } 2.2. Интерпретатор SMALL BASIC В работе интерпретатора предусмотрены семь операторов[1]. Внешний формат (выбор служебных слов) отражает содержательную сущность языковой конструкции, для преобразования во внутренний формат предусмотрена структура table: struct commands { /* Таблица служебных слов */ char command[20]; char tok; } table[]= { "print",PRINT,
"input",INPUT, "if",IF, "then",THEN, "goto",GOTO, "for",FOR, "next",NEXT, "to",TO, "gosub",GOSUB, "return",RETURN, "end",END, "",END, }; Признак конца файла (нулевая строка) помещена в конец таблицы. Внутренний формат лексемы функция заносит в таблицу переменных look_up(s) /* Поиск внутреннего формата для текущей лексемы */ look_up(s) char *s; { register int i; char *p; /* преобразование к нижнему регистру */ p=s; while(*p) { *p=tolower(*p); p++; } for(i=0; *table[i].command; i++) if(!strcmp(table[i].command,s)) return table[i].tok; return 0; /* неопознанная команда */ } Исходный текст на языке SMALL BASIC готовится в любом текстовом редакторе и вводится функцией load_program(p, fname), где fname содержит внешнее имя файла. load_program(p, fname) char *p; char *fname; { FILE *fp; int i=0;
printf("filename: %s\n",fname); getch(); if (!(fp=fopen(fname, "rb"))) { printf("not: %s\n",fname); return 0; } i=0; do { *p=getc(fp); p++; i++; } while(!feof(fp) && i
=<выражение> Оператор вывода: PRINT <список переменных> Оператор ввода: INPUT <имя переменной> Оператор перехода: GOTO <номер строки> Условный оператор: IF <условие> THEN оператор Оператор цикла: FOR <имя переменной> = <начальное значение> TO <конечное значение> последовательность операторов NEXT Оператор перехода к подпрограмме и возврат: GOSUB < номер строки >
тело подпрограммы RETURN Таблица 4 Ключевое слово (внешний формат)
Внутренний формат
PRINT INPUT IF THEN FOR NEXT
1 2 3 4 5 6
TO GOTO EOL FINISHED
7 8 9 10
GOSUB
11
RETURN
12
END
13
Содержание служебных слов Вывод Ввод Условие "если" Условие "то" Заголовок цикла "для" Ограничитель тела цикла Заголовок цикла "до" Безусловный переход Конец строки Конец исходного текста Переход к подпрограмме Возврат из п/программы Конец программы
2.2.1. Основной цикл работы анализатора Вводится текст, каждая лексема анализируется на принадлежность к типу переменных. В этом случае за переменной должен следовать оператор присваивания, а если нет, то выполняется проверка на распознавание команды. do { token_type = get_token(); if ( token_type==VARIABLE) { putback(); assignment(); } else switch(tok) { case PRINT:
print(); break; case IF: exec_if(); break; case GOTO: exec_goto(); break; case FOR: exec_for(); break; case NEXT: next(); break; case INPUT: input(); break; case GOSUB: gosub(); break; case RETURN: greturn(); break; case END: exit(0); } } while (tok!=FINISHED); } Из программного текста видно, что реализация анализа командных лексем сделана через case, а последовательное чтение входного потока через оператор с неизвестным количеством повтора do - while. 2.2.2. Оператор присваивания Для реализации оператора присваивания предлагается assignment(), связанная с результатом ввода по get_token(). <имя переменной>=<выражение> assignment() { int var, value; get_token();
функция
if (!isalpha(*token)) { serror(4); return 0; } var = toupper(*token)-'A'; get_token(); if (*token!='=') { serror(3); return 0; } get_exp(&value); variables[var]=value; } Функция get_exp(&value) вычисляет значение опознанной переменной, в противном случае выдается сообщение об ошибке. Простота рассмотренной функции объясняется тем, что основная обработка лексем по распознаванию выполняется в анализаторе выражений. 2.2.3. Оператор вывода Команда PRINT аналог стандартного оператора вывода языка Бейсик. Список переменных - перечисление выводимых переменных через запятую, либо точку с запятой; символьные константы заключаются в кавычки. PRINT <список переменных> /* Простейшая операция вывода */ void print() { int answer; int len=0, spaces; char last_delim, str[80]; do { get_token(); if ((tok==EOL) || (tok==FINISHED)) break; if (token_type==QUOTE) { printf(token); len += strlen(token); get_token();
} else { putback(); get_exp(&answer); get_token(); itoa(answer, str, 10); printf("%d", answer); len += strlen(str); } last_delim=*token; if (*token==',') { spaces=8-(len % 8); len += spaces; while(spaces) { printf(" "); spaces--; } } else if (*token==';'); else if (tok!=EOL && tok!=FINISHED) serror(0); } while ((*token==';') || (*token==',')); if (tok==EOL || tok==FINISHED) { if (last_delim !=';' && last_delim !=',') printf("\n"); } else serror(0); } Функция putback() предусмотрена для возврата лексемы во входной поток. Это необходимо в том случае, когда на вывод попадает выражение. В print() нет вывода выражений. 2.2.4. Оператор ввода Для ввода информации с клавиатуры и размещения ее в переменных предлагается оператор ввода: INPUT <имя переменной> Возможен и другой вариант ввода данных с подсказкой в виде записи <символьная строка>: INPUT "<символьная строка>" <имя переменной>
/* Реализация операции ввода */ void input() { char str[80],var; int i; get_token(); if (token_type==QUOTE) { get_token(); if(*token!=',')serror(1); get_token(); } else printf("?"); var = toupper(*token) -'A'; scanf("%d",&i); variables[var]=i; } Готовность ввода обозначена выводом знака "?" , и выполнение программы переходит в ожидание. 2.2.5. Оператор перехода Оператор перехода прост в использовании и сложен в реализации. В предложенном языке GOTO необходим для программного управления. Нумерация всех строк не обязательна кроме той строки, на которую выполняется ссылка. GOTO <номер строки> Основная сложность в реализации GOTO заключается в организации переходов не только вниз, но и вверх по тексту программы. Каждая метка с указателем места помещается в таблицу: struct label { char name[LAB_LEN]; char *p; }; struct label label_table[NUM_LAB];
Функция scan_labels() просматривает текст и размещает метки в таблице. void scan_labels() { int addr,i; char *temp; label_init(); temp=prog; get_token(); if (token_type==NUMBER) { strcpy(label_table[0].name,token); label_table[0].p=prog; } find_eol(); do { get_token(); if (token_type==NUMBER) { addr=get_next_label(token); if (addr==-1 || addr==-2) { (addr==-1) ?serror(5):serror(6); } strcpy(label_table[addr].name,token); label_table[addr].p=prog; } if (tok!=EOL) find_eol(); } while(tok!=FINISHED); prog=temp; } /* инициализация массива меток */ void label_init() { register int i; for (t=0; t
Возврат индекса свободного места в массиве меток для размещения очередной ссылки-метки. При этом выполняется проверка на дублирование меток код: -2 и переполнение массива меток код: -1. get_next_label(s) char *s; { register int t; for (t=0; t
IF <условие> THEN оператор void exec_if() { int x,y,cond; char op; get_exp(&x); get_token(); if (!strchr("=<>",*token)) { serror(0); return; } op=*token; get_exp(&y); cond=0; switch(op) { case '=': if (x==y) cond=1; break; case '<': if (x': if (x>y) cond=1; break; } if (cond) { get_token(); if (tok!=THEN) { serror(8); return; } } else find_eol(); } Функция exec_if() выполняется следующим образом: - вычислить значение левого выражения; - считать операцию сравнения; - вычислить значение правого выражения; - выполнить операцию сравнения;
- если условие истинно, то выполнить поиск THEN, в противном случае find_eol() выполнить переход на начало следующей строки. 2.2.7. Оператор цикла Предложенная версия оператора FOR реализует цикл только с приращением, равным 1 на каждом шаге цикла. Допускается вложенность цикла. Для этого используется стековая структура. FOR <имя переменной> = <начальное значение> TO <конечное значение> последовательность операторов NEXT Для реализации цикла FOR стек должен иметь следующую структуру: struct for_stack { int var; int target; char *loc; } fstack [FOR_NEST ]; struct for_stack fpop(); int ftos; Значение FOR_NEST ограничитель на вложенность цикла. Для обработки стека предлагаются функции fpush(i) и fpop(). /* перемещение результата в стек FOR */ void fpush(i) struct for_stack i; { if(ftos>FOR_NEST) serror(10); fstack[ftos]=i; ftos++; } struct for_stack fpop() { ftos--; if(ftos<0) serror(11); return(fstack[ftos]); }
Далее предлагается текст функций exec_for() и next() для реализации языковых конструкций FOR и NEХT. /* Цикл for */ void exec_for() { struct for_stack i; int value; get_token(); if (!isalpha(*token)) { serror(4); return; } i.var=toupper(*token)-'A'; get_token(); if(tok!='=') { serror(3); return; } get_token(); if(tok!=TO) serror(9); get_exp(&i.target); if(value>=variables[i.var]) { i.loc=prog; fpush(i); } else while(tok!=NEXT) get_token(); } /* Цикл - параметр конструкции - NEXT */ void next() { struct for_stack i; i=fpop(); variables[i.var]++; if(variables[i.var]>i.target) return; fpush(i); prog=i.loc; }
Следует отметить, использование предусмотрено в теле цикла.
оператора
GOTO
не
2.2.8. Оператор построения подпрограмм Язык Small BASIC не поддерживает отдельные подпрограммы, но с помощью оператора GOSUB и RETURN реализована возможность вызова отдельных частей программы. GOSUB < номер строки > . . тело подпрограммы . . RETURN Обращение GOSUB требует использования стека. Каждому RETURN соответствует единственный GOSUB. char *gstack[SUB_NEST]; int gtos; /* Операция обращения к заданной строке и возврат назад */ void gosub() { char *loc; get_token(); /* */ loc=find_label(token); if (loc=='\0') serror(7); else { gpush(prog); prog=loc; } } /* Операция возврата к заданной строке */ void greturn() {
prog=gpop(); } /* Результат в стек gosub*/ void gpush(s) char *s; { gtos++; if (gtos==SUB_NEST) { serror(12); return; } gstack[gtos]=s; } /* Проверка уровня вложенности gosub*/ char *gpop() { if (gtos==0) { serror(13); return; } (gstack[gtos--]); } Значение prog помещается в стек GOSUB, адрес строки, с которой начинается подпрограмма, помещается в prog. Когда встречается RETURN, из стека GOSUB извлекается очередное значение и присваивается prog. Оператор допускает вложенность GOSUB. Полный текст интерпретатора размещен на сайте www.de.nwpi.ru. 2.3. Тестирование В качестве тестов предлагаются простейшие программы, использующие языковые конструкции, заложенные для Small BASIC. Далее приведены примеры исходных текстов на языке Small BASIC с использованием GOSUB и протоколы выполнения программ. Протоколы демонстрируют процесс выполнения программы и правильность реализации простых вычислений.
Тест 1 PRINT "Команды ввода-вывода" PRINT " O'K " INPUT "H=",H IF H<22 THEN GOTO 200 PRINT "H>=22 (H-10)*2 Результат=",(H-10)*2 PRINT “ H/2=”;H/2 200 A=H+15 IF A>10 THEN PRINT "O'K A>10" PRINT “A=”;A PRINT “A+5=”;A+5 INPUT " H=",H PRINT “Ввели значение H=”;H INPUT "Это тест Y=";Y PRINT “ H+Y=”;H+Y IF A<15 THEN GOSUB 300 PRINT “ Тест выполнен” END 300 PRINT "Это подпрограмма А<15" RETURN Протокол 1 Команды ввода-вывода О'К Н=12 О'К A>10 A=27 A+5=32 H=5 Ввели значение H=5 Это тест Y=9 H+Y=14 Тест выполнен Протокол 2 Команды ввода-вывода О'К Н=7 О'К A>10 A=22 A+5=27
H=12 Это тест Y=1 H+Y=13 Тест выполнен Протокол 3 Команды ввода-вывода О'К Н=25 H>=22 (H-10)*2 Результат=24 H/2=12 О'К A>10 A=40 A+5=45 H=1 Это тест Y=1 H+Y=2 Это подпрограмма А<15 Тест 2 PRINT "Команды Small BASIC" FOR X=1 TO 5 PRINT X,X/2; X,X*X NEXT GOSUB 300 PRINT " Hello !!! " INPUT H IF H<11 THEN GOTO 200 PRINT 12-4/2 PRINT 100 200 A=100/2 IF A>10 THEN PRINT "O'K" PRINT A PRINT A+34 INPUT H PRINT H INPUT "Это тест y=",Y PRINT H+Y END 300 PRINT "ЭТО ПОДПРОГРАММА" RETURN
Протокол 1 Команды Small BASIC 1 01 1 2 12 4 3 13 9 4 2 4 16 5 2 5 25 ЭТО ПОДПРОГРАММА Hello!!! ?25 O'K 50 84 ?10 10 Это тест y=5 30 Протокол 2 Команды Small BASIC 1 01 1 2 12 4 3 13 9 4 2 4 16 5 2 5 25 ЭТО ПОДПРОГРАММА Hello!!! ?5 10 100 O'K 50 84 ?10 10 Это тест y=5 10
3. Алгоритмы построения рекурсивных функций Рекурсивность - это прием в программировании, когда при решении некоторой задачи предполагается обращение к алгоритму решения этой же задачи, т.е. в подпрограмме предусматриваются вызовы ее самое, но с другими параметрами. Рекурсия позволяет построить процедуру или функцию оптимального размера при хорошей читабельности алгоритма, а значит повысить надежность программы[2]. /* Калькулятор выражений */ #include <stdio.h> #include <math.h> #include <stdlib.h> #define false 0 #define true 1 // NextChar - для ввода значений int NextChar; int cont=true; // Прототипы float Factor(); float Term(); float Expression(); // void GetNextChar() { while ((NextChar = getchar()) == ' '); } // рекурсивная функция float Factor() // { float v=0; int count=0; int i; int d_p=false; // if ((NextChar>='0') && (NextChar<='9')) { while ((NextChar>='0') && (NextChar<='9')) { v = v * 10 + NextChar - '0';
NextChar=getchar(); if (d_p) count++; if (NextChar == '.') { NextChar=getchar(); d_p=true; } } for (i=0; i
{ case ' ' :GetNextChar(); break; case '*' :{GetNextChar(); v=v * Factor();} continue; case '^' :{GetNextChar(); v=pow(v,Factor());} continue; case '/' :{GetNextChar(); if ((d = Factor()) != 0) { v=v/d; continue;} else { printf("Ошибка!!! Деление на 0\n"); return 0; } } default: return v; } } } // формирование процесса вычисления введенного выражения // float Expression() { float v; v=Term(); for(;;) { switch(NextChar) { case ' ' :GetNextChar(); break; case '+' : {GetNextChar(); v=v+Term();} continue; case '-' :{GetNextChar(); v=v-Term();} continue; default: return v; } // } //
} // // главный модуль void main(void) { float r; while(1) { printf("\n ВВЕДИТЕ ВЫРАЖЕНИЕ:"); GetNextChar(); r=Expression(); if ((NextChar == '\n') && (cont)) { printf("Результат: %4.2f\n",r); continue; } else { if (NextChar == '\n') exit(0); else printf("Ошибка при вводе выражения \n"); continue; } } } Протокол выполнения ВВЕДИТЕ ВЫРАЖЕНИЕ: 48+52 Результат: 100.00 ВВЕДИТЕ ВЫРАЖЕНИЕ: 48/6+5 Результат: 13.00 ВВЕДИТЕ ВЫРАЖЕНИЕ: 48*2-6*(10-5) Результат: 66.00 ВВЕДИТЕ ВЫРАЖЕНИЕ: 48+д52 Ошибка при вводе выражения ВВЕДИТЕ ВЫРАЖЕНИЕ: (12+52 Пропущена закрывающая скобка ВВЕДИТЕ ВЫРАЖЕНИЕ: 35-7/(9-9) Ошибка!!! Деление на 0
4.Библиографический список 1. Шилдт Г. Теория и практика С++. /Пер. с англ. - СПб.: ВНV - СанктПетербург, 1996. 2. Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. М.: МЦНМО, 2000.
Содержание Введение……………………………………………….………………….……3 1. Задания на выполнение практических занятий….………………….……3 1.1 Общие указания к выполнению заданий на практических занятиях ..3 1.2 Варианты заданий ...……………………………… ……………………4 2. Принципы проектирования интерпретатора ………………..………….. 7 2.1. Синтаксический разбор выражений ………..…………………….. 7 2.1.1. Выражения ……………………………………………………… 7 2.1.2. Лексемы …………………………..……………………….……. 8 2.1.3. Построение выражений ……………………………………….. 10 2.1.4. Анализатор выражений …………………………….…………. 11 2.2. Интерпретатор Small BASIC …………………………..………… 11 2.2.1. Основной цикл работы анализатора………………………….. 14 2.2.2. Оператор присваивания…………………………..…………… 15 2.2.3. Оператор ввода …………………………..……………………. 16 2.2.4. Оператор вывода…………………………..…………………... 17 2.2.5. Оператор перехода…………………………..………………… 18 2.2.6. Условный оператор…………………………..……………….. 20 2.2.7. Оператор цикла…………………………..……………………. 21 2.2.8. Оператор построения подпрограмм………………………….. 23 2.3. Тестирование ……………………………………………………. 25 3. Алгоритмы построения рекурсивных функций………………………. 30 4. Библиографический список ……………………………………………. 35 Редактор М.Ю.Комарова Сводный темплан 2003 г. Лицензия ЛР N 020308 от 14.02.97 ____________________________________________________________ Подписано в печать Б.кн.-журн.
П.л. 2.25
Формат 60х84 1/16 Б.л. 1.125
РПТ РИО СЗТУ
Тираж 100 Заказ ____________________________________________________________ Северо-Западный государственный заочный технический университет
РИО СЗТУ, член Издательско-полиграфической ассоциации вузов Санкт-Петербурга 191186, Санкт-Петербург, ул. Миллионная, 5