Uploaded by Юрьевич Кирилл

Kompilyator PZ

advertisement
Министерство образования и науки Российской Федерации
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ
ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ
«ОРЕНБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ»
Факультет дистанционных образовательных технологий
КОНТРОЛЬНАЯ РАБОТА
по дисциплине «Теория автоматов и формальных языков»
Руководитель
_________________________
_________________________
«___» _______________2018 г.
Студент группы ФД-2ПИнж(ба)РПиС
________________ К.Ю. Лыба
«___» _______________2018 г.
Оренбург 2018
2
Оглавление
ВВЕДЕНИЕ
3
1.
ПОСТАНОВКА ЗАДАЧИ
5
2.
ФОРМАЛЬНАЯ МОДЕЛЬ ЗАДАЧИ
6
3.
СПЕЦИФИКАЦИЯ ОСНОВНЫХ ПРОЦЕДУР И ФУНКЦИЙ
10
3.1 ЛЕКСИЧЕСКИЙ АНАЛИЗАТОР
15
3.2 СИНТАКСИЧЕСКИЙ АНАЛИЗАТОР
15
3.3 СЕМАНТИЧЕСКИЙ АНАЛИЗАТОР
17
3.4 ГЕНЕРАЦИЯ ВНУТРЕННЕГО ПРЕДСТАВЛЕНИЯ ПРОГРАММЫ
17
3.5 ИНТЕРПРЕТАТОР ПРОГРАММЫ
18
4.
СТРУКТУРНАЯ ОРГАНИЗАЦИЯ ДАННЫХ
18
4.1 СПЕЦИФИКАЦИЯ ВХОДНОЙ ИНФОРМАЦИИ
18
4.2 СПЕЦИФИКАЦИЯ ВЫХОДНОЙ ИНФОРМАЦИИ
19
5.
РАЗРАБОТКА АЛГОРИТМА РЕШЕНИЯ ЗАДАЧИ
19
5.1 УКРУПНЕННАЯ СХЕМА АЛГОРИТМА ПРОГРАММНОГО СРЕДСТВА
19
5.2 ДЕТАЛЬНАЯ РАЗРАБОТКА АЛГОРИТМОВ ОТДЕЛЬНЫХ ПОДЗАДАЧ
19
6.
УСТАНОВКА И ЭКСПЛУАТАЦИЯ ПРОГРАММНОГО СРЕДСТВА
19
7.
РАБОТА С ПРОГРАММНЫМ СРЕДСТВОМ
20
ЗАКЛЮЧЕНИЕ
24
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
24
ПРИЛОЖЕНИЕ А – ТЕКСТ ПРОГРАММЫ
25
ПРИЛОЖЕНИЕ Б – КОНТРОЛЬНЫЕ ПРИМЕРЫ
79
2
3
Введение
В
данной
пояснительной
записке
представлены
неформальное
описание разработанного языка программирования и большое количество
примеров, демонстрирующих работоспособность созданного компилятора.
Термин трансляция программы означает весь процесс преобразования
программы, написанной на исходном языке, в рабочее состояние. Этот
процесс обычно осуществляется тремя последовательно соединенными
блоками: лексическим блоком, синтаксическим блоком и генератором кода.
Эти три блока имеют доступ к общему набору таблиц, куда можно помещать
глобальную информацию о программе. Одна из них, например, - это таблица
идентификаторов,
в
которой
накапливается
информация
о
каждой
переменной. Фактически такие блоки означают так называемые проходы
трансляции, которые могут быть совмещенными, когда синтаксический блок
не ожидает окончательного завершения работы лексического блока, а
периодически обращается к нему по мере необходимости.
Генератор
кода
обычно
порождает
команды
ассемблера
соответствующей ЭВМ, чем достигается максимальная скорость выполнения
программы. В этом случае транслятор называется компилятором. Если же
результатом
генерации
является
код
некоторой
виртуальной
ЭВМ
(виртуальность означает, что не существует реального процессора с такими
машинными командами), то транслятор является интерпретатором. Так как
скомпилированную программу исполняют непосредственно машинные
команды ЭВМ, а интерпретирующую программу выполняет промежуточная
программа, то обычно скорость выполнения интерпретирующей программы
на порядок ниже скомпилированной. Однако для интерпретатора гораздо
проще создавать отладочные средства, да и мобильность (переносимость)
интерпретирующей программы намного выше скомпилированной.
3
4
Лексический блок предназначен для того, чтобы разбивать цепочку
символов исходной программы на слова, которые являются лексемами:
идентификаторами, числами, строками и другими элементами программы.
Лексический блок также игнорирует комментарии и пустые символы. В
результате
работа
синтаксического
блока
значительно
упрощается.
Сложность построения лексического блока самая низкая по сравнению с
другими блоками транслятора (впрочем, это не относится к языку
программирования
Фортран,
который
слишком
перегружен
всякими
умолчаниями в написании программы). Разработка лексического блока
основана на теории конечных автоматов.
Синтаксический
блок
переводит
последовательность
лексем,
построенную лексическим блоком, в другую последовательность, которая
непосредственно отражает порядок, в котором должны выполняться
операции в программе. Например, если программист пишет выражение
A+B*C, он подразумевает, что числа, представленные идентификаторами B и
C, будут перемножены, и к результату прибавлено число, представленное
идентификаторм A. Указанное выражение можно перевести так:
УМНОЖ(B,C,R1), СЛОЖ (A,R1,R2)
где УМНОЖ(B,C,R1) интерпретируется как "умножить B на C и заслать
результат в R1", а СЛОЖ (A,R1,R2) интерпретируется как "сложить A и R1 и
поместить результат в R2". Таким образом, пять лексем ('A', '+','B', '*', 'C'),
выданных лексическим блоком, преобразуются в две новые единицы,
которые описывают то же действие.
Наряду
семантические
с
синтаксическим
условия,
в
разбором
частности,
необходимо
наличие
выполнять
дубликатных
или
неопределенных переменных, возможность выполнения контекста некоторых
операторов (продолжение или выполнение цикла) и т.д.
4
5
Заключительным действием транслятора является порождение команд
реальной ЭВМ или псевдокоманд для интерпретации (в нашем случае будет
применяться ПОЛИЗ – польская инверсная запись, которая будет содержать
как операнды, так и операции интерпретации в заданном семантическом
порядке).
Реальные компиляторы обычно создают синтаксическое дерево
разбора, которое может быть успешно использовано при оптимизации
скомпилированной программы; число проходов компилятора может быть
даже более трех, но в данной работе для простоты будет представлен
однопроходный компилятор. Считается, что конструирование компиляторов
в настоящее время почти полностью формализовано до такой степени, что
существуют даже системы автоматической генерации компилятора по
атрибутной грамматике, содержащей в себе как описание синтаксиса, так и
семантики языка. Однако такие системы (например, YACC) не могут учесть
все нюансы транслируемого языка. В данной работе для анализа
арифметических и логических выражений будеть применяться метод
рекурсивного спуска, который позволяет простым и удобным способом
генерировать для этих выражений ПОЛИЗ.
1. Постановка задачи
В соответствии с заданным вариантом необходимо разработать
компилятор и интерпретатор модельного языка. Для более реальных
тестовых программ в данной работе исходный модельный язык был
расширен некоторыми операторами, что позволило продемонстрировать
множество интересных задач и показать тем самым надежную работу
компилятора. При этом реализация таких дополнительных операторов не
привнесло дополнительной сложности в функционирование компилятора.
Компилятор выполнен по однопроходной схеме, что позволило
значительно упростить его структуру. Лексический анализатор выполнен в
виде класса, сам же синтаксический разбор построен в виде обычной
5
6
структурированной функции. В качестве среды разработки был использован
компилятор Builder C++ 6.0 как наиболее подходящий для построения
оконных приложений (как по нашему мнению, даже более подходящий, чем
C#).
2. Формальная модель задачи
Модельный язык программирования может быть представлен в виде
следующей формальной грамматики (синим цветом в грамматике выделены
дополнительные конструкции языка, привнесенные для более практического
применения компилируемого языка):
<программа> ::= "{"{/(<описание> | <оператор>);/} "}"
<выражение>::=<операнд>{<операции_группы_отношения><операнд>}
<операнд>::=<слагаемое>{<операции_группы_сложения><слагаемое>}
<слагаемое>::=<множитель>{<операции_группы_умножения><множитель>}
<множитель>::=<идентификатор>|<число>|<логическая_константа>|
<унарная_операция><множитель>(<выражение>)
<число>::= <целое>|<действительное>
<логическая_константа>::=true|false
<идентификатор>::=<буква>{<буква>|<цифра>}
<буква>::=A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|
a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|_
<цифра>::=0|1|2|3|4|5|6|7|8|9|0
<целое>::=<двоичное>|<восьмеричное>|<десятичное>|<шестнадцатеричное>
<двоичное>::={/0|1/}(B|b)
<восьмеричное>::={/0|1|2|3|4|5|6|7/}(O|o)
<десятичное>::={/<цифра>/}[D|d]
6
7
<шестнадцатеричное>::=<цифра>{<цифра>|A|B|C|D|E|F|a|b|c|d|e|f}(H|h)
<действительное>::=<числовая_строка><порядок>|
[<числовая_строка>].<числовая_строка>[<порядок>]
<числовая_строка>::={/<цифра>/}
<порядок>::=(E|e)[+|-]<числовая_строка>
<операции_группы_отношения> ::= <> | = | < | <= | > | >=
<операции_группы_сложениия> ::= + | – | or
<операции_группы_умножениия> ::= * | / | mod | and
<унарная_операция> ::= not | + | <описание> ::= {<идентификатор> {, <идентификатор>} : <тип>;}
<тип:целый,действ,логич> ::= % | ! | $
<оператор> ::= <составной> | <присваивания> | <условный> |
<фиксированного_цикла>|<условного_цикла>|<ввода>|<вывода>|
<добавленные>
<составной> ::="{" <оператор> {; <оператор> } "}"
<присваивания> ::= [let] <идентификатор> = <выражение>
<условный> ::= if <выражение> then <оператор> [else <оператор>] end_else
<фиксированного_цикла>::=for([<присваивания>];[<выражение>];
[<присваивания>]) <оператор>
<условного_цикла> ::= do while <выражение> <оператор> loop
<ввода>::= input ([<строка>] <идент> {пробел [<строка>] <идент>})
<вывода>::=output(<выражение>[:<число>]{пробел<выражение>[:<число>]})
<добавленные>::= stop() | break | continue | erase() | outputln | rand() |
rand_real()
7
8
<комментарий>
::= /* */ | //
Определим следующие семантические условия. Символьная строка
представляет собой набор символов, взятых в кавычки, причем символ ‘#’
интерпретируется как перевод строки (это означает невозможность печати
символа кавычки, который, впрочем можно заменить апострофом). Оператор
output может печатать наряду с выражениями также символьные строки,
причем печатаемый текст не отправляется в результат до тех пор, пока в в
символьной строке не встретится символ ‘#’ или же не выполнится оператор
outputln, который выполняет печать с переводом строки. При этом оператор
outputln может быть записан без параметров в виде outputln(), что означает
печать одного перевода строки.
Если при печати вслед за выражением следует целое число, то для
целочисленного выражения это означает размер печатаемого поля с
выпавниванием текста вправо (что очень удобно при печати табличных
данных), а для действительного – число цифр после десятичной точки.
Оператор break означает выход из соответствующего цикла. Оператор
continue для фиксированного цикла означает переход к присваиванию,
выполняющему переадресацию цикла или же к проверке выполнения цикла,
если переадресация отсутствует. Впрочем, если проверка выполнения цикла
отсутствует, переход выполняется к телу цикла. Для условного цикла
оператор continue всегда выполняет переход к проверке выполнения цикла.
Разумеется, операторы break и continue могут быть определены только
внутри циклов.
Оператор erase() предназначен для очистки напечатанного результата,
что удобно при многократном выполнении теста.
Оператор stop() предназначен для досрочного завершения выполнения
программы, не дожидаясь выполнения последнего оператора программы.
8
9
Оператор rand() предназначен для генерации случайного целого числа
и может быть использован как число только в выражениях. Аналогично,
оператор rand_real() эквивалентен случайному вещественному числу в
диапазоне от 0 до 1.
Операция mod означает получение остатка от деления целых чисел.
Значение логической константы false принимается за числовое
значение 0, константы true – соответственно, за 1.
В выражениях допускаются любые комбинации целых и вещественных
операндов, причем результат операции будет вещественным, если хотя бы
один операнд вещественный (как это принято в языке Си). Если выполняется
присваивание вещественного выражения целочисленной переменной, это не
считается ошибкой, а выражение приводится к целому виду. При
присваивании любого выражения логической переменной считается, что
нулевое значение выражения есть false, ненулевое – true.
Если в операторе input перед вводимой переменной указана строка, в
диалоговом
окне
ввода
значения
эта
строка
выступает
в
роли
комментарийного текста, поясняющего смысл операции ввода.
Однострочный комментарий вида // аналогичен соответствующему
комментарию языка C++.
Следует заметить, что целые числа в модельном языке определены не
совсем удачно, так как тип константы определяется не сразу, что привносит
некоторую сложность в лексический разбор. Такое определение целых чисел
обычно встречается в ассемблерных языках. В языках высокого уровня
константы обычно определяются так, чтобы их префиксная часть сразу же
определяла тип константы (сравните формат записи шестнадцатеричного
числа в Си как 0xAB01 и в нашем модельном языке в виде 0AB01H).
9
10
Определение
языка
можно
было
бы
представить
также
в
эквивалентном виде диаграмм Вирта, однако это заняло бы очень много
места и не добавило ничего нового по сравнению с формальной
грамматикой.
3. Спецификация основных процедур и функций
Как уже было сказано ранее, лексический анализатор выполнен в виде
класса, определение которого выглядит следующим образом:
// Класс распознавания лексем
class Lex
{
private:
// Состояния конечного автомата лексического анализатора
enum
{
BeginLex,
// Начало лексемы
ContinueIdent,
// Продолжение идентификатора
ContinueNum,
// Продолжение числа
ContinueComment, // Продолжение многострочного комментария
BeginEndComment, // Начало завершения многострочного комментария
EndComment,
// Завершение комментария
EndString,
// Завершение строки
ContinueLt,
// Продолжение разбора <
ContinueGt
// Продолжение разбора >
};
// Окно с программой на вкладке лексического анализа
TMemo
*memo;
// Окно с исходной программой
TRichEdit
*re;
// Остальные таблицы с результатом лексического разбора
TStringGrid *grid3, *grid4, *grid5;
AnsiString str;
int
n2;
// true - если это новый идентификатор
bool new_ident;
// Координаты возможной ошибочной лексемы
int beg_num_save, beg_ind_save, len_lex_save;
// Индекс результата
int ind_result;
// Строка результата
AnsiString result;
// Количество переменных в таблице на начало оператора описания переменных
int beg_cnt_var;
10
11
// Количество чисел
int cnt_number;
// Количество символьных строк
int cnt_sym_str;
// Текущее состояние автомата
int state;
// Текст лексической ошибки
AnsiString txt_error;
// Индекс символа в исходной строке
int ind;
// Номер исходной строки
int num;
// Двоичное значение целого числа
int val;
// Двоичное значение вещественного числа
double vald;
// Текущий сканируемый символ исходной программы
char c;
// Признак чтения символа из файла (true - если символ уже прочитан в 'c')
bool yes_c;
// Текущий идентификатор
AnsiString ident;
// Индекс начала лексемы
int beg_ind;
// Длина лексемы
int len_lex;
// Номер строки с лексемой
int beg_num;
// Вернуть -1 или код односимвольного разделителя
int get_delim(void);
// Вернуть -1 или код служебного слова
int get_word(void);
// Сбор шестнадцатиричного числа
void make16(void);
// Сбор десятичного числа
void make10(void);
// Сбор вещественного числа
void make_double(void);
// Сбор двоичного числа с контролем на ошибку
int make2(void);
// Сбор восьмеричного числа с контролем на ошибку
int make8(void);
11
12
// Поместить идентификатор в таблицу переменных;
// если это возможно - return индекс таблицы, иначе -1
int set_var(void);
// Поместить число в таблицу чисел;
// если это возможно - return индекс таблицы, иначе -1
// t - INTEGER или REAL
int set_number(int t);
// Поместить символьную строку в таблицу строк;
// если это возможно - return индекс таблицы, иначе -1
int set_sym_str(void);
// Отправить лексему в результат
void out_result(int n1, int n2);
// Чтение очередного символа программы
// В случае конца программы возвращается 0
// Для конца строки возвращается '\n'
char Input(void);
public:
// Конструктор, которому передается адрес объекта окна редактирования программы
// и адрес объекта окна результата
Lex
(
TRichEdit *p1,
TMemo *p2,
TStringGrid *p3,
TStringGrid *p4,
TStringGrid *p5,
TMemo *p6
);
// Инициализация начала описания переменных
void init_declare(void);
// Установка типов переменных после трансляции очередного описания
void set_declare(int type);
// Контроль идентификатора на дубликат при описании
void double_ident(void);
// Контроль идентификатора на неопределенность
void undef_ident(void);
// Контроль идентификатора на неопределенность
void undef_ident0(void);
// Опубликовать таблицы идентификаторов, чисел и строк
void show_table(void);
// Возбудить исключительную ситуацию по ошибке
void er(char *s);
// Чтение идентификатора
AnsiString getIdent(void);
// Чтение числовой константы
int getNum(void);
double getNumD(void);
12
13
// Чтение номера строки с лексической ошибкой
int getLineNum(void);
// Чтение позиции в строке с лексической ошибкой
int getLinePos(void);
// Чтение длины ошибочной лексемы
int getLenLex(void);
// Сохранить координаты возможной ошибочной лексемы
void save_coord(void);
// Восстановить координаты ошибочной лексемы
void restore_coord(void);
// Вернуть наименование лексической ошибки
AnsiString Error(void);
// Прочитать очередную лексему
// Если ошибок нет, тип лексемы содержится в type
int lex(void);
};
Собственно сам синтаксический разбор и генерация результирующей
программы выполняется с помощью следующего набора функций:
// Если текущая лексема не t, сообщить об ошибке s
void __fastcall TForm1::wait_lex(int t, char *s);
// Чтение очередной лексемы
int __fastcall TForm1::get_lex(void);
// Чтение очередной лексемы и проверка ее на END
int __fastcall TForm1::get_end(void);
// Прочитать лексему, совпадающую с параметром p
// mess - название лексемы
void __fastcall TForm1::token(int p, char *mess);
// Компиляция выражения
void __fastcall TForm1::expr(void);
// Операции отношения
void __fastcall TForm1::exp1(void);
// Операции сложения
void __fastcall TForm1::exp2(void);
// Операции умножения и деления
void __fastcall TForm1::exp3(void);
// Унарный +, - или not
void __fastcall TForm1::exp4(void);
// Выражение в скобках
void __fastcall TForm1::exp5(void);
// Получение идентификатора или числа
void __fastcall TForm1::atom(void);
// Поместить в ПОЛИЗ очередной элемент
void __fastcall TForm1::put_poliz(char op, int t, int n);
13
14
// Компиляция оператора присваивания
void __fastcall TForm1::compile_set(void);
// Оператор if
void __fastcall TForm1::oper_if(void);
// Оператор continue
void __fastcall TForm1::oper_continue(void);
// Оператор break
void __fastcall TForm1::oper_break(void);
// Оператор do
void __fastcall TForm1::oper_do(void);
// Оператор for
void __fastcall TForm1::oper_for(void);
// Оператор output
void __fastcall TForm1::oper_output(void);
// Оператор outputln
void __fastcall TForm1::oper_outputln(void);
// Оператор input
void __fastcall TForm1::oper_input(void);
// Оператор stop (завершить выполнение)
void __fastcall TForm1::oper_stop(void);
// Оператор erase() (стереть окно результата)
void __fastcall TForm1::oper_erase(void);
// Оператор
void __fastcall TForm1::oper(void);
// Оператор объявления переменных
// На входе уже прочитан первый идентификатор
// и лексемы ":" или ","
void __fastcall TForm1::declare(void);
// Трансляция
void __fastcall TForm1::compile(void);
Интерпретация ПОЛИЗа выполняется с помощью следующего набора
функций:
// Получить значение из элемента n стека операндов
// Например, для верхнего элемента стека n = 1
VT __fastcall TForm1::get_elem(int n);
// Получить два операнда из стека операндов
void __fastcall TForm1::get_elem2(void);
// Сброс незначащих нулей дробной части
void __fastcall TForm1::delete_zero(void);
// Интерпретация ПОЛИЗа
void __fastcall TForm1::run_poliz(void);
14
15
3.1 Лексический анализатор
Лексический анализатор реализован в виде представленного выше
класса.
Существенно,
что
лексический
анализатор
при
работе
синтаксического разбора возвращает не номер таблицы и позицию лексемы в
этой таблице, а только одно число – собственно код лексемы. Возврат в виде
двух параметров реализован только для иллюстрации работы лексического в
виде соответствующих таблиц. Но для реального синтаксического разбора
такой возврат лексемы чрезвычайно неудобен, поэтому синтаксический
анализатор работает только с одним параметром, который является
обобщенным понятием лексемы, т.е. лексемы ‘<>’ и
input
при анализе
программы не должны отличаться как представители разных таблиц.
Реализация лексического анализатора выполнена по классической
схеме работы по состояниям, хотя проще можно было бы выполнить простой
прямой разбор. В случае опрежеления ошибки лексический анализатор
возбуждает
исключительную
ситуацию,
что
позволило
значительно
упростить обнаружение ошибок.
Иллюстрация работы лексического анализатора в виде диаграммы
конечного состояния здесь не приводится, поскольку довольно подробно
закомментированный код дает гораздо больше информации, достаточной для
понимания работы лексического анализатора, чем его схематическое
изображение в виде конечного автомата.
3.2 Синтаксический анализатор
Синтаксический анализатор выполняет две основные задачи: проверка
правильности конструкций программы, которая представляется в виде уже
выделенных слов входного языка, и преобразование её в вид, удобный для
дальнейшей семантической (смысловой) обработки и генерации кода.
15
16
Синтаксический разбор выполнен в виде прямого разбора без
использования синтаксически ориентированных алгоритмов, за исключением
разбора выражений, где применялся метод рекурсивного спуска.
В основе метода рекурсивного спуска лежит левосторонний разбор
строки языка. Исходной сентенциальной формой является начальный символ
грамматики, а целевой – заданная строка языка. На каждом шаге разбора
правило грамматики применяется к самому левому нетерминалу сентенции.
Данный процесс соответствует построению дерева разбора цепочки сверху
вниз (от корня к листьям).
Дана
G({a, b, c, }, {S , A, B}, P, S )
грамматика
с
правилами
P : 1) S  AB ; 2) A  a; 3) A  cA; 4) B  bA . Требуется выполнить
анализ строки cabca.
Левосторонний вывод цепочки имеет вид:
S  AB  cAB  caB  cabA  cabcA  cabca  .
Нисходящее дерево разбора цепочки представлено на рис 1.
S
A
c
A
a
B

