Прежде всего, необходимо выбрать способ представления таблицы состояний. Наиболее очевидный выбор - использование класса TtdRecordList, описанного в главе 2. Этот класс позволяет при необходимости увеличивать размер внутреннего массива. При этом заранее не нужно определять, сколько состояний может существовать для данного регулярного выражения.
В качестве подсказки будем использовать отдельные конструктивные блоки, показанные на рис. 10.6. Простейшим является выражение, которое распознает отдельный символ. Как видно из первой части рисунка 10.6, нам требуется начальное состояние, в котором будет выполняться распознавание символа, и которое будет иметь единственную связь с конечным состоянием (каждый из этих элементов будет также требоваться). Создадим простую подпрограмму, которая будет создавать новое состояние (как запись) и дописывать его в таблицу переходов. Код реализации этого простого метода приведен в листинге 10.7. Как видите, он принимает тип соответствия, символ, указатель на класс символов и две связи с другими состояниями. Конечно, не все из этих параметров будут требоваться для каждого создаваемого состояния. Но проще использовать один метод, который может создавать любой тип записи состояния, нежели целый набор таких методов, по одному для каждого возможного типа состояния.
Листинг 10.7. Добавление нового состояния в таблицу состояний
function TtdRegexEngine.rcAddState( aMatchType : TtdNFAMatchType;
aChar : AnsiChar; aCharClass : PtdCharSet;
aNextStateclass="underline" integer; aNextState2: integer): integer;
var
StateData : TNFAState;
begin
{определить поля в записи состояния}
if (aNextStatel = NewFinalState) then
StateData.sdNextState1 := succ(FTable.Count) else
StateData.sdNextState1 := aNextStatel;
StateData.sdNextState2 := aNextState2;
StateData.sdMatchType := aMatchType;
if (aMatchType = mtChar) then
StateData.sdChar := aChar else
if (aMatchType = mtClass) or (aMatchType = mtNegClass) then
StateData.sdClass := aCharClass;
{добавить новое состояние}
Result := FTable.Count;
FTable.Add(@StateData);
end;
При взгляде на первую часть рисунка 10.6 кажется, что для этой простой подпрограммы распознавания символа нужно создать два новых состояния. В действительности же можно ограничиться созданием только одного - начального состояния - и принять, что конечным состоянием будет следующее состояние, которое требуется добавить в список. Будем считать его "виртуальным" конечным состоянием. Если бы этот подход удалось применить в каждой из подпрограмм синтаксического анализа, можно было бы избавиться от необходимости создания конечного состояния, эквивалентного начальному состоянию другого подвыражения. Поэтому с этого момента будем считать, что все подпрограммы синтаксического анализа будут возвращать свое начальное состояние, и что конечное состояние, если оно действительно существует, будет номером индекса следующего состояния, которое необходимо добавить в таблицу переходов.
Из листинга 10.7 видно, что в действительности при передаче номера специального состояния NewFinalState в качестве номера следующего состояния мы определяем ссылку на индекс следующего элемента, который должен быть добавлен в таблицу переходов. Конечно, этот элемент еще не существует, но мы предполагаем, что он будет существовать, или что произойдет что-либо еще, позволяющее определить новую ссылку.
Код реализации метода распознавания отдельного символа приведен в листинге 10.8. Снова обратившись к листингу 10.5, обратите внимание на то, как был изменен первоначальный метод синтаксического анализа символа. Во-первых, мы больше не генерируем никаких исключений или сообщений об ошибках. Вместо этого мы возвращаем номер специального состояния ErrorState. Мы также отслеживаем код ошибки для каждой происходящей ошибки. Если какие-либо ошибки отсутствуют, новое состояние добавляется в таблицу переходов и возвращается как результат выполнения функции. Естественно, это состояние является начальным состоянием данного выражения. В действительности эта подпрограмма - метод класса машины обработки регулярных выражений.
Листинг 10.8. Синтаксический анализ отдельного символа и добавление его состояния
function TtdRegexEngine.rcParseChar : integer;
var
Ch : AnsiChar;
begin
{если встречается конец строки, это является ошибкой}
if (FPosn^ = #0) then begin
Result := ErrorState;
FErrorCode := recSuddenEnd;
Exit;
end;
{если текущий символ - один из метасимволов, это ошибка}
if FPosn^ in Metacharacters then begin
Result := ErrorState;
FErrorCode := recMetaChar;
Exit;
end;
{в противном случае состояние, соответствующее символу, добавляется в таблицу состояний}
{.. если он является отмененным символом: вместо него нужно извлечь следующий символ}