/> /> /> />
МИНИСТЕРСТВО ОБРАЗОВАНИЯ РЕСПУБЛИКИ БЕЛАРУСЬ
УЧРЕЖДЕНИЕ ОБРАЗОВАНИЯ «БЕЛОРУССКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ИНФОРМАТИКИ И РАДИОЭЛЕКТРОНИКИ»
Кафедра информатики /> />
КУРСОВОЙ ПРОЕКТ
по предмету «Объектно-ориентированное программирование»
на тему «Интерпретатор языка программирования». /> />
Выполнил ст. гр.********
************.
Проверил
****************.
/> />
МИНСК 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) ab не равно a>b больше a=b больше либо равно 5 a&b простые “И” 6 a^b простые исключающее “ИЛИ” a~=b логическая эквивалентность a|b “ИЛИ”
Выраженияинтерпретируются в соответствии с приоритетом операций и имеющимися в нихкруглыми скобками. При этом все унарные операции выполняются справа налево,бинарные операции одинакового приоритета – слева направо. Если в выражении хотябы один из операндов операции не имеет требуемый тип, или операция не можетбыть произведена корректно по другой причине, например, в случае деления наноль, то возникает ошибка.
Вызов функции имеетследующий синтаксис: [,,...,]. Дажеесли список параметров пуст, квадратные скобки все равно пишутся. Фактическимипараметрами функции должны быть выражения.
Например, 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
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
s := s+i
i := i+1
loop
Здесь переменная s получает значение суммы чисел от 1до n.
Оператор цикла for имеет вид:
for :=:
[операторы]
next
В начале выполнения циклавычисляются выражение1 и выражение2 (их значения должны быть целыми, иначевозникает ошибка), затем переменной-счетчику присваивается значение выражение1и, если оно меньше или равно значению выражение2, выполнение переходит внутрьцикла, иначе – за строку с ключевым словом next. После каждой итерации цикла значение счетчикаувеличивается на единицу и сравнивается со значением выражение2 (оновычисляется только один раз в начале), если оно оказывается меньшим или равнымзначению выражение2, то выполняется следующая итерация цикла, иначе – циклзавершается. Значение счетчика в цикле, в принципе, можно менять, не если оноокажется не целым на момент окончания очередной итерации, возникает ошибка.Оператор цикла for может быть использован только вфункции. Пример:
for i :=0: size[a]
a{i} := a{i}*2
next
Оператор возврата return незамедлительно прерывает выполнениефункции (может быть использован только в функции). Например,
if a
result := 1
return
endif
Если при выполнениифункции не встретился оператор return,выход из функции происходит как только управление переходит ниже последнейстроки функции.
Оператор error прерывает выполнение программы –искусственно генерируется ошибка времени выполнения. Он может быть использовантолько в функции.
Пример:
a:=toint[str]
if a
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. Сортировка массива.
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 ik
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 «Invalidarguments»
error
endif
if (n
println «Invalidarguments»
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 «Invalidargument»
error
elseif n
println «Invalidargument»
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
result:=-1
return
endif
for i:=0:n-1
ch:=substr[str,i,1]
if~(((ch>=«0»)&(ch=«A»)&(ch=«a»)&(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 «Invalidarguments»
error
endif
if(a
println «Not atriangle»
error
endif
cos_alpha:=(a*a+b*b-c*c)/(2*a*b)
if(cos_alpha>=1)|(cos_alpha
println «Not atriangle»
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. Первая лексема – открывающая круглаяскобка. Записываем ее в стек.
Стек: (Результат:.
3. Вторая лексема – идентификатор «а».За ним нет открывающей квадратной или фигурной скобки, поэтому записываем его врезультат.
4. Стек: (Результат: а
5. Следующая лексема – операция умножения.Записываем ее в стек. На вершине стека нет операций с большим или равнымприоритетом, ничего выталкивать не нужно.
6. Стек: (*Результат: а
7. Вторая лексема – идентификатор «с».За ним нет открывающей квадратной или фигурной скобки, поэтому записываем его врезультат.
Стек: (*Результат: ас
8. Следующая лексема – знак «+». Передним находится идентификатор, поэтому он является знаком операции сложения. Онвыталкивает из стека операцию умножения как имеющую более высокий приоритет,затем сам дописывается в стек.
9. Стек: (+ Результат: ас*
10. Следующая лексема– знак «минус». Перед ним нет ни закрывающей скобки ни идентификатора, поэтомуон является знаком операции унарный минус (обозначим ее как «_»),записываем ее в стек.
11. Стек: (+_Результат:ас*
12. Следующая лексема– идентификатор b. За ним следуетфигурная скобка, поэтому он рассматривается как имя массива. В фигурных скобкахнаходится строка «а+с», которая, будучи преобразованной по рассматриваемомуалгоритму, даст в результате «ас+». Допишем это в результат разбора исходноговыражения. Затем допишем в результат имя массива («b») и операцию индексации (обозначим ее «{}»). И, наконец, вытолкнемнаходящуюся на вершине стека операцию унарный минус.
13. Стек: (+Результат:ас*ас+b{}_
14. Следующая (зазакрывающей фигурной скобкой) лексема – закрывающая круглая скобка. Онавытолкнет из стека в результат находящуюся перед открывающей скобкой операциюсложения, затем открывающая скобка будет удалена из стека.
Стек;Результат: ac*ac+b{}_+
15. Следующая лексема– операция деления. Она дописывается в стек (перед этим стек пуст, ничеговыталкивать не нужно).
Стек: /Результат: ac*ac+b{}_+
16. Последняя лексема– идентификатор «а». После него нет никаких скобок, поэтому он сразу жедобавляется к результату.
Стек: /Результат: ac*ac+b{}_+a
17. В концевыталкиваем из стека оставшуюся в нем операцию умножения в результат. Итогополучаем ac*ac+_b{}+a/, что является обратной польскойзаписью исходного выражения.
При загрузке функцииобработка ее текста осуществляется в конструкторе класса Subroutine, который принимает два параметра –имя функции и текст функции (в виде массива строк). При этом отдельнорассматривается первая строка – заголовок функции. Для ее анализа используется private-метод Subroutine.AnalyseHeader(), в котором проверяетсясоответствие этой строки требуемому формату и извлекается список формальныхпараметров. Также проверяется соответствие имени функции в заголовке требуемому(первому параметру конструктора). При этом используется объект класса Parser. Затем по очереди подвергаютсяразбору с помощью метода LineCompiler.CompileOperator() остальные строки, результат«компиляции» каждой из которых добавляется в список операторов функции. Приэтом используется стек вложенности операторов (применяется объект класса System.Collections.Stack). После обработки каждой строки проверяется тип полученногооператора с помощью метода IOperator.GetType(). Если оператор открывает блок кода(if, elseif, else, while, for), то его номер заносится в стек. Если операторзакрывает блок кода, то из стека извлекается номер парного оператора иприсваиваются необходимые значения свойствам NextPos, LoopPosи т. д. соответствующих объектов. Операторы elseif и elseрассматриваются одновременно и как закрывающие расположенный выше блок кода, икак открывающие следующий. Нужно отметить, что в первый элемент списка операторовфункции (с нулевым индексом) в объекте Subroutine помещается пустой оператор (объект EmptyCommand), благодаря чему каждой строкетекста функции соответствует элемент этого списка с индексом, равным номеруэтой строки. Основная часть кода конструктора класса Subroutine находится в блоке try, при возникновении исключения SyntaxErrorException в котором генерируется исключениекласса LineSyntaxException, объект которого содержит информациюо месте ошибки (имя функции и номер строки).
/>Графический интерфейспользователя.
Главной форме приложения,которая изображена на рис. 7, соответствует класс Form1. Основную часть формы занимает компонент ConsoleBox, созданный на основе класса UserControl. Он включает в себя один экземпляркомпонента RichTextBox, «растянутый» с помощью свойства Dock на всю доступную площадь. Компонент ConsoleBox представляет собой окно консоли, вкоторой пользователь вводит команды, и на которую выводятся результаты работыкоманд. Класс ConsoleBox является единственным классом вокончательной версии проекта, реализующим рассмотренный выше интерфейс IConsole. Важнейшие члены класса ConsoleBox:
· методы Print(string) и PrintLn(string) – реализуют методы интерфейса IConsole, производят вывод текста в окноконсоли.
· метод Prompt() – выводит приглашение команднойстроки (“>>>”) и переводит консоль в режим ожидания команды.
· событие GetCommand (object sender, ConsoleBoxGetCommandEventArgs e) – возникает, когда в режиме ожидания команды была нажатаклавиша Enter. При этом в параметре e, имеющем тип класса ConsoleBoxGetCommandEventArgs, который унаследован от System.EventArgs, в свойстве Command содержится введенная пользователем команда в видестроки.
Методы Print, PrintLn и Prompt рассчитанына безопасное использование из другого потока. В них используется вызов private-методов через объект класса System.Windows.Forms.MethodInvoker. Возможны два состояния компонентаконсоли – режим ожидания ввода команды и режим работы команды. Ввод текста вполе RichTextBox допускается только в режиме ожиданияввода команды и только после последнего приглашения командной строки, что обеспечиваетсяс помощью свойства RichTextBox.SelectionProtected. Вызов метода Prompt() переводит консоль в режим ожиданиякоманды. При нажатии Enter в режимеожидания команды, помимо генерации события GetCommand, происходит переход из режимаожидания в режим работы команды.
/>
Рис. 7.
Главная форма.
При нажатии кнопки«Функции» на главной форме выводится диалоговое окно, которому соответствуеткласс FunctionsForm (см. рис. 8). В этом окне в верхнемполе отображается список успешно загруженных функций, в нижнем – функций,загрузка которых прошла неудачно по причине наличия синтаксических ошибок.Кнопки позволяют редактировать, удалить (в этом случае требуется подтверждение)выбранную функцию, создать новую функцию (в этом случае будет запрошено имяфункции, и, если оно не является корректным идентификатором, функция создана небудет). Для запроса имени при создании функции используется форма,описывающаяся классом InputForm (см.рис. 9). Если функция создана успешно, она открывается для редактирования. Придвойном щелчке по имени функции в любом из списков в окне «Функции» также онаоткрывается для редактирования. Окно «Функции» является модальным диалогом идолжно быть закрыто для продолжения работы с интерпретатором. Оно закрываетсяпри открытии функции для редактирования. При этом вместо него на экранепоявляется окно редактора кода.
/>
Рис. 8.
Окно «Функции»
/>
Рис. 9.
Окно ввода именисоздаваемой функции.
Окну редактора кодасоответствует класс EditorForm (см.рис. 10). Кнопка «Сохранить» в нем сохраняет функцию в файле, расположенном вподкаталоге subroutines рабочего каталога интерпретатора, сименем, совпадающим с именем функции (без расширения). Кнопка «Выход» — закрывает окно редактора (с запросом на сохранение). В метке справа от кнопокотображается номер строки текущего положения курсора (начала выделения) втексте. В ее текст номер текущей строки заносится приблизительно 10 раз всекунду, что обеспечивается с помощью таймера (компонент System.Windows.Forms.Timer).Окно редактора кода не является модальным – в любоймомент работы с интерпретатором может быть открыто сколько угодно таких окондля разных функций. Заблокированы открытие функции второй раз (в двух окнаходновременно) и выход из интерпретатора до закрытия всех окон редактора кода.Основную часть окна редактора кода составляет компонент SourceBox, который также как и ConsoleBox, унаследован от классаUserControl. Он содержит элемент управления RichTextBox, в котором, собственно, иосуществляется редактирование текста функции, и элемент TextBox, расположенный за RichTextBox на заднем плане и невидимый дляпользователя. На него переключается фокус на время выполнения синтаксическогоцветовыделения, так как для изменения цвета фрагмента текста в RichTextBox необходимо этот фрагмент выделить,что приводило бы к заметному мерцанию текста, если бы фокус ввода оставался уполя RichTextBox. Такой подход к решению проблемыпозволяет реализовать синтаксическое цветовыделение с использованием свойствкласса RichTextBox небольшим объемом кода (иначе быпришлось производить «ручную» перерисовку с непосредственным использованием GDI+). Но к сожалению, заметно снижаетсябыстродействие, в связи с этим были введены следующие ограничения:синтаксическое цветовыделение производится только при изменении номера строки,в которой находится курсор, например, при нажатии Enter, а также при щелчке левой кнопкой мыши в окнередактора (в RichTextBox). При этом обрабатываются толькостроки текста, отображаемые в данный момент времени в окне. Конечно, этонесколько неудобно для пользователя, подобное можно наблюдать, например, втакой среде программирования, как MS Visual Basic 6. Для выполнения синтаксического цветовыделенияиспользуется вложенный private-класс HighlightParser, который имеет методы для разборастроки на отдельные лексемы, для определения положения в строке и типа этихлексем. Применить класс interpr.logic.Parser здесь нельзя, так как он работает с преобразованнойстрокой (удалены лишние пробелы и комментарии). Класс SourceBox также имеет методы для чтения текстафункции из файла и сохранения текста в файле.
/>
Рис.10.
Окноредактора кода.
При нажатии на кнопку«Переменные» в главном окне интерпретатора отображается диалоговое окно сосписком переменных среды консоли (см. рис. 11). Переменные отображаются вместес их значениями (приведенными к строковому типу). Данное окно позволяет удалитьвыбранную или все переменные из памяти. Этому окну соответствует класс VariablesForm. При нажатии кнопки «Перезапуск»производится перезапуск интерпретатора (возможно, с прерыванием зациклившейсяили долго работающей пользовательской функции). При перезапуске невосстанавливаются измененные значения переменных среды консоли, поэтомупредусмотрена возможность сохранения значений переменных. Сохранение переменныхпроисходит автоматически при выходе из интерпретатора и вручную при нажатиикнопки «Сохранить переменные». Переменные сохраняются в двоичном файл variables, который автоматически создается врабочем каталоге интерпретатора, и считываются из него при запуске илиперезапуске интерпретатора. Сохранять переменные вручную имеет смысл передзапуском пользовательской функции, которая может зациклиться или слишком долгоработать, чтобы можно было прервать ее работу, не опасаясь потерять результатыпредыдущих вычислений. Работа с переменными осуществляется с помощью методовкласса Facade, обращающихся к соответствующимметодам классов из пространства имен interpr.logic.
Классы, относящиеся кпользовательскому интерфейсу интерпретатора, показаны на диаграмме на рис. 12.
/>
Рис. 11.
Окно «Переменные».
/>
Рис. 12.
Классы, связанные сграфическим интерфейсом пользователя.
/>Взаимодействие подсистеминтерпретатора. Класс Facade.
Как уже было сказановыше, класс Facade является посредником между двумяосновными подсистемами – графическим интерфейсом пользователя и логикой работыинтерпретатора. Здесь использован паттерн Facade. Все обращения извне к классам пространства имен interpr.logic производятся через вызов методов класса Facade. Сама же подсистема логики работыинтерпретатора не хранит ссылок, как это требует данный паттерн, ни на класс Facade, ни на другие классы, не входящие внее. Таким образом, класс Facadeявляется как бы мостом между пространством имен interpr.logicи классами, реализующими пользовательский интерфейс.
При запускеинтерпретатора в обработчике события Load класса Form1 происходит начальная инициализацияприложения. Вначале вызывается статический метод Facade.Create(),которому передается ссылка на элемент управления ConsoleBox, расположенный на главной формеинтерпретатора. Тип этого параметра – интерфейс IConsole. Переданная ссылка но объект консолиприсваивается свойству InterprEnvironment.CurrentConsole. Вметоде Facade.Create() создается единственный объект класса Facade, к которому в дальнейшем доступосуществляется через статическое свойство только для чтения Facade.Instance. Здесь используется паттерн Singleton.
При первом обращении ксвойству InterprEnvironment.Instance вызывается конструктор класса InterprEnvironment, В нем создается объект ConsoleNamespace для пространства имен консоли. Затемпроизводится восстановление переменных, сохраненных в файле variables в рабочем каталоге интерпретатора.Если этот файл отсутствует, то он создается (пустой) и восстановление непроизводится. Данный файл является двоичным. В его начале записывается общеечисло переменных, затем для каждой из них сохраняется информация о типе (одинсимвол), имя (строка) и значение. Для массива после имени записывается общеечисло элементов, затем каждый из элементов в виде пары «тип-значение».Восстановление переменных производится в методе ConsoleNamespace.Restore(). Если восстановление не прошло успешно по причиненеправильного формата файла variables, то в методе Restore()генерируется исключение NamespaceSerialisationException. Оно перехватывается в конструкторе класса InterprEnvironment, в результате чего изменяетсязначение соответствующего поля, после этого свойство InterprEnvironment.NotRestored, как и обращающееся к нему свойство Facade.NotRestored, возвращает истину. В случае, еслитакая ошибка произошла, в обработчике Form1.Form1_Load выдается соответствующее сообщение пользователю.
На следующем шагеинициализации устанавливается обработчик для события Facade.Done(завершение выполнения команды). Затем загружаются пользовательские функции спомощью метода Facade.LoadSubs(), вызывающего метод InterprEnvironment.LoadSubs(). Если при загрузке какой-либофункции произошла ошибка, сообщение выводится на консоль. Наконец, вызываетсяметод Prompt() (вывести приглашение и ждать вводакоманды) элемента управления ConsoleBox, расположенного на главной форме.
Класс Facade имеет целый ряд методов для работы спользовательскими функциями и переменными среды консоли, которые вызываютсоответствующие методы объекта InterprEnvironment.Instance. Срединих: LoadSub(), LoadSubs(), GetSubs(), UnloadSub(), GetVariables(), DeleteVariable(), SaveVariables(). Через эти методы производятсяоперации во многих обработчиках событий пользовательского интерфейса.
Но, пожалуй, наиболееважным из методов класса Facadeявляется ExecuteCommand() – выполнить команду. Он вызываетсяв обработчике события GetCommand элемента ConsoleBox наглавной форме. В нем в отдельном потоке запускается на выполнение частный методThrStart(), в котором введенная с консоли командасначала «компилируется» методом LineCompiler.CompileCommand(), затем выполняется, по окончаниичего генерируется событие Facade.Done(), в обработчике которого консольпереводится в состояние ожидания следующей команды методом ConsoleBox.Prompt(). И «компиляция» и выполнение команды производятся вблоках try, в случае возникновения исключенияна консоль выдается соответствующее сообщение об ошибке.
Необходимость выполнятькоманды в отдельном потоке связана с тем, что только в этом случае можнопрервать зациклившуюся или долго работающую пользовательскую функцию безаварийного завершения интерпретатора. Для перезапуска интерпретатора, возможно,с прерыванием работы пользовательской функции, предназначен метод Facade.Restart(). В нем в отдельном потоке запускается метод DoRestart(), в котором выполняются следующиедействия. Во-первых, если в данный момент времени выполняется команда, товызывается статический метод Subroutine.Moment.Break().В нем с помощью метода Interlocked.Exchange() (безопасное при параллельномвыполнении присваивание) статическому полю Subroutine.Moment.s_break присваивается значение 1. На каждойитерации цикла в методе Subroutine.Moment.Run(), помимо выполнения очередного оператора функции,проверяется значение этого поля. Если оно равно единице, то генерируетсяисключение CalcException, то есть выполнение командызавершается с ошибкой времени выполнения. После вызова Subroutine.Moment.Break()в методе DoRestart() следует цикл без тела, которыйвыполняется до тех пор, пока выполнение команды не будет завершено, чего,конечно же, не приходится долго ждать. После того, как выполнение будетпрервано, производится повторная инициализация, аналогичная происходящей призапуске интерпретатора.
Для реализациимногопоточности используется стандартный класс System.Threading.Thread. Его конструктору передается одинпараметр типа делегата System.Threading.ThreadStart (процедура без параметров). Метод,на который указывает этот делегат, начинает выполняться в отдельном потоке привызове метода Start() объекта потока. Когда метод,запущенный в потоке, возвращается, выполнение потока завершается. Повторноеиспользование того же объекта класса Thread невозможно, его нужно создавать заново. При использованиимногопоточности следует принимать ряд мер предосторожности для обеспечениябезопасного доступа к общим данным. Например, присваивание значений переменным,используемым несколькими потоками, по возможности следует производить с помощьюметода Interlocked.Exchange, который гарантирует атомарностьоперации, то есть то, что ее выполнение не будет прервано до полного завершениядля передачи управления другому потоку. Также обращаться к методам и свойствамэлементов графического интерфейса пользователя напрямую можно только из тогоже потока, в котором они были созданы. Если необходимо воздействовать награфический интерфейс пользователя из других потоков, то это следует делать в методе(процедуре без параметров), вызываемом с помощью делегата System.Windows.Forms.MethodInvoker. В языке C# имеются и другие средства синхронизации работы потоков,которые не используются в данном интерпретаторе.
/>Заключение
Мною выполненинтерпретатор несложного языка программирования. Интерпретатор работает винтерактивном режиме, выполняя команды, вводимые с консоли, которые могутсодержать вызовы пользовательских функций (подпрограмм). Пользовательскиефункции могут содержать структурные конструкции – циклы, ветвления, вызовыдругих функций (возможна и рекурсия). Возможна работа с числовыми и строковымиданными, а также с одномерными массивами. Имеется достаточно большое числовстроенных математических и других функций. Предварительного объявленияпеременных не требуется, синтаксис математический выражений – традиционный дляязыков высокого уровня. Это делает интерпретатор удобным в использовании.Данный интерпретатор может применяться как в учебных целях, например, дляобучения школьников основам программирования, так и качестве «программируемогомикрокалькулятора» для практических расчетов, сложность которых не требуетприменения специфического программного обеспечения.
/>Приложение. Исходный текст (сокращенно).
Ввиду большого объемаисходного кода, приведены лишь наиболее важные его фрагменты./>1. Класс VarBase.
using System;
using System.IO;
namespace interpr.logic.vartypes {
public abstract class VarBase: ICloneable,IComputable {
public bool IsArray() {
return (this is ArrayVar);
}
public bool IsNum() {
return (this is NumVar);
}
public bool IsString() {
return (this isStringVar);
}
public bool IsInt() {
return (this is IntVar);
}
public bool IsReal() {
return (this is RealVar);
}
public bool IsSingle() {
return (this isSingleVar);
}
public virtual VarBase Compute() {
return this.Clone() asVarBase;
}
public abstract System.ObjectClone();
public override abstract stringToString();
public abstract voidSerialise(BinaryWriter bw);
}
}/>2. Класс ArrayVar.
using System.Collections;
using System.IO;
namespace interpr.logic.vartypes {
public class ArrayVar: VarBase {
public virtual IntVar Size {
get { return newIntVar(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 newCalcException(«Ошибка»);
}
if (index
throw newCalcException(«Индекс не может быть отрицательным»);
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
throw newCalcException(«Индекс не может быть отрицательным»);
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(intindex) {
bool result = index>=0;
result =result&&(index
result =result&&(m_list[index]!=null);
return new IntVar(result);
}
public override System.ObjectClone() {
ArrayVar res = new ArrayVar();
int li = 0;
SingleVar e = null;
while (li
e = (SingleVar)m_list[li++];
if (e != null)
res.m_list.Add(e.Clone());
else
res.m_list.Add(null);
}
return res;
}
public override voidSerialise(BinaryWriter bw) {
bw.Write('a');
int size = m_list.Count;
bw.Write(size);
for (int i = 0; i
if (m_list[i] ==null)
bw.Write('n');
else
(m_list[i]as VarBase).Serialise(bw);
}
}
public override System.StringToString() {
System.String res ="[";
int li = 0;
SingleVar e = null;
if (li
e = (SingleVar)m_list[li++];
if (e != null) {
res +=e.ToString();
}
else
res +="-";
}
while (li
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 SubroutinesManagerm_subsman = null;
private ConsoleNamespacem_console_vars;
private bool m_not_restored =false;
public bool NotRestored {
get { returnm_not_restored; }
}
public ConsoleNamespaceConsoleNamespace {
get { returnm_console_vars; }
}
publicConsoleNamespace.VariableReport[] GetGlobalVarsList() {
returnm_console_vars.GetVariableList();
}
private InterprEnvironment() {
m_current_namespace = newConsoleNamespace();
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 newOtherException(«Error in Environment.LoadSubs()»);
s_instance.m_subsman =SubroutinesManager.GetInstance();
s_instance.m_subsman.ReloadAll();
}
private static InterprEnvironments_instance = null;
public static InterprEnvironment Instance{
get {
if (s_instance== null)
s_instance= new InterprEnvironment();
returns_instance;
}
}
public static void Reset() {
s_instance = newInterprEnvironment();
}
public void SaveVars() {
m_console_vars.Save();
}
public bool LoadSub(string name) {
returnm_subsman.Load(name);
}
private Namespacem_current_namespace = null;
public Namespace CurrentNamespace {
get { returnm_current_namespace; }
set { m_current_namespace= value; }
}
private IConsole m_current_console= null;
public IConsole CurrentConsole {
get { returnm_current_console; }
set { m_current_console =value; }
}
public Operation GetFunction(stringname) {
if (name ==«abs»)
returnOperation.ABS;
...........................
if (name ==«size»)
returnOperation.SIZE;
return new SubName(name);
}
public string[] LoadedSubs {
get { returnm_subsman.SubroutineNames; }
}
private class SubroutinesManager {
private ArrayList m_subs =new ArrayList();
private ArrayList m_names= new ArrayList();
privateSubroutinesManager() {
DirectoryInfo di=
newDirectoryInfo(Directory.GetCurrentDirectory() + @"\subroutines");
if (!di.Exists){
di.Create();
}
}
public bool Load(stringname) {
FileInfo fi =new FileInfo(Directory.GetCurrentDirectory() + @"\subroutines\" +name);
if (!fi.Exists)
thrownew OtherException(«Error in SubroutinesManager.Load()»);
return LoadFile(fi);
}
private boolLoadFile(FileInfo file) {
try {
StreamReadersr = file.OpenText();
LinkedListll = 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++;
}
Subroutinesub;
try {
sub= new Subroutine(strs, file.Name);
} catch(LineSyntaxException ex) {
InterprEnvironment.Instance.CurrentConsole.PrintLn(«Синтаксическая ошибкав » + ex.Function +"[] at line " + ex.Line + " " + ex.Message);
returnfalse;
} catch(SyntaxErrorException ex) {
InterprEnvironment.Instance.CurrentConsole.PrintLn(«Синтаксическая ошибкав » + file.Name +" " + ex.Message);
returnfalse;
}
Set(file.Name,sub);
} catch {
thrownew OtherException(«Error in Environment.Load()»);
}
return true;
}
public Subroutinethis[string name] {
get {
intsres = m_names.IndexOf(name);
if(sres
returnnull;
else
returnm_subs[sres] as Subroutine;
}
}
private void Set(stringname, 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 staticSubroutinesManager s_inst = null;
public staticSubroutinesManager GetInstance() {
if (s_inst ==null)
s_inst= new SubroutinesManager();
return s_inst;
}
public string[]SubroutineNames {
get {
intcount = m_names.Count;
string[]res = new string[count];
for(int i = 0; i
res[i]= (m_names[i] as String);
}
for(int i = 0; i
intk = i;
for(int j = i + 1; j
k= (string.Compare(res[j], res[k])
if(i != k) {
stringtemp = res[i];
res[i]= res[k];
res[k]= temp;
}
}
returnres;
}
}
public void ReloadAll() {
m_subs = newArrayList();
m_names = newArrayList();
DirectoryInfo di=
newDirectoryInfo(Directory.GetCurrentDirectory() + @"\subroutines");
if (!di.Exists){
di.Create();
}
foreach(FileInfo file in di.GetFiles()) {
if(Parser.IsID(file.Name)) {
LoadFile(file);
}
}
}
public void Unload(stringname) {
int index =m_names.IndexOf(name);
if (index >=0) {
m_names.RemoveAt(index);
m_subs.RemoveAt(index);
}
}
}
public Subroutine GetSub(stringname) {
Subroutine res =m_subsman[name];
if (res == null)
throw newCalcException(«Функция » + 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 {
publicNamespaceSerializationException(): base() {}
}
public class Namespace {
protected class Pair {
internal string m_str;
internal VarBase m_var;
}
protected ArrayList m_list = newArrayList();
protected int m_n = 0;
private Namespacem_previous_namespace = null;
public Namespace PreviousNamespace{
get { return m_previous_namespace;}
}
public Namespace(Namespaceprevious) {
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)
returnp.m_var;
} while (i
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
}
p = new Pair();
p.m_var = var;
p.m_str = name;
m_list.Add(p);
m_n++;
}
public voidAssignToElement(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 asArrayVar)[index] = var;
return;
}
} while (i
}
p = new Pair();
p.m_var = new ArrayVar();
(p.m_var asArrayVar)[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
}
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 { returnm_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 = newLinkedList();
private LinkedList.Iterator m_i =null;
public void Add(VarBase var) {
if (m_read)
throw newOtherException(«Write to the argument list after reading begin»);
m_list.Add(var);
}
public VarBase Get() {
if (!m_read)
throw newOtherException(«Try to read from argument list before reset»);
if (!m_i.HasPrevious)
throw newOtherException(«Try to read from empty argument list»);
m_read = true;
IComputable obj =(m_i.Previous() as IComputable);
if (obj == null)
throw newCalcException(«Переменная не инициализированна.»);
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 = newParser(str);
Analyse(p);
}
public Expression(Parser p) {
Analyse(p);
}
private class Element {
internal IComputable m_o;
internal Element m_next;
internalElement(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(IComputableobj) {
m_c++;
if (m_c == 1)
m_top = m_bottom= new Element(obj, null);
else {
Element t = newElement(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 newSyntaxErrorException(«Синтаксическая ошибка в выражении»);
}
}
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) {
objecto = stk.RemoveLast();
if(o == O_BR) break;
AddFront(newCall(o as Operation));
}
while((!stk.IsEmpty()) && IsUnary(stk.Last)) {
AddFront(newCall(stk.RemoveLast() as Operation));
}
}
else if(Parser.IsID(si)) {
boolbfun = false;
boolbarray = false;
if(itr.HasMore) {
strings = (itr.Step() as System.String);
if(s == "[")
bfun= true;
elseif (s == "{")
barray= true;
else
itr.Previous();
}
if(bfun) {
LinkedListl = null;
while(true) {
l= new LinkedList();
intlevel = 0;
while(true) {
if(!itr.HasMore)
thrownew SyntaxErrorException(«Синтаксическая ошибка в выражении»);
stringsj = (itr.Step() as System.String);
if(sj == "[") {
level++;
l.Add(sj);
}
elseif (sj == "]") {
if(level == 0)
gotolabel1;
else{
level--;
l.Add(sj);
}
}
elseif (sj == ",") {
if(level > 0)
l.Add(sj);
else
break;
}
else
l.Add(sj);
}
OPZ(l);
}
label1:
if(l != null)
OPZ(l);
Operationsub = env.GetFunction(si);
AddFront(newCall(sub));
while((stk.Count > 0) && IsUnary(stk.Last)) {
AddFront(newCall((Operation) stk.RemoveLast()));
}
}
else if(barray) {
LinkedListl = new LinkedList();
intlevel = 0;
while(true) {
if(!itr.HasMore)
thrownew SyntaxErrorException(«Синтаксическая ошибка в выражении»);
Stringsj = (String) itr.Step();
if(sj == "{") {
level++;
l.Add(sj);
}
elseif (sj == "}") {
if(level == 0)
break;
else{
level--;
l.Add(sj);
}
}
else
l.Add(sj);
}
OPZ(l);
VarNamev = new VarName(si);
AddFront(v);
AddFront(newCall(Operation.INDEX));
while((stk.Count > 0) && IsUnary(stk.Last)) {
AddFront(newCall(stk.RemoveLast() as Operation));
}
}
else {
VarNamev = new VarName(si);
AddFront(v);
while((stk.Count > 0) && IsUnary(stk.Last)) {
AddFront(newCall(stk.RemoveLast() as Operation));
}
}
}
else {
Operationop = StrToOperation(si);
if (op== null) {
SingleVarsv = SingleVar.FromString(si);
if(si == null)
thrownew SyntaxErrorException(«Синтаксическая ошибка в выражении»);
AddFront(sv);
while((stk.Count > 0) && IsUnary(stk.Last)) {
AddFront(newCall(stk.RemoveLast() as Operation));
}
}
else {
//operation
if(op == Operation.ADD) {
itr.Previous();
if(!itr.HasPrevious) {
stk.Add(Operation.UPLUS);
itr.Step();
continue;
}
Stringstrpr = (String) itr.Previous();
itr.Step();
itr.Step();
if((StrToOperation(strpr) != null) || (strpr == "(") ||
(strpr== "[") || (strpr == "{")) {
stk.Add(Operation.UPLUS);
continue;
}
}
elseif (op == Operation.SUB) {
itr.Previous();
if(!itr.HasPrevious) {
stk.Add(Operation.UMINUS);
itr.Step();
continue;
}
Stringstrpr = (String) itr.Previous();
itr.Step();
itr.Step();
if((StrToOperation(strpr) != null) || (strpr == "(") ||
(strpr== "[") || (strpr == "{")) {
stk.Add(Operation.UMINUS);
continue;
}
}
elseif (op == Operation.NOT) {
stk.Add(op);
continue;
}
if(stk.IsEmpty() || (stk.Last == O_BR)) {
stk.Add(op);
}
else{
intpr = Priority(op);
while(true) {
if(stk.IsEmpty())
break;
Objectstktop = stk.Last;
if(stktop is Operation) {
intpr1 = Priority(stktop as Operation);
if((pr
AddFront(newCall(stktop as Operation));
stk.RemoveLast();
}
else
break;
}
else
break;
}
stk.Add(op);
}
}
}
}
while (!stk.IsEmpty()) {
Object o =stk.RemoveLast();
AddFront(newCall(o as Operation));
}
}
public VarBase Calculate() {
if (m_c == 0)
throw newCalcException(«Ошибка: пустое выражение.»);
Element top1 = null;
Element cur = m_top;
try {
for (; cur !=null; cur = cur.m_next) {
if(cur.m_o is Call) {
intrc = (cur.m_o as Call).ReqCount;
ArgListal = new ArgList();
for(int i = 0; i
if(top1 == null)
thrownew CalcException(«Ошибка при вычислении выражения»);
al.Add(top1.m_o.Compute());
top1= top1.m_next;
}
(cur.m_oas 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 newCalcException(«Ошибка при вычислении выражения»);
}
}
private static OperationStrToOperation(String str) {
//не возвращает унарные плюс и минус
if (str == "+")
returnOperation.ADD;
else if (str =="-")
returnOperation.SUB;
else if (str =="*")
returnOperation.MUL;
else if (str =="/")
returnOperation.DIV;
else if (str =="~")
returnOperation.NOT;
else if (str =="|")
returnOperation.OR;
else if (str =="&")
returnOperation.AND;
else if (str =="^")
return Operation.XOR;
else if (str =="~=")
returnOperation.BE;
else if (str =="=")
returnOperation.EQ;
else if (str =="")
returnOperation.NE;
else if (str ==">=")
returnOperation.GE;
else if (str =="
returnOperation.LE;
else if (str ==">")
returnOperation.GT;
else if (str =="
returnOperation.LT;
else
return null;
}
private static intPriority(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 boolIsBinary(Operation op) {
return Priority(op)
}
private static bool IsUnary(objectobj) {
return ((obj ==Operation.NOT) || (obj == Operation.UPLUS) ||
(obj ==Operation.UMINUS));
}
private class BR_c {}
private static object O_BR = newBR_c();
}
}/>9. Класс Operation (сокращенно).
using System;
using interpr.logic.vartypes;
namespace interpr.logic {
public abstract class Operation {
public abstract int ReqCount { get;}
public abstract VarBasePerform(ArgList al);
public static readonly OperationABS = new ABS_c();
private class ABS_c: Operation {
public override intReqCount {
get { return 1;}
}
public override VarBasePerform(ArgList al) {
if (al.Count !=ReqCount)
thrownew OtherException(«Invalid argument list»);
al.Reset();
VarBase arg1 =al.Get();
if (arg1 isIntVar)
return((arg1 as IntVar).Val>0)? (arg1.Clone() as IntVar): (new IntVar(-((IntVar)arg1).Val));
else if (arg1 isRealVar)
return((arg1 as RealVar).Val>0)? (arg1.Clone() as RealVar): (newRealVar(-((RealVar) arg1).Val));
else
thrownew CalcException(«Неправильные аргументы функции»);
}
}
public static readonly OperationADD = new ADD_c();
private class ADD_c: Operation {
public override intReqCount {
get { return 2;}
}
public override VarBasePerform(ArgList al) {
if (al.Count !=ReqCount)
thrownew OtherException(«Invalid argument list»);
al.Reset();
VarBase arg1 =al.Get();
VarBase arg2 =al.Get();
if(!(arg1.IsSingle()&&arg2.IsSingle()))
thrownew CalcException(«Неверные типы операндов»);
return (arg1 asSingleVar).add(arg2 as SingleVar);
}
}
public static readonly OperationAND = new AND_c();
private class AND_c: Operation {
public override intReqCount {
get { return 2;}
}
public override VarBasePerform(ArgList al) {
if (al.Count !=ReqCount)
thrownew OtherException(«Invalid argument list»);
al.Reset();
VarBase arg1 =al.Get();
VarBase arg2 =al.Get();
if(!(arg1.IsSingle()&&arg2.IsSingle()))
thrownew CalcException(«Неверные типы операндов»);
return (arg1 asSingleVar).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 ints_keywords_length = s_keywords.Length;
private static bool IsLD(char c) {
return ((c >= 'a')&& (c = 'A') && (c
|| ((c >='1') && (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
if(!IsLD(str[i]))
returnfalse;
for (i = 0; i
if (str ==s_keywords[i])
returnfalse;
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
}
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
if (a[i] == '#'){
break;
} else if (a[i]== '\"') {
m_a[j]= '\"';
i++;
j++;
while((i
m_a[j]= a[i];
i++;
j++;
}
if (i== n)
throw newSyntaxErrorException(«Не закрытая строковая константа»);
else {
m_a[j]= '\"';
i++;
j++;
}
} else if(IsSp(a[i])) {
boolflag = false;
if ((i> 0) && (IsLD(a[i — 1]))) {
m_a[j]= ' ';
j++;
flag= true;
}
while((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
flag = true;
} else if(char.IsDigit(m_a[cur]) || (m_a[cur] == '.')) {
flag = false;
while ((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);
} elseif ((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
cur++;
res= new String(m_a, beg, cur — beg);
} elseif (char.IsDigit(m_a[cur])) {
while((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
cur++;
if(cur == end)
res= new String(m_a, beg, cur — beg);
elseif ((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
cur++;
res= new String(m_a, beg, cur — beg);
}else if (char.IsDigit(m_a[cur])) {
while((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
cur++;
res =new String(m_a, beg, cur — beg);
} else if(m_a[cur] == '\"') {
do {
cur++;
if(m_a[cur] == '\"') {
if((cur
cur++;
else
break;
}
} while(true);
cur++;
res =new String(m_a, beg, cur — beg);
} else if (cur
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
cur++;
m_new_cur = cur;
return res;
}
public object Current {
get { return GetCurrent();}
}
public bool MoveNext() {
if (m_at_begin) {
m_at_begin =false;
returnHasMore();
}
if (m_new_cur
GetCurrent();
m_cur = m_new_cur;
m_new_cur = -1;
return HasMore();
}
public IEnumerator GetEnumerator(){
return this;
}
public static bool IsUserID(stringname) {
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 CommandCompileCommand(string str) {
Parser p = newParser(str);
if (!p.HasMore()) {
return newEmptyCommand();
}
String pstr =p.GetString();
int posa =pstr.IndexOf(":=");
if (posa >= 0) {
int cq = 0;
for (int iq = 0;iq
if(pstr[iq] == '\"')
cq++;
if (cq%2 == 0) {
try {
if(posa == 0)
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
try{
if(pstr[posa — 1] == '}') {
intposob = pstr.IndexOf('{');
if((posob posa))
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew AssignCommand(pstr.Substring(0, posob),
pstr.Substring(posob + 1, posa — posob — 2),
pstr.Substring(posa + 2));
}else {
returnnew AssignCommand(pstr.Substring(0, posa),
pstr.Substring(posa + 2));
}
}catch {
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
}
} catch(CalcException ex) {
thrownew SyntaxErrorException(ex.Message);
}
}
}
p.MoveNext();
string firsttoken =(p.Current as String);
try {
if (firsttoken== «clear») {
if(!p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
Commandcc = new ClearCommand(p.Current as String);
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returncc;
}
if (firsttoken== «print») {
Expressionexpr = new Expression(p);
returnnew PrintCommand(expr);
} else if(firsttoken == «println») {
Expressionexpr = new Expression(p);
returnnew PrintLnCommand(expr);
} else if(firsttoken == «call») {
Expressionexpr = new Expression(p);
returnnew CallCommand(expr);
} else {
p.Reset();
Expressionexpr1 = new Expression(p);
returnnew PrintLnCommand(expr1);
}
} catch(SyntaxErrorException ex) {
throw ex;
} catch (Exception ex) {
throw newSyntaxErrorException(ex.Message);
}
}
public static IOperatorCompileOperator(string str) {
Parser p = newParser(str);
if (!p.HasMore()) {
return newEmptyCommand();
}
String pstr =p.GetString();
p.MoveNext();
string firsttoken =(p.Current as String);
if (firsttoken ==«for») {
try {
returnParseForStatement(p.GetString());
} catch(SyntaxErrorException ex) {
throwex;
} catch(Exception ex) {
thrownew SyntaxErrorException(ex.Message);
}
}
int posa = pstr.IndexOf(":=");
if (posa >= 0) {
int cq = 0;
for (int iq = 0;iq
if(pstr[iq] == '\"')
cq++;
if (cq%2 == 0) {
try {
if(posa == 0)
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
try{
if(pstr[posa — 1] == '}') {
intposob = pstr.IndexOf('{');
if((posob posa))
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew AssignCommand(pstr.Substring(0, posob),
pstr.Substring(posob + 1, posa — posob — 2),
pstr.Substring(posa + 2));
}else {
returnnew AssignCommand(pstr.Substring(0, posa),
pstr.Substring(posa + 2));
}
}catch {
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
}
} catch(CalcException ex) {
thrownew SyntaxErrorException(ex.Message);
}
}
}
try {
if (firsttoken== «clear») {
if(!p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
Commandcc = new ClearCommand(p.Current as String);
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returncc;
} else if (firsttoken== «next») {
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew NextOperator();
} else if(firsttoken == «else») {
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew ElseOperator();
} else if(firsttoken == «endif») {
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew EndifOperator();
} else if(firsttoken == «loop») {
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew LoopOperator();
} else if(firsttoken == «return») {
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew ReturnOperator();
} else if(firsttoken == «error») {
if(p.MoveNext())
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
returnnew ErrorOperator();
}
Expression expr= new Expression(p);
if (firsttoken== «print»)
returnnew PrintCommand(expr);
else if(firsttoken == «println»)
returnnew PrintLnCommand(expr);
else if(firsttoken == «call»)
returnnew CallCommand(expr);
else if(firsttoken == «while»)
returnnew WhileOperator(expr);
else if(firsttoken == «if»)
returnnew IfOperator(expr);
else if(firsttoken == «elseif»)
returnnew ElseifOperator(expr);
else
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
} catch (SyntaxErrorExceptionex) {
throw ex;
} catch (Exception ex) {
throw newSyntaxErrorException(ex.Message);
}
}
private static IOperatorParseForStatement(string str) {
str = str.Substring(3);
int assignpos =str.IndexOf(":=");
if (assignpos
throw newSyntaxErrorException(«Неправильный синтаксис оператора for»);
string countername =str.Substring(0, assignpos).Trim();
if(!Parser.IsID(countername))
throw newSyntaxErrorException(«Неправильный синтаксис оператора for»);
str =str.Substring(assignpos + 2);
int colonpos =str.IndexOf(":");
if (colonpos
throw newSyntaxErrorException(«Неправильный синтаксис оператора for»);
string expr1str =str.Substring(0, colonpos);
string expr2str =str.Substring(colonpos + 1);
Expression expr1 = newExpression(expr1str);
Expression expr2 = newExpression(expr2str);
return newForOperator(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.Momentpos);
OperatorKind GetKind();
}
}/>13. Класс Command.
namespace interpr.logic.operators {
public abstract class Command: IOperator {
public abstract void Execute();
public void Execute(Subroutine.Momentpos) {
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
thrownew OtherException(«Error in LoopOperator.NextPos»);
returnm_next_pos;
}
set { m_next_pos = value;}
}
public void Step(Subroutine.Momentpos, int forpos) {
Namespace cn =InterprEnvironment.Instance.CurrentNamespace;
VarBase res =cn[m_counter_var];
if (!res.IsInt())
throw newCalcException(«Тип переменной — счетчика цикла был изменен»);
int resval = (res asIntVar).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 voidExecute(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
throw newOtherException(«Error in NextOperator.ForPos»);
returnm_for_pos;
}
set { m_for_pos = value; }
}
public ForOperator ForOp {
get { return m_for_op; }
set { m_for_op = value; }
}
public voidExecute(interpr.logic.Subroutine.Moment pos) {
m_for_op.Step(pos,m_for_pos);
}
publicinterpr.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(stringstr) {
Parser header_p = newParser(str);
if (!header_p.MoveNext())
throw newSyntaxErrorException(«Ошибка в заголовке функции»);
if ((header_p.Current asSystem.String) != m_name)
throw newSyntaxErrorException(«Имя функции не совпадает с именем файла»);
if ((!header_p.MoveNext())|| ((header_p.Current as String) != "["))
throw newSyntaxErrorException(«Ошибка в заголовке функции»);
if((!header_p.MoveNext()))
throw newSyntaxErrorException(«Ошибка в заголовке функции»);
if ((header_p.Current asSystem.String != "]")) {
string readstr;
while (true) {
readstr= (header_p.Current as System.String);
if(!Parser.IsID(readstr))
thrownew SyntaxErrorException(«Ошибка в заголовке функции»);
m_args.Add(readstr);
if (!header_p.MoveNext())
thrownew SyntaxErrorException(«Ошибка в заголовке функции»);
readstr= (header_p.Current as System.String);
if(readstr == ",") {
if(!header_p.MoveNext())
thrownew SyntaxErrorException(«Ошибка в заголовке функции»);
}
else if(readstr == "]")
break;
else
thrownew SyntaxErrorException(«Ошибка в заголовке функции»);
}
}
if (header_p.MoveNext())
throw newSyntaxErrorException(«Ошибка в заголовке функции»);
if(m_args.IndexOf(«result») >= 0)
throw newSyntaxErrorException(«Параметр функции не может иметь имя\»result\"");
}
public Subroutine(string[] code,string name) {
m_name = name;
if (code.Length == 0)
throw newSyntaxErrorException(«Файл функции пуст»);
AnalyseHeader(code[0]);
int clen = code.Length;
int i = 0;
try {
Stack stk = newStack();
m_operators.Add(new EmptyCommand());//чтобы индексация начиналась с единицы
for (i = 1; i
IOperatorop = LineCompiler.CompileOperator(code[i]);
if (op== null)
thrownew SyntaxErrorException(«Синтаксическая ошибка»);
m_operators.Add(op);
switch(op.GetKind()) {
caseOperatorKind.If:
caseOperatorKind.While:
caseOperatorKind.For:
{
stk.Push(i);
break;
}
caseOperatorKind.Elseif:
{
if(stk.Count == 0)
thrownew SyntaxErrorException(«Лишнее elseif»);
intj = (int) stk.Pop();
switch((m_operators[j] as IOperator).GetKind()) {
caseOperatorKind.If:
{
(m_operators[j]as IfOperator).NextPos = i;
break;
}
caseOperatorKind.Elseif:
{
(m_operators[j]as ElseifOperator).NextPos = i;
break;
}
default:
thrownew SyntaxErrorException(«Лишнее elseif»);
}
stk.Push(i);
break;
}
caseOperatorKind.Else:
{
if(stk.Count == 0)
thrownew SyntaxErrorException(«Лишнее else»);
intj = (int) stk.Pop();
stk.Push(i);
switch((m_operators[j] as IOperator).GetKind()) {
caseOperatorKind.If:
{
(m_operators[j]as IfOperator).NextPos = i;
break;
}
caseOperatorKind.Elseif:
{
(m_operators[j]as ElseifOperator).NextPos = i;
break;
}
default:
thrownew SyntaxErrorException(«Лишнее else»);
}
break;
}
caseOperatorKind.Endif:
{
if(stk.Count == 0)
thrownew SyntaxErrorException(«Лишнее endif»);
intj = (int) stk.Pop();
switch((m_operators[j] as IOperator).GetKind()) {
caseOperatorKind.If:
{
(m_operators[j]as IfOperator).NextPos = i;
break;
}
caseOperatorKind.Elseif:
{
(m_operators[j]as ElseifOperator).NextPos = i;
break;
}
caseOperatorKind.Else:
{
(m_operators[j]as ElseOperator).NextPos = i;
break;
}
default:
thrownew SyntaxErrorException(«Лишнее endif»);
}
break;
}
caseOperatorKind.Loop:
{
if(stk.Count == 0)
thrownew SyntaxErrorException(«Лишнее loop»);
intj = (int) stk.Pop();
if((m_operators[j] as IOperator).GetKind() != OperatorKind.While)
thrownew SyntaxErrorException(«Лишнее loop»);
(m_operators[i]as LoopOperator).WhilePos = j;
(m_operators[j]as WhileOperator).LoopPos = i;
break;
}
caseOperatorKind.Next:
{
if(stk.Count == 0)
thrownew SyntaxErrorException(«Лишнее next»);
intj = (int) stk.Pop();
if((m_operators[j] as IOperator).GetKind() != OperatorKind.For)
thrownew 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)
thrownew SyntaxErrorException(«Не закрытый блок»);
}
catch(SyntaxErrorException ex) {
throw newLineSyntaxException(ex.Message, m_name, i + 1);
}
m_count =m_operators.Count;
}
private string m_name;
private ArrayList m_args = newArrayList();
private ArrayList m_operators = newArrayList();
private int m_count;
public int ReqCount {
get { return m_args.Count;}
}
public VarBase Perform(ArgList al){
Namespace ns = newNamespace(InterprEnvironment.Instance.CurrentNamespace);
ns[«result»] =new IntVar(0);
int argc = m_args.Count;
if (al.Count != argc)
throw newCalcException(«Неверное число параметров»);
al.Reset();
for (int i = 0; i
ns[m_args[i] asSystem.String] = al.Get();
}
InterprEnvironment.Instance.CurrentNamespace= ns;
Moment moment = newMoment(this);
if (m_count > 1) {
try {
moment.Run();
}
catch(SyntaxErrorException ex) {
throwex;
}
catch(CalcException ex) {
thrownew CalcException(«Ошибка в функции » + m_name + "[] в строке" + (moment.Pos + 1) + ": " + ex.Message);
}
}
VarBase res =ns[«result»];
InterprEnvironment.Instance.CurrentNamespace= ns.PreviousNamespace;
if (res == null)
throw new CalcException(«Ошибка в функции » + m_name + "[]: переменная result не определена на момент выхода");
return res;
}
public class Moment {
private Subroutine m_sub;
private int m_pos;
private static int s_break= 0;
public static void Break(){
Interlocked.Exchange(refs_break, 1);
}
public int Pos {
get { returnm_pos; }
}
public Moment(Subroutinesub) {
m_sub = sub;
m_pos = 1;
s_break = 0;
}
public void GoTo(int to) {
m_pos = to;
}
public void Next() {
m_pos++;
}
public void Run() {
while (m_pos
if(s_break == 1)
thrownew CalcException(«Прервано пользователем»);
(m_sub.m_operators[m_pos]as IOperator).Execute(this);
}
}
public void Return() {
m_pos =m_sub.m_count;
}
public IOperator Current {
get { returnm_sub.m_operators[m_pos] as IOperator; }
}
}
}
}/>17. Класс Facade.
using System.Threading;
using interpr.logic;
using interpr.logic.operators;
namespace interpr {
public class Facade {
private static Facade s_instance =null;
public static void Create(IConsoleconsole) {
if (s_instance == null)
s_instance = newFacade(console);
}
public static Facade Instance {
get { return s_instance; }
}
private IConsole m_console;
private InterprEnvironment m_env;
private string m_cmd;
private bool m_doing = false;
private Facade(IConsole console) {
m_console = console;
m_env =InterprEnvironment.Instance;
m_env.CurrentConsole =m_console;
}
public delegate voidCommandDoneHandler();
public event CommandDoneHandlerDone;
private void ThrStart() {
m_doing = true;
Command cmd;
do {
try {
cmd =LineCompiler.CompileCommand(m_cmd);
}
catch(SyntaxErrorException ex) {
m_env.CurrentConsole.PrintLn(«Ошибка: » + ex.Message);
break;
}
try {
cmd.Execute();
}
catch(CalcException ex) {
m_env.CurrentConsole.PrintLn(«Ошибка: » + ex.Message);
m_env.CurrentNamespace= m_env.ConsoleNamespace;
break;
}
} while (false);
Done();
m_doing = false;
}
public void ExecuteCommand(stringcmd) {
if (m_doing)
throw newOtherException(«Error in Bridge.ExecuteCommand()»);
m_cmd = cmd;
new Thread(newThreadStart(ThrStart)).Start();
}
private void DoRestart() {
if (m_doing)
Subroutine.Moment.Break();
while (m_doing) {}
InterprEnvironment.Reset();
m_env =InterprEnvironment.Instance;
m_env.CurrentConsole =m_console;
m_env.LoadSubs();
}
public void Restart() {
new Thread(newThreadStart(DoRestart)).Start();
}
public bool Busy {
get { return m_doing; }
}
public void SaveVariables() {
m_env.SaveVars();
}
public void LoadSubs() {
m_env.LoadSubs();
}
publicConsoleNamespace.VariableReport[] GetVariables() {
returnm_env.GetGlobalVarsList();
}
public string[] GetSubs() {
return m_env.LoadedSubs;
}
public void DeleteVariable(stringname) {
m_env.ConsoleNamespace.Remove(name);
}
public bool LoadSub(string name) {
returnm_env.LoadSub(name);
}
public void UnloadSub(string name){
m_env.UnloadSub(name);
}
public bool NotRestored {
get {
returnm_env.NotRestored;
}
}
}
}/>18. Класс SourceBox.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace interpr {
public class SourceBox: UserControl {
private RichTextBox m_tb;
private TextBox m_tb_2;
....................................................................
private int m_curline = 0;//текущая строка
private int m_lincount = 0;//общее число строк
private HighlightParser m_hp = newHighlightParser();
private static Font s_nfont =
new Font(«LucidaConsole», 10, FontStyle.Regular);
private static Font s_cfont =
new Font(«LucidaConsole», 12, FontStyle.Bold);
private int GetCurrentLine() {
returnm_tb.GetLineFromCharIndex(m_tb.SelectionStart);
}
private int GetLinesCount() {
return m_tb.Lines.Length;
}
private String GetLine(int index) {
return m_tb.Lines[index];
}
private void m_tb_KeyPress(objectsender, KeyPressEventArgs e) {
if (e.KeyChar == '\r') {
string txt =m_tb.Text;
int i =m_tb.SelectionStart — 2;
int j;
while (i >=0) {
if(txt[i] == '\n')
return;
else if(txt[i] == '\t') {
j= 0;
while((i >= 0) && (txt[i] == '\t')) {
j++;
i--;
}
if((i
m_tb.SelectedText= new String('\t', j);
return;
}
}
i--;
}
}
}
private bool GetLinePos(int index,out int beg, out int len) {
if ((index = GetLinesCount())) {
beg = len = 0;
return false;
}
int i;
string[] ls = m_tb.Lines;
beg = 0;
for (i = 0; i
beg +=ls[i].Length + 1;
len = ls[index].Length;
return true;
}
private void SelectLine(int index){
int beg, len;
if (!GetLinePos(index, outbeg, out len))
throw newIndexOutOfRangeException();
m_tb.SelectionStart = beg;
m_tb.SelectionLength =len;
}
private void HighlightLine(intindex) {
int beg, len;
int curbeg =m_tb.SelectionStart;
int curlen =m_tb.SelectionLength;
GetLinePos(index, out beg,out len);
string str =m_tb.Lines[index];
m_hp.Reset(str);
while (m_hp.HasMore()) {
int tbeg, tlen;
HighlightParser.TokenTypetype;
m_hp.GetNext(outtbeg, out tlen, out type);
m_tb.SelectionStart= beg + tbeg;
m_tb.SelectionLength= tlen;
switch (type) {
caseHighlightParser.TokenType.Comment:
{
m_tb.SelectionColor= Color.DarkGreen;
break;
}
caseHighlightParser.TokenType.Identifier:
{
m_tb.SelectionColor= Color.Purple;
break;
}
caseHighlightParser.TokenType.Keyword:
{
m_tb.SelectionColor= Color.Blue;
break;
}
caseHighlightParser.TokenType.Number:
{
m_tb.SelectionColor= Color.Red;
break;
}
caseHighlightParser.TokenType.String:
{
m_tb.SelectionColor= Color.Brown;
break;
}
caseHighlightParser.TokenType.Other:
{
m_tb.SelectionColor= Color.Black;
break;
}
}
}
m_tb.SelectionStart =curbeg;
m_tb.SelectionLength =curlen;
}
public enum LineState {
ErrorLine,
CurrentLine,
NormalLine
}
private void ColorLine(int index,LineState state) {
int curbeg =m_tb.SelectionStart;
int curlen =m_tb.SelectionLength;
SelectLine(index);
switch (state) {
caseLineState.ErrorLine:
{
m_tb.SelectionColor= Color.Red;
break;
}
caseLineState.CurrentLine:
{
m_tb.SelectionFont= s_cfont;
break;
}
caseLineState.NormalLine:
{
m_tb.SelectionFont= s_nfont;
HighlightLine(index);
break;
}
}
m_tb.SelectionStart =curbeg;
m_tb.SelectionLength =curlen;
}
private void HighlightText(boolanyway) {
int l = GetCurrentLine();
int lc = GetLinesCount();
if ((l != m_curline) ||(lc != m_lincount) || anyway) {
m_tb_2.Focus();
m_curline = l;
m_lincount = lc;
int bi =m_tb.GetCharIndexFromPosition(new Point(0, 0));
int ei = m_tb.GetCharIndexFromPosition(newPoint(m_tb.Size));
int bl =m_tb.GetLineFromCharIndex(bi);
int el =m_tb.GetLineFromCharIndex(ei);
if (bl > 0)bl--;
if (el
for (int i = bl;i
HighlightLine(i);
m_tb.Focus();
}
}
private void m_tb_KeyUp(objectsender, KeyEventArgs e) {
HighlightText(false);
}
private void m_tb_MouseUp(objectsender, MouseEventArgs e) {
if (e.Button ==MouseButtons.Left)
HighlightText(true);
}
public string[] Lines {
get { return (string[])m_tb.Lines.Clone(); }
}
public bool LoadFile(stringfilename) {
try {
m_tb.LoadFile(filename,RichTextBoxStreamType.PlainText);
HighlightText(true);
return true;
}
catch {
return false;
}
}
public bool SaveFile(stringfilename) {
try {
m_tb.SaveFile(filename,RichTextBoxStreamType.PlainText);
return true;
}
catch {
return false;
}
}
public int CurrentLine {
get { returnm_tb.GetLineFromCharIndex(m_tb.SelectionStart); }
}
private class HighlightParser {
private char[] m_a;
private int m_len;
private int m_cur;
public enum TokenType {
String,
Number,
Keyword,
Comment,
Identifier,
Other
}
public void Reset(stringstr) {
m_a =str.ToCharArray();
m_len =str.Length;
m_cur = 0;
while ((m_cur
m_cur++;
}
public bool HasMore() {
return m_cur
}
private bool IsKeyword(stringstr) {
return
(str ==«if») ||
(str== «else») ||
(str== «elseif») ||
(str== «endif») ||
(str== «while») ||
(str== «loop») ||
(str== «return») ||
(str== «result») ||
(str== «call») ||
(str== «print») ||
(str== «println») ||
(str== «readln») ||
(str== «clear») ||
(str== «for») ||
(str== «next») ||
(str== «error»);
}
public void GetNext(outint beg, out int len, out TokenType type) {
if (m_cur >=m_len)
thrownew IndexOutOfRangeException();
beg = m_cur;
if (m_a[m_cur]== '\"') {
m_cur++;
while((m_cur
m_cur++;
if(m_cur
m_cur++;
len =m_cur — beg;
type =TokenType.String;
}
else if(isL(m_a[m_cur])) {
m_cur++;
while((m_cur
m_cur++;
len =m_cur — beg;
if(IsKeyword(new string(m_a, beg, len)))
type= TokenType.Keyword;
else
type= TokenType.Identifier;
}
else if(m_a[m_cur] == '#') {
len =m_len — m_cur;
m_cur =m_len;
type =TokenType.Comment;
}
else if(m_a[m_cur] == '.') {
if(GetNumber()) {
len= m_cur — beg;
type= TokenType.Number;
}
else {
m_cur= beg + 1;
len= 1;
type= TokenType.Other;
}
}
else if(char.IsDigit(m_a[m_cur])) {
GetNumber();
len =m_cur — beg;
type =TokenType.Number;
}
else {
m_cur++;
len =1;
type =TokenType.Other;
}
while ((m_cur
m_cur++;
}
private bool GetNumber() {
if(!((m_a[m_cur] == '.') || char.IsDigit(m_a[m_cur])))
returnfalse;
while ((m_cur
m_cur++;
if (m_cur ==m_len)
returntrue;
else if(m_a[m_cur] == '.') {
m_cur++;
while((m_cur
m_cur++;
if (m_cur== m_len)
returntrue;
else if((m_a[m_cur] == 'e') || (m_a[m_cur] == 'E')) {
intp1 = m_cur;
m_cur++;
if(m_cur == m_len) {
m_cur= p1;
returntrue;
}
elseif ((m_a[m_cur] == '-') || (m_a[m_cur] == '+')) {
m_cur++;
if((m_cur == m_len) || !char.IsDigit(m_a[m_cur])) {
m_cur= p1;
returntrue;
}
while((m_cur
m_cur++;
returntrue;
}
elseif (char.IsDigit(m_a[m_cur])) {
while((m_cur
m_cur++;
returntrue;
}
else{
m_cur= p1;
returntrue;
}
}
else
returntrue;
}
else if((m_a[m_cur] == 'e') || (m_a[m_cur] == 'E')) {
int p1= m_cur;
m_cur++;
if(m_cur == m_len) {
m_cur= p1;
returntrue;
}
else if((m_a[m_cur] == '-') || (m_a[m_cur] == '+')) {
m_cur++;
if((m_cur == m_len) || !char.IsDigit(m_a[m_cur])) {
m_cur= p1;
returntrue;
}
while((m_cur
m_cur++;
returntrue;
}
else if(char.IsDigit(m_a[m_cur])) {
while((m_cur
m_cur++;
returntrue;
}
else {
m_cur= p1;
returntrue;
}
}
else
returntrue;
}
private static boolisLD(char c) {
return ((c >='a') && (c = 'A') && (c
|| ((c>= '1') && (c
}
private static boolisL(char c) {
return ((c >='a') && (c = 'A') && (c
}
}
}
}/>19. Класс Form1.
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace interpr {
public class Form1: Form {
private Panel panel1;
private Button button1;
private Button button2;
private Button button3;
private Button button4;
private Button button5;
private ConsoleBox consoleBox1;
private Facade m_fasade;
private void Form1_Load(objectsender, EventArgs e) {
Facade.Create(consoleBox1);
m_fasade =Facade.Instance;
if (m_fasade.NotRestored){
MessageBox.Show(«Ошибка! Переменные не были успешно восстановлены.»);
}
m_fasade.Done += newFacade.CommandDoneHandler(EndExec);
m_fasade.LoadSubs();
consoleBox1.Prompt();
}
private void EndExec() {
consoleBox1.Prompt();
}
private void button1_Click(objectsender, EventArgs e) {
if (m_fasade.Busy) {
MessageBox.Show(«Немогу открыть окно функций во время выполнения комманды!»);
return;
}
FunctionsForm ff = newFunctionsForm(m_fasade);
ff.ShowDialog();
EditorForm ef =ff.LastOpenedEditorForm;
if (ef != null) {
ef.Activate();
ff.SetLastEditorFormNull();
}
else
consoleBox1.Focus();
}
private void button2_Click(objectsender, EventArgs e) {
if (m_fasade.Busy) {
MessageBox.Show(«Немогу открыть окно переменных во время выполнения комманды!»);
return;
}
VariablesForm vf = newVariablesForm(m_fasade);
vf.ShowDialog();
consoleBox1.Focus();
}
private voidconsoleBox1_GetCommand(object sender, ConsoleBoxGetCommandEventArgs e) {
if (e.Command.Length >0)
m_fasade.ExecuteCommand(e.Command);
else
consoleBox1.Prompt();
}
private void button3_Click(objectsender, EventArgs e) {
m_fasade.Restart();
if (m_fasade.NotRestored){
MessageBox.Show(«Ошибка!Переменные не были успешно восстановлены.»);
}
consoleBox1.Focus();
}
private void button5_Click(objectsender, EventArgs e) {
if (m_fasade.Busy) {
MessageBox.Show(«Немогу сохранить переменные во время выполнения программы»);
return;
}
m_fasade.SaveVariables();
consoleBox1.Focus();
}
private void Form1_Closing(objectsender, CancelEventArgs e) {
if(EditorForm.ThereAreOpened()) {
MessageBox.Show(«Сначала закройте все окна редактора кода.»);
e.Cancel = true;
return;
}
m_fasade.SaveVariables();
}
private void button4_Click(objectsender, EventArgs e) {
this.Close();
}
}
}
/>Использованная литература идокументация.
1. А. Ахо, Дж.Хопкрофт, Д. Ульман. Структуры данных и алгоритмы – М. «Вильямс», 2003.
2. Э. Гамма, Р.Хелм, Р. Джонсон, Дж. Влиссидес. Приемы объектно-ориентированногопроектирования: паттерны проектирования – СПб., «Питер», 2001.
3. Д. Грис.Конструирование компиляторов для цифровых вычислительных машин – М. «Мир»,1975.
4. Г. Корнелл, Дж.Моррисон. Программирование на VB.NET. Учебный курс – СПб., «Питер», 2002.
5. Э. Троелсен. C# и платформа .NET – СПб., «Питер», 2004.
6. MSDNLibrary – April 2003.