b
A
c
A
a
Рис.1 Дерево нисходящего разбора цепочки cabca
Метод рекурсивного спуска реализует разбор цепочки сверху вниз
следующим образом. Для каждого нетерминального символа грамматики
своя процедура, носящая его имя. Задача этой процедуры – начиная с
указанного места исходной цепочки, найти подцепочку, которая выводится
16
17
из этого нетерминала. Если такую подцепочку считать не удается, то
процедура завершает свою работу вызовом процедуры обработки ошибок,
которая выдает сообщение о том, что цепочка не принадлежит языку
грамматики и останавливает разбор. Если подцепочку удалось найти, то
работа процедуры считается нормально завершенной и осуществляется
возврат в точку вызова. Тело каждой такой процедуры составляется
непосредственно по правилам вывода соответствующего нетерминала, при
этом
терминалы
распознаются
самой
процедурой,
а
нетерминалам
соответствуют вызовы процедур, носящих их имена.
Работу функций по разбору выражений методом рекурсивного спуска
можно увидеть в соответствующем приложении.
3.3 Семантический анализатор
Семантический
анализ
программы
конкретизирует
работу
с
идентификаторами (определяет их дубликат или отсутствие описания), а
также контролирует использование операторов continue и break. При
компиляции выражений был принят принцип умолчания языка C++, который
позволяет комбинировать все типы числовых данных без их явного
приведения.
3.4 Генерация внутреннего представления программы
Программа генерируется в виде ПОЛИЗа, причем не только
выражения, а собственно все элементы языка. Элемент ПОЛИЗа представлен
в виде следующей структуры:
// Элемент ПОЛИЗа
struct POLIZ
{
// 0 - операнд, 1 - операция
char op;
// Для операции
// Для обычной переменной
// Для целой константы
// Для вещественной константы
int t;
-
соответствующая операция
IDENT
NUM
NUMD
17
18
// Для операнда - индекс в таблице или номер рабочей переменной
int n;
};
Формирование ПОЛИЗа происходит в процессе синтаксического
разбора, когда после формирования операндов в ПОЛИЗ помещается
соответствующая операция. Работу функций по генерации ПОЛИЗа можно
увидеть в соответствующем приложении.
3.5 Интерпретатор программы
Интерпретатор программы выполняет ПОЛИЗ при помощи стека
операндов,
куда
соответствующей
помещаются
операцией.
все
При
операнды,
обнаружении
находящиеся
элемента
перед
ПОЛИЗа,
представляющего собой операцию, интерпретатор извлекает из стека
операнды в количестве, необходимом для выполнения операции, выполняет
операцию над операндами и помещает результат в стек операндов в виде
рабочей переменной целого или вещественного типа.
В процессе интерпретации выполняется возможный контроль на
зацикливание программы и деление на ноль. Ввод данных выполняется через
интерфейс окна, который содержит обозначение вводимой переменной и
возможный комментарий к текущему вводу. Результат оператора вывода
отображается в соответствующей вкладке компилятора.
4. Структурная организация данных
Данные описаны в виде соответствующих структур, приведенных в
исходном тексте программы и здесь повторно не описываются, поскольку
довольно подробно закомментированы.
4.1 Спецификация входной информации
Входная информация представлена в виде текста компилируемой
программы, размещенного в программном компоненте RichEdit.
18
19
4.2 Спецификация выходной информации
Выходная
информация
представлена
иллюстрацией
работы
лексического анализатора в виде таблиц и собственно самим результатом
выполнения программы в виде текстовых сообщений.
5. Разработка алгоритма решения задачи
Компилятор реализован в виде одного прохода при помощи хорошо
структурированных
функций,
которые
можно
посмотреть
в
соответствующем приложении.
5.1 Укрупненная схема алгоритма программного средства
Поскольку компилятор реализован в виде оконного приложения, то не
имеет
особого
смысла
определять
укрупненную
схему
алгоритма
программного средства, т.к. управление работой компилятора управляется
при помощи очевидного меню.
5.2 Детальная разработка алгоритмов отдельных подзадач
Разработка всех алгоритмов тщательно закомментирована и структурно
определена в виде соответствующих функций. Здесь их повторное описание
не
даст
никакого
дополнительного
понимания
и
будет
простым
дублированием исходного текста компилятора.
6. Установка и эксплуатация программного средства
Компилятор представляет собой самодостаточное приложение в виде
файла Project1.exe, который при желании можно переименовать в требуемое
значение. Этот файл можно скопировать в любое доступное место на диске и
запустить его на выполнение. Никаких дополнительных библиотек,
инсталляций или регистраций не требуется; выполнение компилятора
допустимо во всех ОС Windows, начиная с версии XP. Никаких особых
19
20
требований к производительности компилятор не предъявляет, конфигурация
компьютера может быть минимально стандартной.
7. Работа с программным средством
При
запуске
компилятора
его
интерфейс
будет
представлен
слежующим образом:
Рис.2 Интерфейс компилятора при запуске
Окно компилятора содержит три вкладки, их смысл определен
соответствующим заголовком. Программу можно заготовить самостоятельно
на вкладке Исходная программа, загрузить из обычного символьного файла
при помощи меню Файл или же выбрать как один из встроенных примеров
(см. Приложение A). При помощи меню Трансляция в случае безошибочной
20
21
компиляции на вкладке Лексический анализ можно увидеть результат
лексического анализа:
Рис.3 Результат лексического анализа
После этого при помощи меню Выполнение на вкладке Результат
выполнения можно будет увидеть сам результат:
Рис.4 Результат выполнения
21
22
Если в процессе выполнения программы встречается оператор input с
комментарийным текстом, как например на следующем фрагменте:
Рис.5 Оператор ввода с комментарием
то ввод переменной count будет выполнен при помощи следующего окна:
Рис.6 Ввод переменной с комментарием
22
23
Обратите внимание, что комментарийный текст может содержать отдельные
части, разделенные символом ‘#’. В этом случае комментарий будет
отображен несколькими строками. Если комментарийный текст при вводе
отсутствует, то окно ввода переменной не будет содержать области вывода
комментария. При нажатии на кнопку OK выполняется контроль введенного
значения и присваивание его соответствующей переменной. По кнопке NO
можно не менять значение переменной и продолжить выполнение
программы. По кнопке Завершить выполняется досрочное завершение
выполнения программы.
По умолчанию при запуске компилятора не выполняется контроль
зацикливания программы при ее интерпретации. При необходимости
ограничить число выполняемых операций можно воспользоваться меню
Контроль зацикливания.
Наряду
с
зацикливанием,
при
интерпретации
программы
контролируется деление на ноль для операций / и mod.
Если же при трансляции будет обнаружена какая-то ошибка, эта
ошибка в исходном тексте программы окрашивается красным цветом, а в
статусной строке появляется соответствующий текст ошибки:
Рис.7 Отображение ошибок трансляции
23
24
Заключение
В данной работе реализован интерпретатор модельного языка
программирования, который был расширен дополнительными операторами,
позволившими значительно увеличить выразительность тестовых программ.
Довольно большое количество этих тестов (а не пять!) демонстрирует все
возможности применения элементов модельного языка и вполне могут
служить подтверждением безошибочной и устойчивой работы компилятора.
Дальнейшим развитием данного программного продукта может быть
дополнение
его
функционала
средствами
отладки:
шаговый
и
автоматический режим, точки останова и условные остановы по значениям
переменных. Также желательно в модельный язык добавить типы данных
массив (хотя бы одномерных), что позволит резко увеличить сферу
применения модельного языка для реализации всевозможных алгоритмов.
Список использованных источников
1). Д.Грис Конструирование компиляторов для цифровых вычислительных
машин. Издательство “Мир”, М. 1975
2). Уильям Топп, Уильям Форд. Структуры данных в C++. ЗАО Бином, 1999
3). Бьерн Страуструп Язык программирования С++. Второе дополненное
издание – М.: Бином, 2004
4) Г. Шилдт Самоучитель С++. 3-е дополненное издание – М.: BHV, 1998
24
25
Приложение А – Текст программы
Unit1.h
#ifndef Unit1H
#define Unit1H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<Classes.hpp>
<Controls.hpp>
<StdCtrls.hpp>
<Forms.hpp>
<Menus.hpp>
<ComCtrls.hpp>
<Dialogs.hpp>
<stdio.h>
<ctype.h>
<stdlib.h>
<time.h>
<Grids.hpp>
// Значение и тип элемента стека операндов
struct VT
{
int
type_elem;
double value_elem;
};
// Максимальное количество чисел
#define MAX_NUMBER 100
// Максимальное количество идентификаторов
#define MAX_VAR
100
// Максимальное количество символьных строк
#define MAX_SYM_STR 100
class TForm1 : public TForm
{
__published: // IDE-managed Components
TMainMenu * MainMenu1;
TMenuItem *N1;
TMenuItem *N2;
TMenuItem *N3;
TMenuItem *N4;
TMenuItem *N5;
TMenuItem *N6;
TMenuItem *N7;
TPageControl *PageControl1;
TTabSheet *TabSheet1;
TTabSheet *TabSheet2;
TTabSheet *TabSheet4;
TRichEdit *RichEdit1;
TStatusBar *StatusBar1;
TMenuItem *N8;
TMenuItem *N11;
TMenuItem *N21;
TMenuItem *N31;
TMenuItem *N41;
TMenuItem *N51;
TMenuItem *N9;
TOpenDialog *OpenDialog1;
TSaveDialog *SaveDialog1;
TMenuItem *N10;
TStringGrid *StringGrid1;
TLabel *Label1;
25
26
TLabel *Label2;
TStringGrid *StringGrid2;
TLabel *Label3;
TStringGrid *StringGrid3;
TLabel *Label4;
TStringGrid *StringGrid4;
TMemo *Memo1;
TLabel *Label5;
TLabel *Label6;
TMemo *Memo2;
TLabel *Label7;
TStringGrid *StringGrid5;
TMemo *Memo3;
TMenuItem *N12;
TMenuItem *N13;
TMenuItem *N10001;
TMenuItem *N100001;
TMenuItem *N1000001;
TMenuItem *N14;
TMenuItem *N15;
TMenuItem *N16;
TMenuItem *N17;
TMenuItem *N18;
TMenuItem *N19;
TMenuItem *N20;
TMenuItem *N22;
TMenuItem *N23;
TMenuItem *N24;
TMenuItem *N25;
void __fastcall N2Click(TObject *Sender);
void __fastcall N3Click(TObject *Sender);
void __fastcall N4Click(TObject *Sender);
void __fastcall N5Click(TObject *Sender);
void __fastcall N6Click(TObject *Sender);
void __fastcall N7Click(TObject *Sender);
void __fastcall N11Click(TObject *Sender);
void __fastcall N21Click(TObject *Sender);
void __fastcall N31Click(TObject *Sender);
void __fastcall N41Click(TObject *Sender);
void __fastcall N51Click(TObject *Sender);
void __fastcall N9Click(TObject *Sender);
void __fastcall FormCreate(TObject *Sender);
void __fastcall RichEdit1MouseMove(TObject *Sender, TShiftState Shift,
int X, int Y);
void __fastcall N10Click(TObject *Sender);
void __fastcall N13Click(TObject *Sender);
void __fastcall N10001Click(TObject *Sender);
void __fastcall N100001Click(TObject *Sender);
void __fastcall N1000001Click(TObject *Sender);
void __fastcall N14Click(TObject *Sender);
void __fastcall N15Click(TObject *Sender);
void __fastcall N16Click(TObject *Sender);
void __fastcall N17Click(TObject *Sender);
void __fastcall N18Click(TObject *Sender);
void __fastcall N19Click(TObject *Sender);
void __fastcall N20Click(TObject *Sender);
void __fastcall N22Click(TObject *Sender);
void __fastcall N23Click(TObject *Sender);
void __fastcall N24Click(TObject *Sender);
void __fastcall N25Click(TObject *Sender);
26
27
private:
// User declarations
// Позиция курсора мыши в окне редактирования
TPoint Tp;
// Директория запуска программы
AnsiString Patch;
// Текст ошибки
AnsiString err;
// Текущая прочитанная лексема
int lex;
// Загрузка примера
void __fastcall Example(char *s);
// Обновление статуса программы
void __fastcall UpdateStatus(void);
// Сброс ошибки
void __fastcall ClearError(void);
// Компиляция
void __fastcall compile(void);
// Если текущая лексема не t, сообщить об ошибке s
void __fastcall wait_lex(int t, char *s);
// Чтение очередной лексемы
int __fastcall get_lex(void);
// Чтение очередной лексемы и проверка ее на END
int __fastcall get_end(void);
// Прочитать лексему, совпадающую с параметром p
// mess - название лексемы
void __fastcall token(int p, char *mess);
// Компиляция оператора присваивания
void __fastcall compile_set(void);
// Компиляция выражения
void __fastcall expr(void);
// Операции отношения
void __fastcall exp1(void);
// Операции сложения
void __fastcall exp2(void);
// Операции умножения и деления
void __fastcall exp3(void);
// Унарный + или void __fastcall exp4(void);
// Выражение в скобках
void __fastcall exp5(void);
// Получение идентификатора или числа
void __fastcall atom(void);
27
28
// Оператор объявления переменных
// На входе уже прочитан первый идентификатор
// и лексемы ":" или ","
void __fastcall declare(void);
// Оператор
void __fastcall oper(void);
// Оператор if
void __fastcall oper_if(void);
// Оператор do
void __fastcall oper_do(void);
// Оператор for
void __fastcall oper_for(void);
// Оператор output
void __fastcall oper_output(void);
// Оператор outputln
void __fastcall oper_outputln(void);
// Оператор input
void __fastcall oper_input(void);
// Оператор stop()
void __fastcall oper_stop(void);
// Оператор continue
void __fastcall oper_continue(void);
// Оператор break
void __fastcall oper_break(void);
// Оператор erase() (стереть окно результата)
void __fastcall oper_erase(void);
// Интерпретация ПОЛИЗа
void __fastcall run_poliz(void);
// Получить значение из элемента n стека операндов
VT __fastcall get_elem(int n);
// Получить два операнда из стека операндов
void __fastcall get_elem2(void);
// Поместить в ПОЛИЗ очередной элемент
void __fastcall put_poliz(char op, int t, int n);
// Сброс незначащих нулей дробной части
void __fastcall TForm1::delete_zero(void);
public:
// User declarations
__fastcall TForm1(TComponent* Owner);
// Символьное представление значения переменной
char str_value[100];
// Обозначение переменной
AnsiString name_var;
// Комментарий ввода
AnsiString comment_var;
28
29
// Тип переменной
int type;
// Результат ввода
int
ok_input;
double value_input;
};
extern PACKAGE TForm1 *Form1;
#endif
Unit1.cpp
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"
#include "Unit3.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
// Типы лексем и операций
enum
{
INTEGER, REAL, LOGIC, SEMICOLON,
OR, AND, NOT, LET,
IF, THEN, ELSE, FOR,
DO, WHILE, LOOP, INPUT_,
OUTPUT, NE, EQ_SET, LT,
LE, GT, GE, ADD,
SUB, MUL, DIV, LEFT,
RIGHT, LEFT_FIG, RIGHT_FIG, COMMA,
COLON, END, LET1, NUM,
NUMD, IDENT, STR, STOP,
BREAK, CONTINUE, END_ELSE, MOD,
ERR, OUTPUTLN, WORK, WORKD,
NEG, EQ, OUT_, OUTN,
GOTO, TEST, OUTLN, ERASE,
INPUT_STR, RAND, RAND_REAL
};
// Структура элемента таблицы идентификаторв
struct WORD_
{
char *txt;
int key;
};
// Структура элемента таблицы разделителей
struct DELIM
{
char c;
int key;
};
29
30
// Признак успешной трансляции (если true)
bool ok_compile = false;
// Уровень вложения циклов
int level;
// Максимальный уровень вложения циклов
#define MAX_LEVEL 10
// Максимальное количество break на любом уровне цикла
#define MAX_BREAK 30
// Служебные слова
WORD_ word[] =
{
"mod",
"or",
"and",
"not",
"let",
"if",
"then",
"else",
"for",
"do",
"while",
"loop",
"input",
"output",
"outputln",
"stop",
"erase",
"break",
"continue",
"end_else",
"rand",
"rand_real",
NULL,
};
MOD,
OR,
AND,
NOT,
LET,
IF,
THEN,
ELSE,
FOR,
DO,
WHILE,
LOOP,
INPUT_,
OUTPUT,
OUTPUTLN,
STOP,
ERASE,
BREAK,
CONTINUE,
END_ELSE,
RAND,
RAND_REAL,
-1
// Разделители (не все, а только односимвольные)
DELIM delim[] =
{
'(', LEFT,
')', RIGHT,
'{', LEFT_FIG,
'}', RIGHT_FIG,
':', COLON,
';', SEMICOLON,
'+', ADD,
'-', SUB,
'*', MUL,
'=', EQ_SET,
',', COMMA,
'%', INTEGER,
'!', REAL,
'$', LOGIC,
0,
-1
};
30
31
// Элемент ПОЛИЗа
struct POLIZ
{
// 0 - операнд, 1 - операция
char op;
// Для
// Для
// Для
// Для
int t;
операции
обычной переменной
целой константы
вещественной константы
-
соответствующая операция
IDENT
NUM
NUMD
// Для операнда - индекс в таблице или номер рабочей переменной
int n;
};
// Максимальное количество интерпретируемых операций
// для исключения зацикливания
int cnt_loop = 0;
// Максимальный размер ПОЛИЗа
#define MAX_POLIZ 2000
// Собственно сам ПОЛИЗ
POLIZ poliz[MAX_POLIZ];
// Индекс ПОЛИЗа
int ind_poliz;
// Результирующая строка печати
AnsiString result_print;
// Элемент стека операндов
struct STACK_OP
{
// Для обычной переменной
// Для целой рабочей переменной
// Для вещественной рабочей переменной
// Для целой константы
// Для вещественной константы
int t;
-
IDENT
WORK
WORKD
NUM
NUMD
// Индекс обычной переменной или константы в соответствующей таблице
int n;
// Значение рабочей переменной
double val;
};
// Максимальный размер стека операндов
#define MAX_STACK_OP 50
// Собственно сам стек операндов
STACK_OP stack_op[MAX_STACK_OP];
// Индекс стека операндов
int ind_stack_op;
// Переменные, извлеченные из стека операндов
VT vt1, vt2;
// Массив индексов начала проверки выхода из цикла
int ind_test_loop[MAX_LEVEL + 1];
31
32
// Массив индексов операторов break
// На каждом уровне цикла вначале идет количество break,
// а затем их индексы в ПОЛИЗе
int ind_break_loop[MAX_LEVEL][MAX_BREAK + 1];
// Таблица чисел
AnsiString number[MAX_NUMBER];
double
number_bin[MAX_NUMBER];
// Количество переменных
int cnt_var;
// Таблица переменных
AnsiString var[MAX_VAR];
// Значения переменных
double value_var[MAX_VAR];
// Тип переменных
int type_var[MAX_VAR];
// Признаки использования переменных
char yes_var[MAX_VAR];
// Индекс переменной в таблице идентификаторов
int ind_var;
// Таблица символьных строк
AnsiString sym_str[MAX_SYM_STR];
// Класс распознавания лексем
class Lex
{
private:
// Состояния конечного автомата лексического анализатора
enum
{
BeginLex,
// Начало лексемы
ContinueIdent,
// Продолжение идентификатора
ContinueNum,
// Продолжение числа
ContinueComment, // Продолжение многострочного комментария
BeginEndComment, // Начало завершения многострочного комментария
EndComment,
// Завершение комментария
EndString,
// Завершение строки
ContinueLt,
// Продолжение разбора <
ContinueGt
// Продолжение разбора >
};
// Окно с программой на вкладке лексического анализа
TMemo
*memo;
// Окно с исходной программой
TRichEdit
*re;
// Остальные таблицы с результатом лексического разбора
TStringGrid *grid3, *grid4, *grid5;
AnsiString str;
int
n2;
32
33
// true - если это новый идентификатор
bool new_ident;
// Координаты возможной ошибочной лексемы
int beg_num_save, beg_ind_save, len_lex_save;
// Индекс результата
int ind_result;
// Строка результата
AnsiString result;
// Количество переменных в таблице на начало оператора описания переменных
int beg_cnt_var;
// Количество чисел
int cnt_number;
// Количество символьных строк
int cnt_sym_str;
// Текущее состояние автомата
int state;
// Текст лексической ошибки
AnsiString txt_error;
// Индекс символа в исходной строке
int ind;
// Номер исходной строки
int num;
// Двоичное значение целого числа
int val;
// Двоичное значение вещественного числа
double vald;
// Текущий сканируемый символ исходной программы
char c;
// Признак чтения символа из файла (true - если символ уже прочитан в 'c')
bool yes_c;
// Текущий идентификатор
AnsiString ident;
// Индекс начала лексемы
int beg_ind;
// Длина лексемы
int len_lex;
// Номер строки с лексемой
int beg_num;
// Вернуть -1 или код односимвольного разделителя
int get_delim(void)
{
char q;
for (n2 = 0; (q = delim[n2].c) != c && q; ++n2)
;
return delim[n2].key;
}
33
34
// Вернуть -1 или код служебного слова
int get_word(void)
{
char *q, *s;
s = ident.c_str();
for (n2 = 0; (q = word[n2].txt) != NULL && strcmp(s, q); ++n2)
;
return word[n2].key;
}
// Сбор шестнадцатиричного числа
void make16(void)
{
char *s, c;
for (val = 0, s = ident.c_str(); (c = *s) != 0; ++s)
{
c = c <= '9' ? c - '0' : c - 'A' + 10;
val = (val << 4) | c;
}
}
// Сбор десятичного числа
void make10(void)
{
val = atoi(ident.c_str());
}
// Сбор вещественного числа
void make_double(void)
{
vald = atof(ident.c_str());
}
// Сбор двоичного числа с контролем на ошибку
int make2(void)
{
char *s, c;
for (val = 0, s = ident.c_str(); (c = *s) != 0; ++s)
{
if (c > '1')
er("Двоичное число ошибочно");
c -= '0';
val = (val << 1) | c;
}
return NUM;
}
// Сбор восьмеричного числа с контролем на ошибку
int make8(void)
{
char *s, c;
for (val = 0, s = ident.c_str(); (c = *s) != 0; ++s)
{
if (c > '7')
er("Восьмеричное число ошибочно");
c -= '0';
val = (val << 3) | c;
}
return NUM;
}
34
35
// Поместить идентификатор в таблицу переменных;
// если это возможно - return индекс таблицы, иначе -1
int set_var(void)
{
// Поиск идентификатора в таблице
for (ind_var = 0; ind_var < cnt_var && var[ind_var] != ident; ++ind_var)
;
// Поиск удачен?
if (ind_var < cnt_var)
{
new_ident = false;
return ind_var;
}
// Нет места в таблице?
if (cnt_var == MAX_VAR)
return -1;
// Разместим идентификатор в таблице
var[cnt_var] = ident;
new_ident = true;
return ind_var = cnt_var++;
}
// Поместить число в таблицу чисел;
// если это возможно - return индекс таблицы, иначе -1
// t - INTEGER или REAL
int set_number(int t)
{
char *s;
// Поиск числа в таблице
for (ind_var = 0; ind_var < cnt_number && number[ind_var] != ident; ++ind_var)
;
// Поиск удачен?
if (ind_var < cnt_number)
return ind_var;
// Нет места в таблице?
if (cnt_number == MAX_NUMBER)
return -1;
// Разместим число в символьной таблице
number[cnt_number] = ident;
// Разместим двоичное значение в таблице
if (t == REAL)
number_bin[cnt_number] = vald;
else
number_bin[cnt_number] = val;
return ind_var = cnt_number++;
}
// Поместить символьную строку в таблицу строк;
// если это возможно - return индекс таблицы, иначе -1
int set_sym_str(void)
{
// Поиск строки в таблице
for (ind_var = 0; ind_var < cnt_sym_str && sym_str[ind_var] != ident; ++ind_var)
;
35
36
// Поиск удачен?
if (ind_var < cnt_sym_str)
return ind_var;
// Нет места в таблице?
if (cnt_sym_str == MAX_SYM_STR)
return -1;
// Разместим строку в таблице
sym_str[cnt_sym_str] = ident;
return ind_var = cnt_sym_str++;
}
// Отправить лексему в результат
void out_result(int n1, int n2)
{
char str[100];
sprintf(str, "(%d,%2d)", n1, n2);
result += str;
if (++ind_result == 6)
{
memo->Lines->Add(result + "\n");
result = "";
ind_result = 0;
return;
}
result += " ";
}
// Чтение очередного символа программы
// В случае конца программы возвращается 0
// Для конца строки возвращается '\n'
char Input(void)
{
// Символ уже прочитан при сканировании предыдущей лексемы?
if (yes_c)
{
if (c)
yes_c = false;
return c;
}
// Надо читать очередную строку?
if (ind == -1)
{
// Конец исходной программы?
if (num == re->Lines->Count)
return (char)0;
// Забираем очередную строку программы
str = re->Lines->Strings[num++];
// Устанавливаем индекс чтения очередного символа исходной строки
ind = 0;
}
// Прочитали конец строки?
if (ind == str.Length())
{
// Устанавливаем признак чтения очередной строки
ind = -1;
36
37
// Возвращаем признак конца строки
return '\n';
}
// Возвращаем очередной символ строки
// Внимание! В Builder нумерация символов в AnsiString начинается с 1, а не с 0
++len_lex;
return str[++ind];
}
public:
// Конструктор, которому передается адрес объекта окна редактирования программы
// и адрес объекта окна результата
Lex (TRichEdit *p1, TMemo *p2, TStringGrid *p3, TStringGrid *p4, TStringGrid *p5, TMemo
*p6)
{
int i;
re = p1;
memo = p2;
grid3 = p3;
grid4 = p4;
grid5 = p5;
ind = -1;
num = 0;
// Читаем первый символ программы
yes_c = false;
c = Input();
yes_c = true;
state = BeginLex;
cnt_number = 0;
cnt_var = 0;
cnt_sym_str = 0;
ind_result = 0;
memo->Clear();
p6->Clear();
for (i = 0; i < re->Lines->Count; ++i)
{
// Забираем очередную строку программы
result = re->Lines->Strings[i];
// Публикаем ее
p6->Lines->Add(result + "\n");
}
result = "";
// Устанавливаем внешний вид таблиц результата лексического разбора
grid3->RowCount = 10;
grid4->RowCount = 10;
grid5->RowCount = 5;
for (i = 0; i < 10; ++i)
{
grid3->Cells[1][i] = "";
grid4->Cells[1][i] = "";
}
37
38
for (i = 0; i < 5; ++i)
{
grid5->Cells[1][i] = "";
}
}
// Инициализация начала описания переменных
void init_declare(void)
{
beg_cnt_var = cnt_var - 1;
}
// Установка типов переменных после трансляции очередного описания
void set_declare(int type)
{
while (beg_cnt_var != cnt_var)
type_var[beg_cnt_var++] = type;
}
// Контроль идентификатора на дубликат при описании
void double_ident(void)
{
if (!new_ident)
{
restore_coord();
er("Повторное описание переменной");
}
}
// Контроль идентификатора на неопределенность
void undef_ident(void)
{
if (new_ident)
er("Переменная не определена");
// Устанавливаем признак использования переменной
yes_var[ind_var] = 1;
}
// Контроль идентификатора на неопределенность
void undef_ident0(void)
{
if (new_ident)
{
restore_coord();
er("Переменная не определена");
}
// Устанавливаем признак использования переменной
yes_var[ind_var] = 1;
}
// Опубликовать таблицы идентификаторов, чисел и строк
void show_table(void)
{
int i;
// Идентификаторы
if (cnt_var)
{
if (cnt_var > grid4->RowCount)
{
while (grid4->RowCount != cnt_var)
{
38
39
grid4->Cells[0][grid4->RowCount] = grid4->RowCount;
++grid4->RowCount;
}
}
for (i = 0; i < cnt_var; ++i)
grid4->Cells[1][i] = var[i];
}
// Числа
if (cnt_number)
{
if (cnt_number > grid3->RowCount)
{
while (grid3->RowCount != cnt_number)
{
grid3->Cells[0][grid3->RowCount] = grid3->RowCount;
++grid3->RowCount;
}
}
for (i = 0; i < cnt_number; ++i)
grid3->Cells[1][i] = number[i];
}
// Строки
if (cnt_sym_str)
{
if (cnt_sym_str > grid5->RowCount)
{
while (grid5->RowCount != cnt_sym_str)
{
grid5->Cells[0][grid5->RowCount] = grid5->RowCount;
++grid5->RowCount;
}
}
for (i = 0; i < cnt_sym_str; ++i)
grid5->Cells[1][i] = sym_str[i];
}
// Остаток результата
if (ind_result)
memo->Lines->Add(result + "\n");
}
// Возбудить исключительную ситуацию по ошибке
void er(char *s)
{
txt_error = s;
throw 0;
}
// Чтение идентификатора
AnsiString getIdent(void)
{
return ident;
}
// Чтение числовой константы
int getNum(void)
{
return val;
}
double getNumD(void)
{
return vald;
}
39
40
// Чтение номера строки с лексической ошибкой
int getLineNum(void)
{
return beg_num;
}
// Чтение позиции в строке с лексической ошибкой
int getLinePos(void)
{
return beg_ind;
}
// Чтение длины ошибочной лексемы
int getLenLex(void)
{
return len_lex;
}
// Сохранить координаты возможной ошибочной лексемы
void save_coord(void)
{
beg_num_save = beg_num;
beg_ind_save = beg_ind;
len_lex_save = len_lex;
}
// Восстановить координаты ошибочной лексемы
void restore_coord(void)
{
beg_num = beg_num_save;
beg_ind = beg_ind_save;
len_lex = len_lex_save;
}
// Вернуть наименование лексической ошибки
AnsiString Error(void)
{
return txt_error;
}
// Прочитать очередную лексему
// Если ошибок нет, тип лексемы содержится в type
int lex(void)
{
int offs, r;
char *s, cc, c2[2];
c2[1] = 0;
// Переход по состояниям автомата
for (;;)
{
switch (state)
{
case BeginLex:
// Игнорируем пустые символы, оставаясь в том же состоянии
while ((c = Input()) == ' ' || c == '\t' || c == '\n')
{
if (c == '\n')
out_result(1, 0);
}
40
41
// Координаты начала лексемы
beg_ind = ind;
beg_num = num;
len_lex = 1;
// Встретили конец программы?
if (c == 0)
{
yes_c = true;
return END;
}
// Это начало числовой константы?
if (isdigit(c))
{
// Изменяем состояние, продолжаем разбор
state = ContinueNum;
// Устанавливаем значение старшего символа числа
ident = c;
continue;
}
// Это начало идентификатора?
if (c == '_' || isalpha(c))
{
// Изменяем состояние, продолжаем разбор
state = ContinueIdent;
// Устанавливаем значение старшего символа идентификатора
ident = c;
continue;
}
// Распознавание односимвольных разделителей
state = BeginLex;
if ((val = get_delim()) >= 0)
{
yes_c = false;
out_result(1, n2 + 7);
return val;
}
// Распознавание строк, двухсимвольных разделителей и комментария
switch (c)
{
case '"':
ident = "";
state = EndString;
continue;
case '/':
state = ContinueComment;
continue;
case '<':
state = ContinueLt;
continue;
case '>':
state = ContinueGt;
continue;
}
41
42
er("Неизвестный символ");
case ContinueLt:
yes_c = false;
state = BeginLex;
switch (c = Input())
{
case '>':
out_result(1, 5);
return NE;
case '=':
out_result(1, 4);
return LE;
}
yes_c = true;
out_result(1, 2);
return LT;
case ContinueGt:
state = BeginLex;
if ((c = Input()) == '=')
{
yes_c = false;
out_result(1, 3);
return GE;
}
yes_c = true;
out_result(1, 1);
return GT;
case ContinueComment:
switch (Input())
{
case '/':
// Игнорируем строку до конца
ind = -1;
// Выполняем подход к следующей лексеме
state = BeginLex;
continue;
case '*':
// Переходим в состояние поиска конца многострочного комментария
state = BeginEndComment;
continue;
}
yes_c = false;
// Операция деления
state = BeginLex;
out_result(1, 6);
return DIV;
case BeginEndComment:
// Неоконченный комментарий?
if ((c = Input()) == 0)
er("Неожиданный конец многострочного комментария");
42
43
switch (c)
{
case '*':
state = EndComment;
break;
case '\n':
out_result(1, 0);
}
continue;
case EndComment:
// Неоконченный комментарий?
if ((c = Input()) == 0)
er("Неожиданный конец многострочного комментария");
if (c == '*')
continue;
// Конец комментария?
if (c == '/')
{
// Выполняем подход к следующей лексеме
state = BeginLex;
continue;
}
// Продолжаем "ловить" конец комментария
state = BeginEndComment;
continue;
case EndString:
// Строка закончена?
if ((c = Input()) == '"')
{
// Поместим строку в таблицу
if ((r = set_sym_str()) < 0)
er("Переполнение таблицы символьных строк");
out_result(4, r);
// Возвращаем лексему
state = BeginLex;
return STR;
}
if (c == '\n' || c == 0)
er("Незаконченная строка");
// Продолжаем собирать строку
ident += c;
continue;
case ContinueIdent:
// Остаемся в том же состоянии при сканировании допустимых символов идентификатора
// Продолжаем собирать идентификатор
if ((c = Input()) == '_' || isdigit(c) || isalpha(c))
{
ident += c;
continue;
}
// Идентификатор закончился, устанавливаем начальное состояние автомата
state = BeginLex;
43
44
yes_c = true;
// Может это логическая константа?
if (ident == "false" || ident == "true")
{
// Считаем false значением 0, true значением 1
// Поместим целое число в таблицу
val = ident == "true";
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
// Возвращаем лексему
return NUM;
}
// Может это служебное слово?
if ((r = get_word()) >= 0)
{
out_result(0, n2);
return r;
}
// Нет, это обычный идентификатор, поместим его в таблицу
if ((r = set_var()) < 0)
er("Переполнение таблицы идентификаторов");
out_result(3, r);
return IDENT;
case ContinueNum:
// Остаемся в том же состоянии при сканировании цифр
// Продолжаем собирать число
if (isdigit(c = Input()))
{
ident += c;
continue;
}
// Устанавливаем начальное состояние автомата
state = BeginLex;
// Это шестнадцатиричное число?
if (isxdigit(c))
{
offs = 0;
// Может это ситуация 123E+ как вещественное число?
if (tolower(c) == 'e')
{
ident += 'e';
if ((c = Input()) == '+' || c == '-')
goto lab1;
// Запомним смещение до символа 'e', если это
// вдруг не шестнадцатиричное число
offs = strlen(ident.c_str());
}
// Добавляем все символы шестнадцатиричного числа
do
{
c2[0] = tolower(c);
ident += c2;
} while (isxdigit(c = Input()));
44
45
// Шестнадцатиричное число должно оканчиваться символами H,h
if (tolower(c) != 'h')
{
// Может это была запись двоичного числа?
if (ident[1] == '0' || ident[1] == '1')
{
for (s = ident.c_str(); (cc = *++s) == '0' || cc == '1';)
;
if ((cc == 'b' || cc == 'B') && s[1] == 0)
{
// Удаляем символ B
ident.Delete(ident.Length(), 1);
// Соберем двоичное число
make2();
// Поместим целое число в таблицу
ident += "b";
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
yes_c = true;
// Возвращаем лексему
return NUM;
}
}
// Может это была запись десятичного числа?
if (isdigit(ident[1]))
{
for (s = ident.c_str(); isdigit(*++s);)
;
if ((*s == 'd' || *s == 'D') && s[1] == 0)
{
// Удаляем символ D
ident.Delete(ident.Length(), 1);
// Соберем десятичное число
make10();
// Поместим целое число в таблицу
ident += "d";
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
yes_c = true;
// Возвращаем лексему
return NUM;
}
}
// Может это была запись вещественного числа?
if (offs)
{
s = ident.c_str() + offs;
if (isdigit(*s))
{
while (isdigit(*++s))
;
45
46
if (*s == 0)
{
// Действительно, это запись вида 123E17
// Соберем вещественное число
make_double();
// Поместим вещественное число в таблицу
if ((r = set_number(REAL)) < 0)
er("Переполнение таблицы чисел");
yes_c = true;
out_result(3, r);
return NUMD;
}
}
}
er("Шестнадцатиричное число ошибочно");
}
// Соберем шестнадцатиричное число
make16();
// Поместим целое число в таблицу
ident += "h";
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
return NUM;
}
switch (c = tolower(c))
{
// Это конец шестнадцатиричного числа?
case 'h':
// Соберем шестнадцатиричное число
make16();
// Поместим целое число в таблицу
ident += c;
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
// Возвращаем лексему
return NUM;
// Это конец двоичного числа?
case 'b':
// Соберем двоичное число с контролем на ошибку
if (make2() == ERR)
return ERR;
// Поместим целое число в таблицу
ident += c;
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
// Возвращаем лексему
return NUM;
// Это конец восьмеричного числа?
case 'o':
// Соберем восьмеричное число с контролем на ошибку
46
47
if (make8() == ERR)
return ERR;
// Поместим целое число в таблицу
ident += c;
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
// Возвращаем лексему
return NUM;
// Это конец десятичного числа?
case 'd':
// Соберем десятичное число
make10();
// Поместим целое число в таблицу
ident += c;
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
// Возвращаем лексему
return NUM;
// Это начало дробной части вещественного числа?
case '.':
// Собираем дробную часть
ident += '.';
if (!isdigit(c = Input()))
er("Ошибочная запись вещественного числа");
do
{
ident += c;
} while (isdigit(c = Input()));
if (tolower(c) != 'e')
{
// Соберем вещественное число
make_double();
yes_c = true;
// Поместим вещественное число в таблицу
if ((r = set_number(REAL)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
return NUMD;
}
case 'e':
ident += 'e';
if ((c = Input()) == '+' || c == '-')
{
lab1:
ident += c;
c = Input();
}
if (!isdigit(c))
er("Ошибочная запись вещественного числа");
lab2:
47
48
do
{
ident += c;
} while (isdigit(c = Input()));
// Соберем вещественное число
make_double();
yes_c = true;
// Поместим вещественное число в таблицу
if ((r = set_number(REAL)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
return NUMD;
}
// Это обычное десятичное число
make10();
yes_c = true;
// Поместим целое число в таблицу
if ((r = set_number(INTEGER)) < 0)
er("Переполнение таблицы чисел");
out_result(3, r);
// Возвращаем лексему
return NUM;
}
}
}
};
// Объект лексического разбора
Lex *Scan;
// Загрузить программу
void __fastcall TForm1::N2Click(TObject *Sender)
{
if (OpenDialog1->Execute())
{
ClearError();
RichEdit1->Clear();
ok_compile = false;
try
{
RichEdit1->Lines->LoadFromFile(OpenDialog1->FileName);
}
catch (...)
{
MessageBox(NULL, "Нельзя загрузить файл с программой", "Загрузка программы", MB_OK);
}
}
// Восстановим текущую директорию
SetCurrentDirectory(Patch.c_str());
// Сделаем активной страницу с программой
PageControl1->ActivePageIndex = 0;
}
// Сохранить программу
void __fastcall TForm1::N3Click(TObject *Sender)
{
48
49
FILE
*f;
AnsiString str;
int
i;
if (SaveDialog1->Execute())
{
str = SaveDialog1->FileName;
if ((f = fopen(str.c_str(), "w")) == NULL)
{
MessageBox(NULL, "Нельзя создать файл для сохранения программы", "Сохранение программы",
MB_OK);
SetCurrentDirectory(Patch.c_str());
return;
}
for (i = 0; i < RichEdit1->Lines->Count; ++i)
{
str = RichEdit1->Lines->Strings[i];
fprintf(f, "%s\n", str.c_str());
}
fclose(f);
}
SetCurrentDirectory(Patch.c_str());
}
// Удалить программу
void __fastcall TForm1::N4Click(TObject *Sender)
{
if (MessageDlg("Удалить программу?", mtConfirmation, TMsgDlgButtons() << mbYes << mbNo,
0) == mrYes)
{
ClearError();
RichEdit1->Clear();
ok_compile = false;
// Сделаем активной страницу с программой
PageControl1->ActivePageIndex = 0;
}
}
// Выход
void __fastcall TForm1::N5Click(TObject *Sender)
{
Close();
}
// Выполнение
void __fastcall TForm1::N7Click(TObject *Sender)
{
if (!ok_compile)
{
MessageBox(NULL, "Выполните успешную трансляцию", "Выполнение программы", MB_OK);
return;
}
run_poliz();
}
// О программе
void __fastcall TForm1::N9Click(TObject *Sender)
{
Form2 = new TForm2(NULL);
Form2->ShowModal();
delete Form2;
}
49
50
// Тексты примеров
char src1[] =
"/*\n"
" Вычисление целочисленного квадратного корня\n"
"*/\n"
"{\n"
" div, res, X, Y : %;\n"
"\n"
" X = 101;\n"
" for (;;)\n"
" {\n"
"
outputln\n"
"
(\n"
"
\"Введите целое число для вычисления квадратного корня#\"\n"
"
\"или 0 для завершения программы\"\n"
"
);\n"
"
input (X);\n"
"
if X = 0 then stop(); end_else;\n"
"
div = 1; res = 0; Y = X;\n"
"\n"
"
do while X <> 0\n"
"
{\n"
"
if X < div then break; end_else;\n"
"
res = res + 1;\n"
"
if X = div then break; end_else;\n"
"
X = X - div;\n"
"
div = div + 2;\n"
"
} loop;\n"
"\n"
"
outputln (\"Квадратный корень из \" Y \" равен \" res);\n"
"
X = Y;\n"
" }\n"
"}\n";
// Далее тексты примеров не приводятся для сокращения объема описания,
// так как они повторяются в Приложении Б
char src2[] =
"// Вычисление вещественного квадратного корня\n";
char src3[] =
"// Печать таблицы умножения\n";
char src4[] =
"// Сокращение дроби при помощи алгоритма Евклида\n";
char src5[] =
"// Печать календаря для заданного года и месяца\n";
char src6[] =
"// Схематическое изображение новогодней елки\n";
char src7[] =
"// Всевозможные константы\n";
char src8[] =
"// Нахождение простых чисел в заданном интервале\n";
char src9[] =
"// Запись числа словами в пределах от 1 до 999\n";
char src10[] =
"// Дата Пасхи\n";
char src11[] =
"// Решение квадратного уравнения\n";
char src12[] =
"// Баше - математическая игра\n";
char src13[] =
"// Вычисление числа Пи методом Монте-Карло\n";
char src14[] =
"// Вычисление интеграла методом Симпсона\n";
char src15[] =
"// Решение системы 3-х уравнений с тремя неизвестными\n";
50
51
char src16[] =
"// Ханойская башня\n";
char src17[] =
"// Ошибочная программа\n";
// Загрузка примера
void __fastcall TForm1::Example(char *s)
{
char *q;
ClearError();
ok_compile = false;
RichEdit1->Clear();
while (*s)
{
q = strchr(s, '\n');
*q = 0;
RichEdit1->Lines->Add(s);
*q = '\n';
s = q + 1;
}
// Отобразим пример с первой строки
RichEdit1->Perform(EM_LINESCROLL, 0, -RichEdit1->Perform(EM_GETFIRSTVISIBLELINE, 0, 0));
// Сделаем активной страницу с программой
PageControl1->ActivePageIndex = 0;
}
// Загрузка примера
void __fastcall TForm1::Example(char *s)
{
char *q;
ClearError();
ok_compile = false;
RichEdit1->Clear();
while (*s)
{
q = strchr(s, '\n');
*q = 0;
RichEdit1->Lines->Add(s);
*q = '\n';
s = q + 1;
}
// Отобразим пример с первой строки
RichEdit1->Perform(EM_LINESCROLL, 0, -RichEdit1->Perform(EM_GETFIRSTVISIBLELINE, 0, 0));
// Сделаем активной страницу с программой
PageControl1->ActivePageIndex = 0;
}
// Пример1
void __fastcall TForm1::N11Click(TObject *Sender)
{
Example(src1);
}
// Пример2
void __fastcall TForm1::N21Click(TObject *Sender)
51
52
{
Example(src2);
}
// Пример3
void __fastcall TForm1::N31Click(TObject
{
Example(src3);
}
// Пример4
void __fastcall TForm1::N41Click(TObject
{
Example(src4);
}
// Пример5
void __fastcall TForm1::N51Click(TObject
{
Example(src5);
}
// Пример6
void __fastcall TForm1::N14Click(TObject
{
Example(src6);
}
// Пример7
void __fastcall TForm1::N15Click(TObject
{
Example(src7);
}
// Пример8
void __fastcall TForm1::N16Click(TObject
{
Example(src8);
}
// Пример9
void __fastcall TForm1::N17Click(TObject
{
Example(src9);
}
// Пример10
void __fastcall TForm1::N18Click(TObject
{
Example(src10);
}
// Пример11
void __fastcall TForm1::N19Click(TObject
{
Example(src11);
}
// Пример12
void __fastcall TForm1::N20Click(TObject
{
Example(src12);
}
// Пример13
void __fastcall TForm1::N22Click(TObject
{
Example(src13);
}
// Пример14
void __fastcall TForm1::N23Click(TObject
{
Example(src14);
}
// Пример15
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
*Sender)
52
53
void __fastcall TForm1::N24Click(TObject *Sender)
{
Example(src15);
}
// Пример16
void __fastcall TForm1::N25Click(TObject *Sender)
{
Example(src16);
}
// Пример17
void __fastcall TForm1::N26Click(TObject *Sender)
{
Example(src17);
}
// Начало работы программы
void __fastcall TForm1::FormCreate(TObject *Sender)
{
int i, j;
char *txt, c;
char *table[] = { "\\n", ">", "<", ">=", "<=", "<>", "/", NULL };
// В качестве десятичной точки используется символ '.'
DecimalSeparator = '.';
// Сохраним директорию запуска программы
Patch = GetCurrentDir();
// Ошибок пока нет
err = "";
Tp.x = Tp.y = 0;
StringGrid1->ColWidths[0]
StringGrid2->ColWidths[0]
StringGrid3->ColWidths[0]
StringGrid4->ColWidths[0]
StringGrid5->ColWidths[0]
StringGrid5->ColWidths[1]
=
=
=
=
=
=
22;
22;
22;
22;
22;
1830;
// Опубликуем таблицы лексического анализа
for (i = 0; (txt = word[i].txt) != NULL; ++i)
{
StringGrid1->Cells[0][i] = i;
StringGrid1->Cells[1][i] = txt;
}
for (i = 0; (txt = table[i]) !=
{
StringGrid2->Cells[0][i]
StringGrid2->Cells[1][i]
}
for (j = 0; (c = delim[j].c) !=
{
StringGrid2->Cells[0][i]
StringGrid2->Cells[1][i]
}
NULL; ++i)
= i;
= txt;
0; ++i, ++j)
= i;
= c;
for (i = 0; i < 10; ++i)
{
StringGrid3->Cells[0][i] = i;
StringGrid4->Cells[0][i] = i;
}
53
54
for (i = 0; i < 5; ++i)
{
StringGrid5->Cells[0][i] = i;
}
// Инициализация датчика случайных чисел
srand(time(NULL));
}
// Обновление статуса программы
void __fastcall TForm1::UpdateStatus(void)
{
long i;
i = SendMessage(RichEdit1->Handle, EM_CHARFROMPOS, 0, (long)&Tp);
int LineNum = SendMessage(RichEdit1->Handle, EM_LINEFROMCHAR, i, 0);
int CharBeforeLine = SendMessage(RichEdit1->Handle, EM_LINEINDEX, LineNum, 0);
StatusBar1->SimpleText = "Строка = " + IntToStr(LineNum + 1) + ", Позиция = " +
IntToStr(i - CharBeforeLine + 1) + err;
}
// Слежение за координатами мыши в окне исходной программы
void __fastcall TForm1::RichEdit1MouseMove(TObject *Sender, TShiftState Shift, int X, int
Y)
{
long i;
Tp.x = X;
Tp.y = Y;
UpdateStatus();
}
// Сброс ошибки
void __fastcall TForm1::ClearError(void)
{
int
i;
for (i = 0; i < RichEdit1->Lines->Count; ++i)
{
RichEdit1->SelStart = RichEdit1->Perform(EM_LINEINDEX, i, 0);
RichEdit1->SelLength = RichEdit1->Lines->Strings[i].Length();
RichEdit1->SelAttributes->Color = clBlack;
}
err = "";
UpdateStatus();
// Убираем выделение текста
RichEdit1->SelLength = 0;
}
void __fastcall TForm1::N10Click(TObject *Sender)
{
ClearError();
// Сделаем активной страницу с программой
PageControl1->ActivePageIndex = 0;
}
// Если текущая лексема не t, сообщить об ошибке s
void __fastcall TForm1::wait_lex(int t, char *s)
{
char txt[100];
54
55
if (lex != t)
{
sprintf(txt, "Ожидалось: %s", s);
Scan->er(txt);
}
}
// Чтение очередной лексемы
int __fastcall TForm1::get_lex(void)
{
return lex = Scan->lex();
}
// Чтение очередной лексемы и проверка ее на END
int __fastcall TForm1::get_end(void)
{
if ((lex = Scan->lex()) == END)
Scan->er("Неожиданный конец программы");
return lex;
}
// Прочитать лексему, совпадающую с параметром p
// mess - название лексемы
void __fastcall TForm1::token(int p, char *mess)
{
get_end();
wait_lex(p, mess);
}
// Компиляция выражения
void __fastcall TForm1::expr(void)
{
get_end();
exp1();
}
// Операции отношения
void __fastcall TForm1::exp1(void)
{
int w;
exp2();
if (lex == NE || lex == EQ_SET || lex == LT || lex == GT || lex == LE || lex == GE)
{
w = (lex == EQ_SET) ? EQ : lex;
get_end();
exp1();
// Поместить в ПОЛИЗ соответствующую операцию
put_poliz(1, w, 0);
}
}
// Операции сложения
void __fastcall TForm1::exp2(void)
{
int w;
exp3();
while (lex == ADD || lex == SUB || lex == OR)
{
w = lex;
55
56
get_end();
exp3();
// Поместить в ПОЛИЗ соответствующую операцию
put_poliz(1, w, 0);
}
}
// Операции умножения и деления
void __fastcall TForm1::exp3(void)
{
int w;
exp4();
while (lex == MUL || lex == DIV || lex == MOD || lex == AND)
{
w = lex;
get_end();
exp4();
// Поместить в ПОЛИЗ соответствующую операцию
put_poliz(1, w, 0);
}
}
// Унарный +, - или not
void __fastcall TForm1::exp4(void)
{
int w = -1;
switch (lex)
{
case NOT:
w = NOT;
goto lab;
case SUB:
w = NEG;
case ADD:
lab:
get_end();
}
exp5();
// При необходимости поместить в ПОЛИЗ NOT или NEG
if (w >= 0)
put_poliz(1, w, 0);
}
// Выражение в скобках
void __fastcall TForm1::exp5(void)
{
if (lex == LEFT)
{
get_end();
exp1();
if (lex != RIGHT)
Scan->er("Нарушен баланс скобок ()");
get_end();
56
57
}
else
atom();
}
// Получение идентификатора или числа
void __fastcall TForm1::atom(void)
{
// Поместить в ПОЛИЗ идентификатор или число
put_poliz(0, lex, ind_var);
switch (lex)
{
default:
Scan->er("Ожидалось: идентификатор, rand, rand_real или число");
case RAND:
case RAND_REAL:
token(LEFT, "(");
token(RIGHT, ")");
break;
case IDENT:
Scan->undef_ident();
case NUM:
case NUMD:
;
}
get_end();
}
// Поместить в ПОЛИЗ очередной элемент
void __fastcall TForm1::put_poliz(char op, int t, int n)
{
// Есть место в ПОЛИЗе?
if (ind_poliz == MAX_POLIZ)
Scan->er("Переполнение ПОЛИЗа");
poliz[ind_poliz].op = op;
poliz[ind_poliz].t = t;
poliz[ind_poliz].n = n;
++ind_poliz;
}
// Компиляция оператора присваивания
void __fastcall TForm1::compile_set(void)
{
// Поместить в ПОЛИЗ переменную результата
put_poliz(0, IDENT, ind_var);
// Вычислить выражение
expr();
wait_lex(SEMICOLON, ";");
get_end();
// Поместить в ПОЛИЗ операцию присваивания
put_poliz(1, LET, 0);
}
// Оператор if
57
58
void __fastcall TForm1::oper_if(void)
{
int ind, ind2;
expr();
// Сохранить текущий индекс ПОЛИЗа
ind = ind_poliz;
// Поместить в ПОЛИЗ операцию условного перехода
// Пока адрес перехода не известен
put_poliz(1, TEST, 0);
wait_lex(THEN, "then");
get_end();
oper();
switch (lex)
{
default:
Scan->er("Ожидалось: else или end_else");
case ELSE:
// Сохранить текущий индекс ПОЛИЗа
ind2 = ind_poliz;
// Поместить в ПОЛИЗ операцию безусловного перехода
// Пока адрес перехода не известен
put_poliz(1, GOTO, 0);
// Установим адрес перехода по лжи для начала if
poliz[ind].n = ind_poliz;
ind = ind2;
get_end();
oper();
wait_lex(END_ELSE, "end_else");
case END_ELSE:
// Коррекция адреса перехода
poliz[ind].n = ind_poliz;
token(SEMICOLON, ";");
get_end();
}
}
// Оператор continue
void __fastcall TForm1::oper_continue(void)
{
if (level == 0)
Scan->er("Оператор continue вне цикла");
// Операция безусловного перехода на переадресацию цикла
put_poliz(1, GOTO, ind_test_loop[level - 1]);
token(SEMICOLON, ";");
get_end();
}
// Оператор break
void __fastcall TForm1::oper_break(void)
{
int n;
if (level == 0)
58
59
Scan->er("Оператор break вне цикла");
// Операция безусловного перехода выхода из цикла
// Адрес выхода еще не сформирован, место коррекции запоминается
n = ++ind_break_loop[level - 1][0];
// Не слишком ли много операторов break?
if (n == MAX_BREAK + 1)
Scan->er("Слишком много операторов break для текущего цикла");
ind_break_loop[level - 1][n] = ind_poliz;
// Операция выхода из цикла (не до конца сформированная)
put_poliz(1, GOTO, 0);
token(SEMICOLON, ";");
get_end();
}
// Оператор do
void __fastcall TForm1::oper_do(void)
{
int ind_test, ind_break;
int i, *adr;
if (level == MAX_LEVEL)
Scan->er("Слишком большой уровень вложения циклов");
// Пока на данном уровне цикла нет операторов break;
ind_break_loop[level][0] = 0;
// Увеличиваем уровень вложения циклов
++level;
// Компилируем оператор do
token(WHILE, "while");
// Запомним индекс ПОЛИЗа начала условия выполнения цикла
ind_test_loop[level - 1] = ind_test = ind_poliz;
expr();
// Адрес операции выхода из цикла
ind_break = ind_poliz;
// Поместить в ПОЛИЗ операцию условного выхода из цикла
// Пока адрес перехода не известен
put_poliz(1, TEST, 0);
oper();
wait_lex(LOOP, "loop");
token(SEMICOLON, ";");
get_end();
// Операция безусловного перехода на начало цикла
put_poliz(1, GOTO, ind_test);
// Коррекция адреса выхода из цикла
poliz[ind_break].n = ind_poliz;
// Уменьшаем уровень вложения циклов
--level;
// Коррекция адресов перехода для break
59
60
adr = ind_break_loop[level];
for (i = *adr; i; --i)
{
poliz[*++adr].n = ind_poliz;
}
}
// Оператор for
void __fastcall TForm1::oper_for(void)
{
int ind_test, ind_break = -1, ind_next, ind_body;
int i, *adr;
if (level == MAX_LEVEL)
Scan->er("Слишком большой уровень вложения циклов");
// Пока на данном уровне цикла нет операторов break;
ind_break_loop[level][0] = 0;
// Увеличиваем уровень вложения циклов
++level;
// Компилируем оператор for
token(LEFT, "(");
switch (get_end())
{
default:
Scan->er("Ожидалось: ; let или идентификатор");
case LET:
token(IDENT, "идентификатор");
case IDENT:
// Контроль идентификатора на неопределенность
Scan->undef_ident();
// Ожидаем знак присваивания
token(EQ_SET, "=");
compile_set();
break;
case SEMICOLON:
get_end();
}
// Запомним индекс ПОЛИЗа начала условия выполнения цикла
ind_test = ind_poliz;
if (lex != SEMICOLON)
{
exp1();
// Запомним индекс ПОЛИЗа c выходом из цикла
ind_break = ind_poliz;
// Поместить в ПОЛИЗ операцию условного выхода из цикла
// Пока адрес перехода не известен
put_poliz(1, TEST, 0);
wait_lex(SEMICOLON, ";");
}
// Поместить в ПОЛИЗ операцию безусловного перехода на тело цикла
60
61
// Пока адрес перехода не известен
ind_body = ind_poliz;
put_poliz(1, GOTO, 0);
ind_test_loop[level - 1] = ind_next = ind_poliz;
switch (get_end())
{
default:
Scan->er("Ожидалось: let ) или идентификатор");
case LET:
token(IDENT, "идентификатор");
case IDENT:
// Контроль идентификатора на неопределенность
Scan->undef_ident();
// Поместить в ПОЛИЗ переменную результата
put_poliz(0, IDENT, ind_var);
// Ожидаем знак присваивания
token(EQ_SET, "=");
expr();
// Поместить в ПОЛИЗ операцию присваивания
put_poliz(1, LET, 0);
// Есть проверка
wait_lex(RIGHT, ")");
case RIGHT:
get_end();
// Поместить в ПОЛИЗ операцию перехода на начало проверки цикла
put_poliz(1, GOTO, ind_test);
// Коррекция адреса перехода на тело цикла
poliz[ind_body].n = ind_poliz;
oper();
// Поместить в ПОЛИЗ операцию перехода на переадресацию цикла
put_poliz(1, GOTO, ind_next);
// Для условного цикла скорректировать адрес выхода из цикла
if (ind_break >= 0)
poliz[ind_break].n = ind_poliz;
}
// Уменьшаем уровень вложения циклов
--level;
// Коррекция адресов перехода для break
adr = ind_break_loop[level];
for (i = *adr; i; --i)
{
poliz[*++adr].n = ind_poliz;
}
}
// Оператор output
void __fastcall TForm1::oper_output(void)
{
61
62
bool b = false;
int n;
token(LEFT, "(");
get_end();
for (;;)
{
switch (lex)
{
case STR:
// Поместить в ПОЛИЗ операнд строки
put_poliz(0, STR, ind_var);
// Поместить в ПОЛИЗ операцию печати строки
put_poliz(1, STR, 0);
b = true;
get_end();
continue;
case RIGHT:
if (!b)
Scan->er("Оператор output пустой");
token(SEMICOLON, ";");
get_end();
return;
}
b = true;
exp1();
if (lex == COLON)
{
token(NUM, "число");
if ((n = Scan->getNum()) < 0 || n > 20)
Scan->er("Размер печатаемого поля не в диапазоне 0...20");
// Поместить в ПОЛИЗ размер поля печати
put_poliz(0, NUM, ind_var);
// Поместить в ПОЛИЗ операцию печати с размером поля
put_poliz(1, OUTN, 0);
get_end();
}
else
{
// Поместить в ПОЛИЗ операцию печати без размера поля
put_poliz(1, OUT_, 0);
}
}
}
// Оператор outputln
void __fastcall TForm1::oper_outputln(void)
{
int n;
token(LEFT, "(");
get_end();
for (;;)
{
switch (lex)
{
62
63
case STR:
// Поместить в ПОЛИЗ операнд строки
put_poliz(0, STR, ind_var);
// Поместить в ПОЛИЗ операцию печати строки
put_poliz(1, STR, 0);
get_end();
continue;
case RIGHT:
token(SEMICOLON, ";");
get_end();
// Поместить в ПОЛИЗ операцию печати перевода строки
put_poliz(1, OUTLN, 0);
return;
}
exp1();
if (lex == COLON)
{
token(NUM, "число");
if ((n = Scan->getNum()) < 2 || n > 20)
Scan->er("Размер печатаемого поля не в диапазоне 2...20");
// Поместить в ПОЛИЗ размер поля печати
put_poliz(0, NUM, ind_var);
// Поместить в ПОЛИЗ операцию печати с размером поля
put_poliz(1, OUTN, 0);
get_end();
}
else
{
// Поместить в ПОЛИЗ операцию печати без размера поля
put_poliz(1, OUT_, 0);
}
}
}
// Оператор input
void __fastcall TForm1::oper_input(void)
{
bool b = false;
int w;
token(LEFT, "(");
for (;;)
{
w = INPUT_;
switch (get_end())
{
default:
Scan->er(b ? "Ожидалось: ) строка или идентификатор" : "Ожидалось: идентификатор");
case RIGHT:
if (!b)
Scan->er("Ожидалось: идентификатор");
token(SEMICOLON, ";");
get_end();
return;
63
64
case STR:
// Поместить в ПОЛИЗ операнд строки
put_poliz(0, STR, ind_var);
token(IDENT, "идентификатор");
w = INPUT_STR;
case IDENT:
// Контроль идентификатора на неопределенность
Scan->undef_ident();
b = true;
// Поместить в ПОЛИЗ операнд переменной ввода
put_poliz(0, IDENT, ind_var);
// Поместить в ПОЛИЗ операцию ввода переменной
put_poliz(1, w, 0);
}
}
}
// Оператор stop (завершить выполнение)
void __fastcall TForm1::oper_stop(void)
{
token(LEFT, "(");
token(RIGHT, ")");
token(SEMICOLON, ";");
get_end();
// Поместить в ПОЛИЗ операцию останова
put_poliz(1, STOP, 0);
}
// Оператор erase() (стереть окно результата)
void __fastcall TForm1::oper_erase(void)
{
token(LEFT, "(");
token(RIGHT, ")");
token(SEMICOLON, ";");
get_end();
// Поместить в ПОЛИЗ операцию стирания окна
put_poliz(1, ERASE, 0);
}
// Оператор
void __fastcall TForm1::oper(void)
{
switch (lex)
{
default:
Scan->er("Синтаксическая ошибка");
case LEFT_FIG:
get_end();
do
{
oper();
} while (lex != RIGHT_FIG);
get_end();
64
65
return;
case LET:
token(IDENT, "идентификатор");
case IDENT:
// Контроль идентификатора на неопределенность
Scan->undef_ident();
token(EQ_SET, "=");
case LET1:
compile_set();
return;
case IF:
oper_if();
return;
case FOR:
oper_for();
return;
case DO:
oper_do();
return;
case OUTPUT:
oper_output();
return;
case OUTPUTLN:
oper_outputln();
return;
case INPUT_:
oper_input();
return;
case STOP:
oper_stop();
return;
case ERASE:
oper_erase();
return;
case CONTINUE:
oper_continue();
return;
case BREAK:
oper_break();
return;
}
}
// Оператор объявления переменных
// На входе уже прочитан первый идентификатор
// и лексемы ":" или ","
void __fastcall TForm1::declare(void)
{
for (;;)
65
66
{
// Контроль идентификатора на дубликат описания
Scan->double_ident();
switch (lex)
{
default:
Scan->er("Ожидалось: , или :");
case COMMA:
token(IDENT, "идентификатор");
// Сохраним координаты лексемы для печати возможной ошибки
Scan->save_coord();
get_end();
continue;
case COLON:
switch (get_end())
{
default:
Scan->er("Ожидалось: % ! или $");
case INTEGER: case REAL: case LOGIC:
// Устанавливаем типы описанных переменных
Scan->set_declare(lex);
}
}
token(SEMICOLON, ";");
get_end();
return;
}
}
// Трансляция
void __fastcall TForm1::compile(void)
{
// Читаем первую лексему, она обязательно должна быть
if (get_lex() == END)
Scan->er("Программа не задана");
// Программа начинается с "{"
wait_lex(LEFT_FIG, "{");
get_end();
// Пока нет ни одного цикла
level = 0;
// Пока ни одна переменная не использована
memset(yes_var, 0, sizeof(yes_var));
// Компиляция описаний и операторов
for (;;)
{
Scan->init_declare();
if (lex == RIGHT_FIG)
break;
// Попытка распознать оператор объявления переменных
// или оператор присваивания с опущенным let
if (lex == IDENT)
{
66
67
// Сохраним координаты лексемы для печати возможной ошибки
Scan->save_coord();
if (get_end() == COMMA || lex == COLON)
{
// Да, это оператор объявления
declare();
continue;
}
// Здесь ожидаем после идентификатора только "="
if (lex != EQ_SET)
Scan->er("Ожидалось: , : или =");
// Контроль идентификатора на неопределенность
Scan->undef_ident0();
lex = LET1;
}
else if (lex == LET)
{
get_end();
wait_lex(IDENT, "идентификатор");
// Контроль идентификатора на неопределенность
Scan->undef_ident();
get_end();
wait_lex(EQ_SET, "=");
lex = LET1;
}
// Обработка исполнительных операторов
oper();
}
if (get_lex() != END)
Scan->er("Программа содержит лишние операторы");
// Поместить в ПОЛИЗ операцию останова
put_poliz(1, STOP, 0);
}
// Получить значение из элемента n стека операндов
// Например, для верхнего элемента стека n = 1
VT __fastcall TForm1::get_elem(int n)
{
STACK_OP *p;
VT
vt;
p = stack_op + ind_stack_op - n;
switch (p->t)
{
// Случайное целое число
case RAND:
vt.type_elem = INTEGER;
vt.value_elem = rand();
break;
// Случайное вещественное число
case RAND_REAL:
vt.type_elem = REAL;
67
68
vt.value_elem = (double)rand() / RAND_MAX;
break;
// Значение идентификатора (может быть целым или вещественным)
case IDENT:
vt.type_elem = (type_var[p->n] == REAL) ? REAL : INTEGER;
vt.value_elem = value_var[p->n];
break;
// Значение целочисленной константы, сформированной
// в результате предыдущих операций ПОЛИЗа
case WORK:
vt.type_elem = INTEGER;
vt.value_elem = (int)p->val;
break;
// Значение вещественной константы, сформированной
// в результате предыдущих операций ПОЛИЗа
case WORKD:
vt.type_elem = REAL;
vt.value_elem = p->val;
break;
// Целочисленная константа
case NUM:
vt.type_elem = INTEGER;
vt.value_elem = number_bin[p->n];
break;
// Вещественная константа
default: // NUMD
vt.type_elem = REAL;
vt.value_elem = number_bin[p->n];
}
return vt;
}
// Получить два операнда из стека операндов
void __fastcall TForm1::get_elem2(void)
{
vt1 = get_elem(2);
vt2 = get_elem(1);
}
// Сброс незначащих нулей дробной части
void __fastcall TForm1::delete_zero(void)
{
char *s;
// Есть десятичная точка?
if ((s = strchr(str_value, '.')) != NULL)
{
for (s = str_value + strlen(str_value); *--s == '0';)
;
if (*s != '.')
++s;
*s = 0;
}
}
// Интерпретация ПОЛИЗа
void __fastcall TForm1::run_poliz(void)
68
69
{
int
n, cnt, int_v;
char
c, *s, txt[100];
double double_v;
// Начинаем интерпретацию с начала ПОЛИЗа
ind_poliz = 0;
// Стек операндов пока пуст
ind_stack_op = 0;
// Сброс окна результата
Memo3->Clear();
// Все переменные программы перед началом выполнения нулевые
memset(value_var, 0, sizeof(value_var));
// Пока нет результата output
result_print = "";
// Сделаем активной страницу с результатом выполнения
PageControl1->ActivePageIndex = 2;
// Курсор - песочные часы (а вдруг слишком долго будет выполняться программа)
Screen->Cursor = crHourGlass;
// Контроль зацикливания программы (в случае завершения этого цикла - каламбур?)
for (cnt = 0; (cnt_loop == 0) ? true : cnt < cnt_loop;)
{
// Очередной элемент ПОЛИЗа операнд?
if (poliz[ind_poliz].op == 0)
{
// Стек операндов переполнен?
if (ind_stack_op == MAX_STACK_OP)
{
// Стандартный курсор
Screen->Cursor = crDefault;
MessageBox(NULL, "Переполнение стека операндов", "Выполнение программы", MB_OK);
return;
}
// Поместим операнд в стек операндов
stack_op[ind_stack_op].t = poliz[ind_poliz].t;
stack_op[ind_stack_op].n = poliz[ind_poliz].n;
++ind_stack_op;
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
continue;
}
// Учет количества операций для предотвращения зацикливания
++cnt;
// Признак отсутствия оператора input с комментарием
comment_var = "";
// Выполним соответствующую операцию
switch (poliz[ind_poliz].t)
{
// Логическое ИЛИ
case OR:
69
70
// Получим операнды логической операции
get_elem2();
// В рабочей переменной сохраняем результат операции
int_v = vt1.value_elem != 0.0 || vt2.value_elem != 0.0;
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = WORK;
stack_op[ind_stack_op - 1].val = int_v;
break;
// Логическое И
case AND:
// Получим операнды логической операции
get_elem2();
// В рабочей переменной сохраняем результат операции
int_v = vt1.value_elem != 0.0 && vt2.value_elem != 0.0;
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = WORK;
stack_op[ind_stack_op - 1].val = int_v;
break;
// Сложение
case ADD:
// Получим операнды операции сложения
get_elem2();
// В рабочей переменной сохраняем результат операции
double_v = vt1.value_elem + vt2.value_elem;
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого или вещественного типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = (vt1.type_elem == INTEGER && vt2.type_elem == INTEGER) ?
WORK : WORKD;
stack_op[ind_stack_op - 1].val = double_v;
break;
// Вычитание
case SUB:
// Получим операнды операции вычитания
get_elem2();
// В рабочей переменной сохраняем результат операции
double_v = vt1.value_elem - vt2.value_elem;
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого или вещественного типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = (vt1.type_elem == INTEGER && vt2.type_elem == INTEGER) ?
WORK : WORKD;
stack_op[ind_stack_op - 1].val = double_v;
break;
// Умножение
case MUL:
// Получим операнды операции умножения
get_elem2();
70
71
// В рабочей переменной сохраняем результат операции
double_v = vt1.value_elem * vt2.value_elem;
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого или вещественного типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = (vt1.type_elem == INTEGER && vt2.type_elem == INTEGER) ?
WORK : WORKD;
stack_op[ind_stack_op - 1].val = double_v;
break;
// Деление
case DIV:
// Получим операнды операции деления
get_elem2();
// Контроль деления на ноль
if (vt2.value_elem == 0.0)
{
// Стандартный курсор
Screen->Cursor = crDefault;
MessageBox(NULL, "Деление на ноль", "Выполнение программы", MB_OK);
return;
}
// В рабочей переменной сохраняем результат операции
// Операция деления зависит от типа операндов
if (vt1.type_elem == INTEGER && vt2.type_elem == INTEGER)
double_v = (int)vt1.value_elem / (int)vt2.value_elem;
else
double_v = vt1.value_elem / vt2.value_elem;
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого или вещественного типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = (vt1.type_elem == INTEGER && vt2.type_elem == INTEGER) ?
WORK : WORKD;
stack_op[ind_stack_op - 1].val = double_v;
break;
// Остаток
case MOD:
// Получим операнды операции получения остатка
get_elem2();
// Контроль деления на ноль
if (vt2.value_elem == 0.0)
{
// Стандартный курсор
Screen->Cursor = crDefault;
MessageBox(NULL, "Деление на ноль (остаток)", "Выполнение программы", MB_OK);
return;
}
// Остаток допустим только для целых чисел
if (vt1.type_elem != INTEGER || vt2.type_elem != INTEGER)
{
// Стандартный курсор
Screen->Cursor = crDefault;
MessageBox(NULL, "Остаток допустим только для целых чисел", "Выполнение программы",
MB_OK);
return;
}
double_v = (int)vt1.value_elem % (int)vt2.value_elem;
71
72
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = WORK;
stack_op[ind_stack_op - 1].val = double_v;
break;
// Присваивание
case LET:
// Получим значение верхнего элемента стека
vt1 = get_elem(1);
// В зависимости от типа результирующей переменной выполняем присваивание
n = stack_op[ind_stack_op - 2].n;
switch (type_var[n])
{
case INTEGER:
value_var[n] = (int)vt1.value_elem;
break;
case REAL:
value_var[n] = vt1.value_elem;
break;
default: // LOGIC
value_var[n] = vt1.value_elem != 0.0;
}
// Из стека операндов удаляется информация оператора присваивания
ind_stack_op -= 2;
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
continue;
// Унарный минус
case NEG:
// Получим значение верхнего элемента стека
vt1 = get_elem(1);
// В рабочей переменной сохраняем результат операции
double_v = -vt1.value_elem;
// Вместо верхнего элемента стека операнда размещаем
// рабочую переменную целого или вещественного типа
stack_op[ind_stack_op - 1].t = (vt1.type_elem == INTEGER) ? WORK : WORKD;
stack_op[ind_stack_op - 1].val = double_v;
break;
// Логическое отрицание
case NOT:
// Получим значение верхнего элемента стека
vt1 = get_elem(1);
// В рабочей переменной сохраняем результат операции
double_v = vt1.value_elem == 0.0 ? 1 : 0;
// Вместо верхнего элемента стека операнда размещаем
// рабочую переменную целого типа
stack_op[ind_stack_op - 1].t = WORK;
stack_op[ind_stack_op - 1].val = double_v;
break;
72
73
// Операции сравнения
case EQ:
case NE:
case LE:
case GE:
case LT:
case GT:
// Получим операнды операции сравнения
get_elem2();
// В рабочей переменной сохраняем результат операции
switch (poliz[ind_poliz].t)
{
case EQ:
double_v = vt1.value_elem == vt2.value_elem;
break;
case NE:
double_v = vt1.value_elem != vt2.value_elem;
break;
case LE:
double_v = vt1.value_elem <= vt2.value_elem;
break;
case GE:
double_v = vt1.value_elem >= vt2.value_elem;
break;
case LT:
double_v = vt1.value_elem < vt2.value_elem;
break;
default: // GT
double_v = vt1.value_elem > vt2.value_elem;
}
// Вместо двух элементов стека операнда размещаем
// рабочую переменную целого типа
--ind_stack_op;
stack_op[ind_stack_op - 1].t = WORK;
stack_op[ind_stack_op - 1].val = double_v;
break;
// Печать символьной строки
case STR:
// Получить адрес печатаемой строки
s = sym_str[stack_op[--ind_stack_op].n].c_str();
// При печати строки символ # заменяется на перевод строки
while ((c = *s++) != 0)
{
if (c == '#')
{
result_print += '\n';
Memo3->Lines->Add(result_print);
result_print = "";
}
else
{
result_print += c;
}
}
73
74
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
continue;
// Печать одного перевода строки
case OUTLN:
result_print += "\n";
Memo3->Lines->Add(result_print);
result_print = "";
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
continue;
// Печать числа без размера поля
case OUT_:
// Получим значение верхнего элемента стека
vt1 = get_elem(1);
// В зависимости от типа выполним печать
if (vt1.type_elem == REAL)
{
sprintf(str_value, "%lf", vt1.value_elem);
// Сброс незначащих нулей дробной части вещественного числа
delete_zero();
}
else
{
sprintf(str_value, "%d", (int)vt1.value_elem);
}
result_print += str_value;
--ind_stack_op;
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
continue;
// Печать числа с размером поля
case OUTN:
// Получим значение размера поля
vt1 = get_elem(1);
// Получим печатаемое значение
vt2 = get_elem(2);
// В зависимости от типа выполним печать
if (vt2.type_elem == REAL)
{
sprintf(str_value, "%.*lf", (int)vt1.value_elem, vt2.value_elem);
// Сброс незначащих нулей дробной части вещественного числа
delete_zero();
}
else
{
sprintf(str_value, "%*d", (int)vt1.value_elem, (int)vt2.value_elem);
}
result_print += str_value;
ind_stack_op -= 2;
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
continue;
74
75
// Ввод значения переменной с комментарием
case INPUT_STR:
// Получить адрес комментария
s = sym_str[stack_op[ind_stack_op - 2].n].c_str();
// При печати строки символ # заменяется на перевод строки
while ((c = *s++) != 0)
{
if (c == '#')
comment_var += "\r\n";
else
comment_var += c;
}
// На место строки запишем элемент идентификатора
stack_op[ind_stack_op - 2] = stack_op[ind_stack_op - 1];
--ind_stack_op;
// Ввод значения переменной без комментария
case INPUT_:
// Получим текущее значение переменной
vt1 = get_elem(1);
// В зависимости от типа выполним печать текущего значения
if (vt1.type_elem == REAL)
{
sprintf(str_value, "%lf", vt1.value_elem);
// Сброс незначащих нулей дробной части
delete_zero();
}
else
{
sprintf(str_value, "%d", (int)vt1.value_elem);
}
n = stack_op[--ind_stack_op].n;
name_var = var[n];
type = type_var[n];
// Выполнение ввода
Form3 = new TForm3(NULL);
Form3->ShowModal();
delete Form3;
// Если ввод удачный, выполним присваивание
if (ok_input == 1)
value_var[n] = value_input;
// Завершить выполнение программы?
else if (ok_input == -1)
{
// Стандартный курсор
Screen->Cursor = crDefault;
MessageBox(NULL, "Выполнение программы завершено", "Выполнение программы", MB_OK);
return;
}
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
continue;
// Безусловный переход
case GOTO:
75
76
ind_poliz = poliz[ind_poliz].n;
continue;
// Условный переход по false
case TEST:
// Получим значение условия перехода
vt1 = get_elem(1);
--ind_stack_op;
// Нет перехода?
if (vt1.value_elem)
++ind_poliz;
else
ind_poliz = poliz[ind_poliz].n;
continue;
// Стереть окно результата
case ERASE:
Memo3->Clear();
break;
// Завершение выполнения программы
case STOP:
// Стандартный курсор
Screen->Cursor = crDefault;
MessageBox(NULL, "Выполнение программы завершено", "Выполнение программы", MB_OK);
return;
}
// Переходим к следующему элементу ПОЛИЗа
++ind_poliz;
}
// Стандартный курсор
Screen->Cursor = crDefault;
MessageBox(NULL, "Выполнение прекращено, возможно зацикливание", "Выполнение программы",
MB_OK);
}
void __fastcall TForm1::N6Click(TObject *Sender)
{
AnsiString str = "Ошибок не обнаружено";
int
i;
bool
b;
err = "";
ClearError();
ok_compile = true;
ind_poliz = 0;
// Создадим объект лексического сканера
Scan = new Lex(RichEdit1, Memo2, StringGrid3, StringGrid4, StringGrid5, Memo1);
// Сделаем активной страницу с программой
PageControl1->ActivePageIndex = 0;
// Выполним синтаксический разбор
try
{
compile();
76
77
// В случае корректной трансляции формируем возможный
// список неиспользованных переменных
b = true;
for (i = 0; i < cnt_var; ++i)
{
if (yes_var[i] == 0)
{
if (b)
{
str += "\nНеиспользованные переменные:";
b = false;
}
str += "\n" + var[i];
}
}
}
// Перехват ошибочной ситуации
catch (int)
{
err = " В строке " + IntToStr(Scan->getLineNum()) + " : " + Scan->Error();
RichEdit1->SelStart = RichEdit1->Perform(EM_LINEINDEX, Scan->getLineNum() - 1, 0) +
Scan->getLinePos() - 1;
RichEdit1->SelLength = Scan->getLenLex();
RichEdit1->SelAttributes->Color = clRed;
RichEdit1->SetFocus();
// Убираем выделение текста
RichEdit1->SelLength = 0;
str = "Программа ошибочна";
ok_compile = false;
}
// Обновляем статусную строку
UpdateStatus();
// Обнародуем результат лексического разбора
Scan->show_table();
MessageBox(NULL, str.c_str(), "Трансляция программы", MB_OK);
// Удаляем сканер
delete Scan;
}
// Отсутствие контроля на зацикливание
void __fastcall TForm1::N13Click(TObject *Sender)
{
cnt_loop = 0;
Caption = "Транслятор (выполнение без контроля зацикливания)";
}
// Не более 1000 операций
void __fastcall TForm1::N10001Click(TObject *Sender)
{
cnt_loop = 1000;
Caption = "Транслятор (выполнение не более 1000 операций)";
}
// Не более 10000 операций
void __fastcall TForm1::N100001Click(TObject *Sender)
{
cnt_loop = 10000;
77
78
Caption = "Транслятор (выполнение не более 10000 операций)";
}
// Не более 100000 операций
void __fastcall TForm1::N1000001Click(TObject *Sender)
{
cnt_loop = 100000;
Caption = "Транслятор (выполнение не более 100000 операций)";
}
78
79
Приложение Б – Контрольные примеры
Пример 0
/*
Здесь
могла
быть
ваша
реклама!
*/
// Но пока здесь минимальная программа, которую
// можно написать на любом языке программирования
{
outputln("Привет, мир!#Всего тебе хорошего!");
}
Результат 0
Пример 1
/*
Вычисление целочисленного квадратного корня
*/
{
div, res, X, Y : %;
X = 101;
for (;;)
{
outputln
(
"Введите целое число для вычисления квадратного корня#"
"или 0 для завершения программы"
);
input(X);
if X = 0 then stop(); end_else;
div = 1; res = 0; Y = X;
do while X <> 0
{
if X < div then break; end_else;
res = res + 1;
if X = div then break; end_else;
X = X - div;
div = div + 2;
79
80
} loop;
outputln("Квадратный корень из " Y " равен " res);
X = Y;
}
}
Результат 1
Пример 2
// Вычисление вещественного квадратного корня
{
EPS, x, nx, n, w : !;
EPS = 1E-15;
n = 2;
for (;;)
{
outputln
(
"Введите вещественное число для вычисления квадратного корня#"
"или 0 для завершения программы"
);
input(n);
if n = 0 then stop(); end_else;
if n < 0 then
{
outputln("Число должно быть положительным");
continue;
} end_else;
for (x = 1; ; x = nx)
{
nx = (x + n / x) / 2.0;
w = x - nx;
if w < 0.0 then let w = -w; end_else;
if w < EPS then break; end_else;
}
outputln("Квадратный корень из " n " равен " x:10);
}
}
80
81
Результат 2
Пример 3
// Печать таблицы умножения
{
i, j, k : %;
outputln("Таблица умножения");
for (i = 1; i <= 9; i = i + 1)
{
for (j = 1; j <= 9; j = j + 1)
{
let k = i * j;
output(" " j "*" i "=" k:2);
}
outputln();
}
}
Результат 3
81
82
Пример 4
/*
Сокращение дроби при помощи алгоритма Евклида
для вычисления наибольшего общего делителя
Например, дробь 462/1071 сократится до 22/51
*/
{
numerator, denumerator : %;
a, b, NOD
: %;
numerator = 462;
denumerator = 1071;
for (;;)
{
// Ввод очередных параметров
outputln("#Введите параметры дроби или 0 для завершения программы");
input(numerator);
if numerator = 0 then stop(); end_else;
input(denumerator);
if denumerator = 0 then stop(); end_else;
// Вычисляем НОД числителя и знаменателя
a = numerator; b = denumerator;
do while (a <> 0) and (b <> 0)
{
if a >= b then a = a mod b; else b = b mod a; end_else;
} loop;
// Выполняем сокращение дроби
NOD = a + b;
a = numerator / NOD;
b = denumerator / NOD;
if b = 1 then
outputln(numerator "/" denumerator " = " a);
else
outputln(numerator "/" denumerator " = " a "/" b);
end_else;
}
}
Результат 4
82
83
Пример 5
// Печать календаря для заданного года и месяца
// Базируется на книге Касаткина 'Логическое программирование в занимательных задачах'
// (Задача логическая - ключ арифметический)
{
year, month, day, r, t, i : %;
r1
: !;
year = 2018;
month = 9;
for (;;)
{
day = 1;
outputln("#Введите год для печати календаря (от 2000 до 3000)#или 0 для завершения
программы");
input(year);
if year = 0 then stop(); end_else;
if (year < 2000) or (year > 3000) then
{
outputln("Недопустимое задание года"); continue;
} end_else;
outputln("#Введите номер месяца (от 1 до 12)#или 0 для завершения программы");
input(month);
if month = 0 then stop(); end_else;
if (month < 1) or (month > 12) then
{
outputln("Недопустимое задание месяца"); continue;
} end_else;
r = (month - 1) * 31 + day;
if month < 3 then // январь-февраль
r1 = year - 1;
else
{
t = month * 0.4 + 2.3;
r = r - t;
r1 = year;
} end_else;
t = r1 * 0.25;
r = r + t;
t = r1 * 0.01;
t = (t + 1) * 0.75;
r = r - t;
t = (year * 365 + r - 366) mod 7;
if month = 2 then
if (year mod 4 = 0) and (year mod 100 <> 0) or (year mod 400 = 0) then
day = 29;
else
day = 28;
end_else;
else
if (month = 4) or (month = 6) or (month = 9) or (month = 11) then
day = 30;
else
day = 31;
end_else;
end_else;
outputln("#Календарь за " year "/" month);
output("# Пн Вт Ср Чт Пт Сб Вс#");
for (i = 0; i < t; i = i + 1)
output("
");
for (i = 1; i <= day; i = i + 1)
{
output(i:3);
83
84
t = (t + 1) mod 7;
if t = 0 then outputln(); end_else;
}
if t <> 0 then outputln(); end_else;
}
}
Результат 5
84
85
Пример 6
// Схематическое изображение новогодней елки
{
i, j, n : %;
bool
: $;
bool = true;
n = 13;
// Еще один пример записи бесконечного цикла (наряду с for(;;))
do while bool
{
outputln("Высота елки в диапазоне 3...20 или 0 для завершения");
input(n);
if n = 0 then break; end_else;
if (n < 3) or (n > 20) then
{
outputln("Высота елки ошибочна");
continue;
} end_else;
erase();
for (i = 0; i < n; i = i + 1)
output(" ");
outputln("*");
for (i = 0; i < n; i = i + 1)
{
for (j = 1; j < n - i; j = j + 1)
output(" ");
for (j = -1; j < i; j = j + 1)
output("/");
output("|");
for (j = -1; j < i; j = j + 1)
output("\");
outputln();
}
for (i = 0; i < n - 1; i = i + 1)
output(" ");
outputln("|||");
for (i = 0; i < n - 1; i = i + 1)
output(" ");
outputln("~~~");
} loop;
}
85
86
Результат 6
Пример 7
// Всевозможные константы
{
i: %;
d: !;
b: $;
i = 0111000b;
outputln("0111000b = " i);
i = 10111000B;
outputln("10111000B = " i);
i = 10111000o;
outputln("10111000o = " i);
i = 777O;
outputln("777O = " i);
i = 10111000Bh;
outputln("10111000Bh = " i);
i = 0ffffBh;
outputln("0ffffBh = " i);
i = 12d;
outputln("12d = " i);
i = 4D;
outputln("4D = " i);
i = -4;
outputln("-4 = " i);
b = true;
86
87
outputln("true = " b);
b = false;
outputln("false = " b);
d = 3.14;
outputln("3.14 = " d);
d = 3e12;
outputln("3e12 = " d);
d = -3e-3;
outputln("-3e-3 = " d);
d = 3.14E+7;
outputln("3.14E+7 = " d);
}
Результат 7
Пример 8
// Нахождение простых чисел в заданном интервале
{
Gran, n, i, k : %;
flag
: $;
Gran = 1000;
for (;;)
{
outputln
(
"#Введите границу интервала для поиска простых чисел"
"#Ввод границы <= 3 означает завершение работы"
);
87
88
input(Gran);
if Gran <= 3 then stop(); end_else;
erase();
output("#
2
3");
k = 2; n = 5;
do while n <= Gran
{
let flag = true;
for (i = 2; i <= n - 1; i = i + 1)
{
if n mod i = 0 then { flag = false; break; } end_else;
}
if flag then
{
output(n:7);
k = k + 1;
if k mod 10 = 0 then outputln(); end_else;
} end_else;
n = n + 2;
} loop;
outputln("##Всего простых чисел = " k);
}
}
Результат 8
88
89
Пример 9
// Запись числа словами в пределах от 1 до 999
{
num, num1, num2, n : %;
num1 = 17;
num2 = 301;
for (;;)
{
outputln
(
"#Введите первое число для печати словами (от 1 до 999)#или 0 для завершения программы"
);
input(num1);
if num1 = 0 then stop(); end_else;
if num1 > 999 then
{
outputln("Слишком большое первое число"); continue;
} end_else;
outputln
(
"#Введите последнее число для печати словами (от 1 до 999)#или 0 для завершения
программы"
);
input(num2);
if num2 = 0 then stop(); end_else;
if num2 > 999 then
{
outputln("Слишком большое последнее число"); continue;
} end_else;
if num1 > num2 then
{
outputln("Первое число больше последнего"); continue;
} end_else;
do while num1 <= num2
{
num = num1; num1 = num1 + 1;
// Получаем сотни
n = num / 100;
if n = 1 then output("сто ");
end_else;
if n = 2 then output("двести ");
end_else;
if n = 3 then output("триста ");
end_else;
if n = 4 then output("четыреста "); end_else;
if n = 5 then output("пятьсот ");
end_else;
if n = 6 then output("шестьсот "); end_else;
if n = 7 then output("семьсот ");
end_else;
if n = 8 then output("восемьсот "); end_else;
if n = 9 then output("девятьсот "); end_else;
num = num mod 100;
if num >= 20 then
{
n = num / 10;
num = num mod 10;
if n = 2 then output("двадцать ");
end_else;
if n = 3 then output("тридцать ");
end_else;
if n = 4 then output("сорок ");
end_else;
if n = 5 then output("пятьдесят ");
end_else;
if n = 6 then output("шестьдесят "); end_else;
if n = 7 then output("семьдесят ");
end_else;
if n = 8 then output("восемьдесят "); end_else;
if n = 9 then output("девяносто ");
end_else;
89
90
} end_else;
if num = 1 then output("один");
if num = 2 then output("два");
if num = 3 then output("три");
if num = 4 then output("четыре");
if num = 5 then output("пять");
if num = 6 then output("шесть");
if num = 7 then output("семь");
if num = 8 then output("восемь");
if num = 9 then output("девять");
if num = 10 then output("десять");
if num = 11 then output("одиннадцать");
if num = 12 then output("двенадцать");
if num = 13 then output("тринадцать");
if num = 14 then output("четырнадцать");
if num = 15 then output("пятнадцать");
if num = 16 then output("шестнадцать");
if num = 17 then output("семнадцать");
if num = 18 then output("восемнадцать");
if num = 19 then output("девятнадцать");
outputln();
} loop;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
end_else;
}
}
Результат 9
90
91
Пример 10
// Дата Пасхи
{
year, a, b : %;
year = 2018;
for (;;)
{
outputln("#Введите год для вычисления даты Пасхи или 0 для завершения программы");
input(year);
if year = 0 then stop(); end_else;
output("В " year " году дата Пасхи ");
a = (19 * (year mod 19) + 15) mod 30;
b = (2 * (year mod 4) + 4 * (year mod 7) + 6 * a + 6) mod 7;
a = a + b;
if a > 9 then
{
// Дата Пасхи по Юлианскому календарю в апреле
a = a - 9;
// Перевод даты в Григорианский календарь
a = a + 13;
// Дата в переделах апреля?
if a <= 30 then
outputln(a " апреля");
else
outputln(a - 30 " мая");
end_else;
}
else
{
// Дата Пасхи по Юлианскому календарю в марте
a = a + 22;
// Перевод даты в Григорианский календарь
a = a + 13;
// Дата в переделах марта?
if a <= 31 then
outputln(a " марта");
else
outputln(a - 31 " апреля");
end_else;
} end_else;
}
}
91
92
Результат 10
Пример 11
// Решение квадратного уравнения
{
EPS, x, nx, n, w, A, B, C, D : !;
// Кстати, в этой программе компилятор обнаружит неиспользованную переменную 'n'
EPS = 1E-15;
A = -1;
B = -2;
C = 15;
for (;;)
{
input("Коэффициент для X^2" A);
if A = 0 then
{
outputln("Уравнение не является квадратным"); continue;
} end_else;
input("Коэффициент для X" B "Свободный член" C);
// Печатаем исходное уравнение
outputln();
if A = -1 then
output("-");
else
if A <> 1 then
output(A);
end_else;
end_else;
output("X^2");
if B <> 0.0 then
{
if B = 1 then
output(" + ");
else
if B = -1 then
output(" - ");
else
if B < 0 then
output(" - " -B);
else
output(" + " B);
92
93
end_else;
end_else;
end_else;
output("X");
} end_else;
if C <> 0.0 then
if C < 0 then
output(" - " -C);
else
output(" + " C);
end_else;
end_else;
outputln(" = 0");
// Вычисляем дискриминант
D = B * B - 4 * A * C;
// Есть ли корни?
if D < 0 then
{
outputln("Уравнение не имеет вещественных корней"); continue;
} end_else;
// Только один корень?
if D = 0 then
{
outputln("Уравнение имеет один корень X = " -B / (2 * A)); continue;
} end_else;
// Вычисляем квадратный корень дискриминанта
// (Используется алгоритм 2-го примера)
for (x = 1; ; x = nx)
{
nx = (x + D / x) / 2.0;
w = x - nx;
if w < 0.0 then let w = -w; end_else;
if w < EPS then break; end_else;
}
// Печатаем результат
outputln("Уравнение имеет два корня");
outputln("X1 = " (-B + x) / (2 * A));
outputln("X2 = " (-B - x) / (2 * A));
}
}
Результат 11
93
94
Пример 12
//
//
//
//
{
Баше - математическая игра, в которой два игрока из кучки,
содержащей первоначально N предметов, по очереди берут не
менее одного и не более M предметов. Проигравшим считается
тот, кому нечего брать.
N, M, K : %;
step
: $;
N = 15;
M = 3;
for (;;)
{
// Ввод параметров очередной игры
input("Размер кучки предметов" N);
if (N < 10) or (N > 100) then
{
outputln("Размер кучки должен быть в диапазоне 10...100");
continue;
} end_else;
input("Максимальное количество забираемых предметов" M);
if (M < 1) or (M > N) then
{
outputln("Ошибочно максимальное количество забираемых предметов");
continue;
} end_else;
outputln("Размер кучки = " N ", размер выбора " M);
input("Определение первого хода игры#0 - ход компьютера, 1 - ход человека" step);
// Выполнение игры
for (;;)
{
// Ход человека?
if step then
{
for (;;)
{
input("Сколько берете предметов" K);
if (K >= 1) and (K <= M) and (K <= N) then break; end_else;
outputln("Выбор ошибочен");
}
outputln("Человек взял " K " предметов");
}
else
{
// Берем столько предметов, чтобы после хода
// количество оставшихся предметов было кратно (M+1)
for (K = 1; K <= M; K = K + 1)
{
if ((N - K) mod (M + 1)) = 0 then break; end_else;
}
// Не удалось это сделать?
if K > M then
{
// Пытаемся случайным выбором сбить с толку человека
// в надежде, что когда-то он ошибется
K = rand() mod M + 1;
} end_else;
outputln("Компьютер взял " K " предметов");
} end_else;
// Уменьшаем число предметов в кучке
94
95
// и анализируем завершение игры
N = N - K;
outputln("В кучке осталось " N " предметов");
if N = 0 then
{
if step then
outputln("Победа человека");
else
outputln("Победа компьютера");
end_else;
break;
} end_else;
// Меняем очередность хода
step = not step;
}
}
}
Результат 12
95
96
Пример 13
// Вычисление
{
x, y
Pi
Count
inner_count
i: %;
числа Пи методом Монте-Карло
:
:
:
:
!;
!;
%;
%;
//
//
//
//
Координаты случайной точки
Вычисленное значение числа Пи
Общее число случайных точек
Число точек, попавших внутрь круга
count = 100000;
for (;;)
{
input("Число случайных точек" count);
if (count < 100) or (count > 10000000) then
{
outputln("Число точек вне диапазона 100...10000000"); continue;
} end_else;
// Бросаем случайные точки
inner_count = 0;
for (i = 0; i < count; i = i + 1)
{
// Генерируем случайные координаты
x = rand_real() * 2 - 1; y = rand_real() * 2 - 1;
// Точка в пределах круга c единичным радиусом?
if x * x + y * y <= 1 then inner_count = inner_count + 1; end_else;
}
// Формируем результат
Pi = 4.0 * inner_count / count;
// Печатаем результат
outputln
(
"#Почти точное значение: 3.14159265358979323846..."
"#Вычисленное значение : " Pi:20
);
}
}
Результат 13
96
97
Пример 14
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
{
Вычисление интеграла методом Симпсона
https://rosettacode.org/wiki/Numerical_integration#C
double int_simpson(double from, double to, double n, double (*func)())
{
double h = (to - from) / n;
double sum1 = 0.0;
double sum2 = 0.0;
int
i;
for (i = 0; i < n; ++i)
sum1 += func (from + h * i + h / 2.0);
for (i = 1; i < n; ++i)
sum2 += func (from + h * i);
return h / 6.0 * (func (from) + func (to) + 4.0 * sum1 + 2.0 * sum2);
}
h, x, y, from, to, sum1, sum2, _from, _to, Integral : !;
i, n, func
: %;
from = -3;
to = +10; // Кстати, это пример унарного плюса!
n = 1000;
for (;;)
{
input("Интегрируемая функция:#0 - X^3#1 - X^2#2 - X#3 - 1/X" func);
if (func < 0) or (func > 3) then
{
outputln("Ошибочен выбор интегрируемой функции");
continue;
} end_else;
input("Минимальная граница интегрирования" from);
if (func = 3) and (from < 0.001) then
{
outputln("Для функции 1/X минимальная граница д.б. не меньше 0.001");
continue;
} end_else;
input("Максимальная граница интегрирования" to);
if to <= from then
{
outputln("Максимальная граница не больше минимальной");
continue;
} end_else;
input("Количество интервалов разбиения" n);
if (n < 2) or (n > 1000000) then
{
outputln("Количество интервалов разбиения вне диапазона [2...1000000]");
continue;
} end_else;
output("#Интегрируемая функция: ");
if func = 0 then
outputln("X^3");
else
if func = 1 then
outputln("X^2");
else
97
98
if func = 2 then
outputln("X");
else
outputln("1/X");
end_else;
end_else;
end_else;
outputln("Интервал интегрирования [" from " ... " to "]");
outputln("Интервалов разбиения " n);
h = (to - from) / n;
sum1 = 0;
sum2 = 0;
for (i = 0; i < n; i = i + 1)
{
// sum1 += func (from + h * i + h / 2.0);
x = from + h * i + h / 2;
if func = 0 then
y = x * x * x;
else
if func = 1 then
y = x * x;
else
if func = 2 then
y = x;
else
y = 1 / x;
end_else;
end_else;
end_else;
sum1 = sum1 + y;
}
for (i = 1; i < n; i = i + 1)
{
// sum2 += func (from + h * i);
x = from + h * i;
if func = 0 then
y = x * x * x;
else
if func = 1 then
y = x * x;
else
if func = 2 then
y = x;
else
y = 1 / x;
end_else;
end_else;
end_else;
sum2 = sum2 + y;
}
// return h / 6.0 * (func (from) + func (to) + 4.0 * sum1 + 2.0 * sum2);
if func = 0 then
_from = from * from * from;
else
if func = 1 then
_from = from * from;
else
if func = 2 then
_from = from;
98
99
else
_from = 1 / from;
end_else;
end_else;
end_else;
if func = 0 then
_to = to * to * to;
else
if func = 1 then
_to = to * to;
else
if func = 2 then
_to = to;
else
_to = 1 / to;
end_else;
end_else;
end_else;
Integral = h / 6 * (_from + _to + 4 * sum1 + 2 * sum2);
outputln("Интеграл равен " Integral);
}
}
Результат 14
99
100
Пример 15
/*
Решение системы 3-х уравнений с тремя неизвестными
{ -----------------------------------------------------------------------{ решение уравнений вида:
{ |a1*x + b1*y + c1*z = d1|
{ |a2*x + b2*y + c2*z = d2|
{ |a3*x + b3*y + c3*z = d3|
{
{ метод решения:
{
|d1 b1 c1|
|a1 d1 c1|
|a1 b1 d1|
{
|d2 b2 c2|
|a2 d2 c2|
|a2 b2 d2|
{
|d3 b3 c3|
|a3 d3 c3|
|a3 b3 d3|
{ x = ---------y = ---------z = ---------{
|a1 b1 c1|
|a1 b1 c1|
|a1 b1 c1|
{
|a2 b2 c2|
|a2 b2 c2|
|a2 b2 c2|
{
|a3 b3 c3|
|a3 b3 c3|
|a3 b3 c3|
{
{ выражаем определители третьего порядка:
{ e := (a1*b2*c3+b1*c2*a3+c1*a2*b3-a3*b2*c1-b3*c2*a1-c3*a2*b1);
{ ex := (d1*b2*c3+b1*c2*d3+c1*d2*b3-d3*b2*c1-b3*c2*d1-c3*d2*b1);
{ ey := (a1*d2*c3+d1*c2*a3+c1*a2*d3-a3*d2*c1-d3*c2*a1-c3*a2*d1);
{ ez := (a1*b2*d3+b1*d2*a3+d1*a2*b3-a3*b2*d1-b3*d2*a1-d3*a2*b1);
{ x = ex/e
{ y = ey/e
{ z = ez/e
{ -----------------------------------------------------------------------*/
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
{
a1, a2, a3, b1, b2, b3, c1, c2, c3, d1, d2, d3, x, y, z, e, ex, ey, ez:!;
a1 = 2; b1 = -3; c1 = 1; d1 = 7;
a2 = 1; b2 = 4; c2 = -1; d2 = 6;
a3 = 3; b3 = -2; c3 = 2; d3 = 14;
for (;;)
{
input(a1 b1 c1 d1 a2 b2 c2 d2 a3 b3 c3 d3);
outputln("#Система уравнений");
outputln(a1 "x " b1 "y " c1 "z = " d1);
outputln(a2 "x " b2 "y " c2 "z = " d2);
outputln(a3 "x " b3 "y " c3 "z = " d3);
e = (a1*b2*c3 + b1 * c2*a3 + c1 * a2*b3 - a3 * b2*c1 - b3 * c2*a1 - c3 * a2*b1);
ex = (d1*b2*c3 + b1 * c2*d3 + c1 * d2*b3 - d3 * b2*c1 - b3 * c2*d1 - c3 * d2*b1);
ey = (a1*d2*c3 + d1 * c2*a3 + c1 * a2*d3 - a3 * d2*c1 - d3 * c2*a1 - c3 * a2*d1);
ez = (a1*b2*d3 + b1 * d2*a3 + d1 * a2*b3 - a3 * b2*d1 - b3 * d2*a1 - d3 * a2*b1);
if (e = 0) then
if (ex = 0) and (ey = 0) and (ez = 0) then
outputln("Бесконечное множество решений");
else
outputln("Нет решений");
end_else;
else
{
x = ex / e; y = ey / e; z = ez / e;
outputln("x = " x ", y = " y ", z = " z);
} end_else;
}
}
100
101
Результат 15
Пример 16
// Нерекурсивная Ханойская башня
/*
В одной из древних легенд говорится следующее.
"В храме Бенареса находится бронзовая плита с тремя алмазными стержнями.
На один из стержней Бог при сотворении мира нанизал 64 диска разного
диаметра из чистого золота так, что наибольший диск лежит на бронзовой плите,
а остальные образуют пирамиду, сужающуюся кверху. Это - башня Брамы.
Работая день и ночь, жрецы переносят диски с одного стержня на другой,
следуя законам Брамы:
1. диски можно перемещать с одного стержня на другой только по одному;
2. нельзя класть больший диск на меньший.
Когда все 64 диска будут перенесены с одного стержня на другой,
и башня, и храмы, и жрецы-брамины превратятся в прах, и наступит конец света".
Эта древняя легенда породила задачу о Ханойской башне:
переместить m дисков с одного из трех стержней на другой,
соблюдая "законы Брамы".
*/
{
cnt, n, m, k, l, s, iz, v, c, x : %;
for (cnt = 3; ;)
{
for (;;)
{
input("Введите число дисков в диапазоне 1...7#или 0 для завершения программы" cnt);
if cnt = 0 then stop(); end_else;
if (cnt >= 1) and (cnt <= 7) then break; end_else;
outputln("Ошибочно число дисков " n);
}
outputln("#Число дисков = " cnt);
// Получить число перекладываний дисков
// n = 2**cnt - 1;
n = 1;
for (k = 0; k < cnt; k = k + 1)
n = n * 2;
n = n - 1;
outputln("Число перекладываний = " n);
101
102
// Выполнить все перекладывания
for (m = 1; m <= n; m = m + 1)
{
iz = 1; c = 2; v = 3; k = 1; l = n; s = (k + l) / 2;
for (;;)
{
if m = s then
{
outputln("Переложить диск со стержня " iz " на стержень " v);
break;
} end_else;
if m < s then
{
x = v; v = c; c = x; l = s - 1;
}
else
{
x = iz; iz = c; c = x; k = s + 1;
} end_else;
s = (k + l) / 2;
}
}
}
}
Результат 16
102
103
Пример 17
// Ошибочная программа
{
i: %;
I = 100;
}
Результат 17
103
Download