Интерактивный интерпретатор (47083)

Посмотреть архив целиком

МИНИСТЕРСТВО ОБРАЗОВАНИЯ РЕСПУБЛИКИ БЕЛАРУСЬ


УЧРЕЖДЕНИЕ ОБРАЗОВАНИЯ «БЕЛОРУССКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ИНФОРМАТИКИ И РАДИОЭЛЕКТРОНИКИ»


Кафедра информатики

КУРСОВОЙ ПРОЕКТ


по предмету «Объектно-ориентированное программирование»

на тему «Интерпретатор языка программирования».

Выполнил ст. гр.********

************.


Проверил

****************.


МИНСК 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. Класс Expression. 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 - число

конкатенация строк (число преобразуется в строку)

строка

числа

сумма

число

a-b

числа

разность

число

4

a=b

простые (оба – числа либо строки одновременно)

равно

целый (-1 или 1)

a<>b

не равно

a>b

больше

a

меньше

a<=b

меньше либо равно

a>=b

больше либо равно

5

a&b

простые

И”

6

a^b

простые

исключающее “ИЛИ”

a~=b

логическая эквивалентность

a|b

ИЛИ”


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

Вызов функции имеет следующий синтаксис: <имя функции>[<фактический параметр 1>,<фактический параметр 2>,...,<фактический параметр 3>]. Даже если список параметров пуст, квадратные скобки все равно пишутся. Фактическими параметрами функции должны быть выражения.

Например, 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}

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*b-c*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*c-d)/e в ОПЗ имеет вид abc*d-e/+. Применим к нему описанный выше алгоритм вычисления.

1. Заносим в стек a.

2. Заносим в стек b.

3. Заносим в стек c.

Состояние стека на этот момент: a, b, c – вершина.

4. Извлекаем из стека операнды операции умножения – b и c и заносим в стек результат.

Стек: a, b*c.

5. Заносим в стек d.

Стек: a, b*c, d.

6. Извлекаем из стека операнды, производим вычитание, заносим в стек результат.

Стек: a, b*c-d.

7. Заносим в стек e.

Стек: a, b*c-d, e.

8. Извлекаем из стека операнды, производим деление, заносим в стек результат.

Стек: a, (b*c-d)/e.

9. Извлекаем из стека операнды, производим сложение, заносим в стек результат.

Итого получаем в стеке a+(b*c-d)/e, что и требовалось.

Для представления выражений в интерпретаторе используется класс Expression. Он содержит обратную польскую запись выражения в виде связанного списка (однонаправленного). Звено этого списка, равно как и стека, используемого при вычислении выражения, представляется объектом вложенного класса Expression.Element, содержащим ссылки на следующее звено и на объект, реализующий интерфейс IComputable, который содержит один метод logic.vartypes.VarBase Compute() – получить значение. Вычисление значения выражения по рассмотренном выше алгоритму производится в методе VarBase Expression.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, в выражениях (экземплярах класса Expression), хранятся только константные значения. Переменные же представляются здесь объектами класса 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, со ссылкой на объект Expression, представляющий выражение, результат вычисления которого должен быть выведен на консоль. В методе Execute() результат вычисления выражения сначала приводится к строке (вызывается метод ToString), затем выводится на консоль вызовом методов объекта InterprNamespace.CurrentConsole.

Команда call реализуется с помощью класса CallCommand, в методе execute() которого просто вычисляется выражение из поля m_expr, результат же вычисления выражения никак не используется.

Конструкторы этих трех классов принимают один параметр типа Expression.

Класс EmptyCommand, представляющий пустую команду (пустая строка либо строка комментария), содержит лишь пустые конструктор без параметров и метод Execute().

Класс ClearCommand содержит поле типа string, в котором хранится имя удаляемой переменной. В методе execute() вызывается метод Remove объекта текущего пространства имен.

И, наконец, класс AssignCommand представляет команду присваивания. Он имеет два конструктора, принимающие два или три параметра соответственно, для операторов присваивания значения переменной или элементу массива. В первом из этих параметров содержится имя переменной или массива в левой части оператора присваивания, в остальных – присваиваемое выражение и, во втором случае, индексное выражение. Выражения передаются в их строковой записи, они «компилируются» в объекты класса Expression в конструкторе последнего. Работа с переменными осуществляется с помощью объекта текущего пространства имен, возвращаемого свойством 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.

Условия и границы циклов там, где они нужны, хранятся в виде объектов типа Expression. Логика выполнения операторов следующая:

  • При выполнении оператора 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 – для присваивания значения переменной или элементу массива. Ему в качестве одного из параметров передается часть строки справа от символов «:=», которая разбирается как выражение в конструкторе класса Expression. Если же данный оператор не является оператором присваивания, то из строки выделяется первая лексема, которая последовательно сравнивается с ключевыми словами, с которых начинаются различные операторы (команды). Если совпадений не найдено, то в методе 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) – является ли данная строка корректным идентификатором, не совпадающим с именем какой-либо из встроенных функций.

