Синтаксический анализатор

«Синтаксический анализ (парсинг) — это процесс сопоставления линейной последовательности лексем языка с его формальной грамматикой. Результатом обычно является дерево разбора (синтаксическое дерево)»
Формальная грамматика состоит из таких компонентов как:

  • Лексем, научно называется терминал.
  • Нетерминал - объект, обозначающий какую-либо сущность языка. В нашем случае это сложение, вычитание, … (не путать с соответ. лексемой).
  • Правил — это объект из двух частей, левой и правой. В частях могут содержаться терминалы и нетерминалы.
  • Начальное правило.
Так в нашем случае будут следующие нетерминалы:
  • CValue — Число либо скобочное выражение
  • SummaValue — Сложение чисел
  • MultipleValue — Умножение чисел.
По формальной грамматике строиться в дальнейшем код программы.
Формальная грамматика может быть представлена нескольких формах:

Текстовая форма грамматики

  1. SummaValueMultipleValue SummaOperatorToken SummaValue
  2. SummaValueMultipleValue
  3. MultipleValueCValue MultipleOperatorToken SummaValue
  4. MultipleValueCValue
  5. CValueNumberToken
  6. CValueOpenBraceToken SummaValue CloseBraceToken
  7. CValueSummaOperatorToken NumberToken
Выше приведено семь правил, каждое правило состоит из двух частей, левой и правой разделенных стрелкой, терминалы выделены курсивом, нетерминалы — жирным текстом, начальное правило задано первым.
Седьмое правило используется когда перед числом необходимо поставить знак.
Существуют и другие не менее известные текстовые формы

Нотация Бэкуса — Наура


<SummaValue> ::= <MultipleValue> { SummaOperatorToken <SummaValue> }
<MultipleValue> ::= <CValue> { MultipleOperatorToken <SummaValue> }
<CValue> ::= NumberToken
| OpenBraceToken <SummaValue> CloseBraceToken
| SummaOperatorToken NumberToken



Терминалы представлены как есть, нетерминалы в угловых скобках, правила разделяются двумя двоеточиями и знаком равно.
Альтернативные комбинации правых частей правил разделяются вертикальной чертой.
В фигурных скобках заданы терминалы и не терминалы, которые могут отсутствовать или повторяться несколько раз.
Еще так же согласно данной нотации в квадратных скобках заключаются терминалы и нетерминалы которые могут отсутствовать, но не повторяться.

Графическая форма грамматики


Код синтаксического анализатора


Будем использовать формальную грамматику как средство для написание кода синтаксического анализатора.
Повторюсь что задача синтаксического анализа — «процесс сопоставления линейной последовательности лексем языка с его формальной грамматикой». Описание грамматики мы рассмотрели.
Синтаксический анализатор представим как такую функцию:


/**
* Анализирует список лексем начиная с указанного индекса (0 - начало),
* и если входная последовательность лексем совпадает, то возвращает результат
* @param beginIndex Индекс лексемы с которой начать анализ
* @param tokens Список лексем
* @return Результат анализа либо null, если входная последовательность лексем не удовлетворяет правилу.
*/
public abstract Result parse(int beginIndex, List<Token> tokens);



Так рассмотрим правило CValue


<CValue> ::= NumberToken
| OpenBraceToken <SummaValue> CloseBraceToken
| SummaOperatorToken NumberToken



Оно состоит из трех вариантов, каждый из которых начинается с терминала и второе правило содержит один нетерминал.
Код в данном случае этого анализатора будет такой (псевдокод):

CValue( beginIndex, tokens ){
// Сопоставление лексем согласно первому варианту
if ( matched( beginIndex, tokens, NumberToken ) ){

// Вычисления
return результат
} else
// Сопоставление лексем согласно второму варианту
if ( matched( beginIndex, tokens, OpenBraceToken, SummaValue, CloseBraceToken ) ){

// Вычисления
return результат
} else
// Сопоставление лексем согласно третьему варианту
if ( matched( beginIndex, tokens, SummaOperatorToken, NumberToken ) ){

// Вычисления
return результат
}
return Ошибка
}



В данном коде присутствует функция matched, ее задача сопоставить лексемы согласно переданным параметрам, где параметры могут быть двух типов: терминал (лексема) и нетерминал (функция Result parse(int beginIndex, List<Token> tokens) — функция синтаксического анализатора). А результат — это массив распознанных терминалы/нетерминалы.
Код функции matched таков:


