Смекни!
smekni.com

Интерактивный интерпретатор (стр. 4 из 12)

Следует также отметить интерфейс 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() – получить значение. Вычисление значения выражения по рассмотренном выше алгоритму производится в методе VarBaseExpression.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() позволяют определить тип значения. Они используют оператор RTTIis языка 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, метод VarBasePerform(ArgListal), выполняет операцию (функцию) над аргументами, содержащимися в объекте ArgList, передаваемыми в качестве параметров. Этот метод возвращает значение, являющееся результатом операции (функции). Никакого аналога типа void не предусмотрено – операция (функция) может не вернуть то или иное значение лишь в случае ошибки. От класса Operation унаследован класс SubName, представляющий пользовательскую функцию по ее имени, и многочисленные классы, представляющие стандартные операции и встроенные функции. Последние являются вложенными в сам класс Operation, притом имеют спецификатор доступа private. Для каждого из них в классе Operation имеется открытое статическое поле только для чтения, инициализирующееся объектом соответствующего типа. Создание других объектов этих вложенных классов невозможно. Здесь также использован паттерн Singleton. Кроме того, можно говорить о применении паттерна Strategy – объект класса Call (контекст) конфигурируется объектом одного из классов, производных от Operation (стратегия), таким образом, для различного поведения (выполнения различных операций и функций) используется один и тот же интерфейс. Диаграмма классов, поясняющая структуру паттерна Strategy применительно к данному случаю, приведена на рис. 5.