Інтерактивний інтерпретатор

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
МІНІСТЕРСТВО ОСВІТИ І «Білоруський державний університет інформатики і радіоелектроніки»
Кафедра інформатики
КУРСОВИЙ ПРОЕКТ
з предмету «Об'єктно-орієнтоване програмування»
на тему "Інтерпретатор мови програмування».
Виконав ст. гр. ********
************.
Перевірив
****************.
МІНСЬК 2005

Зміст.


Зміст. 2
Введення .. 3
Постановка завдання. 4
Опис реалізованого в інтерпретаторі мови програмування. 5
Приклади функцій користувача .. 12
1. Сортування масиву. 12
2. Обчислення НОД за алгоритмом Евкліда. 12
3. Рекурсивне обчислення факторіала. 13
4. Перевірка, чи є рядок коректним ідентифікатором. 13
5. Обчислення кута трикутника за трьома сторонами. 14
Проектування і реалізація програми-інтерпретатора 15
Внутрішнє подання і виконання програми. 18
Обробка тексту програми. 24
Графічний інтерфейс користувача. 27
Взаємодія підсистем інтерпретатора. Клас Facade. 31
Висновок .. 33
Додаток. Оригінальний текст (скорочено). 34
1. Клас VarBase. 34
2. Клас ArrayVar. 34
3. Клас InterprEnvironment. 36
4. Клас Namespace. 40
5. Інтерфейс IСomputable. 42
6. Клас Call. 42
7. Клас ArgList. 42
8. Клас Exdivssion. 43
9. Клас Operation (скорочено). 49
10. Клас Parser. 50
11. Клас LineCompiler. 56
12. Інтерфейс IOperator. 60
13. Клас Command. 60
14. Клас ForOperator. 61
15. Клас NextOperator. 62
16. Клас Subroutine. 62
17. Клас Facade. 67
18. Клас SourceBox. 69
19. Клас Form1. 75
Використана література та документація. 78


Введення

Стандартний «Калькулятор» Windows є, мабуть, єдиною має широке поширення програмою, призначеної для дрібних обчислень. Його не можуть замінити через свою громіздкість, ні електронні таблиці, ані професійні математичні пакети. Але в той же час ця програма має суттєві недоліки, причина яких проста - призначений для користувача інтерфейс зроблений «за образом і подобою» кишенькового калькулятора, тому запозичені всі незручності останнього. Наприклад, при роботі користувач бачить тільки одне число і після отримання результату не може перевірити, чи правильно були введені операнди. Другою проблемою є неможливість додавання користувацьких функцій - якщо доводиться робити обчислення за однією і тією ж формулою сто разів, сто разів доводиться натискати відповідну кнопку для кожної арифметичної операції у виразі. На мою думку, найбільш зручні для повсякденного використання в якості заміни «Калькулятору» інтерактивні інтерпретатори, до числа яких відносяться MatLab і Python. Але основне призначення цих програмних пакетів зовсім інше, немає сенсу встановлювати їх на комп'ютер лише для того, щоб виконувати декілька складань і множень пару раз на день. Тому я вирішив написати нескладний інтерактивний інтерпретатор, не громіздкий і зручний для дрібних обчислень.

Постановка завдання.
Потрібно реалізувати інтерпретатор щодо нескладного мови програмування, працює в інтерактивному режимі, тобто виконує вводяться користувачем команди з клавіатури. При цьому повинна бути присутнім можливість створення користувацьких функцій, в яких можуть бути присутні оператори управління перебігом програми - розгалуження, цикли та інші, а також оператори виводу (на консоль). Повинні бути також передбачені можливість збереження проміжних результатів обчислення (у тому числі між сеансами роботи з інтерпретатором) і можливість переривання виконання зациклилися користувача функції без завершення роботи інтерпретатора. Даний інтерпретатор повинен бути зручний у використанні як заміна стандартному «Калькулятору» Windows, принаймні, для людини, що володіє мінімальними навичками програмування.

Опис реалізованого в інтерпретаторі мови програмування.

Інтерпретатор працює в режимі консолі і виконує команди, що вводяться з клавіатури. Ці команди можуть містити виклики для користувача функцій. Код для користувача функцій створюється за допомогою вікна редактора коду інтерпретатора. Інтерпретатор надає можливість створення, редагування та видалення користувача функцій. Функції зберігаються у файлах, ім'я яких не має розширення і збігається з ім'ям функції. Файли використовуваних функцій повинні знаходитися в підкаталозі subroutines робочого каталогу інтерпретатора. Збережені користувача функції завантажуються автоматично при запуску інтерпретатора. Всі оператори управління перебігом програми можуть використовуватися тільки в тексті користувача функції. Всі інші оператори (команди) можуть бути викликані безпосередньо з консолі. Крім того, в консолі можна ввести вираз без додаткових ключових слів, і воно буде обчислено, його значення буде виведено на екран. Для виведення ж даних з функції необхідно використовувати оператори виводу (див. нижче).
Ідентифікатором (ім'ям) змінної служить послідовність символів довільної довжини, що складається з букв (латинського алфавіту), цифр і знаків підкреслення, не починається з цифри. Ті ж обмеження поширюються і на імена функцій. Регістр букв має значення. Можливо наявність лише однієї змінної з кожним ім'ям в кожної функції і в середовищі консолі. Але допускається збіг імені змінної з ім'ям якої-небудь функції. Також імена змінних і функцій не повинні збігатися з ключовими словами мови, до яких відносяться:
· Call
· Clear
· Else
· Elseif
· Endif
· Error
· For
· If
· Loop
· Next
· Print
· Println
· Result
· Return
· While
Попереднього оголошення змінних не потрібно. Змінна присутній у пам'яті з моменту присвоєння їй значення, при цьому тип змінної визначається за типом присвоюється їй значення. Масив також створюється при присвоєнні значення якого-небудь його елементу. Спроба отримати значення ще не ініціалізованої змінної або спроба розглянути як масив змінну, яка не є такою, призводить до помилки. Кожна функція і середовище консолі мають власні, не залежні один від одного набори змінних. Звернутися з функції або середовища консолі до "чужої" змінної неможливо.
Є такі типи даних: цілий, речовий, рядковий, масив. Цілий, речовинний і рядковий типи називаються простими на противагу масиву; речовинний і цілий типи називаються числовими. Тип змінної не описується, але може бути визначений за допомогою функцій issingle, isarray, isstring, isnum, isint, isreal. Крім того, виконання операції над аргументами неприпустимих типів може призвести до помилки. Масив може зберігати елементи будь-яких простих типів, причому типи різних елементів одного і того ж масиву можуть не збігатися. У міру заповнення масиву можлива поява в ньому «порожніх місць», наприклад, після команд a {0}: = 1; a {2}: = 4; a {4}: = 5 (пропущені елементи з індексами 1 і 3). Спроба отримати значення ще не ініціалізованої елемента масиву призводить до помилки. Перевірити, инициализирован чи елемент масиву з заданим індексом, можна за допомогою функції defined.
Будь-яка послідовність пробілів і табуляцій, що йдуть підряд, вважається за один пробіл, якщо вона знаходиться між символами, які можуть входити в ідентифікатори (літери, цифри, підкреслення), або ігнорується інакше. Також ігноруються інтерпретатором коментарі, якими є рядки або закінчення рядків, що починаються з символу '#'. Ці правила не поширюються на рядкові константи, що укладаються в подвійні лапки. Строковою константою не є послідовність символів в лапках, якщо відкриває лапки знаходиться після символу початку коментарю '#', але цей символ може бути присутнім в рядковій константі між лапками. Так наприклад, команда a: = b + "#" + c # comment еквівалентна команді a: = b + "#" + c, але не рівносильна команді a: = b + "#" + c, або a + b + "(остання команда синтаксично дійсності).
Кожен рядок, що вводиться з консолі, містить одну команду або жодної (порожній рядок чи коментар) команди. Те ж стосується і рядків файлу функції, окрім першої, яка повинна містити опис функції у вигляді: <імя_функціі> [<спісок_параметров>], де список параметрів, що укладається в квадратні дужки, складається з імен параметрів, розділених комою (цей рядок також може містити і коментар після опису функції). Квадратні дужки пишуться навіть при порожньому списку параметрів. Імена параметрів (формальних) підкоряються тим же обмеженням, що й імена змінних, мало того, вони розглядаються як змінні, визначені в даній функції, але на початку виконання функції вони приймають значення відповідних фактичних параметрів. Потрібно відзначити, що спроба передачі в якості фактичного параметра функції змінної з невизначеним значенням завжди призводить до помилки, навіть якщо у функції до цього параметру немає жодного звернення .. Також призводить до помилки виклик функції з числом фактичних параметрів, що не відповідає числу формальних параметрів. Крім того, в кожній функції є змінна з визначеним ім'ям result. Її значення на момент виходу з функції і є повертаним значенням функції. У момент початку виконання функції її значення дорівнює 0 (ціле число). Якщо змінна result була видалена командою clear і залишилася невизначеною на момент виходу з функції, виникає помилка.
Значення цілого, речового та строкового типу можуть бути представлені в програмі у вигляді констант (літералів). Цілий літерал представляє собою послідовність цифр. Він представляє число, звичайної записом якого є. Речовий літерал представляє собою десяткову або експоненційну запис дійсного числа, при цьому, у випадку експоненційної запису, буква "е" може бути як малої, так і великої. Наприклад: 12.0, 1.6e87, 2Е-7, 89.0. У числових літерали не може міститися початковий символ «+» або «-», вони можуть представляти тільки позитивне число. Негативні значення можна отримати застосуванням операції унарний мінус. Ціла частина дійсного числа не може бути опущена. Дрібна частина (точка і хоча б одна цифра після неї) має бути присутня, якщо не вказаний порядок, наприклад, 11е-6 - допустима запис, а 11.е-4 і 61. - Ні. Строковий літерал полягає в подвійні лапки, якщо в нього потрібно включити подвійну лапку, то вона пишеться двічі.
Спеціальний логічний тип даних відсутній, логічні значення представляються змінними цілого, речового або строкового типу - істині відповідає позитивне число або непорожня рядок, брехні - непозитивно число або порожній рядок. Результат усіх стандартних логічних операцій - цілі числа 1 (істина) або -1 (брехня). При спробі розглянути масив як логічне значення виникає помилка.
Вираженням є:
· Ідентифікатор змінної;
· Константа цілого, речового або строкового типу;
· Звернення до елементу масиву з заданим індексом, що має синтаксис <ідентифікатор масиву> {<індекс>} (індекс укладений у фігурні дужки). Індекс повинен бути виразом. Перед відкриває фігурною дужкою повинно стояти ім'я змінної, що є масивом (але не інший вираз, що має тип масиву). Значення індексу має бути невід'ємним цілим, інакше виникає помилка;
· Результат застосування унарний операції до вираження;
· Результат застосування бінарної операції до двох виразів;
· Виклик функції (без ключового слова call). У цьому випадку функція зобов'язана повертати значення, інакше виникає помилка. Фактичними параметрами функції повинні бути вирази;
· Вираз, укладену в круглі дужки.
Операції, що використовуються у виразах, і їх пріоритети (операнди позначені як a і b; для суми чисел, різниці і твори результат - ціле число, якщо обидва операнди - цілі, інакше - дійсне число) перераховані в таблиці.



Рівень пріоритету
Синтаксис
Типи операндів
Сенс
Тип результату
1
~ A
простий
логічне заперечення
цілий (-1 або 1)
-A
число
унарний мінус
той же, що і a
+ A
число
унарний плюс
2
a * b
числа
твір
число
a / b
числа
речовий поділ
речовий
3
a + b
рядка або a - рядок, b - число
конкатенація рядків (число перетворюється в рядок)
рядок
числа
сума
число
ab
числа
різниця
число
4
a = b
прості (обидва - числа якого рядка одночасно)
одно
цілий (-1 або 1)
a <> b
не дорівнює
a> b
більше
a <b
менше
a <= b
менше або дорівнює
a> = b
більше або дорівнює
5
a & b
прості
"І"
6
a ^ b
прості
виключає "АБО"
a ~ = b
логічна еквівалентність
a | b
"АБО"
Вирази інтерпретуються у відповідності з пріоритетом операцій і наявними в них круглими дужками. При цьому всі унарні операції виконуються справа наліво, бінарні операції однакового пріоритету - зліва направо. Якщо у виразі хоча б один з операндів операції не має необхідний тип, або операція не може бути проведена коректно з іншої причини, наприклад, у разі поділу на нуль, то виникає помилка.
Виклик функції має наступний синтаксис: <ім'я функції>; [<фактичний параметр 1>, <фактичний параметр 2 >,...,< фактичний параметр 3>]. Навіть якщо список параметрів порожній, квадратні дужки все одно пишуться. Фактичними параметрами функції повинні бути вирази.
res = new String (m_a, beg, cur - beg);
}
} Else
res = new String (m_a, beg, cur - beg);
}
} Else
res = new String (m_a, beg, cur - beg);
}
if (flag) {
if (IsLD (m_a [cur])) {
while ((cur <end) & & IsLD (m_a [cur]))
cur + +;
res = new String (m_a, beg, cur - beg);
} Else if (m_a [cur] == '\ "') {
do {
cur + +;
if (m_a [cur] == '\ "') {
if ((cur <end - 1) & & (m_a [cur + 1] == '\ "'))
cur + +;
else
break;
}
} While (true);
cur + +;
res = new String (m_a, beg, cur - beg);
} Else if (cur <end - 1) {
switch (m_a [cur]) {
case ':':
{
cur + +;
if (m_a [cur] == '=') {
cur + +;
res = ":=";
} Else
res = ":";
break;
}
case '~':
{
cur + +;
if (m_a [cur] == '=') {
cur + +;
res = "~=";
} Else
res = "~";
break;
}
case '>':
{
cur + +;
if (m_a [cur] == '=') {
cur + +;
res = ">=";
} Else
res = ">";
break;
}
case '<':
{
cur + +;
switch (m_a [cur]) {
case '=':
{
cur + +;
res = "<=";
break;
}
case '>':
{
cur + +;
res = "<>";
break;
}
default:
{
res = "<";
break;
}
}
break;
}
default:
{
res = m_a [cur]. ToString ();
cur + +;
break;
}
}
} Else {
res = m_a [cur]. ToString ();
cur + +;
}
}
if ((cur <end) & & IsSp (m_a [cur]))
cur + +;
m_new_cur = cur;
return res;
}
public object Current {
get {return GetCurrent ();}
}
public bool MoveNext () {
if (m_at_begin) {
m_at_begin = false;
return HasMore ();
}
if (m_new_cur <0)
GetCurrent ();
m_cur = m_new_cur;
m_new_cur = -1;
return HasMore ();
}
public IEnumerator GetEnumerator () {
return this;
}
public static bool IsUserID (string name) {
if (! IsID (name))
return false;
if (name == "abs")
return false;
if (name == "cos")
return false;
if (name == "sin")
return false;
if (name == "tg")
return false;
if (name == "arccos")
return false;
if (name == "arcsin")
return false;
if (name == "arctg")
return false;
if (name == "exp")
return false;
if (name == "pow")
return false;
if (name == "ln")
return false;
if (name == "lg")
return false;
if (name == "log")
return false;
if (name == "sqrt")
return false;
if (name == "pi")
return false;
if (name == "idiv")
return false;
if (name == "iff")
return false;
if (name == "imod")
return false;
if (name == "random")
return false;
if (name == "substr")
return false;
if (name == "strlen")
return false;
if (name == "strpos")
return false;
if (name == "toint")
return false;
if (name == "toreal")
return false;
if (name == "tostring")
return false;
if (name == "isarray")
return false;
if (name == "issingle")
return false;
if (name == "isstring")
return false;
if (name == "isnum")
return false;
if (name == "isreal")
return false;
if (name == "isint")
return false;
if (name == "size")
return false;
return true;
}
}
}

11. Клас LineCompiler.

