скачать рефераты
  RSS    

Меню

Быстрый поиск

скачать рефераты

скачать рефератыКурсовая работа: Интерактивный интерпретатор

Как было сказано выше, ряд операторов может быть использован только в функциях. Соответствующие классы реализуют интерфейс 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() (аналогично параметру функции), затем в результат дописываются имя массива и операция обращения к элементу массива.

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

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

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


Новости

Быстрый поиск

Группа вКонтакте: новости

Пока нет

Новости в Twitter и Facebook

  скачать рефераты              скачать рефераты

Новости

скачать рефераты

© 2010.