Рефераты. Интерактивный интерпретатор

Рис. 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.

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17



2012 © Все права защищены
При использовании материалов активная ссылка на источник обязательна.