using System;
using interpr.logic.operators;
namespace interpr.logic {
public class LineCompiler {
private LineCompiler () {}
public static Command CompileCommand (string str) {
Parser p = new Parser (str);
if (! p.HasMore ()) {
return new EmptyCommand ();
}
String pstr = p.GetString ();
int posa = pstr.IndexOf (":=");
if (posa> = 0) {
int cq = 0;
for (int iq = 0; iq <posa; iq + +)
if (pstr [iq] == '\ "')
cq + +;
if (cq% 2 == 0) {
try {
if (posa == 0)
throw new SyntaxErrorException ("Синтаксична помилка");
try {
if (pstr [posa - 1] == '}') {
int posob = pstr.IndexOf ('{');
if ((posob <0) | | (posob> posa))
throw new SyntaxErrorException ("Синтаксична помилка");
return new AssignCommand (pstr.Substring (0, posob),
pstr.Substring (posob + 1, posa - posob - 2),
pstr.Substring (posa + 2));
} Else {
return new AssignCommand (pstr.Substring (0, posa),
pstr.Substring (posa + 2));
}
} Catch {
throw new SyntaxErrorException ("Синтаксична помилка");
}
} Catch (CalcException ex) {
throw new SyntaxErrorException (ex.Message);
}
}
}
p.MoveNext ();
string firsttoken = (p.Current as String);
try {
if (firsttoken == "clear") {
if (! p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
Command cc = new ClearCommand (p.Current as String);
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return cc;
}
if (firsttoken == "print") {
Exdivssion expr = new Exdivssion (p);
return new PrintCommand (expr);
} Else if (firsttoken == "println") {
Exdivssion expr = new Exdivssion (p);
return new PrintLnCommand (expr);
} Else if (firsttoken == "call") {
Exdivssion expr = new Exdivssion (p);
return new CallCommand (expr);
} Else {
p.Reset ();
Exdivssion expr1 = new Exdivssion (p);
return new PrintLnCommand (expr1);
}
} Catch (SyntaxErrorException ex) {
throw ex;
} Catch (Exception ex) {
throw new SyntaxErrorException (ex.Message);
}
}
public static IOperator CompileOperator (string str) {
Parser p = new Parser (str);
if (! p.HasMore ()) {
return new EmptyCommand ();
}
String pstr = p.GetString ();
p.MoveNext ();
string firsttoken = (p.Current as String);
if (firsttoken == "for") {
try {
return ParseForStatement (p.GetString ());
} Catch (SyntaxErrorException ex) {
throw ex;
} Catch (Exception ex) {
throw new SyntaxErrorException (ex.Message);
}
}
int posa = pstr.IndexOf (":=");
if (posa> = 0) {
int cq = 0;
for (int iq = 0; iq <posa; iq + +)
if (pstr [iq] == '\ "')
cq + +;
if (cq% 2 == 0) {
try {
if (posa == 0)
throw new SyntaxErrorException ("Синтаксична помилка");
try {
if (pstr [posa - 1] == '}') {
int posob = pstr.IndexOf ('{');
if ((posob <0) | | (posob> posa))
throw new SyntaxErrorException ("Синтаксична помилка");
return new AssignCommand (pstr.Substring (0, posob),
pstr.Substring (posob + 1, posa - posob - 2),
pstr.Substring (posa + 2));
} Else {
return new AssignCommand (pstr.Substring (0, posa),
pstr.Substring (posa + 2));
}
} Catch {
throw new SyntaxErrorException ("Синтаксична помилка");
}
} Catch (CalcException ex) {
throw new SyntaxErrorException (ex.Message);
}
}
}
try {
if (firsttoken == "clear") {
if (! p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
Command cc = new ClearCommand (p.Current as String);
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return cc;
} Else if (firsttoken == "next") {
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return new NextOperator ();
} Else if (firsttoken == "else") {
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return new ElseOperator ();
} Else if (firsttoken == "endif") {
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return new EndifOperator ();
} Else if (firsttoken == "loop") {
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return new LoopOperator ();
} Else if (firsttoken == "return") {
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return new ReturnOperator ();
} Else if (firsttoken == "error") {
if (p.MoveNext ())
throw new SyntaxErrorException ("Синтаксична помилка");
return new ErrorOperator ();
}
Exdivssion expr = new Exdivssion (p);
if (firsttoken == "print")
return new PrintCommand (expr);
else if (firsttoken == "println")
return new PrintLnCommand (expr);
else if (firsttoken == "call")
return new CallCommand (expr);
else if (firsttoken == "while")
return new WhileOperator (expr);
else if (firsttoken == "if")
return new IfOperator (expr);
else if (firsttoken == "elseif")
return new ElseifOperator (expr);
else
throw new SyntaxErrorException ("Синтаксична помилка");
} Catch (SyntaxErrorException ex) {
throw ex;
} Catch (Exception ex) {
throw new SyntaxErrorException (ex.Message);
}
}
private static IOperator ParseForStatement (string str) {
str = str.Substring (3);
int assignpos = str.IndexOf (":=");
if (assignpos <0)
throw new SyntaxErrorException ("Неправильний синтаксис оператора for");
string countername = str.Substring (0, assignpos). Trim ();
if (! Parser.IsID (countername))
throw new SyntaxErrorException ("Неправильний синтаксис оператора for");
str = str.Substring (assignpos + 2);
int colonpos = str.IndexOf (":");
if (colonpos <0)
throw new SyntaxErrorException ("Неправильний синтаксис оператора for");
string expr1str = str.Substring (0, colonpos);
string expr2str = str.Substring (colonpos + 1);
Exdivssion expr1 = new Exdivssion (expr1str);
Exdivssion expr2 = new Exdivssion (expr2str);
return new ForOperator (countername, expr1, expr2);
}
}
}

12. Інтерфейс IOperator.

namespace interpr.logic.operators {
public enum OperatorKind {
Plain,
If,
Elseif,
Else,
Endif,
While,
Loop,
For,
Next,
Return
}
public interface IOperator {
void Execute (Subroutine.Moment pos);
OperatorKind GetKind ();
}
}

13. Клас Command.

namespace interpr.logic.operators {
public abstract class Command: IOperator {
public abstract void Execute ();
public void Execute (Subroutine.Moment pos) {
Execute ();
pos.Next ();
}
public OperatorKind GetKind () {
return OperatorKind.Plain;
}
}
}

14. Клас ForOperator.

using interpr.logic.vartypes;
namespace interpr.logic.operators {
public class ForOperator: IOperator {
private int m_next_pos = -1;
private string m_counter_var = null;
private Exdivssion m_begin = null;
private Exdivssion m_end = null;
private IntVar m_end_res = null;
public ForOperator (string counter, Exdivssion beg, Exdivssion end) {
m_counter_var = counter;
m_begin = beg;
m_end = end;
}
public int NextPos {
get {
if (m_next_pos <0)
throw new OtherException ("Error in LoopOperator.NextPos");
return m_next_pos;
}
set {m_next_pos = value;}
}
public void Step (Subroutine.Moment pos, int forpos) {
Namespace cn = InterprEnvironment.Instance.CurrentNamespace;
VarBase res = cn [m_counter_var];
if (! res.IsInt ())
throw new CalcException ("Тип змінної - лічильника циклу був змінений");
int resval = (res as IntVar). Val;
resval + +;
res = new IntVar (resval);
cn [m_counter_var] = res;
if (resval> m_end_res.Val)
pos.GoTo (m_next_pos + 1);
else
pos.GoTo (forpos + 1);
}
public void Execute (Subroutine.Moment pos) {
VarBase resb, rese;
resb = m_begin.Calculate ();
if (! resb.IsInt ())
throw new CalcException ("Межі зміни лічильника повинні бути цілими");
IntVar resbi = resb as IntVar;
Namespace cn = InterprEnvironment.Instance.CurrentNamespace;
cn [m_counter_var] = resb;
rese = m_end.Calculate ();
if (! rese.IsInt ())
throw new CalcException ("Межі зміни лічильника повинні бути цілими");
m_end_res = rese as IntVar;
if (resbi.Val> m_end_res.Val)
pos.GoTo (m_next_pos + 1);
else
pos.Next ();
}
public OperatorKind GetKind () {
return OperatorKind.For;
}
}
}

15. Клас NextOperator

namespace interpr.logic.operators {
public class NextOperator: IOperator {
private int m_for_pos = -1;
private ForOperator m_for_op = null;
public NextOperator () {}
public int ForPos {
get {
if (m_for_pos <0)
throw new OtherException ("Error in NextOperator.ForPos");
return m_for_pos;
}
set {m_for_pos = value;}
}
public ForOperator ForOp {
get {return m_for_op;}
set {m_for_op = value;}
}
public void Execute (interpr.logic.Subroutine.Moment pos) {
m_for_op.Step (pos, m_for_pos);
}
public interpr.logic.operators.OperatorKind GetKind () {
return OperatorKind.Next;
}
}
}

16. Клас Subroutine.

using System;
using System.Collections;
using System.Threading;
using interpr.logic.vartypes;
using interpr.logic.operators;
namespace interpr.logic {
public sealed class Subroutine {
private void AnalyseHeader (string str) {
Parser header_p = new Parser (str);
if (! header_p.MoveNext ())
throw new SyntaxErrorException ("Помилка в заголовку функції");
if ((header_p.Current as System.String)! = m_name)
throw new SyntaxErrorException ("Ім'я функції не збігається з ім'ям файлу");
if ((! header_p.MoveNext ()) | | ((header_p.Current as String)! = "["))
throw new SyntaxErrorException ("Помилка в заголовку функції");
if ((! header_p.MoveNext ()))
throw new SyntaxErrorException ("Помилка в заголовку функції");
if ((header_p.Current as System.String! = "]")) {
string readstr;
while (true) {
readstr = (header_p.Current as System.String);
if (! Parser.IsID (readstr))
throw new SyntaxErrorException ("Помилка в заголовку функції");
m_args.Add (readstr);
if (! header_p.MoveNext ())
throw new SyntaxErrorException ("Помилка в заголовку функції");
readstr = (header_p.Current as System.String);
if (readstr == ",") {
if (! header_p.MoveNext ())
throw new SyntaxErrorException ("Помилка в заголовку функції");
}
else if (readstr == "]")
break;
else
throw new SyntaxErrorException ("Помилка в заголовку функції");
}
}
if (header_p.MoveNext ())
throw new SyntaxErrorException ("Помилка в заголовку функції");
if (m_args.IndexOf ("result")> = 0)
throw new SyntaxErrorException ("Параметр функції не може мати ім'я \" result \ "");
}
public Subroutine (string [] code, string name) {
m_name = name;
if (code.Length == 0)
throw new SyntaxErrorException ("Файл функції порожній");
AnalyseHeader (code [0]);
int clen = code.Length;
int i = 0;
try {
Stack stk = new Stack ();
m_operators.Add (new EmptyCommand ()); / / щоб індексація починалася з одиниці
for (i = 1; i <clen; i + +) {
IOperator op = LineCompiler.CompileOperator (code [i]);
if (op == null)
throw new SyntaxErrorException ("Синтаксична помилка");
m_operators.Add (op);
switch (op.GetKind ()) {
case OperatorKind.If:
case OperatorKind.While:
case OperatorKind.For:
{
stk.Push (i);
break;
}
case OperatorKind.Elseif:
{
if (stk.Count == 0)
throw new SyntaxErrorException ("Зайвий elseif");
int j = (int) stk.Pop ();
switch ((m_operators [j] as IOperator). GetKind ()) {
case OperatorKind.If:
{
(M_operators [j] as IfOperator). NextPos = i;
break;
}
case OperatorKind.Elseif:
{
(M_operators [j] as ElseifOperator). NextPos = i;
break;
}
default:
throw new SyntaxErrorException ("Зайвий elseif");
}
stk.Push (i);
break;
}
case OperatorKind.Else:
{
if (stk.Count == 0)
throw new SyntaxErrorException ("Зайвий else");
int j = (int) stk.Pop ();
stk.Push (i);
switch ((m_operators [j] as IOperator). GetKind ()) {
case OperatorKind.If:
{
(M_operators [j] as IfOperator). NextPos = i;
break;
}
case OperatorKind.Elseif:
{
(M_operators [j] as ElseifOperator). NextPos = i;
break;
}
default:
throw new SyntaxErrorException ("Зайвий else");
}
break;
}
case OperatorKind.Endif:
{
if (stk.Count == 0)
throw new SyntaxErrorException ("Зайвий endif");
int j = (int) stk.Pop ();
switch ((m_operators [j] as IOperator). GetKind ()) {
case OperatorKind.If:
{
(M_operators [j] as IfOperator). NextPos = i;
break;
}
case OperatorKind.Elseif:
{
(M_operators [j] as ElseifOperator). NextPos = i;
break;
}
case OperatorKind.Else:
{
(M_operators [j] as ElseOperator). NextPos = i;
break;
}
default:
throw new SyntaxErrorException ("Зайвий endif");
}
break;
}
case OperatorKind.Loop:
{
if (stk.Count == 0)
throw new SyntaxErrorException ("Зайвий loop");
int j = (int) stk.Pop ();
if ((m_operators [j] as IOperator). GetKind ()! = OperatorKind.While)
throw new SyntaxErrorException ("Зайвий loop");
(M_operators [i] as LoopOperator). WhilePos = j;
(M_operators [j] as WhileOperator). LoopPos = i;
break;
}
case OperatorKind.Next:
{
if (stk.Count == 0)
throw new SyntaxErrorException ("Зайвий next");
int j = (int) stk.Pop ();
if ((m_operators [j] as IOperator). GetKind ()! = OperatorKind.For)
throw new SyntaxErrorException ("Зайвий next");
(M_operators [i] as NextOperator). ForPos = j;
(M_operators [i] as NextOperator). ForOp = (m_operators [j] as ForOperator);
(M_operators [j] as ForOperator). NextPos = i;
break;
}
}
}
if (stk.Count! = 0)
throw new SyntaxErrorException ("Не закритий блок");
}
catch (SyntaxErrorException ex) {
throw new LineSyntaxException (ex.Message, m_name, i + 1);
}
m_count = m_operators.Count;
}
private string m_name;
private ArrayList m_args = new ArrayList ();
private ArrayList m_operators = new ArrayList ();
private int m_count;
public int ReqCount {
get {return m_args.Count;}
}
public VarBase Perform (ArgList al) {
Namespace ns = new Namespace (InterprEnvironment.Instance.CurrentNamespace);
ns ["result"] = new IntVar (0);
int argc = m_args.Count;
if (al.Count! = argc)
throw new CalcException ("Неправильне число параметрів");
al.Reset ();
for (int i = 0; i <argc; i + +) {
ns [m_args [i] as System.String] = al.Get ();
}
InterprEnvironment.Instance.CurrentNamespace = ns;
Moment moment = new Moment (this);
if (m_count> 1) {
try {
moment.Run ();
}
catch (SyntaxErrorException ex) {
throw ex;
}
catch (CalcException ex) {
throw new CalcException ("Помилка у функції" + m_name + "[] в рядку" + (moment.Pos + 1) + ":" + ex.Message);
}
}
VarBase res = ns ["result"];
InterprEnvironment.Instance.CurrentNamespace = ns.PreviousNamespace;
if (res == null)
throw new CalcException ("Помилка у функції" + m_name + "[]: мінлива result не визначена на момент виходу");
return res;
}
public class Moment {
private Subroutine m_sub;
private int m_pos;
private static int s_break = 0;
public static void Break () {
Interlocked.Exchange (ref s_break, 1);
}
public int Pos {
get {return m_pos;}
}
public Moment (Subroutine sub) {
m_sub = sub;
m_pos = 1;
s_break = 0;
}
public void GoTo (int to) {
m_pos = to;
}
public void Next () {
m_pos + +;
}
public void Run () {
while (m_pos <m_sub.m_count) {
if (s_break == 1)
throw new CalcException ("Перервано користувачем");
(M_sub.m_operators [m_pos] as IOperator). Execute (this);
}
}
public void Return () {
m_pos = m_sub.m_count;
}
public IOperator Current {
get {return m_sub.m_operators [m_pos] as IOperator;}
}
}
}
}

17. Клас Facade.

using System.Threading;
using interpr.logic;
using interpr.logic.operators;
namespace interpr {
public class Facade {
private static Facade s_instance = null;
public static void Create (IConsole console) {
if (s_instance == null)
s_instance = new Facade (console);
}
public static Facade Instance {
get {return s_instance;}
}
private IConsole m_console;
private InterprEnvironment m_env;
private string m_cmd;
private bool m_doing = false;
private Facade (IConsole console) {
m_console = console;
m_env = InterprEnvironment.Instance;
m_env.CurrentConsole = m_console;
}
public delegate void CommandDoneHandler ();
public event CommandDoneHandler Done;
private void ThrStart () {
m_doing = true;
Command cmd;
do {
try {
cmd = LineCompiler.CompileCommand (m_cmd);
}
catch (SyntaxErrorException ex) {
m_env.CurrentConsole.PrintLn ("Помилка:" + ex.Message);
break;
}
try {
cmd.Execute ();
}
catch (CalcException ex) {
m_env.CurrentConsole.PrintLn ("Помилка:" + ex.Message);
m_env.CurrentNamespace = m_env.ConsoleNamespace;
break;
}
} While (false);
Done ();
m_doing = false;
}
public void ExecuteCommand (string cmd) {
if (m_doing)
throw new OtherException ("Error in Bridge.ExecuteCommand ()");
m_cmd = cmd;
new Thread (new ThreadStart (ThrStart)). Start ();
}
private void DoRestart () {
if (m_doing)
Subroutine.Moment.Break ();
while (m_doing) {}
InterprEnvironment.Reset ();
m_env = InterprEnvironment.Instance;
m_env.CurrentConsole = m_console;
m_env.LoadSubs ();
}
public void Restart () {
new Thread (new ThreadStart (DoRestart)). Start ();
}
public bool Busy {
get {return m_doing;}
}
public void SaveVariables () {
m_env.SaveVars ();
}
public void LoadSubs () {
m_env.LoadSubs ();
}
public ConsoleNamespace.VariableReport [] GetVariables () {
return m_env.GetGlobalVarsList ();
}
public string [] GetSubs () {
return m_env.LoadedSubs;
}
public void DeleteVariable (string name) {
m_env.ConsoleNamespace.Remove (name);
}
public bool LoadSub (string name) {
return m_env.LoadSub (name);
}
public void UnloadSub (string name) {
m_env.UnloadSub (name);
}
public bool NotRestored {
get {
return m_env.NotRestored;
}
}
}
}

18. Клас SourceBox.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace interpr {
public class SourceBox: UserControl {
private RichTextBox m_tb;
private TextBox m_tb_2;

.................................................. ..................
private int m_curline = 0; / / поточний рядок
private int m_lincount = 0; / / загальна кількість рядків
private HighlightParser m_hp = new HighlightParser ();
private static Font s_nfont =
new Font ("Lucida Console", 10, FontStyle.Regular);
private static Font s_cfont =
new Font ("Lucida Console", 12, FontStyle.Bold);
private int GetCurrentLine () {
return m_tb.GetLineFromCharIndex (m_tb.SelectionStart);
}
private int GetLinesCount () {
return m_tb.Lines.Length;
}
private String GetLine (int index) {
return m_tb.Lines [index];
}
private void m_tb_KeyPress (object sender, KeyPressEventArgs e) {
if (e.KeyChar == '\ r') {
string txt = m_tb.Text;
int i = m_tb.SelectionStart - 2;
int j;
while (i> = 0) {
if (txt [i] == '\ n')
return;
else if (txt [i] == '\ t') {
j = 0;
while ((i> = 0) & & (txt [i] == '\ t')) {
j + +;
i -;
}
if ((i <0) | | (txt [i] == '\ n')) {
m_tb.SelectedText = new String ('\ t', j);
return;
}
}
i -;
}
}
}
private bool GetLinePos (int index, out int beg, out int len) {
if ((index <0) | | (index> = GetLinesCount ())) {
beg = len = 0;
return false;
}
int i;
string [] ls = m_tb.Lines;
beg = 0;
for (i = 0; i <index; i + +)
beg + = ls [i]. Length + 1;
len = ls [index]. Length;
return true;
}
private void SelectLine (int index) {
int beg, len;
if (! GetLinePos (index, out beg, out len))
throw new IndexOutOfRangeException ();
m_tb.SelectionStart = beg;
m_tb.SelectionLength = len;
}
private void HighlightLine (int index) {
int beg, len;
int curbeg = m_tb.SelectionStart;
int curlen = m_tb.SelectionLength;
GetLinePos (index, out beg, out len);
string str = m_tb.Lines [index];
m_hp.Reset (str);
while (m_hp.HasMore ()) {
int tbeg, tlen;
HighlightParser.TokenType type;
m_hp.GetNext (out tbeg, out tlen, out type);
m_tb.SelectionStart = beg + tbeg;
m_tb.SelectionLength = tlen;
switch (type) {
case HighlightParser.TokenType.Comment:
{
m_tb.SelectionColor = Color.DarkGreen;
break;
}
case HighlightParser.TokenType.Identifier:
{
m_tb.SelectionColor = Color.Purple;
break;
}
case HighlightParser.TokenType.Keyword:
{
m_tb.SelectionColor = Color.Blue;
break;
}
case HighlightParser.TokenType.Number:
{
m_tb.SelectionColor = Color.Red;
break;
}
case HighlightParser.TokenType.String:
{
m_tb.SelectionColor = Color.Brown;
break;
}
case HighlightParser.TokenType.Other:
{
m_tb.SelectionColor = Color.Black;
break;
}
}
}
m_tb.SelectionStart = curbeg;
m_tb.SelectionLength = curlen;
}
public enum LineState {
ErrorLine,
CurrentLine,
NormalLine
}
private void ColorLine (int index, LineState state) {
int curbeg = m_tb.SelectionStart;
int curlen = m_tb.SelectionLength;
SelectLine (index);
switch (state) {
case LineState.ErrorLine:
{
m_tb.SelectionColor = Color.Red;
break;
}
case LineState.CurrentLine:
{
m_tb.SelectionFont = s_cfont;
break;
}
case LineState.NormalLine:
{
m_tb.SelectionFont = s_nfont;
HighlightLine (index);
break;
}
}
m_tb.SelectionStart = curbeg;
m_tb.SelectionLength = curlen;
}
private void HighlightText (bool anyway) {
int l = GetCurrentLine ();
int lc = GetLinesCount ();
if ((l! = m_curline) | | (lc! = m_lincount) | | anyway) {
m_tb_2.Focus ();
m_curline = l;
m_lincount = lc;
int bi = m_tb.GetCharIndexFromPosition (new Point (0, 0));
int ei = m_tb.GetCharIndexFromPosition (new Point (m_tb.Size));
int bl = m_tb.GetLineFromCharIndex (bi);
int el = m_tb.GetLineFromCharIndex (ei);
if (bl> 0) bl -;
if (el <lc) el + +;
for (int i = bl; i <el; i + +)
HighlightLine (i);
m_tb.Focus ();
}
}
private void m_tb_KeyUp (object sender, KeyEventArgs e) {
HighlightText (false);
}
private void m_tb_MouseUp (object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left)
HighlightText (true);
}
public string [] Lines {
get {return (string []) m_tb.Lines.Clone ();}
}
public bool LoadFile (string filename) {
try {
m_tb.LoadFile (filename, RichTextBoxStreamType.PlainText);
HighlightText (true);
return true;
}
catch {
return false;
}
}
public bool SaveFile (string filename) {
try {
m_tb.SaveFile (filename, RichTextBoxStreamType.PlainText);
return true;
}
catch {
return false;
}
}
public int CurrentLine {
get {return m_tb.GetLineFromCharIndex (m_tb.SelectionStart);}
}
private class HighlightParser {
private char [] m_a;
private int m_len;
private int m_cur;
public enum TokenType {
String,
Number,
Keyword,
Comment,
Identifier,
Other
}
public void Reset (string str) {
m_a = str.ToCharArray ();
m_len = str.Length;
m_cur = 0;
while ((m_cur <m_len) & & Char.IsWhiteSpace (m_a [m_cur]))
m_cur + +;
}
public bool HasMore () {
return m_cur <m_len;
}
private bool IsKeyword (string str) {
return
(Str == "if") | |
(Str == "else") | |
(Str == "elseif") | |
(Str == "endif") | |
(Str == "while") | |
(Str == "loop") | |
(Str == "return") | |
(Str == "result") | |
(Str == "call") | |
(Str == "print") | |
(Str == "println") | |
(Str == "readln") | |
(Str == "clear") | |
(Str == "for") | |
(Str == "next") | |
(Str == "error");
}
public void GetNext (out int beg, out int len, out TokenType type) {
if (m_cur> = m_len)
throw new IndexOutOfRangeException ();
beg = m_cur;
if (m_a [m_cur] == '\ "') {
m_cur + +;
while ((m_cur <m_len) & & (m_a [m_cur]! = '\ "'))
m_cur + +;
if (m_cur <m_len)
m_cur + +;
len = m_cur - beg;
type = TokenType.String;
}
else if (isL (m_a [m_cur])) {
m_cur + +;
while ((m_cur <m_len) & & isLD (m_a [m_cur]))
m_cur + +;
len = m_cur - beg;
if (IsKeyword (new string (m_a, beg, len)))
type = TokenType.Keyword;
else
type = TokenType.Identifier;
}
else if (m_a [m_cur] == '#') {
len = m_len - m_cur;
m_cur = m_len;
type = TokenType.Comment;
}
else if (m_a [m_cur] == '.') {
if (GetNumber ()) {
len = m_cur - beg;
type = TokenType.Number;
}
else {
m_cur = beg + 1;
len = 1;
type = TokenType.Other;
}
}
else if (char.IsDigit (m_a [m_cur])) {
GetNumber ();
len = m_cur - beg;
type = TokenType.Number;
}
else {
m_cur + +;
len = 1;
type = TokenType.Other;
}
while ((m_cur <m_len) & & Char.IsWhiteSpace (m_a [m_cur]))
m_cur + +;
}
private bool GetNumber () {
if (! ((m_a [m_cur] == '.') | | char.IsDigit (m_a [m_cur])))
return false;
while ((m_cur <m_len) & & char.IsDigit (m_a [m_cur]))
m_cur + +;
if (m_cur == m_len)
return true;
else if (m_a [m_cur] == '.') {
m_cur + +;
while ((m_cur <m_len) & & char.IsDigit (m_a [m_cur]))
m_cur + +;
if (m_cur == m_len)
return true;
else if ((m_a [m_cur] == 'e') | | (m_a [m_cur] == 'E')) {
int p1 = m_cur;
m_cur + +;
if (m_cur == m_len) {
m_cur = p1;
return true;
}
else if ((m_a [m_cur] == '-') | | (m_a [m_cur] =='+')) {
m_cur + +;
if ((m_cur == m_len) | |! char.IsDigit (m_a [m_cur])) {
m_cur = p1;
return true;
}
while ((m_cur <m_len) & & char.IsDigit (m_a [m_cur]))
m_cur + +;
return true;
}
else if (char.IsDigit (m_a [m_cur])) {
while ((m_cur <m_len) & & char.IsDigit (m_a [m_cur]))
m_cur + +;
return true;
}
else {
m_cur = p1;
return true;
}
}
else
return true;
}
else if ((m_a [m_cur] == 'e') | | (m_a [m_cur] == 'E')) {
int p1 = m_cur;
m_cur + +;
if (m_cur == m_len) {
m_cur = p1;
return true;
}
else if ((m_a [m_cur] == '-') | | (m_a [m_cur] =='+')) {
m_cur + +;
if ((m_cur == m_len) | |! char.IsDigit (m_a [m_cur])) {
m_cur = p1;
return true;
}
while ((m_cur <m_len) & & char.IsDigit (m_a [m_cur]))
m_cur + +;
return true;
}
else if (char.IsDigit (m_a [m_cur])) {
while ((m_cur <m_len) & & char.IsDigit (m_a [m_cur]))
m_cur + +;
return true;
}
else {
m_cur = p1;
return true;
}
}
else
return true;
}
private static bool isLD (char c) {
return ((c> = 'a') & & (c <= 'z')) | | ((c> = 'A') & & (c <= 'Z')) | | (c == "0" )
| | ((C> = '1 ') & & (c <= '9')) | | (c =='_');
}
private static bool isL (char c) {
return ((c> = 'a') & & (c <= 'z')) | | ((c> = 'A') & & (c <= 'Z')) | | (c == '_' );
}
}
}
}

19. Клас Form1.

using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace interpr {
public class Form1: Form {
private Panel panel1;
private Button button1;
private Button button2;
private Button button3;
private Button button4;
private Button button5;
private ConsoleBox consoleBox1;
private Facade m_fasade;
private void Form1_Load (object sender, EventArgs e) {
Facade.Create (consoleBox1);
m_fasade = Facade.Instance;
if (m_fasade.NotRestored) {
MessageBox.Show ("Помилка! Змінні не були успішно відновлені.");
}
m_fasade.Done + = new Facade.CommandDoneHandler (EndExec);
m_fasade.LoadSubs ();
consoleBox1.Prompt ();
}
private void EndExec () {
consoleBox1.Prompt ();
}
private void button1_Click (object sender, EventArgs e) {
if (m_fasade.Busy) {
MessageBox.Show ("Не можу відкрити вікно функцій під час виконання команди!");
return;
}
FunctionsForm ff = new FunctionsForm (m_fasade);
ff.ShowDialog ();
EditorForm ef = ff.LastOpenedEditorForm;
if (ef! = null) {
ef.Activate ();
ff.SetLastEditorFormNull ();
}
else
consoleBox1.Focus ();
}
private void button2_Click (object sender, EventArgs e) {
if (m_fasade.Busy) {
MessageBox.Show ("Не можу відкрити вікно змінних під час виконання команди!");
return;
}
VariablesForm vf = new VariablesForm (m_fasade);
vf.ShowDialog ();
consoleBox1.Focus ();
}
private void consoleBox1_GetCommand (object sender, ConsoleBoxGetCommandEventArgs e) {
if (e.Command.Length> 0)
m_fasade.ExecuteCommand (e.Command);
else
consoleBox1.Prompt ();
}
private void button3_Click (object sender, EventArgs e) {
m_fasade.Restart ();
if (m_fasade.NotRestored) {
MessageBox.Show ("Помилка! Змінні не були успішно відновлені.");
}
consoleBox1.Focus ();
}
private void button5_Click (object sender, EventArgs e) {
if (m_fasade.Busy) {
MessageBox.Show ("Не можу зберегти змінні під час виконання програми");
return;
}
m_fasade.SaveVariables ();
consoleBox1.Focus ();
}
private void Form1_Closing (object sender, CancelEventArgs e) {
if (EditorForm.ThereAreOpened ()) {
MessageBox.Show ("Спочатку закрийте всі вікна редактора коду.");
e.Cancel = true;
return;
}
m_fasade.SaveVariables ();
}
private void button4_Click (object sender, EventArgs e) {
this.Close ();
}
}
}

Використана література та документація.

1. А. Ахо, Дж. Хопкрофта, Д. Ульман. Структури даних і алгоритми - М. «Вільямс», 2003.
2. Е. Гамма, Р. Хелм, Р. Джонсон, Дж. Вліссідес. Прийоми об'єктно-орієнтованого проектування: патерни проектування - СПб., «Пітер», 2001.
3. Д. Гріс. Конструювання компіляторів для цифрових обчислювальних машин - М. «Світ», 1975.
4. Г. Корнелл, Дж. Моррісон. Програмування на VB.NET. Навчальний курс - СПб., «Пітер», 2002.
5. Е. Троелсен. C # і платформа. NET - СПб., «Пітер», 2004.
6. MSDN Library - April 2003.

Наприклад, function1 [a, b + c, function2 [a, function3 []], 56.12e-1]. Існує ряд визначених функцій, з іменами яких не повинні збігатися імена користувача функцій. Їх список наведено в таблиці.


Функція
Повертане
значення
Опис
abs [число]
того ж типу, що й параметр
абсолютна величина
cos [число]
речовий
косинус
sin [число]
синус
tg [число]
тангенс
arctg [число]
арктангенс
arcsin [число]
арксинус
arccos [число]
арккосинус
exp [число]
ступінь підстави натуральних логарифмів (експоненти)
pow [число, число]
перший параметр в ступені другий параметр (перший параметр має бути невід'ємним)
ln [число]
натуральний логарифм
lg [число]
десятковий логарифм
log [число, число]
логарифм першого аргументу по основі, заданому другим аргументом
sqrt [число]
квадратний корінь
pi []
константа pi (відношення довжини кола до діаметра)
idiv [ціле число, ціле число]
ціле
приватне цілочисельного ділення
imod [ціле число, ціле число]
ціле
залишок цілочисельного ділення
substr [рядок, ціле число, ціле число]
рядок
підрядок (перший параметр - вихідна рядок, другий параметр - індекс першого символу, третій - довжина підрядка; якщо відбувається вихід за межі початкового рядка, то помилки немає, але довжина результату - менше зазначеної в третьому параметрі)
strlen [рядок]
ціле
довжина рядка
strpos [рядок, рядок]
ціле
позиція першого символу першого входження другого рядка в першу, або -1, якщо збігів немає (нумерація символів з нуля)
toint [простий]
ціле
перетворення до цілого (якщо неможливо - виникає помилка)
toreal [простий]
речовий
перетворення до матеріального (якщо неможливо - виникає помилка)
tostring [будь]
рядок
перетворення до рядку
issingle [будь]
ціле (-1 або 1)
чи є значення виразу не масивом
isarray [будь]
чи є значення виразу масивом
isstring [будь]
чи є значення виразу рядком
isnum [будь]
є значення виразу числом
isint [будь]
чи є значення виразу цілим числом
isreal [будь]
чи є значення виразу дійсним числом
size [масив]
число елементів масиву
defined [масив, ціле]
чи визначено в масиві елемент з заданим індексом
iff [простий, будь-який, будь-який]
будь-який
якщо перший параметр - істина, то повертає значення другого параметра, інакше - третього

Якщо при виклику стандартної функції тип хоча б одного з параметрів не відповідає необхідному, виникає помилка.
Оператор виклику call дозволяє обчислити будь-який вираз, проігнорувавши його значення, наприклад, викликати функцію як процедуру. Він має синтаксис:
call <вираз>
Наприклад, call procedure1 [param1, param2].
Оператор присвоєння має синтаксис <мінлива>: = <вираз> або <масив> {<вираз-індекс >}:=< вираз>.
У результаті змінна або елемент масиву приймають значення, рівне значенню виразу у правій частині оператора присвоювання, якщо воно було обчислено коректно.
Умовний оператор має вигляд:
if <вираз>
[Оператори]
[Elseif <вираз>]
[Оператори]
[Elseif <вираз>]
...
[Else]
[Оператори]
endif
Послідовно перевіряються вирази-умови в рядках з ключовими словами if і elseif. Як тільки отримано істинне значення умови (позитивне число або непорожня рядок), то виконуються оператори, наступні за рядком з даними умовою, потім виконання переходить на рядок, наступну за endif. Якщо ні одна з умов не виявилося істинним, то виконуються оператори, розташовані після else, якщо рядок з else є в цьому умовному операторі, інакше управління переходить нижче endif. Умовний оператор може бути використаний тільки у функції. Приклади:
1.)
if a <0
a: = abs [a]
flag: = 1
endif
2.)
if (ch = "a") | (ch = "A")
call proc_a []
elseif (ch = "b") | (ch = "B")
call proc_b []
elseif (ch = "c") | (ch = "C")
call proc_c []
else
error
endif
Оператор циклу while має вигляд:
while <вираз>
[Оператори]
loop
Виконання блоку операторів повторюється, поки істинно значення виразу-умови, потім управління передається на рядок, наступну за loop. При цьому, якщо значення виразу спочатку помилково, то оператори не будуть виконані ніколи. Оператор циклу while може бути використаний тільки у функції. Приклад:
i: = 1
s: = 0
while i <= n
s: = s + i
i: = i +1
loop
Тут змінна s отримує значення суми чисел від 1 до n.
Оператор циклу for має вигляд:
for <мінлива-лічильник>: = <вираз1>: <вираз2>
[Оператори]
next
На початку виконання циклу обчислюються вираз1 і вираз2 (їх значення повинні бути цілими, інакше виникає помилка), потім змінній-лічильнику присвоюється значення вираз1 і, якщо воно менше або дорівнює значенню вираз2, виконання переходить всередину циклу, інакше - за рядок з ключовим словом next . Після кожної ітерації циклу значення лічильника збільшується на одиницю і порівнюється зі значенням вираз2 (воно обчислюється тільки один раз на початку), якщо воно виявляється меншим або рівним значенню вираз2, то виконується наступна ітерація циклу, інакше - цикл завершується. Значення лічильника в циклі, в принципі, можна змінювати, не якщо воно виявиться не цілим на момент закінчення чергової ітерації, виникає помилка. Оператор циклу for може бути використаний тільки у функції. Приклад:
for i: = 0: size [a]
a {i}: = a {i} * 2
next
Оператор повернення return негайно перериває виконання функції (може бути використаний тільки у функції). Наприклад,
if a <b
result: = 1
return
endif

Якщо при виконанні функції не зустрівся оператор return, вихід з функції відбувається як тільки управління переходить нижче останнього рядка функції.
Оператор error перериває виконання програми - штучно генерується помилка часу виконання. Він може бути використаний тільки у функції.
Приклад:
a: = toint [str]
if a <0
error
endif
Для виводу даних використовуються оператори print і println. Оператор print має синтаксис print <вираз>. Значення виразу автоматично наводиться до рядка (т. е.команди println [a] і println [tostring [a]] - рівносильні). Цей рядок виводиться на консоль. Оператор println має аналогічний синтаксис і призначення. Відмінність полягає в тому, що println робить переклад на новий рядок після виведення, print - ні. Крім того, якщо при роботі в консолі введено вираз без ключових слів і оператора присвоєння, то результат його обчислення виводиться на консоль в окремому рядку - це скорочена форма оператора println.
Оператор clear дозволяє видалити змінну з пам'яті, наприклад, команда "clear n" видаляє з пам'яті змінну n, після чого вона вважається невизначеною. Видалити окремі елементи масиву не можна. Виконання оператора clear над невизначеної змінної не має ніякого ефекту і не призводить до помилки. За допомогою оператора clear можна також видалити фактичні параметри функції і навіть змінну result, що необхідно перед роботою з нею як з масивом. Але якщо змінна result не визначена на момент виходу з функції, то виникає помилка часу виконання. Синтаксис оператора clear має вигляд:
clear <імя_переменной1>

Приклади функцій користувача

1. Сортування масиву.

sort [a]
# Сортує масив а за зростанням.
# Методом прямого вибору
if ~ Isarray [a]
println "Invalid argument"
error
endif
n: = size [a]
for i: = 0: n-2
k: = i
for j: = i +1: n-1
k: = iff [a {j} <a {k}, j, k]
next
if i <> k
t: = a {i}
a {i}: = a {k}
a {k}: = t
endif
next
result: = a

2. Обчислення НОД за алгоритмом Евкліда

nod [n, m]
# Обчислює найменший загальний дільник
# Натуральних чисел n і m
# За алгоритмом Евкліда
if ~ isint [n] | ~ isint [m]
println "Invalid arguments"
error
endif
if (n <0) | (m <0)
println "Invalid arguments"
error
endif
if n = 0
result: = m
return
endif
if m = 0
result: = n
return
endif
while m> 0
t: = n
n: = m
m: = imod [t, m]
loop
result: = n

3. Рекурсивне обчислення факторіала.

factor [n]
# Рекурсивне обчислення факторіала числа n
if ~ isint [n]
println "Invalid argument"
error
elseif n <0
println "Invalid argument"
error
elseif (n = 0) | (n = 1)
result: = 1
else
result: = n * factor [n-1]
endif

4. Перевірка, чи є рядок коректним ідентифікатором.

test _ d [str]
# Повертає 1, якщо рядок є коректним
# Ідентифікатором, тобто складається тільки з
# Літер, цифр, знаків підкреслення і починається
# C цифри, при цьому має ненульову довжину,
# І -1 в іншому випадку
if ~ isstring [str]
println "Invalid argument"
error
endif
n: = strlen [str]
if n = 0
result: =- 1
return
endif
ch: = substr [str, 0,1]
if (ch> = "0") & (ch <= "9")
result: =- 1
return
endif
for i: = 0: n-1
ch: = substr [str, i, 1]
if ~ (((ch> = "0") & (ch <= "9 "))|(( ch> =" A ") & (ch <=" Z "))|(( ch> = "a ") & (ch <=" z "))|( ch ="_"))
result: =- 1
return
endif
next
result: = 1

5. Обчислення кута трикутника за трьома сторонами.

angle [a, b, c]
# Обчислює кут трикутника зі сторонами
# A, b і c між сторонами a і b (в градусах)
if ~ isnum [a] | ~ isnum [b] | ~ isnum [c]
println "Invalid arguments"
error
endif
if (a <= 0) | (b <= 0) | (c <= 0)
println "Not a triangle"
error
endif
cos_alpha: = (a * a + b * bc * c) / (2 * a * b)
if (cos_alpha> = 1) | (cos_alpha <=- 1)
println "Not a triangle"
error
endif
alpha: = arccos [cos_alpha]
result: = alpha * 180/pi []

Проектування і реалізація програми-інтерпретатора

Для реалізації інтерпретатора було вирішено використовувати платформу Microsoft. NET v.1.1 і мова програмування C #. Це пов'язано з тим, що платформа. NET забезпечує досить високу продуктивність (швидкодія) додатків при значному збільшенні швидкості розробки. Останнє забезпечується за рахунок наявності зручних візуальних засобів розробки, великої та потужної стандартної бібліотеки класів, використання автоматичного збирання сміття, коли пам'ять з-під більш невикористовуваних об'єктів звільняється автоматично. Мова C # ж є основною мовою платформи. NET, що дозволяє повністю використовувати всі переваги технології Microsoft. NET, він має дуже гнучкий синтаксис, що дозволяє реалізовувати досить складні алгоритми порівняно невеликими, але легко читаються фрагментами коду.
У програмі можна виділити дві основні групи класів, дві підсистеми, відповідальні за логіку роботи інтерпретатора і графічний інтерфейс користувача відповідно. Оскільки перша підсистема містить значно більше число класів, ніж друга, було вирішено розташувати її в окремому просторі імен logic, вкладеному в кореневе простір імен проекту. Класи, відповідальні за графічний інтерфейс користувача, розташовані безпосередньо в кореневому просторі імен проекту. Крім того, в просторі імен logic є два вкладених простору імен - operators і vartypes, що відповідають двом основним ієрархій успадкування в проекті - операторам програми і типам даних. Кореневе простір імен має ім'я interpr. Діаграма пакетів проекту зображена на рис. 1.


Роль посередника між користувальницьким інтерфейсом і підсистемою, що реалізує логіку роботи інтерпретатора, виконує клас Facade (фасад). Він також відповідальний за створення окремого потоку для виконання команд користувача (вводяться з консолі). Виконувати їх у тому ж потоці, що й обробляти повідомлення користувача інтерфейсу не можна так як в цьому випадку зациклилися користувача функцію буде неможливо перервати. Багато методів класу Facade зводяться до простого викликом методів інших класів з простору імен logic. Цей клас надалі буде розглянуто більш докладно.
Для обробки помилок застосовується механізм структурної обробки виключень. При цьому використовуються такі класи для користувача винятків (для помилок у класах простору імен interpr.logic):
· CalcException - помилка з вини користувача (синтаксична або в обчисленнях);
· SyntaxErrorException - синтаксична помилка, що виявляється під час «компіляції», тобто при завантаження функції або перетворення введеної команди у внутрішній формат. Успадкований від CalcException;
· LineSyntaxException - синтаксична помилка в конкретному операторі функції. Містить інформацію про місце виявлення (ім'я функції, рядок).
· OtherException - помилки, пов'язані з некоректною роботою інтерпретатора не з вини користувача. Клас використовується для налагоджувальних цілей. При нормальній роботі таке виключення ніколи не повинно генеруватися.
· LinkedListException - помилка в методах класу LinkedList. Успадкований від класу OtherException.
· NamespaceSerializationException - успадкований безпосередньо від System.Exception. Таке виключення - генерується якщо простір імен консолі не може бути успішно відновлено.
Відповідна діаграма класів зображена на рис. 2.


Можна виділити кілька груп класів у просторі імен interpr.logic - класи, відповідальні за обчислення виразів, за виконання функцій користувача, за перетворення тексту команд і функцій користувача у внутрішній формат («компіляцію» тексту програми), класи, які беруть участь в організації інтерактивної роботи інтерпретатора . Ці групи класів, так само як і підсистема графічного інтерфейсу користувача, будуть розглянуті нижче. У просторі імен interpr.logic також є один клас допоміжного призначення - LinkedList. Він представляє двозв'язна список. У ньому є методи і властивості додавання та читання елементів на початку і кінці списку, визначення кількості елементів списку. При цьому, при спробі читання з пустого списку, генерується ісключеніеLinkedListException. Метод GetIterator (), що існує в двох версіях перевантажених (для першого елемента списку і для заданого індексу), повертає об'єкт вкладеного класу LinkedList.Iterator, який представляє собою ітератор, що дозволяє читати елементи списку, переміщаючись по ньому від початку до кінця, а також рухатися у зворотному напрямку. Елемент списку представляється об'єктом приватного вкладеного класу Link, що містить три поля з видимістю internal - одне для зберігання значення елемента списку та два для посилань на попередній і наступний елементи.
Слід також відзначити інтерфейс interpr.logic.IConsole, що становить щось, що може бути використано для виведення тексту. Він має два методи - void Print (string str) і void PrintLn (string str), призначення яких зрозуміло з назви.
Основні класи простору імен interpr.logic показані на діаграмі на рис. 3.



Рис. 3.
Класи простору імен interpr. Logic.

Внутрішнє подання і виконання програми.

Більшість операторів реалізованого мови програмування містять висловлювання. Вираз є сукупність операндів і операцій над ними, яка може бути обчислена, тобто на підставі якої можна отримати деяке значення-результат. У мові програмування вираження представляються побудованими за певними вимогами рядками. При обробці тексту програми (цей процес буде розглянутий у наступному параграфі) строкове подання виразів переводиться в уявлення внутрішнє. У даному інтерпретаторі внутрішнє представлення виразів використовує так звану зворотну польський запис (ОПЗ). Розглянемо ОПЗ докладніше.
Звичайна математична запис арифметичних виразів являє собою так звану інфіксную запис, в якій знаки операцій розташовуються між операндами. При цьому для уточнення порядку обчислення операцій використовуються пріоритети операцій і круглі дужки. Така форма запису зручна для людини, але незручна для ЕОМ. Тому часто використовують так звану постфіксній або зворотній польський запис. У цьому випадку знак операції записуються після всіх її операндів, а обчислення проводиться за досить простому алгоритму: вираз в ОПЗ послідовно переглядаємо зліва направо. Якщо зустрічаємо операнд, то заносимо його в стек, якщо ж зустрічаємо операцію, то вибираємо її операнди з стека, виконуємо операцію і заносимо результат в стек. На початку обчислення виразу стек порожній. Якщо вираз записано коректно, то при виконанні кожної операції число елементів стека буде не менше числа її операндів, і в кінці процесу в стеку залишиться рівно одне значення - результат обчислення виразу. Особливістю ОПЗ є відсутність необхідності у використанні дужок.
Наприклад, вираз a + (b * cd) / e в ОПЗ має вигляд abc * de / +. Застосуємо до нього описаний вище алгоритм обчислення.
1. Заносимо в стек a.
2. Заносимо в стек b.
3. Заносимо в стек c.
Стан стека на цей момент: a, b, c - вершина.
4. Витягаємо з стека операнди операції множення - b і c і заносимо в стек результат.
Стек: a, b * c.
5. Заносимо в стек d.
Стек: a, b * c, d.
6. Витягаємо з стека операнди, виробляємо віднімання, заносимо в стек результат.
Стек: a, b * cd.
7. Заносимо в стек e.
Стек: a, b * cd, e.
8. Витягаємо з стека операнди, виробляємо розподіл, заносимо в стек результат.
Стек: a, (b * cd) / e.
9. Витягаємо з стека операнди, виробляємо складання, заносимо в стек результат.
Разом отримуємо в стеку a + (b * cd) / e, що і було потрібно.
Для представлення виразів у інтерпретаторі використовується клас Exdivssion. Він містить зворотній польський запис вираження у вигляді пов'язаного списку (однонаправленої). Ланка цього списку, так само як і стека, використовуваного при обчисленні виразу, представляється об'єктом вкладеного класу Exdivssion.Element, що містить посилання на наступну ланку і на об'єкт, який реалізує інтерфейс IComputable, який містить один метод logic.vartypes.VarBase Compute () - отримати значення . Обчислення значення виразу за розглянутому вище алгоритму проводиться в методі VarBase Exdivssion.Calculate (). Рядок, що містить запис вираження, обробляється в конструкторі цього класу. Інтерфейс IComputable реалізований трьома класами:
· VarBase - абстрактний клас, що представляє значення будь-якого типу даних;
· VarName - представляє змінну за її імені;
· Call - представляє виклик операції або функції.
Спочатку розглянемо класи, що представляють значення різних типів. Всі вони є нащадками щойно названого класу VarBase. Як було сказано вище, у мові існує чотири типи даних - ціле число, дійсне число, рядок і масив. При цьому числові і рядковий типи, на противагу масиву, називаються простими типами. Для простих значень базовим є абстрактний клас SingleVar. Цілий і речовий типи також особливо виділяються як числові, і для них існує свій базовий абстрактний клас NumVar. Нарешті, кожному з чотирьох типів даних відповідає свій конкретний клас - IntVar, RealVar, StringVar і ArrayVar. Ця ієрархія класів знаходиться в просторі назв interpr.logic.vartypes. Вона зображена на діаграмі на рис. 4.


Рис. 4.
Класи простору імен interpr. Logic. Vartypes.
Метод Compute () класу VarBase просто повертає посилання this. Методи IsArray (), IsSingle (), IsString (), IsNum (), IsInt (), IsReal () дозволяють визначити тип значення. Вони використовують оператор RTTI is мови C #. У класі VarBase оголошені абстрактними успадковані від System.Object методи Clone () і ToString (), що вимагає обов'язкового їх перевизначення у неабстрактних нащадків. Абстрактний метод Serialise () зберігає об'єкт (значення і його тип) у файлі. Клас ArrayVar має методи для присвоєння і отримання значень окремих елементів масиву, отримання розміру масиву, з'ясування питання, чи визначене значення елемента масиву з заданим індексом. Клас SingleVar визначає абстрактний метод ToBool (), який повертає логічне значення об'єкта. У класі NumVar також є абстрактний метод ToDouble (), який повертає значення об'єкта як дійсне число. Ці класи і їхні нащадки містять також методи для виконання над значеннями арифметичних і логічних операцій.
У вигляді об'єктів класів, похідних від VarBase, у виразах (екземплярах класу Exdivssion), зберігаються тільки константні значення. Змінні ж представляються тут об'єктами класу VarName, що містять ім'я (ідентифікатор) змінної. Самі ж значення змінних зберігаються в об'єктах класу Namespace або похідного від нього ConsoleNamespace.
Клас Namespace представляє простір імен (область видимості) для користувача функції, клас ConsoleNamespace - середовища консолі. При роботі інтерпретатора створюється стек просторів імен (областей видимості), на вершині якого знаходиться простір імен виконується в даний момент функції, на дні - середовища консолі. Кожного разу при виклику функції створюється і додається на вершину стека новий об'єкт Namespace, при виході з функції він знищується. Клас Namespace має поле, що містить посилання на попередній елемент стека, у знаходиться на дні стека об'єкта ConsoleNamespace воно завжди містить нульовий вказівник.
Посилання на вершину і на дно стека просторів імен зберігаються в полях класу InterprEnvironment. Доступ до поточного простору імен здійснюється через його властивість CurrentNamespace. Для цього класу при запуску інтерпретатора створюється єдиний об'єкт, що зберігається в його статичному полі і повертається статичним властивістю тільки для читання Instance. Таким чином, тут використаний патерн Singleton. Клас InterprEnvironment виконує кілька різних функцій. Серед них, по-перше, зберігання посилання на об'єкт IConsole, за допомогою якого проводиться вивід. По-друге - робота зі змінними середовища консолі - їх збереження у файлі, відновлення з файлу (виробляється під час ініціалізації об'єкту при запуску або перезапуску інтерпретатора), одержання їх списку. По-третє - завантаження та зберігання призначених для користувача функцій. Остання функція буде розглянута докладніше нижче.
Останній з класів, що реалізують інтерфейс IComputable, - клас Call представляє виклик операції, вбудованою або користувача функції у виразі. Він має два поля. Перше з них зберігає посилання на об'єкт класу ArgList, який містить список операндів. Воно ініціалізується методом SetArgList () при кожному виконанні операції або функції. Друге поле містить посилання на абстрактний клас Operation, який і представляє операцію або функцію. Цей клас містить абстрактне властивість тільки для читання ReqCount, повертає необхідну кількість операндів (аргументів). До цієї властивості звертається властивість класу Call з таким же ім'ям. Другий абстрактний член класу Operation, метод VarBase Perform (ArgList al), виконує операцію (функцію) над аргументами, що містяться в об'єкті ArgList, що передаються в якості параметрів. Цей метод повертає значення, що є результатом операції (функції). Ніякого аналога типу void не передбачено - операція (функція) може не повернути те чи інше значення лише у разі помилки. Від класу Operation успадкований клас SubName, що представляє для користувача функцію по її імені, і численні класи, що представляють стандартні операції та вбудовані функції. Останні є вкладеними в сам клас Operation, притому мають специфікатор доступу private. Для кожного з них у класі Operation є відкрите статичне поле тільки для читання, ініціалізує об'єкт відповідного типу. Створення інших об'єктів цих вкладених класів неможливо. Тут також використаний патерн Singleton. Крім того, можна говорити про застосування патерну Strategy - об'єкт класу Call (контекст) конфігурується об'єктом одного з класів, похідних від Operation (стратегія), таким чином, для різного поведінки (виконання різноманітних операцій та функцій) використовується один і той же інтерфейс. Діаграма класів, яка пояснює структуру патерну Strategy стосовно до даного випадку, наведена на рис. 5.

Рис. 5.
Використання патерну Strategy при виконання операцій.
Для користувача функцію представляє об'єкт класу Subroutine, що містить список операторів функції. Цей клас містить вкладений клас Subroutine.Moment, відповідний поточної позиції виконання у функції; його методи дозволяють передати управління на наступний оператор або на оператор із заданим номером, виконати функцію від початку до кінця. Довільний оператор мови представляється інтерфейсом IOperator. Цей інтерфейс і всі реалізують його класи знаходяться в просторі імен interpr.logic.operators.
Інтерфейс IOperator має два методи. Перший з них, GetKind (), повертає значення типу перерахування OperatorKind, яке характеризує вид оператора. Другий - void Execute (Subroutine.Moment pos) виконує оператор. Як параметр передається об'єкт Subroutine.Moment, за допомогою якого управління у функції передається на потрібне місце. Потрібно відзначити, що навіть якщо даний оператор не порушує лінійної послідовності виконання, то все одно відповідальність за перехід на наступний оператор лежить на методі Execute () об'єкта оператора.
Як було сказано вище, ряд операторів може бути використаний тільки у функціях. Відповідні класи реалізують інтерфейс IOperator безпосередньо. Інші оператори представляють собою команди, які можуть бути введені в консолі. Загальною властивістю таких операторів є те, що вони не порушують лінійної послідовності виконання, зустрівшись у функції. Класи, їх представляють, є нащадками абстрактного класу Command, що реалізує інтерфейс IOperator. Метод Execute () у класі Command має перевантажену версію без параметрів, оголошену абстрактною. Версія ж з інтерфейсу, приймаюча параметр типу Subroutine.Moment, в цьому класі реалізована таким чином: викликається метод Execute () без параметрів, потім управління передається на наступний оператор. У класі Command метод GetKind () повертає значення OperatorKind.Plain, цей метод тут не є віртуальним і не перевизначається у нащадків.
Розглянемо тепер окремі класи, що реалізують інтерфейс IOperator. Почнемо з нащадків класу Command.
По-перше, присутні дві команди, які відповідають за виведення на консоль - print і println. Вони видаються класами PrintCommand і PrintLnCommand відповідно. Структура цих класів повністю аналогічна. Вони містять поле m_expr, з посиланням на об'єкт Exdivssion, що представляє вираз, результат обчислення якого повинен бути виведений на консоль. У методі Execute () результат обчислення виразу спочатку наводиться до рядка (викликається метод ToString), потім виводиться на консоль викликом методів об'єкта InterprNamespace.CurrentConsole.
Команда call реалізується за допомогою класу CallCommand, у методі execute () якого просто обчислюється вираз з поля m_expr, результат ж обчислення виразу ніяк не використовується.
Конструктори цих трьох класів беруть один параметр типу Exdivssion.
Клас EmptyCommand, що представляє порожню команду (порожній рядок або рядок коментаря), містить лише порожні конструктор без параметрів і метод Execute ().
Клас ClearCommand містить поле типу string, в якому зберігається ім'я видаляється змінної. У методі execute () викликається метод Remove об'єкта поточного простору імен.
І, нарешті, клас AssignCommand представляє команду присвоєння. Він має два конструктора, що приймають два або три параметри відповідно, для операторів присвоювання значення змінної або елементу масиву. У першому з цих параметрів міститься ім'я змінної або масиву в лівій частині оператора присвоювання, в інших - присваиваемое вираз і, в другому випадку, індексне вираз. Вирази передаються у тому строковою запису, вони «компілюються» в об'єкти класу Exdivssion в конструкторі останнього. Робота зі змінними здійснюється за допомогою об'єкту поточного простору імен, що повертається властивістю InterprEnvironment.Instance.CurrentNamespace.
До числа класів, що представляють оператори управління послідовністю виконання, відносяться ErrorOperator, ReturnOperator, ForOperator, NextOperator, WhileOperator, LoopOperator, IfOperator, ElseifOperator, ElseOperator, EndifOperator. Для кожного з них є своє значення в перерахуванні OperatorKind, яке і повертається методом GetKind відповідного класу.
Метод execute () класу ErrorOperator містить всього один рядок - генерацію виключення CalcException. Такий же короткий метод виконання і в класі ReturnOperator - викликається метод return () об'єкта Subroutine.Moment pos, який негайно передає виконання за кінець функції.
Інші ж з розглянутих операторів працюють в парі з іншими операторами - while - з loop, for - з end, if - з elseif, else і endif. Відповідні класи мають поля, що містять номери (позиції) відповідних парних операторів, і властивості для доступу до них:
· У класі ForOperator - властивість NextPos - позиція оператора next;
· У класі NextOperator - властивість ForPos - позиція оператора for;
· У класі WhileOperator - властивість LoopPos - позиція оператора loop;
· У класі LoopOperator - властивість WhilePos - позиція оператора while;
· У класах IfOperator, ElseIfOperator і ElseOperator - властивість NextPos - позиція найближчого знизу відповідного оператора elseif, else або endif.
Умови і межі циклів там, де вони потрібні, зберігаються у вигляді об'єктів типу Exdivssion. Логіка виконання операторів наступна:
· При виконанні оператора while метод Execute () класу WhileOperator обчислює вираз-умова і, залежно від його логічного значення, передає управління або наступному оператору, або оператору, наступного за оператором loop. Метод Execute () класу LoopOperator передає управління на відповідний оператор while.
· При виконанні оператора for метод Execute () класу ForOperator обчислює значення виразів-кордонів циклу, запам'ятовує значення верхньої межі у відповідному полі класу, потім, якщо нижня межа більше верхньої межі, передає управління на оператор, наступний за next, інакше - на наступний оператор . При виконанні ж оператора next викликається метод Step () в об'єкта, що представляє парний оператор for, який збільшує на одиницю змінну-лічильник циклу і, в залежності від результату порівняння останньої з верхньою межею циклу, зраджує управління на оператор, наступний або за for, або за next. При цьому за весь час виконання циклу метод Execute () класу ForOperator виконується тільки один раз.
· При виконанні оператора if метод Execute () класу IfOperator переглядає поспіль відповідні оператори elseif, else і endif до знаходження блоку коду, до якого слід передати управління. При цьому використовуються властивість NextPos класів IfOperator, ElseOperator, ElseifOperator і метод TestCondition класу ElseifOperator, перевіряючий міститься в операторі умова. Для визначення виду оператора, на який вказує значення властивості NextPos чергового розглянутого оператора, у відповідного об'єкту викликається віртуальний метод GetKind.
Діаграма класів простору імен interpr.logic.operators наведена на рис. 6.

Рис. 6.
Класи простору імен interpr. Logic. Operators.
Користувальницькі функції завантажуються або при запуску інтерпретатора, або при збереженні їх у редакторі коду. Для зберігання завантажених функцій використовуються об'єкти класу Subroutine. Функція представляється списком операторів (контейнер ArrayList, в якому зберігаються об'єкти типу інтерфейсу IOperator). Також в класі є поля, що містять загальне число операторів, список імен формальних параметрів функції і ім'я функції. Як було сказано вище, в класі Subroutine знаходиться вкладений клас Subroutine.Moment. Він представляє поточну позицію виконання у функції і в своїх полях зберігає номер посилання на об'єкт Subroutine і номер поточного оператора. Його методи працюють з приватними полями екземпляра класу Subroutine. Тому успадкування від класу Subroutine стає небажаним, і він оголошений як sealed.
За зберігання завантажених користувача функцій відповідальний клас SubroutinesManager, вкладений (зі специфікатором доступу private) в клас InterprEnvironment. Він зберігає в двох полях типу System.Collections.ArrayList список завантажених функцій, як примірників класу Subroutine, та список їхніх імен, відповідність між функцією та її ім'ям встановлюється по індексу в списках. Singleton-об'єкт класу InterprEnvironment зберігає посилання на один об'єкт класу SubroutinesManager. До її методам звертаються методи класу InterprEnvironment, що працюють з одними функціями, серед яких:
· GetSub (string) - отримати об'єкт функції по її імені;
· LoadSub (string) - завантажити функцію із заданим ім'ям;
· LoadSubs () - завантажити функції з усіх файлів в каталозі subroutines;
· UnloadSub (string) - вивантажити функцію із заданим ім'ям.
У виразах ж для користувача функції представляються об'єктами класу VarName, які містять ім'я функції, за яким під час виконання з допомогою методу InterprEnvironment.GetSub () Поучаєтся відповідний об'єкт Subroutine. Це пов'язано з тим, що якщо б у виразах в об'єктах Call зберігалася б посилання безпосередньо на Subroutine, функція, що викликає іншу функцію, не могла б бути завантажена коректно раніше завантаження останньої.

Обробка тексту програми.

Текст програми може існувати у двох видах - команди, що вводяться з консолі, і призначені для користувача функції. В обох випадках один рядок (за винятком заголовка функції) перетворюється в один оператор, можливо, порожній. У першому випадку цей оператор повинен представлятися об'єктом класу, похідного від Command, у другому - будь-яким об'єктом, що реалізують інтерфейс IOperator.
Для перетворення рядка тексту програми в об'єкт, який реалізує інтерфейс IOperator, використовуються статичні методи класу LineCompiler: Command CompileCommand (string) для команди, введеної з консолі і IOperator CompileOperator (string) для рядка функції. Клас LineCompiler не має нестатичні членів, крім закритого конструктора, який, заміщаючи конструктор з базового класу System.Object, не дає можливості створювати екземпляри цього класу. Алгоритм роботи обох названих методів аналогічний. Спочатку перевіряється наявність у рядку лексеми «:=», притому не між подвійними лапками (не в рядковій константі). Якщо вона знайдена, то даний рядок розглядається як оператор присвоювання. Спочатку аналізується ліва частина оператора присвоєння. У залежності від її виду, використовується потрібний конструктор класу AssignCommand - для надання значення змінної або елементу масиву. Йому в якості одного з параметрів передається частина рядки праворуч від символів «:=», яка розбирається як вираження у конструкторі класу Exdivssion. Якщо ж даний оператор не є оператором присвоєння, то з рядка виділяється перша лексема, яка послідовно порівнюється з ключовими словами, з яких починаються різні оператори (команди). Якщо збігів не знайдено, то в методі CompileOperator () генерується виключення SyntaxErrorException - синтаксична помилка, у методі ж CompileCommand () в цьому випадку рядок розглядається як скорочена форма команди println (тільки вираз). Як тільки вид оператора визначено, частина рядка аналізується відповідним чином. Для багатьох операторів - if, else if, while, print, println - вона розглядається як один вираз. При цьому на кожному з етапів аналізу рядки при виявленні помилки може виникнути виняток SyntaxErrorException.
Для лексичного розбору рядка (розбиття на лексеми) використовується клас Parser. Кожен його примірник використовується для розбору одного рядка. Клас має один конструктор, який приймає один параметр типу string, що містить оброблювану рядок. У конструкторі рядок піддається перетворенню - видаляються коментар, якщо він присутній, і зайві прогалини. Клас Parser реалізує стандартні інтерфейси System.IEnumerable і System.IEnumerator. Інтерфейс IEnumerable представляє об'єкт-список того чи іншого виду, який допускає послідовний перебір елементів. Він має єдиний метод IEnumerator GetEnumerator (). Інтерфейс IEnumerator представляє об'єкт, який використовується для перебору елементів списку. У даному разі цю роль виконує сам об'єкт класу Parser, тому метод GetEnumerator повертає посилання this. Цей інтерфейс містить методи MoveNext () - перейти на наступний елемент, Reset () - скидання на початок списку і властивість Current - поточний елемент списку. У даному випадку об'єкт Parser розглядається як список рядків-лексем, що входять до складу розглядуваної рядка. Властивість Current доступно тільки для читання і його блок get містить виклик методу private string GetCurrent (), що виділяє поточну лексему з рядка. Рядок ділиться на лексеми наступних видів:
· Рядкова константа;
· Ідентифікатор;
· Число (ціле або дійсне, можливо, в експоненційної формі);
· Службовий символ;
· Складовою службовий символ (':=','<=','>=','~=','<>').
Метод GetCurrent () виділяє в рядку довгу можливу лексему, що починається з поточної позиції.
Крім того, клас Parser має два відкритих статичних методу: bool IsID (string) - чи є даний рядок коректним ідентифікатором і bool IsUserID (string) - чи є даний рядок коректним ідентифікатором, що не збігається з ім'ям якої-небудь із вбудованих функцій.
Перетворення виразів в описаний раніше внутрішнє подання проводиться в конструкторі класу Exdivssion, який має дві перевантажені версії, що приймають параметри типу string і Parser відповідно. В обох викликається private-метод Analyse (), в якому лексеми з рядка заносяться в список типу LinkedList (цей клас був розглянутий вище), який потім передається як параметр іншому private-методу OPZ (). В останньому і зосереджена основна частина алгоритму розбору виразу. Цей алгоритм відноситься до так званих висхідним методам синтаксичного розбору, в яких дерево розбору будується «знизу вгору». Синтаксичний аналіз тут поєднаний із семантичною обробкою - побудовою зворотної польської записи висловлювання. Перетворення вираження в ОПЗ проводиться таким чином:
· Спочатку створюється порожній стек операцій (об'єкт класу LinkedList).
· Послідовно перебираються лексеми, що входять до розбираємося рядок. Якщо зустрічається операнд - змінна (ідентифікатор, після якого немає відкриває квадратної або фігурної дужки) або константа, то він відразу ж додається до результату, потім, якщо на вершині стека операндів є унарні операції, вони виштовхуються в результат.
· Кожна бінарна операція має свій пріоритет (можна отримати у вигляді числа з допомогою private-функції Exdivssion.Priority ()).
· Бінарна операція виштовхує з стека в результат операції з більшим чи рівним пріоритетом (з вершини стека), потім сама записується в стек. Для символів '+' і '-' проводиться перевірка, чи є вони в кожному конкретному випадку знаком бінарної операції або унарний - у разі унарний операції перед її знаком знаходиться відкриває дужка яка інша операція, або операція знаходиться на початку рядка.
· Унарний операція відразу записується в стек.
· Відкриваюча кругла дужка відразу записується в стек.
· Закривна кругла дужка виштовхує в результат всі операції з стека до відкриваючої дужки, потім дужки знищуються, і виштовхуються з вершини стека в результат унарні операції, якщо вони тут є.
· Якщо після ідентифікатора у виразі зустрічається відкриває квадратна дужка, то виділяються списки лексем, з яких складаються вирази-операнди функції (вони розташовані в квадратних дужках і розділені комами; враховується можлива вкладеність викликів функцій), для кожного з них послідовно викликається рекурсивно метод Analyse1 ( ), при цьому в результат дописуються результати розбору цих висловів, потім, в результат дописується виклик функції (її ім'я - що стоїть перед відкриває квадратної дужкою лексема).
· Якщо після ідентифікатора зустрічається відкриває фігурна дужка, то що стоїть перед нею лексема розглядається як ім'я масиву (якщо вона не є коректним ідентифікатором, то це свідчить про синтаксичну помилку). Вираз у фігурних дужках обробляється рекурсивним викликом Analyse1 () (аналогічно параметру функції), потім в результат дописуються ім'я масиву і операція звернення до елементу масиву.
· Після обробки виклику функції або звернення до елементу масиву в результат виштовхуються з вершини стека унарні операції, якщо вони присутні.
· У кінці розбору в результат виштовхується весь вміст стека.
· Константи записуються в результат як об'єкти класів, що представляють відповідні типи даних, змінні - як об'єкти VarName, операції та виклики функцій - як об'єкти Call.
Розглянемо приклад. Нехай є рядок (a * c +-b {а + з}) / а. Застосуємо описаний алгоритм.
1. Спочатку стек операндів і результат порожні.
2. Перша лексема - відкриває кругла дужка. Записуємо її в стек.
Стек: (Результат: <порожньо>.
3. Друга лексема - ідентифікатор «а». За ним немає відкриває квадратної або фігурної дужки, тому записуємо його в результат.
4. Стек: (Результат: а
5. Наступна лексема - операція множення. Записуємо її в стек. На вершині стека немає операцій з більшим чи рівним пріоритетом, нічого виштовхувати не потрібно.
6. Стек: (* Результат: а
7. Друга лексема - ідентифікатор «з». За ним немає відкриває квадратної або фігурної дужки, тому записуємо його в результат.
Стек: (* Результат: ас
8. Наступна лексема - знак «+». Перед ним знаходиться ідентифікатор, тому він є знаком операції додавання. Він виштовхує з стека операцію множення як має більш високий пріоритет, потім сам дописується в стек.
9. Стек: (+ Результат: ас *
10. Наступна лексема - знак «мінус». Перед ним немає ні закриває дужки ні ідентифікатора, тому він є знаком операції унарний мінус (позначимо її як «_»), записуємо її в стек.
11. Стек: (+ _Результат: ас *
12. Наступна лексема - ідентифікатор b. За ним слідує фігурна дужка, тому він розглядається як ім'я масиву. У фігурних дужках знаходиться рядок «а + с», яка, будучи перетвореною з даного алгоритму, дасть у результаті «ас +». Допишемо це в результат розбору вихідного висловлювання. Потім допишемо в результат ім'я масиву («b») і операцію індексації (позначимо її «{}»). І, нарешті, виштовхніть що знаходиться на вершині стека операцію унарний мінус.
13. Стек: (+ Результат: ас * ас + b {} _
14. Наступна (за закриває фігурною дужкою) лексема - закриває кругла дужка. Вона виштовхне з стека в результат знаходиться перед відкриває дужкою операцію складання, потім відкриває дужка буде вилучена з стека.
Стек; <порожньо> Результат: ac * ac + b {} _ +
15. Наступна лексема - операція поділу. Вона дописується в стек (перед цим стек порожній, нічого виштовхувати не потрібно).
Стек: / Результат: ac * ac + b {} _ +
16. Остання лексема - ідентифікатор «а». Після нього немає ніяких дужок, тому він відразу ж додається до результату.
Стек: / Результат: ac * ac + b {} _ + a
17. В кінці виштовхуємо з стека що залишилася в ньому операцію множення в результат. Разом отримуємо ac * ac + _b {} + a /, що є зворотним польським записом вихідного висловлювання.
При завантаженні функції обробка її тексту здійснюється в конструкторі класу Subroutine, який приймає два параметри - ім'я функції і текст функції (у вигляді масиву рядків). При цьому окремо розглядається перший рядок - заголовок функції. Для її аналізу використовується private-метод Subroutine.AnalyseHeader (), в якому перевіряється відповідність цього рядка необхідного формату і витягується список формальних параметрів. Також перевіряється відповідність імені функції в заголовку необхідному (першому параметру конструктора). При цьому використовується об'єкт класу Parser. Потім по черзі піддаються розбору за допомогою методу LineCompiler.CompileOperator () інші рядки, результат «компіляції» кожної з яких додається до списку операторів функції. При цьому використовується стек вкладеності операторів (застосовується об'єкт класу System.Collections.Stack). Після обробки кожного рядка перевіряється тип отриманого оператора за допомогою методу IOperator.GetType (). Якщо оператор відкриває блок коду (if, elseif, else, while, for), то його номер заноситься в стек. Якщо оператор закриває блок коду, то з стека витягається номер парного оператора і присвоюються необхідні значення властивостям NextPos, LoopPos і т. д. відповідних об'єктів. Оператори elseif і else розглядаються одночасно і як закривають розташований вище блок коду, і як відкривають наступний. Потрібно відзначити, що в перший елемент списку операторів функції (з нульовим індексом) в об'єкті Subroutine розташовується порожній оператор (об'єкт EmptyCommand), завдяки чому кожному рядку тексту функції відповідає елемент цього списку з індексом, рівним номеру цього рядка. Основна частина коду конструктора класу Subroutine знаходиться в блоці try, при виникненні виключення SyntaxErrorException в якому генерується виключення класу LineSyntaxException, об'єкт якого містить інформацію про місце помилки (ім'я функції і номер рядка).

Графічний інтерфейс користувача.

Головною формою додатка, яка зображена на рис. 7, відповідає клас Form1. Основну частину форми займає компонент ConsoleBox, створений на основі класу UserControl. Він включає в себе один примірник компонента RichTextBox, «розтягнутий» за допомогою властивості Dock на всю доступну площу. Компонент ConsoleBox являє собою вікно консолі, в якій користувач вводить команди, і на яку виводяться результати роботи команд. Клас ConsoleBox є єдиним класом в остаточній версії проекту, що реалізує розглянутий вище інтерфейс IConsole. Найважливіші члени класу ConsoleBox:
· Методи Print (string) і PrintLn (string) - реалізують методи інтерфейсу IConsole, виробляють висновок тексту у вікно консолі.
· Метод Prompt () - виводить запрошення командного рядка (">>>") і переводить консоль в режим очікування команди.
· Подія GetCommand (object sender, ConsoleBoxGetCommandEventArgs e) - виникає, коли в режимі очікування команди була натиснута клавіша Enter. При цьому в параметрі e, що має тип класу ConsoleBoxGetCommandEventArgs, який успадкований від System.EventArgs, у властивості Command міститься введена користувачем команда у вигляді рядка.
Методи Print, PrintLn і Prompt розраховані на безпечне використання з іншого потоку. У них використовується виклик private-методів через об'єкт класу System.Windows.Forms.MethodInvoker. Можливі два стани компонента консолі - режим очікування введення команди і режим роботи команди. Введення тексту в полі RichTextBox допускається тільки в режимі очікування введення команди і тільки після останнього запрошення командного рядка, що забезпечується за допомогою властивості RichTextBox.SelectionProtected. Виклик методу Prompt () переводить консоль в режим очікування команди. При натисненні Enter в режимі очікування команди, крім генерації події GetCommand, відбувається перехід з режиму очікування в режим роботи команди.



Рис. 7.
Головна форма.
При натисканні кнопки «Функції» на головній формі виводиться діалогове вікно, якому відповідає клас FunctionsForm (див. рис. 8). У цьому вікні у верхньому полі відображається список успішно завантажених функцій, в нижньому - функцій, завантаження яких пройшла невдало через наявність синтаксичних помилок. Кнопки дозволяють редагувати, видалити (в цьому випадку потрібно підтвердження) обрану функцію, створити нову функцію (в цьому випадку буде запитане ім'я функції, і, якщо воно не є коректним ідентифікатором, функція створена не буде). Для запиту імені при створенні функції використовується форма, описувати класи InputForm (див. рис. 9). Якщо функція створена успішно, вона відкривається для редагування. При подвійному клацанні на ім'я функції в будь-якому зі списків у вікні «Функції» також вона відкривається для редагування. Вікно «Функції» є модальним діалогом і має бути закрите для продовження роботи з інтерпретатором. Воно закривається при відкритті функції для редагування. При цьому замість нього на екрані з'являється вікно редактора коду.

Рис. 8.
Вікно «Функції»


Рис. 9.
Вікно введення імені створюваної функції.
Вікна редактора коду відповідає клас EditorForm (див. рис. 10). Кнопка «Зберегти» у ньому зберігає функцію у файлі, розташованому в підкаталозі subroutines робочого каталогу інтерпретатора, з ім'ям, що збігається з ім'ям функції (без розширення). Кнопка «Вихід» - закриває вікно редактора (з запитом на збереження). У мітці праворуч від кнопок відображається номер рядка поточного положення курсору (початку виділення) в тексті. У її текст номер поточного рядка заноситься приблизно 10 разів на секунду, що забезпечується за допомогою таймера (компонент System.Windows.Forms.Timer). Вікно редактора коду не є модальним - у будь-який момент роботи з інтерпретатором може бути відкрито скільки завгодно таких вікон для різних функцій. Заблоковано відкриття функції вдруге (у двох вікнах одночасно) і вихід із інтерпретатора до закриття всіх вікон редактора коду. Основну частину вікна редактора коду складає компонент SourceBox, який також як і ConsoleBox, успадкований від классаUserControl. Він містить елемент керування RichTextBox, в якому, власне, і здійснюється редагування тексту функції, і елемент TextBox, розташований за RichTextBox на задньому плані і невидимий для користувача. На нього перемикається фокус на час виконання синтаксичного цветовиделенія, так як для зміни кольору фрагмента тексту в RichTextBox необхідно цей фрагмент виділити, що призводило б до помітного мерехтінню тексту, якщо б фокус введення залишався біля поля RichTextBox. Такий підхід до вирішення проблеми дозволяє реалізувати синтаксичне цветовиделеніе з використанням властивостей класу RichTextBox невеликим об'ємом коду (інакше б довелося робити «ручну» перемальовування з безпосереднім використанням GDI +). Але на жаль, помітно знижується швидкодію, у зв'язку з цим були введені такі обмеження: синтаксичне цветовиделеніе проводиться тільки при зміні номера рядка, в якій знаходиться курсор, наприклад, при натисненні Enter, а також при клацанні лівою кнопкою миші у вікні редактора (у RichTextBox ). При цьому обробляються лише рядки тексту, які відображаються в даний момент часу у вікні. Звичайно, це трохи незручно для користувача, це можна спостерігати, наприклад, в такому середовищі програмування, як MS Visual Basic 6. Для виконання синтаксичного цветовиделенія використовується вкладений private-клас HighlightParser, який має методи для розбору рядка на окремі лексеми, для визначення положення в рядку і типу цих лексем. Застосувати клас interpr.logic.Parser тут не можна, тому що він працює з перетвореною рядком (видалені зайві прогалини та коментарі). Клас SourceBox також має методи для читання тексту функції з файлу і збереження тексту у файлі.



Рис. 10.
Вікно редактора коду.

При натисканні на кнопку «Змінні» в головному вікні інтерпретатора відображається діалогове вікно зі списком змінних середовища консолі (див. рис. 11). Змінні відображаються разом з їх значеннями (приведеними до строковому типу). Дане вікно дозволяє видалити обрану або всі змінні з пам'яті. Цьому вікна відповідає клас VariablesForm. При натисканні кнопки «Перезапуск» виробляється перезапуск інтерпретатора (можливо, з перериванням зациклилися або довго працює для користувача функції). При перезапуску не відновлюються змінені значення змінних середовища консолі, тому передбачена можливість збереження значень змінних. Збереження змінних відбувається автоматично при виході з інтерпретатора і вручну при натисканні кнопки «Зберегти змінні». Змінні зберігаються в двійковому файл variables, який автоматично створюється в робочому каталозі інтерпретатора, і зчитуються з нього при запуску або перезапуску інтерпретатора. Зберігати змінні вручну має сенс перед запуском користувача функції, яка може зациклитися або занадто довго працювати, щоб можна було перервати її роботу, не побоюючись втратити результати попередніх обчислень. Робота зі змінними здійснюється за допомогою методів класу Facade, що звертаються до відповідних методів класів з простору імен interpr.logic.
Класи, пов'язані з інтерфейсу інтерпретатора, показані на діаграмі на рис. 12.


Рис. 11.
Вікно «Змінні».




Рис. 12.
Класи, пов'язані з графічним інтерфейсом користувача.

Взаємодія підсистем інтерпретатора. Клас Facade.

Як вже було сказано вище, клас Facade є посередником між двома основними підсистемами - графічним інтерфейсом користувача і логікою роботи інтерпретатора. Тут використано патерн Facade. Усі звернення ззовні до класів простору імен interpr.logic виробляються через виклик методів класу Facade. Сама ж підсистема логіки роботи інтерпретатора не зберігає посилань, як це вимагає даний патерн, ні на клас Facade, ні на інші класи, що не входять в неї. Таким чином, клас Facade є як би мостом між простором імен interpr.logic і класами, що реалізовують користувальницький інтерфейс.
При запуску інтерпретатора в обробнику події Load класу Form1 відбувається початкова ініціалізація програми. Спочатку викликається статичний метод Facade.Create (), якому передається посилання на елемент управління ConsoleBox, розташований на головній формі інтерпретатора. Тип цього параметра - інтерфейс IConsole. Передана посилання але об'єкт консолі присвоюється властивості InterprEnvironment.CurrentConsole. У методі Facade.Create () створюється єдиний об'єкт класу Facade, до якого надалі доступ здійснюється через статична властивість лише для читання Facade.Instance. Тут використовується патерн Singleton.
При першому зверненні до властивості InterprEnvironment.Instance викликається конструктор класу InterprEnvironment, У ньому створюється об'єкт ConsoleNamespace для простору імен консолі. Потім проводиться відновлення змінних, збережених у файлі variables в робочому каталозі інтерпретатора. Якщо цей файл відсутній, то він створюється (порожній) і відновлення не проводиться. Даний файл є двійковим. На його початку записується загальна кількість змінних, потім для кожної з них зберігається інформація про тип (один символ), ім'я (рядок) і значення. Для масиву після імені записується загальна кількість елементів, а потім кожен з елементів у вигляді пари «тип-значення». Відновлення змінних виробляється в методі ConsoleNamespace.Restore (). Якщо відновлення не пройшло успішно унаслідок неправильного формату файлу variables, то в методі Restore () генерується виключення NamespaceSerialisationException. Воно перехоплюється в конструкторі класу InterprEnvironment, в результаті чого змінюється значення відповідного поля, після цього властивість InterprEnvironment.NotRestored, як і звертається до нього властивість Facade.NotRestored, повертає істину. У разі, якщо така помилка сталася, в обробнику Form1.Form1_Load видається відповідне повідомлення користувачеві.
На наступному кроці ініціалізації встановлюється обробник для події Facade.Done (завершення виконання команди). Потім завантажуються для користувача функції за допомогою методу Facade.LoadSubs (), що викликає метод InterprEnvironment.LoadSubs (). Якщо при завантаженні будь-якої функції сталася помилка, повідомлення виводиться на консоль. Нарешті, викликається метод Prompt () (вивести запрошення і чекати введення команди) елемента управління ConsoleBox, розташованого на головній формі.
Клас Facade має цілий ряд методів для роботи з користувацькими функціями і змінними середовища консолі, які викликають відповідні методи об'єкта InterprEnvironment.Instance. Серед них: LoadSub (), LoadSubs (), GetSubs (), UnloadSub (), GetVariables (), DeleteVariable (), SaveVariables (). Через ці методи проводяться операції в багатьох обробниках подій користувальницького інтерфейсу.
Але, мабуть, найбільш важливим з методів класу Facade є ExecuteCommand () - виконати команду. Він викликається в обробнику події GetCommand елемента ConsoleBox на головній формі. У ньому в окремому потоці запускається на виконання приватний метод ThrStart (), в якому введена з консолі команда спочатку «компілюється» методом LineCompiler.CompileCommand (), потім виконується, після закінчення чого генерується подія Facade.Done (), в обробнику якого консоль перекладається в стан очікування наступної команди методом ConsoleBox.Prompt (). І «компіляція» та виконання команди виробляються в блоках try, у разі виникнення виключення на консоль видається відповідне повідомлення про помилку.
Необхідність виконувати команди в окремому потоці пов'язана з тим, що тільки в цьому випадку можна перервати зациклилися або довго працює для користувача функцію без аварійного завершення інтерпретатора. Для перезапуску інтерпретатора, можливо, з перериванням роботи для користувача функції, призначений метод Facade.Restart (). У ньому в окремому потоці запускається метод DoRestart (), в якому виконуються наступні дії. По-перше, якщо в даний момент часу виконується команда, то викликається статичний метод Subroutine.Moment.Break (). У ньому з допомогою методу Interlocked.Exchange () (безпечне при паралельному виконанні присвоювання) статичному полю Subroutine.Moment.s_break присвоюється значення 1. На кожній ітерації циклу в методі Subroutine.Moment.Run (), крім виконання чергового оператора функції, перевіряється значення цього поля. Якщо воно дорівнює одиниці, то генерується виключення CalcException, тобто виконання команди завершується з помилкою часу виконання. Після виклику Subroutine.Moment.Break () в методі DoRestart () слід цикл без тіла, який виконується до тих пір, поки виконання команди не буде завершено, чого, звичайно ж, не доводиться довго чекати. Після того, як виконання буде перервано, проводиться повторна ініціалізація, аналогічна відбувається при запуску інтерпретатора.
Для реалізації багатопоточності використовується стандартний клас System.Threading.Thread. Його конструктору передається один параметр типу делегата System.Threading.ThreadStart (процедура без параметрів). Метод, на який вказує цей делегат, починає виконуватися в окремому потоці при виклику методу Start () об'єкта потоку. Коли метод, запущений в потоці, повертається, виконання потоку завершується. Повторне використання того ж об'єкта класу Thread неможливо, його треба створювати заново. При використанні багатопоточності слід приймати ряд заходів для забезпечення безпечного доступу до загальних даних. Наприклад, присвоювання значень змінним, використовуваним декількома потоками, по можливості слід проводити за допомогою методу Interlocked.Exchange, який гарантує атомарность операції, тобто те, що її виконання не буде перервано до повного завершення для передачі управління іншому потоку. Також звертатися до методів і властивостей елементів графічного інтерфейсу користувача прямо можна тільки з того ж потоку, в якому вони були створені. Якщо необхідно впливати на графічний інтерфейс користувача з інших потоків, то це слід робити в методі (процедурою без параметрів), що викликається за допомогою делегата System.Windows.Forms.MethodInvoker. У мові C # є й інші засоби синхронізації роботи потоків, які не використовуються в даному інтерпретаторі.

Висновок

Мною виконаний інтерпретатор нескладного мови програмування. Інтерпретатор працює в інтерактивному режимі, виконуючи команди, що вводяться з консолі, які можуть містити виклики для користувача функцій (підпрограм). Користувальницькі функції можуть містити структурні конструкції - цикли, розгалуження, виклики інших функцій (можлива й рекурсія). Можлива робота з числовими і рядковими даними, а також з одновимірними масивами. Є досить велика кількість вбудованих математичних та інших функцій. Попереднього оголошення змінних не потрібно, синтаксис математичний виразів - традиційний для мов високого рівня. Це робить інтерпретатор зручним у використанні. Даний інтерпретатор може застосовуватися як у навчальних цілях, наприклад, для навчання школярів основам програмування, так і якості «програмованого мікрокалькулятора» для практичних розрахунків, складність яких не вимагає застосування специфічного програмного забезпечення.

Додаток. Оригінальний текст (скорочено).

Через великий обсяг вихідного коду, наведені лише найбільш важливі його фрагменти.

1. Клас VarBase.

using System;
using System.IO;
namespace interpr.logic.vartypes {
public abstract class VarBase: ICloneable, IComputable {
public bool IsArray () {
return (this is ArrayVar);
}
public bool IsNum () {
return (this is NumVar);
}
public bool IsString () {
return (this is StringVar);
}
public bool IsInt () {
return (this is IntVar);
}
public bool IsReal () {
return (this is RealVar);
}
public bool IsSingle () {
return (this is SingleVar);
}
public virtual VarBase Compute () {
return this.Clone () as VarBase;
}
public abstract System.Object Clone ();
public override abstract string ToString ();
public abstract void Serialise (BinaryWriter bw);
}
}

2. Клас ArrayVar.

using System.Collections;
using System.IO;
namespace interpr.logic.vartypes {
public class ArrayVar: VarBase {
public virtual IntVar Size {
get {return new IntVar (m_list.Count);}
}
private ArrayList m_list;
public ArrayVar () {
m_list = new ArrayList ();
}
public int GetSize () {
return m_list.Count;
}
public void setAt (int index, SingleVar var) {
if (var == null) {
throw new CalcException ("Помилка");
}
if (index <0)
throw new CalcException ("Індекс не може бути негативним");
for (int ind = index, s = m_list.Count; ind> = s; ind -)
m_list.Add (null);
m_list [index] = var.Clone ();
}
public SingleVar getAt (int index) {
if (index <0)
throw new CalcException ("Індекс не може бути негативним");
if (index> = m_list.Count)
throw new CalcException ("Вихід за межі масиву");
else
return (SingleVar) m_list [index];
}
public SingleVar this [int index] {
get {return getAt (index);}
set {setAt (index, value);}
}
public IntVar IsElementDefined (int index) {
bool result = index> = 0;
result = result & & (index <m_list.Count);
result = result & & (m_list [index]! = null);
return new IntVar (result);
}
public override System.Object Clone () {
ArrayVar res = new ArrayVar ();
int li = 0;
SingleVar e = null;
while (li <m_list.Count) {
e = (SingleVar) m_list [li + +];
if (e! = null)
res.m_list.Add (e.Clone ());
else
res.m_list.Add (null);
}
return res;
}
public override void Serialise (BinaryWriter bw) {
bw.Write ('a');
int size = m_list.Count;
bw.Write (size);
for (int i = 0; i <size; i + +) {
if (m_list [i] == null)
bw.Write ('n');
else
(M_list [i] as VarBase). Serialise (bw);
}
}
public override System.String ToString () {
System.String res = "[";
int li = 0;
SingleVar e = null;
if (li <m_list.Count) {
e = (SingleVar) m_list [li + +];
if (e! = null) {
res + = e.ToString ();
}
else
res + = "-";
}
while (li <m_list.Count) {
e = (SingleVar) m_list [li + +];
if (e! = null) {
res + = "," + e.ToString ();
}
else
res + = ", -";
}
return res + "]";
}
}
}

3. Клас InterprEnvironment.

using System;
using System.Collections;
using System.IO;
namespace interpr.logic {
public class InterprEnvironment {
private SubroutinesManager m_subsman = null;
private ConsoleNamespace m_console_vars;
private bool m_not_restored = false;
public bool NotRestored {
get {return m_not_restored;}
}
public ConsoleNamespace ConsoleNamespace {
get {return m_console_vars;}
}
public ConsoleNamespace.VariableReport [] GetGlobalVarsList () {
return m_console_vars.GetVariableList ();
}
private InterprEnvironment () {
m_current_namespace = new ConsoleNamespace ();
m_console_vars = m_current_namespace as ConsoleNamespace;
m_not_restored = false;
try {
m_console_vars.Restore ();
} Catch {
m_not_restored = true;
m_console_vars = new ConsoleNamespace ();
m_current_namespace = m_console_vars;
}
}
public void LoadSubs () {
if (m_current_console == null)
throw new OtherException ("Error in Environment.LoadSubs ()");
s_instance.m_subsman = SubroutinesManager.GetInstance ();
s_instance.m_subsman.ReloadAll ();
}
private static InterprEnvironment s_instance = null;
public static InterprEnvironment Instance {
get {
if (s_instance == null)
s_instance = new InterprEnvironment ();
return s_instance;
}
}
public static void Reset () {
s_instance = new InterprEnvironment ();
}
public void SaveVars () {
m_console_vars.Save ();
}
public bool LoadSub (string name) {
return m_subsman.Load (name);
}
private Namespace m_current_namespace = null;
public Namespace CurrentNamespace {
get {return m_current_namespace;}
set {m_current_namespace = value;}
}
private IConsole m_current_console = null;
public IConsole CurrentConsole {
get {return m_current_console;}
set {m_current_console = value;}
}
public Operation GetFunction (string name) {
if (name == "abs")
return Operation.ABS;

...........................
if (name == "size")
return Operation.SIZE;
return new SubName (name);
}
public string [] LoadedSubs {
get {return m_subsman.SubroutineNames;}
}
private class SubroutinesManager {
private ArrayList m_subs = new ArrayList ();
private ArrayList m_names = new ArrayList ();
private SubroutinesManager () {
DirectoryInfo di =
new DirectoryInfo (Directory.GetCurrentDirectory () + @ "\ subroutines");
if (! di.Exists) {
di.Create ();
}
}
public bool Load (string name) {
FileInfo fi = new FileInfo (Directory.GetCurrentDirectory () + @ "\ subroutines \" + name);
if (! fi.Exists)
throw new OtherException ("Error in SubroutinesManager.Load ()");
return LoadFile (fi);
}
private bool LoadFile (FileInfo file) {
try {
StreamReader sr = file.OpenText ();
LinkedList ll = new LinkedList ();
try {
while (sr.Peek ()! = -1) {
ll.AddFirst (sr.ReadLine ());
}
} Finally {
sr.Close ();
}
string [] strs = new String [ll.Count];
int i = 0;
while (! ll.IsEmpty ()) {
strs [i] = (ll.RemoveLast () as String);
i + +;
}
Subroutine sub;
try {
sub = new Subroutine (strs, file.Name);
} Catch (LineSyntaxException ex) {
InterprEnvironment.Instance.CurrentConsole.PrintLn ("Синтаксична помилка в" + ex.Function + "[] at line" + ex.Line + "" + ex.Message);
return false;
} Catch (SyntaxErrorException ex) {
InterprEnvironment.Instance.CurrentConsole.PrintLn ("Синтаксична помилка в" + file.Name + "" + ex.Message);
return false;
}
Set (file.Name, sub);
} Catch {
throw new OtherException ("Error in Environment.Load ()");
}
return true;
}
public Subroutine this [string name] {
get {
int sres = m_names.IndexOf (name);
if (sres <0)
return null;
else
return m_subs [sres] as Subroutine;
}
}
private void Set (string name, Subroutine sub) {
int sres = m_names.IndexOf (name);
if (sres> = 0) {
m_names.RemoveAt (sres);
m_subs.RemoveAt (sres);
}
m_names.Add (name);
m_subs.Add (sub);
}
private static SubroutinesManager s_inst = null;
public static SubroutinesManager GetInstance () {
if (s_inst == null)
s_inst = new SubroutinesManager ();
return s_inst;
}
public string [] SubroutineNames {
get {
int count = m_names.Count;
string [] res = new string [count];
for (int i = 0; i <count; i + +) {
res [i] = (m_names [i] as String);
}
for (int i = 0; i <count - 1; i + +) {
int k = i;
for (int j = i + 1; j <count; j + +)
k = (string.Compare (res [j], res [k]) <0)? j: k;
if (i! = k) {
string temp = res [i];
res [i] = res [k];
res [k] = temp;
}
}
return res;
}
}
public void ReloadAll () {
m_subs = new ArrayList ();
m_names = new ArrayList ();
DirectoryInfo di =
new DirectoryInfo (Directory.GetCurrentDirectory () + @ "\ subroutines");
if (! di.Exists) {
di.Create ();
}
foreach (FileInfo file in di.GetFiles ()) {
if (Parser.IsID (file.Name)) {
LoadFile (file);
}
}
}
public void Unload (string name) {
int index = m_names.IndexOf (name);
if (index> = 0) {
m_names.RemoveAt (index);
m_subs.RemoveAt (index);
}
}
}
public Subroutine GetSub (string name) {
Subroutine res = m_subsman [name];
if (res == null)
throw new CalcException ("Функція" + name + "не існує");
return res;
}
public void UnloadSub (string name) {
m_subsman.Unload (name);
}
}
}

4. Клас Namespace.

using System;
using System.Collections;
using interpr.logic.vartypes;
namespace interpr.logic {
public class NamespaceSerializationException: Exception {
public NamespaceSerializationException (): base () {}
}
public class Namespace {
protected class Pair {
internal string m_str;
internal VarBase m_var;
}
protected ArrayList m_list = new ArrayList ();
protected int m_n = 0;
private Namespace m_divvious_namespace = null;
public Namespace PreviousNamespace {
get {return m_divvious_namespace;}
}
public Namespace (Namespace divvious) {
m_divvious_namespace = divvious;
}
protected Namespace () {}
public VarBase Get (string name) {
if (m_n == 0)
return null;
int i = 0;
Pair p;
do {
p = (m_list [i + +] as Pair);
if (p.m_str == name)
return p.m_var;
} While (i <m_n);
return null;
}
public void Assign (VarBase var, string name) {
Pair p;
if (m_n! = 0) {
int i = 0;
do {
p = (m_list [i + +] as Pair);
if (p.m_str == name) {
p.m_var = var;
return;
}
} While (i <m_n);
}
p = new Pair ();
p.m_var = var;
p.m_str = name;
m_list.Add (p);
m_n + +;
}
public void AssignToElement (SingleVar var, string name, int index) {
Pair p;
if (m_n! = 0) {
int i = 0;
do {
p = (m_list [i + +] as Pair);
if (p.m_str == name) {
if (! p.m_var.IsArray ())
throw new CalcException ("Змінна не є масивом");
(P.m_var as ArrayVar) [index] = var;
return;
}
} While (i <m_n);
}
p = new Pair ();
p.m_var = new ArrayVar ();
(P.m_var as ArrayVar) [index] = var;
p.m_str = name;
m_list.Add (p);
m_n + +;
}
public void Remove (String name) {
if (m_n == 0)
return;
int i = 0;
do {
Pair p = (m_list [i + +] as Pair);
if (p.m_str == name) {
m_list.RemoveAt (i - 1);
m_n -;
return;
}
} While (i <m_n);
}
public VarBase this [string name] {
set {Assign (value, name);}
get {return Get (name);}
}
}
}

5. Інтерфейс IСomputable.

namespace interpr.logic {
public interface IComputable {
logic.vartypes.VarBase Compute ();
}
}

6. Клас Call.

using interpr.logic.vartypes;
namespace interpr.logic {
public class Call: IComputable {
private Operation m_op;
private ArgList m_al = null;
public Call (Operation op) {
m_op = op;
}
public void SetArgList (ArgList al) {
m_al = al;
}
public int ReqCount {
get {return m_op.ReqCount;}
}
public VarBase Compute () {
return m_op.Perform (m_al);
}
}
}

7. Клас ArgList

using interpr.logic.vartypes;
namespace interpr.logic {
public class ArgList {
private bool m_read = false;
private LinkedList m_list = new LinkedList ();
private LinkedList.Iterator m_i = null;
public void Add (VarBase var) {
if (m_read)
throw new OtherException ("Write to the argument list after reading begin");
m_list.Add (var);
}
public VarBase Get () {
if (! m_read)
throw new OtherException ("Try to read from argument list before reset");
if (! m_i.HasPrevious)
throw new OtherException ("Try to read from empty argument list");
m_read = true;
IComputable obj = (m_i.Previous () as IComputable);
if (obj == null)
throw new CalcException ("змінна не ініціалізірованна.");
return obj.Compute ();
}
public void Reset () {
m_read = true;
m_i = m_list.GetIterator (m_list.Count);
}
public int Count {
get {return m_list.Count;}
}
}
}

8. Клас Exdivssion.

using System;
using interpr.logic.vartypes;
namespace interpr.logic {
public class Exdivssion {
public Exdivssion (String str) {
Parser p = new Parser (str);
Analyse (p);
}
public Exdivssion (Parser p) {
Analyse (p);
}
private class Element {
internal IComputable m_o;
internal Element m_next;
internal Element (IComputable obj, Element next) {
m_o = obj;
m_next = next;
}
}
private Element m_top = null;
private Element m_bottom = null;
private int m_c = 0;
private void AddFront (IComputable obj) {
m_c + +;
if (m_c == 1)
m_top = m_bottom = new Element (obj, null);
else {
Element t = new Element (obj, null);
m_bottom.m_next = t;
m_bottom = t;
}
}
private void Analyse (Parser p) {
try {
LinkedList l = new LinkedList ();
while (p.MoveNext ())
l.Add (p.Current);
OPZ (l);
}
catch (CalcException ex) {
throw ex;
}
catch {
throw new SyntaxErrorException ("Синтаксична помилка у виразі");
}
}
private void OPZ (LinkedList tokens) {
/ * ** Бінарна операція виштовхує з стека в результат
* Всі операції з більшим чи рівним пріоритетом, потім
* Записується в стек сама
* ** Унарний операція записується в стек
* ** Відкриваюча дужка записується в стек
* ** Закриваюча дужка виштовхує в результат всі операції
* З стека до відкриваючої дужки, потім
* Дужки знищуються і виштовхуються унарні операції
* ** Змінна або константа відразу пишуться в результат, потім
* Виштовхуються із стека унарні операції
* ** При виклику функції
* Спочатку окремо розбираються всі операнди, потім в результат
* Дописується сама функція, як операція
* ** Звернення до елементу масиву обробляється аналогічно
* Наприкінці всі залишилися в стеку операції виштовхуються в результат
* /
InterprEnvironment env = InterprEnvironment.Instance;
if (tokens.IsEmpty ()) return;
LinkedList.Iterator itr = tokens.GetIterator ();
LinkedList stk = new LinkedList ();
while (itr.HasMore) {
string si = (itr.Step () as System.String);
if (si == "(") {
stk.Add (O_BR);
}
else if (si == ")") {
while (true) {
object o = stk.RemoveLast ();
if (o == O_BR) break;
AddFront (new Call (o as Operation));
}
while ((! stk.IsEmpty ()) & & IsUnary (stk.Last)) {
AddFront (new Call (stk.RemoveLast () as Operation));
}
}
else if (Parser.IsID (si)) {
bool bfun = false;
bool barray = false;
if (itr.HasMore) {
string s = (itr.Step () as System.String);
if (s == "[")
bfun = true;
else if (s == "{")
barray = true;
else
itr.Previous ();
}
if (bfun) {
LinkedList l = null;
while (true) {
l = new LinkedList ();
int level = 0;
while (true) {
if (! itr.HasMore)
throw new SyntaxErrorException ("Синтаксична помилка у виразі");
string sj ​​= (itr.Step () as System.String);
if (sj == "[") {
level + +;
l.Add (sj);
}
else if (sj == "]") {
if (level == 0)
goto label1;
else {
level -;
l.Add (sj);
}
}
else if (sj == ",") {
if (level> 0)
l.Add (sj);
else
break;
}
else
l.Add (sj);
}
OPZ (l);
}
label1:
if (l! = null)
OPZ (l);
Operation sub = env.GetFunction (si);
AddFront (new Call (sub));
while ((stk.Count> 0) & & IsUnary (stk.Last)) {
AddFront (new Call ((Operation) stk.RemoveLast ()));
}
}
else if (barray) {
LinkedList l = new LinkedList ();
int level = 0;
while (true) {
if (! itr.HasMore)
throw new SyntaxErrorException ("Синтаксична помилка у виразі");
String sj ​​= (String) itr.Step ();
if (sj == "{") {
level + +;
l.Add (sj);
}
else if (sj == "}") {
if (level == 0)
break;
else {
level -;
l.Add (sj);
}
}
else
l.Add (sj);
}
OPZ (l);
VarName v = new VarName (si);
AddFront (v);
AddFront (new Call (Operation.INDEX));
while ((stk.Count> 0) & & IsUnary (stk.Last)) {
AddFront (new Call (stk.RemoveLast () as Operation));
}
}
else {
VarName v = new VarName (si);
AddFront (v);
while ((stk.Count> 0) & & IsUnary (stk.Last)) {
AddFront (new Call (stk.RemoveLast () as Operation));
}
}
}
else {
Operation op = StrToOperation (si);
if (op == null) {
SingleVar sv = SingleVar.FromString (si);
if (si == null)
throw new SyntaxErrorException ("Синтаксична помилка у виразі");
AddFront (sv);
while ((stk.Count> 0) & & IsUnary (stk.Last)) {
AddFront (new Call (stk.RemoveLast () as Operation));
}
}
else {
/ / Operation
if (op == Operation.ADD) {
itr.Previous ();
if (! itr.HasPrevious) {
stk.Add (Operation.UPLUS);
itr.Step ();
continue;
}
String strpr = (String) itr.Previous ();
itr.Step ();
itr.Step ();
if ((StrToOperation (strpr)! = null) | | (strpr == "(") | |
(Strpr == "[") | | (strpr == "{")) {
stk.Add (Operation.UPLUS);
continue;
}
}
else if (op == Operation.SUB) {
itr.Previous ();
if (! itr.HasPrevious) {
stk.Add (Operation.UMINUS);
itr.Step ();
continue;
}
String strpr = (String) itr.Previous ();
itr.Step ();
itr.Step ();
if ((StrToOperation (strpr)! = null) | | (strpr == "(") | |
(Strpr == "[") | | (strpr == "{")) {
stk.Add (Operation.UMINUS);
continue;
}
}
else if (op == Operation.NOT) {
stk.Add (op);
continue;
}
if (stk.IsEmpty () | | (stk.Last == O_BR)) {
stk.Add (op);
}
else {
int pr = Priority (op);
while (true) {
if (stk.IsEmpty ())
break;
Object stktop = stk.Last;
if (stktop is Operation) {
int pr1 = Priority (stktop as Operation);
if ((pr <= pr1) & & (pr1 <6)) {
AddFront (new Call (stktop as Operation));
stk.RemoveLast ();
}
else
break;
}
else
break;
}
stk.Add (op);
}
}
}
}
while (! stk.IsEmpty ()) {
Object o = stk.RemoveLast ();
AddFront (new Call (o as Operation));
}
}
public VarBase Calculate () {
if (m_c == 0)
throw new CalcException ("Помилка: порожнє вираз.");
Element top1 = null;
Element cur = m_top;
try {
for (; cur! = null; cur = cur.m_next) {
if (cur.m_o is Call) {
int rc = (cur.m_o as Call). ReqCount;
ArgList al = new ArgList ();
for (int i = 0; i <rc; i + +) {
if (top1 == null)
throw new CalcException ("Помилка при обчисленні виразу");
al.Add (top1.m_o.Compute ());
top1 = top1.m_next;
}
(Cur.m_o as Call). SetArgList (al);
top1 = new Element ((cur.m_o as Call). Compute (), top1);
}
else {
top1 = new Element (cur.m_o, top1);
}
}
if ((top1 == null) | | (top1.m_next! = null))
throw new CalcException ("Помилка при обчисленні виразу");
return top1.m_o.Compute ();
}
catch (CalcException ex) {
throw ex;
}
catch {
throw new CalcException ("Помилка при обчисленні виразу");
}
}
private static Operation StrToOperation (String str) {
/ / Не повертає унарні плюс і мінус
if (str == "+")
return Operation.ADD;
else if (str == "-")
return Operation.SUB;
else if (str == "*")
return Operation.MUL;
else if (str == "/")
return Operation.DIV;
else if (str == "~")
return Operation.NOT;
else if (str == "|")
return Operation.OR;
else if (str == "&")
return Operation.AND;
else if (str == "^")
return Operation.XOR;
else if (str == "~=")
return Operation.BE;
else if (str == "=")
return Operation.EQ;
else if (str == "<>")
return Operation.NE;
else if (str == ">=")
return Operation.GE;
else if (str == "<=")
return Operation.LE;
else if (str == ">")
return Operation.GT;
else if (str == "<")
return Operation.LT;
else
return null;
}
private static int Priority (Operation op) {
if ((op == Operation.OR) | | (op == Operation.XOR) | |
(Op == Operation.BE))
return 1;
else if (op == Operation.AND)
return 2;
else if ((op == Operation.EQ) | | (op == Operation.NE) | |
(Op == Operation.LE) | | (op == Operation.LT) | |
(Op == Operation.GE) | | (op == Operation.GT))
return 3;
else if ((op == Operation.ADD) | | (op == Operation.SUB))
return 4;
else if ((op == Operation.MUL) | | (op == Operation.DIV))
return 5;
else
return 6;
}
private static bool IsBinary (Operation op) {
return Priority (op) <6;
}
private static bool IsUnary (object obj) {
return ((obj == Operation.NOT) | | (obj == Operation.UPLUS) | |
(Obj == Operation.UMINUS));
}
private class BR_c {}
private static object O_BR = new BR_c ();
}
}

9. Клас Operation (скорочено).

using System;
using interpr.logic.vartypes;
namespace interpr.logic {
public abstract class Operation {
public abstract int ReqCount {get;}
public abstract VarBase Perform (ArgList al);
public static readonly Operation ABS = new ABS_c ();
private class ABS_c: Operation {
public override int ReqCount {
get {return 1;}
}
public override VarBase Perform (ArgList al) {
if (al.Count! = ReqCount)
throw new OtherException ("Invalid argument list");
al.Reset ();
VarBase arg1 = al.Get ();
if (arg1 is IntVar)
return ((arg1 as IntVar). Val> 0)? (Arg1.Clone () as IntVar): (new IntVar (- ((IntVar) arg1). Val));
else if (arg1 is RealVar)
return ((arg1 as RealVar). Val> 0)? (Arg1.Clone () as RealVar): (new RealVar (- ((RealVar) arg1). Val));
else
throw new CalcException ("Неправильні аргументи функції");
}
}
public static readonly Operation ADD = new ADD_c ();
private class ADD_c: Operation {
public override int ReqCount {
get {return 2;}
}
public override VarBase Perform (ArgList al) {
if (al.Count! = ReqCount)
throw new OtherException ("Invalid argument list");
al.Reset ();
VarBase arg1 = al.Get ();
VarBase arg2 = al.Get ();
if (! (arg1.IsSingle () & & arg2.IsSingle ()))
throw new CalcException ("Невірні типи операндів");
return (arg1 as SingleVar). add (arg2 as SingleVar);
}
}
public static readonly Operation AND = new AND_c ();
private class AND_c: Operation {
public override int ReqCount {
get {return 2;}
}
public override VarBase Perform (ArgList al) {
if (al.Count! = ReqCount)
throw new OtherException ("Invalid argument list");
al.Reset ();
VarBase arg1 = al.Get ();
VarBase arg2 = al.Get ();
if (! (arg1.IsSingle () & & arg2.IsSingle ()))
throw new CalcException ("Невірні типи операндів");
return (arg1 as SingleVar). and (arg2 as SingleVar);
}
}
.................................................. .....................................
}
}

10. Клас Parser.

using System;
using System.Collections;
namespace interpr.logic {
public class Parser: IEnumerable, IEnumerator {
private char [] m_a;
private int m_len;
private int m_cur = 0;
private int m_new_cur = -1;
private bool m_at_begin;
private static readonly string [] s_keywords =
new string [] {
"If",
"Else",
"Elseif",
"Endif",
"While",
"Loop",
"Return",
"Call",
"Print",
"Println",
"Readln",
"Clear",
"For",
"Next",
"Error"
};
private static readonly int s_keywords_length = s_keywords.Length;
private static bool IsLD (char c) {
return ((c> = 'a') & & (c <= 'z')) | | ((c> = 'A') & & (c <= 'Z')) | | (c == "0" )
| | ((C> = '1 ') & & (c <= '9')) | | (c =='_');
}
private static bool IsSp (char c) {
return (c == '') | | (c == '\ t');
}
public static bool IsID (string str) {
int l = str.Length;
if (l == 0)
return false;
if (char.IsDigit (str [0]) | | (! IsLD (str [0])))
return false;
int i;
for (i = 1; i <str.Length; i + +)
if (! IsLD (str [i]))
return false;
for (i = 0; i <s_keywords_length; i + +)
if (str == s_keywords [i])
return false;
return true;
}
public void Reset () {
m_cur = 0;
m_new_cur = -1;
m_at_begin = true;
}
public string GetString () {
return new String (m_a, 0, m_len);
}
public bool HasMore () {
return m_cur <m_len;
}
public Parser (string str) {
char [] a = str.ToCharArray ();
int n = a.Length;
int i = 0;
int j = 0;
m_a = new char [n];
while (i <n) {
if (a [i] == '#') {
break;
} Else if (a [i] == '\ "') {
m_a [j] = '\ "';
i + +;
j + +;
while ((i <n) & & (a [i]! = '\ "')) {
m_a [j] = a [i];
i + +;
j + +;
}
if (i == n)
throw new SyntaxErrorException ("Не закрита рядкова константа");
else {
m_a [j] = '\ "';
i + +;
j + +;
}
} Else if (IsSp (a [i])) {
bool flag = false;
if ((i> 0) & & (IsLD (a [i - 1]))) {
m_a [j] = '';
j + +;
flag = true;
}
while ((i <n) & & IsSp (a [i]))
i + +;
if (((i == n) | | (! IsLD (a [i]))) & & flag)
j -;
} Else {
m_a [j] = a [i];
i + +;
j + +;
}
}
m_len = j;
Reset ();
}
private string GetCurrent () {
int cur = m_cur;
int beg = m_cur;
int end = m_len;
string res = null;
bool flag = true;
if ((m_a [cur] == '.') & & ((cur <end - 1) & & (! char.IsDigit (m_a [cur + 1]))) | | (cur == end - 1)) {
flag = true;
} Else if (char.IsDigit (m_a [cur]) | | (m_a [cur] =='.')) {
flag = false;
while ((cur <end) & & char.IsDigit (m_a [cur]))
cur + +;
if (cur == end) {
res = new String (m_a, beg, cur - beg);
} Else if ((m_a [cur] == 'e') | | (m_a [cur] == 'E')) {
cur + +;
if (cur == end) {
cur -;
res = new String (m_a, beg, cur - beg);
} Else if ((m_a [cur] == '+') | | (m_a [cur] =='-')) {
cur + +;
if ((cur == end) | | (! char.IsDigit (m_a [cur]))) {
cur -= 2;
res = new String (m_a, beg, cur - beg);
}
while ((cur <end) & & char.IsDigit (m_a [cur]))
cur + +;
res = new String (m_a, beg, cur - beg);
} Else if (char.IsDigit (m_a [cur])) {
while ((cur <end) & & char.IsDigit (m_a [cur]))
cur + +;
res = new String (m_a, beg, cur - beg);
} Else {
cur -;
res = new String (m_a, beg, cur - beg);
}
} Else if (m_a [cur] == '.') {
cur + +;
if ((cur == end) | | (! char.IsDigit (m_a [cur]))) {
cur -;
res = new String (m_a, beg, cur - beg);
} Else {
while ((cur <end) & & char.IsDigit (m_a [cur]))
cur + +;
if (cur == end)
res = new String (m_a, beg, cur - beg);
else if ((m_a [cur] == 'e') | | (m_a [cur] == 'E')) {
cur + +;
if (cur == end) {
cur -;
res = new String (m_a, beg, cur - beg);
} Else if ((m_a [cur] == '+') | | (m_a [cur] =='-')) {
cur + +;
if ((cur == end) | | (! char.IsDigit (m_a [cur]))) {
cur -= 2;
res = new String (m_a, beg, cur - beg);
}
while ((cur <end) & & char.IsDigit (m_a [cur]))
cur + +;
res = new String (m_a, beg, cur - beg);
} Else if (char.IsDigit (m_a [cur])) {
while ((cur <end) & & char.IsDigit (m_a [cur]))
cur + +;
res = new String (m_a, beg, cur - beg);
} Else {
cur -;
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Курсова
263кб. | скачати


Схожі роботи:
Інтерпретатор muLisp
Інтерпретатор мови Пролог
Інтерпретатор команд MS DOS
Прямий та інтерактивний маркетинг
Інтерактивний урок з російської мови
Комп`ютери та маркетинг інтерактивний маркетинг
© Усі права захищені
написати до нас