Смекни!
smekni.com

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

Для лексического разбора строки (разбиения на лексемы) используется класс Parser. Каждый его экземпляр используется для разбора одной строки. Класс имеет один конструктор, который принимает один параметр типа string, содержащий обрабатываемую строку. В конструкторе строка подвергается преобразованию – удаляются комментарий, если он присутствует, и лишние пробелы. Класс Parser реализует стандартные интерфейсы System.IEnumerable и System.IEnumerator. Интерфейс IEnumerable представляет объект-список того или иного вида, который допускает последовательный перебор элементов. Он имеет единственный метод IEnumeratorGetEnumerator(). Интерфейс IEnumerator представляет объект, который используется для перебора элементов списка. В данном случае эту роль выполняет сам объект класса Parser, поэтому метод GetEnumerator возвращает ссылку this. Этот интерфейс содержит методы MoveNext() – прейти на следующий элемент, Reset() – сброс на начало списка и свойство Current – текущий элемент списка. В данном случае объект Parserрассматривается как список строк-лексем, входящих в состав разбираемой строки. Свойство Current доступно только для чтения и его блок get содержит вызов метода privatestringGetCurrent(), выделяющего текущую лексему из строки. Строка делится на лексемы следующих видов:

· строковая константа;

· идентификатор;

· число (целое или вещественное, возможно, в экспоненциальной форме);

· служебный символ;

· составной служебный символ (‘:=’, ‘<=’, ‘>=’, ‘~=’, ‘<>’).

Метод GetCurrent() выделяет в строке длинную возможную лексему, начинающуюся с текущей позиции.

Кроме того, класс Parser имеет два открытых статических метода: boolIsID(string) – является ли данная строка корректным идентификатором и boolIsUserID(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, объект которого содержит информацию о месте ошибки (имя функции и номер строки).