/**
* Сравнивает последовательность лексем с шаблоном.<br/>
* Шаблоном состоит из массива экземпляров SyntaxParser, либо классов лексем (NumberToken.class, и др.).
* Результатом анализа является:
* <ul>
* <li>Для классы лексемы - ее объект</li>
* <li>Для SyntaxParser - результат вызова метода parse соответствующего объекта</li>
* </ul>
* Кол-во объектов и их последовательность в массиве результата, соответствует шаблону
* @param beginIndex Индекс лексемы с которой начать анализ
* @param tokens Список лексем
* @param pattern Шаблон
* @return Результат анализа либо null, если входная последовательность лексем не удовлетворяет шаблону.
*/
public Result[] match(int beginIndex, List<Token> tokens, Object ... pattern )
{
if( beginIndex<0 )return null;
if( tokens==null )return null;
if( beginIndex>=tokens.size() )return null;

int tokIdx = beginIndex;

// Результат будет здесь
Result[] result = new Result[pattern.length];
int resIdx = 0;

// Последовательно берем элементы шаблона
for( Object p : pattern ){
// Это терминал (лексема) ?
if( p instanceof Class ){
if( tokIdx>=tokens.size() )return null;
Token tok = tokens.get(tokIdx);

// Лексема соответствует ожидаемой ?
if( ((Class)p).isAssignableFrom(tok.getClass()) ){
result[resIdx] = new Result(tok, tokIdx+1);
resIdx++;
tokIdx++; // переходим к следующей лексеме
}else{
return null;
}
// Это нетерминал ?
}else if( p instanceof SyntaxParser ){
// Анализируем последовательность согласно нетерминалу
Result r = ((SyntaxParser)p).parse(tokIdx, tokens);

// Последовательность лексем соответствует нетерминалу ?
if( r!=null ){
tokIdx = r.getNext(); // переходим к концу где остановился нетерминал
result[resIdx] = r;
resIdx++;
}else{
return null;
}
}else{
return null;
}
}
return result;
}



Так будет выглядеть фрагмент кода анализа CValue для выражения OpenBraceToken <SummaValue> CloseBraceToken.


...
Result[] r2 = match(
beginIndex, tokens,
OpenBraceToken.class, SummaValue.instance, CloseBraceToken.class );

if( r2!=null ){
CValue cv = new CValue();
cv.inBraceValue = (Value)r2[1].getResult();
return new Result(cv, r2[2].getNext());
}
...



Все нетерминалы будут дочерними классами класса SyntaxParser. Код класса SyntaxParser будет примерно следующим:


/**
* Синтаксический анализатор (Синтаксический парсер)
*/
public abstract class SyntaxParser
{
/**
* Результат синтаксического анализа
*/
public static class Result
{
private int next = -1;
private Object result = null;

/**
* Конструктор
* @param result Результат анализа - вычисленное значение
* @param next Индекс следующей лексемы, после результата
*/
public Result(Object result,int next)
{
this.result = result;
this.next = next;
}

/**
* Возвращает индекс следующей лексемы, после результата
* @return Индекс следующей лексемы
*/
public int getNext()
{
return next;
}

/**
* Возвращает результат анализа - вычисленное значение
* @return Результат анализа - вычисленное значение
*/
public Object getResult()
{
return result;
}
}

/**
* Анализирует список лексем начиная с указанного индекса (0 - начало),
* и если входная последовательность лексем совпадает, то возвращает результат
* @param beginIndex Индекс лексемы с которой началь анализ
* @param tokens Список лексем
* @return Результат анализа либо null, если входная последовательность лексем не удолетворяет правилу.
*/
public abstract Result parse(int beginIndex, List<Token> tokens);

/**
* Сравнивает последовательность лексем с шаблоном.<br/>
* Шаблоном состоит из массива экземпляров SyntaxParser, либо классов лексем (NumberToken.class, и др.).
* Результатом анализа является:
* <ul>
* <li>Для классы лексемы - ее объект</li>
* <li>Для SyntaxParser - результат вызова метода parse соответствующего объекта</li>
* </ul>
* Кол-во объектов и их последовательность в массиве результата, соответствует шаблону
* @param beginIndex Индекс лексемы с которой началь анализ
* @param tokens Список лексем
* @param pattern Шаблон
* @return Результат анализа либо null, если входная последовательность лексем не удолетворяет шаблону.
*/
public Result[] match(int beginIndex, List<Token> tokens, Object ... pattern )
...
}

Комментариев нет:

Отправить комментарий