Преобразование выражений в описанное ранее внутреннее представление производится в конструкторе класса Expression, который имеет две перегруженные версии, принимающие параметры типа string и Parser соответственно. В обеих вызывается private-метод Analyse(), в котором лексемы из строки заносятся в список типа LinkedList (этот класс был рассмотрен выше), который затем передается в качестве параметра другому private-методу OPZ(). В последнем и сосредоточена основная часть алгоритма разбора выражения. Этот алгоритм относится к так называемым восходящим методам синтаксического разбора, в которых дерево разбора строится «снизу вверх». Синтаксический анализ здесь совмещен с семантической обработкой – построением обратной польской записи выражения. Преобразование выражения в ОПЗ производится следующим образом:

  • Вначале создается пустой стек операций (объект класса LinkedList).

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

  • Каждая бинарная операция имеет свой приоритет (можно получить в виде числа с помощью private-функции Expression.Priority()).

  • Бинарная операция выталкивает из стека в результат операции с большим или равным приоритетом (с вершины стека), затем сама записывается в стек. Для символов ‘+’ и ‘-’ производится проверка, являются они в каждом конкретном случае знаком бинарной операции или унарной – в случае унарной операции перед ее знаком находится открывающая скобка либо другая операция, или операция находится в начале строки.

  • Унарная операция сразу записывается в стек.

  • Открывающая круглая скобка сразу записывается в стек.

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

  • Если после идентификатора в выражении встречается открывающая квадратная скобка, то выделяются списки лексем, из которых состоят выражения-операнды функции (они расположены в квадратных скобках и разделены запятыми; учитывается возможная вложенность вызовов функций), для каждого из них последовательно вызывается рекурсивно метод Analyse1(), при этом в результат дописываются результаты разбора этих выражений, затем, в результат дописывается вызов функции (ее имя – стоящая перед открывающей квадратной скобкой лексема).

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

  • После обработки вызова функции или обращения к элементу массива в результат выталкиваются с вершины стека унарные операции, если они присутствуют.

  • В конце разбора в результат выталкивается все содержимое стека.

  • Константы записываются в результат как объекты классов, представляющих соответствующие типы данных, переменные – как объекты VarName, операции и вызовы функций – как объекты Call.

Рассмотрим пример. Пусть имеется строка (a*c+-b{а+с})/а. Применим описанный алгоритм.

  1. Вначале стек операндов и результат пусты.

  2. Первая лексема – открывающая круглая скобка. Записываем ее в стек.

Стек: (Результат: <пусто>.

  1. Вторая лексема – идентификатор «а». За ним нет открывающей квадратной или фигурной скобки, поэтому записываем его в результат.

  2. Стек: (Результат: а

  3. Следующая лексема – операция умножения. Записываем ее в стек. На вершине стека нет операций с большим или равным приоритетом, ничего выталкивать не нужно.

  4. Стек: (*Результат: а

  5. Вторая лексема – идентификатор «с». За ним нет открывающей квадратной или фигурной скобки, поэтому записываем его в результат.

Стек: (*Результат: ас

  1. Следующая лексема – знак «+». Перед ним находится идентификатор, поэтому он является знаком операции сложения. Он выталкивает из стека операцию умножения как имеющую более высокий приоритет, затем сам дописывается в стек.

  2. Стек: (+ Результат: ас*

  3. Следующая лексема – знак «минус». Перед ним нет ни закрывающей скобки ни идентификатора, поэтому он является знаком операции унарный минус (обозначим ее как «_»), записываем ее в стек.

  4. Стек: (+_Результат: ас*

  5. Следующая лексема – идентификатор b. За ним следует фигурная скобка, поэтому он рассматривается как имя массива. В фигурных скобках находится строка «а+с», которая, будучи преобразованной по рассматриваемому алгоритму, даст в результате «ас+». Допишем это в результат разбора исходного выражения. Затем допишем в результат имя массива («b») и операцию индексации (обозначим ее «{}»). И, наконец, вытолкнем находящуюся на вершине стека операцию унарный минус.

  6. Стек: (+Результат: ас*ас+b{}_

  7. Следующая (за закрывающей фигурной скобкой) лексема – закрывающая круглая скобка. Она вытолкнет из стека в результат находящуюся перед открывающей скобкой операцию сложения, затем открывающая скобка будет удалена из стека.

Стек; <пусто>Результат: ac*ac+b{}_+

  1. Следующая лексема – операция деления. Она дописывается в стек (перед этим стек пуст, ничего выталкивать не нужно).

Стек: /Результат: ac*ac+b{}_+

  1. Последняя лексема – идентификатор «а». После него нет никаких скобок, поэтому он сразу же добавляется к результату.

Стек: /Результат: ac*ac+b{}_+a

  1. В конце выталкиваем из стека оставшуюся в нем операцию умножения в результат. Итого получаем 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

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_previous_namespace = null;


public Namespace PreviousNamespace {

get { return m_previous_namespace; }

}


public Namespace(Namespace previous) {

m_previous_namespace = previous;

}


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. Класс Expression.


using System;

using interpr.logic.vartypes;


namespace interpr.logic {

public class Expression {

public Expression(String str) {

Parser p = new Parser(str);

Analyse(p);

}


public Expression(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--;

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") {

Expression expr = new Expression(p);

return new PrintCommand(expr);

} else if (firsttoken == "println") {

Expression expr = new Expression(p);

return new PrintLnCommand(expr);

} else if (firsttoken == "call") {

Expression expr = new Expression(p);

return new CallCommand(expr);

} else {

p.Reset();

Expression expr1 = new Expression(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();

}

Expression expr = new Expression(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);

Expression expr1 = new Expression(expr1str);

Expression expr2 = new Expression(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 Expression m_begin = null;

private Expression m_end = null;

private IntVar m_end_res = null;


public ForOperator(string counter, Expression beg, Expression 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();