home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue70 / alfresco / AARegex.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2001-05-08  |  39.3 KB  |  1,286 lines

  1. {*********************************************************}
  2. {* AARegex                                               *}
  3. {* Copyright (c) Julian M Bucknall 2001                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Algorithms Alfresco: Regular expression classes       *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AARegex;
  14.  
  15. interface
  16.  
  17. {Notes: these classes parse regular expressions that follow this
  18.         grammar:
  19.  
  20.         <anchorexpr> ::= <expr> |
  21.                          '^' <expr> |
  22.                          <expr> '$' |
  23.                          '^' <expr> '$'
  24.         <expr> ::= <term> |
  25.                    <term> '|' <expr>                 - alternation
  26.         <term> ::= <factor> |
  27.                    <factor><term>                    - concatenation
  28.         <factor> ::= <atom> |
  29.                      <atom> '?' |                    - zero or one
  30.                      <atom> '*' |                    - zero or more
  31.                      <atom> '+'                      - one or more
  32.         <atom> ::= <char> |
  33.                    '.' |                             - any char
  34.                    '(' <expr> ') |                   - parentheses
  35.                    '[' <charclass> ']' |             - normal class
  36.                    '[^' <charclass> ']'              - negated class
  37.         <charclass> ::= <charrange> |
  38.                         <charrange><charclass>
  39.         <charrange> ::= <ccchar> |
  40.                         <ccchar> '-' <ccchar>
  41.         <char> ::= <any character except metacharacters> |
  42.                    '\' <any character at all>
  43.         <ccchar> ::= <any character except '-' and ']'> |
  44.                      '\' <any character at all>
  45.  
  46.         This means that parentheses have maximum precedence, followed
  47.         by square brackets, followed by the closure operators,
  48.         followed by concatenation, finally followed by alternation.
  49. }
  50.  
  51. {turn this compiler define on to log the parsing progress and the
  52.  final transition table; file is c:\regexparse.log}
  53. {$DEFINE LogParse}
  54.  
  55. uses
  56.   SysUtils,
  57.   Classes,
  58.   AAIntDeq,
  59.   AAIntLst;
  60.  
  61. type
  62.   TaaRegexParser = class
  63.     private
  64.       FRegexStr : string;
  65.       FPosn     : PAnsiChar;
  66.     protected
  67.       procedure rpParseAtom;
  68.       procedure rpParseCCChar;
  69.       procedure rpParseChar;
  70.       procedure rpParseCharClass;
  71.       procedure rpParseCharRange;
  72.       procedure rpParseExpr;
  73.       procedure rpParseFactor;
  74.       procedure rpParseTerm;
  75.     public
  76.       constructor Create(const aRegexStr : string);
  77.       destructor Destroy; override;
  78.  
  79.       function Parse(var aErrorPos : integer) : boolean;
  80.   end;
  81.  
  82. type
  83.   PaaCharSet = ^TaaCharSet;
  84.   TaaCharSet = set of char;
  85.  
  86.   TaaNFAMatchType = (  {types of matching performed...}
  87.      mtNone,           {..no match (an epsilon no-cost move)}
  88.      mtAnyChar,        {..any character}
  89.      mtChar,           {..a particular character}
  90.      mtClass,          {..a character class}
  91.      mtNegClass,       {..a negated character class}
  92.      mtTerminal,       {..the final state--no matching}
  93.      mtUnused);        {..an unused state--no matching}
  94.  
  95.   TaaRegexError = (    {error codes for invalid regex strings}
  96.      recNone,          {..no error}
  97.      recSuddenEnd,     {..unexpected end of string}
  98.      recMetaChar,      {..read metacharacter, but needed normal char}
  99.      recNoCloseParen,  {..expected close paren, but not there}
  100.      recExtraChars     {..not at end of string after parsing regex}
  101.      );
  102.  
  103.   TaaUpcaseChar = function (aCh : char) : char;
  104.  
  105.   TaaRegexCompiler = class
  106.     private
  107.       FAnchorEnd  : boolean;
  108.       FAnchorStart: boolean;
  109.       FErrorCode  : TaaRegexError;
  110.       FIgnoreCase : boolean;
  111.       FPosn       : PAnsiChar;
  112.       FRegexStr   : string;
  113.       FStartState : integer;
  114.       FTable      : TList;
  115.       FUpcase     : TaaUpcaseChar;
  116.       {$IFDEF LogParse}
  117.       Log : System.Text;
  118.       {$ENDIF}
  119.     protected
  120.       procedure rcSetIgnoreCase(aValue : boolean);
  121.       procedure rcSetRegexStr(const aRegexStr : string);
  122.       procedure rcSetUpcase(aValue : TaaUpcaseChar);
  123.  
  124.       procedure rcClear;
  125.       procedure rcLevel1Optimize;
  126.       procedure rcLevel2Optimize;
  127.       function rcMatchSubString(const S   : string;
  128.                                 StartPosn : integer) : boolean;
  129.       function rcAddState(aMatchType : TaaNFAMatchType;
  130.                           aChar      : char;
  131.                           aCharClass : PaaCharSet;
  132.                           aNextState1: integer;
  133.                           aNextState2: integer) : integer;
  134.       function rcSetState(aState     : integer;
  135.                           aNextState1: integer;
  136.                           aNextState2: integer) : integer;
  137.  
  138.       function rcParseAnchorExpr : integer;
  139.       function rcParseAtom : integer;
  140.       function rcParseCCChar : char;
  141.       function rcParseChar : integer;
  142.       function rcParseCharClass(aClass : PaaCharSet) : boolean;
  143.       function rcParseCharRange(aClass : PaaCharSet) : boolean;
  144.       function rcParseExpr : integer;
  145.       function rcParseFactor : integer;
  146.       function rcParseTerm : integer;
  147.  
  148.       procedure rcWalkNoCostTree(aList  : TaaIntList;
  149.                                  aState : integer);
  150.  
  151.       {$IFDEF LogParse}
  152.       procedure rcDumpTable;
  153.       {$ENDIF}
  154.     public
  155.       constructor Create(const aRegexStr : string);
  156.       destructor Destroy; override;
  157.  
  158.       function Parse(var aErrorPos : integer;
  159.                      var aErrorCode: TaaRegexError) : boolean;
  160.       function MatchString(const S : string) : integer;
  161.  
  162.  
  163.       property IgnoreCase : boolean
  164.                   read FIgnoreCase write rcSetIgnoreCase;
  165.       property RegexString : string
  166.                   read FRegexStr write rcSetRegexStr;
  167.       property Upcase : TaaUpcaseChar
  168.                   read FUpcase write rcSetUpcase;
  169.   end;
  170.  
  171. implementation
  172.  
  173. const
  174.   MetaCharacters : set of char =
  175.                    ['[', ']', '(', ')', '|', '*', '+', '?', '-', '.',
  176.                     '^', '$'];
  177.   {some handy constants}
  178.   UnusedState = -1;
  179.   NewFinalState = -2;
  180.   CreateNewState = -3;
  181.   ErrorState = -4;
  182.   MustScan = -5;
  183.  
  184. type
  185.   PaaNFAState = ^TaaNFAState;
  186.   TaaNFAState = record
  187.     sdNextState1: integer;
  188.     sdNextState2: integer;
  189.     sdNextList  : TaaIntList;
  190.     sdClass     : PaaCharSet;
  191.     sdMatchType : TaaNFAMatchType;
  192.     sdChar      : char;
  193.   end;
  194.  
  195.  
  196. {===TaaRegexParser===================================================}
  197. constructor TaaRegexParser.Create(const aRegexStr : string);
  198. begin
  199.   inherited Create;
  200.   FRegexStr := aRegexStr;
  201. end;
  202. {--------}
  203. destructor TaaRegexParser.Destroy;
  204. begin
  205.   inherited Destroy;
  206. end;
  207. {--------}
  208. function TaaRegexParser.Parse(var aErrorPos : integer) : boolean;
  209. begin
  210.   Result := true;
  211.   aErrorPos := 0;
  212.   FPosn := PAnsiChar(FRegexStr);
  213.   try
  214.     rpParseExpr;
  215.     if (FPosn^ <> #0) then begin
  216.       Result := false;
  217.       aErrorPos := FPosn - PAnsiChar(FRegexStr) + 1;
  218.     end;
  219.   except
  220.     on E:Exception do begin
  221.       Result := false;
  222.       aErrorPos := FPosn - PAnsiChar(FRegexStr) + 1;
  223.     end;
  224.   end;
  225. end;
  226. {--------}
  227. procedure TaaRegexParser.rpParseAtom;
  228. begin
  229.   case FPosn^ of
  230.     '(' : begin
  231.             inc(FPosn);
  232.             writeln('open paren');
  233.             rpParseExpr;
  234.             if (FPosn^ <> ')') then
  235.               raise Exception.Create('Regex error: expecting a closing parenthesis');
  236.             inc(FPosn);
  237.             writeln('close paren');
  238.           end;
  239.     '[' : begin
  240.             inc(FPosn);
  241.             if (FPosn^ = '^') then begin
  242.               inc(FPosn);
  243.               writeln('negated char class');
  244.               rpParseCharClass;
  245.             end
  246.             else begin
  247.               writeln('normal char class');
  248.               rpParseCharClass;
  249.             end;
  250.             inc(FPosn);
  251.           end;
  252.     '.' : begin
  253.             inc(FPosn);
  254.             writeln('any character');
  255.           end;
  256.   else
  257.     rpParseChar;
  258.   end;{case}
  259. end;
  260. {--------}
  261. procedure TaaRegexParser.rpParseCCChar;
  262. begin
  263.   if (FPosn^ = #0) then
  264.     raise Exception.Create('Regex error: expecting a normal character, found null terminator');
  265.   if FPosn^ in [']', '-'] then
  266.     raise Exception.Create('Regex error: expecting a normal character, ie found a metacharacter');
  267.   if (FPosn^ = '\') then begin
  268.     inc(FPosn);
  269.     writeln('escaped ccchar ', FPosn^);
  270.     inc(FPosn);
  271.   end
  272.   else begin
  273.     writeln('ccchar ', FPosn^);
  274.     inc(FPosn);
  275.   end;
  276. end;
  277. {--------}
  278. procedure TaaRegexParser.rpParseChar;
  279. begin
  280.   if (FPosn^ = #0) then
  281.     raise Exception.Create('Regex error: expecting a normal character, found null terminator');
  282.   if FPosn^ in MetaCharacters then
  283.     raise Exception.Create('Regex error: expecting a normal character, ie found a metacharacter');
  284.   if (FPosn^ = '\') then begin
  285.     inc(FPosn);
  286.     writeln('escaped char ', FPosn^);
  287.     inc(FPosn);
  288.   end
  289.   else begin
  290.     writeln('char ', FPosn^);
  291.     inc(FPosn);
  292.   end;
  293. end;
  294. {--------}
  295. procedure TaaRegexParser.rpParseCharClass;
  296. begin
  297.   rpParseCharRange;
  298.   if (FPosn^ <> ']') then
  299.     rpParseCharClass;
  300. end;
  301. {--------}
  302. procedure TaaRegexParser.rpParseCharRange;
  303. begin
  304.   rpParseCCChar;
  305.   if (FPosn^ = '-') then begin
  306.     inc(FPosn);
  307.     writeln('--range to--');
  308.     rpParseCCChar;
  309.   end;
  310. end;
  311. {--------}
  312. procedure TaaRegexParser.rpParseExpr;
  313. begin
  314.   rpParseTerm;
  315.   if (FPosn^ = '|') then begin
  316.     inc(FPosn);
  317.     writeln('alternation');
  318.     rpParseExpr;
  319.   end;
  320. end;
  321. {--------}
  322. procedure TaaRegexParser.rpParseFactor;
  323. begin
  324.   rpParseAtom;
  325.   case FPosn^ of
  326.     '?' : begin
  327.             inc(FPosn);
  328.             writeln('zero or one');
  329.           end;
  330.     '*' : begin
  331.             inc(FPosn);
  332.             writeln('zero or more');
  333.           end;
  334.     '+' : begin
  335.             inc(FPosn);
  336.             writeln('one or more');
  337.           end;
  338.   end;{case}
  339. end;
  340. {--------}
  341. procedure TaaRegexParser.rpParseTerm;
  342. begin
  343.   rpParseFactor;
  344.   {Note: we have to "break the grammar" here. We've parsed a regular
  345.          subexpression and we're possibly following on with another
  346.          regular subexpression. There's no nice operator to key off
  347.          for concatenation: we just have to know that for
  348.          concatenating two subexpressions, the current character will
  349.          be
  350.            - an open parenthesis
  351.            - an open square bracket
  352.            - an any char operator
  353.            - a character that's not a metacharacter
  354.          i.e., the three possibilities for the start of an "atom" in
  355.          our grammar}
  356.   if (FPosn^ = '(') or
  357.      (FPosn^ = '[') or
  358.      (FPosn^ = '.') or
  359.      ((FPosn^ <> #0) and not (FPosn^ in MetaCharacters)) then
  360.     rpParseTerm;
  361. end;
  362. {====================================================================}
  363.  
  364.  
  365. {===TaaRegexCompiler===================================================}
  366. constructor TaaRegexCompiler.Create(const aRegexStr : string);
  367. begin
  368.   inherited Create;
  369.   FRegexStr := aRegexStr;
  370.   FIgnoreCase := true;
  371.   FUpcase := System.Upcase;
  372.   FTable := TList.Create;
  373.   FTable.Capacity := 64;
  374. end;
  375. {--------}
  376. destructor TaaRegexCompiler.Destroy;
  377. begin
  378.   if (FTable <> nil) then begin
  379.     rcClear;
  380.     FTable.Free;
  381.   end;
  382.   inherited Destroy;
  383. end;
  384. {--------}
  385. function TaaRegexCompiler.MatchString(const S : string) : integer;
  386. var
  387.   i : integer;
  388.   ErrorPos  : integer;
  389.   ErrorCode : TaaRegexError;
  390. begin
  391.   {if the regex string hasn't been parsed yet, do so}
  392.   if (FTable.Count = 0) then begin
  393.     if not Parse(ErrorPos, ErrorCode) then begin
  394.       raise Exception.Create(
  395.          Format('The regex was invalid at position %d', [ErrorPos]));
  396.     end;
  397.   end;
  398.   {now try and see if the string matches (empty strings don't)}
  399.   Result := 0;
  400.   if (S <> '') then
  401.     {if the regex specified a start anchor, then we only need to check
  402.      the string starting at the first position}
  403.     if FAnchorStart then begin
  404.       if rcMatchSubString(S, 1) then
  405.         Result := 1;
  406.     end
  407.     {otherwise we try and match the string at every position and
  408.      return at the first success}
  409.     else begin
  410.       for i := 1 to length(S) do
  411.         if rcMatchSubString(S, i) then begin
  412.           Result := i;
  413.           Break;
  414.         end;
  415.     end;
  416. end;
  417. {--------}
  418. function TaaRegexCompiler.Parse(var aErrorPos : integer;
  419.                                 var aErrorCode: TaaRegexError)
  420.                                                             : boolean;
  421.   {$IFDEF LogParse}
  422.   procedure WriteError(aErrorPos : integer;
  423.                        aErrorCode: TaaRegexError);
  424.   begin
  425.     writeln(Log, '***parse error found at ', aErrorPos);
  426.     case aErrorCode of
  427.       recNone         : writeln(Log, '-->no error');
  428.       recSuddenEnd    : writeln(Log, '-->unexpected end of regex');
  429.       recMetaChar     : writeln(Log, '-->found metacharacter in wrong place');
  430.       recNoCloseParen : writeln(Log, '-->missing close paren');
  431.       recExtraChars   : writeln(Log, '-->extra chars after valid regex');
  432.     end;
  433.     writeln(Log, '"', FRegexStr, '"');
  434.     writeln(Log, '^':succ(aErrorPos));
  435.   end;
  436.   {$ENDIF}
  437. begin
  438.   {$IFDEF LogParse}
  439.   System.Assign(Log, 'c:\regexparse.log');
  440.   System.Rewrite(Log);
  441.   try
  442.     writeln(Log, 'Parsing regex: "', FRegexStr, '"');
  443.   {$ENDIF}
  444.  
  445.   {clear the current transition table}
  446.   rcClear;
  447.   {empty regex strings are not allowed}
  448.   if (FRegexStr = '') then begin
  449.     Result := false;
  450.     aErrorPos := 1;
  451.     aErrorCode := recSuddenEnd;
  452.  
  453.     {$IFDEF LogParse}
  454.     WriteError(aErrorPos, aErrorCode);
  455.     {$ENDIF}
  456.  
  457.     Exit;
  458.   end;
  459.   {parse the regex string}
  460.   FPosn := PAnsiChar(FRegexStr);
  461.   FStartState := rcParseAnchorExpr;
  462.   {if an error occurred or we're not at the end of the regex string,
  463.    clear the transition table, return false and the error position}
  464.   if (FStartState = ErrorState) or (FPosn^ <> #0) then begin
  465.     if (FStartState <> ErrorState) and (FPosn^ <> #0) then
  466.       FErrorCode := recExtraChars;
  467.     rcClear;
  468.     Result := false;
  469.     aErrorPos := succ(FPosn - PAnsiChar(FRegexStr));
  470.     aErrorCode := FErrorCode;
  471.  
  472.     {$IFDEF LogParse}
  473.     WriteError(aErrorPos, aErrorCode);
  474.     {$ENDIF}
  475.   end
  476.   {otherwise add a terminal state, optimize, return true}
  477.   else begin
  478.     rcAddState(mtTerminal, #0, nil, UnusedState, UnusedState);
  479.  
  480.     {$IFDEF LogParse}
  481.     writeln(Log, 'Pre-level1 optimization...');
  482.     rcDumpTable;
  483.     {$ENDIF}
  484.  
  485.     rcLevel1Optimize;
  486.  
  487.     {$IFDEF LogParse}
  488.     writeln(Log, 'Pre-level2 optimization...');
  489.     rcDumpTable;
  490.     {$ENDIF}
  491.  
  492.     rcLevel2Optimize;
  493.  
  494.     Result := true;
  495.     aErrorPos := 0;
  496.     aErrorCode := recNone;
  497.  
  498.     {$IFDEF LogParse}
  499.     writeln(Log, 'Final table...');
  500.     rcDumpTable;
  501.     {$ENDIF}
  502.   end;
  503.  
  504.   {$IFDEF LogParse}
  505.   finally
  506.     System.Close(Log);
  507.   end;
  508.   {$ENDIF}
  509. end;
  510. {--------}
  511. function TaaRegexCompiler.rcAddState(aMatchType : TaaNFAMatchType;
  512.                                      aChar      : char;
  513.                                      aCharClass : PaaCharSet;
  514.                                      aNextState1: integer;
  515.                                      aNextState2: integer) : integer;
  516. var
  517.   StateData : PaaNFAState;
  518. begin
  519.   {create the new state record}
  520.   StateData := AllocMem(sizeof(TaaNFAState));
  521.   {set up the fields in the state record}
  522.   if (aNextState1 = NewFinalState) then
  523.     StateData^.sdNextState1 := succ(FTable.Count)
  524.   else
  525.     StateData^.sdNextState1 := aNextState1;
  526.   StateData^.sdNextState2 := aNextState2;
  527.   StateData^.sdMatchType := aMatchType;
  528.   if (aMatchType = mtChar) then
  529.     StateData^.sdChar := aChar
  530.   else if (aMatchType = mtClass) or (aMatchType = mtNegClass) then
  531.     StateData^.sdClass := aCharClass;
  532.   {add the new state}
  533.   Result := FTable.Count;
  534.   FTable.Add(StateData);
  535. end;
  536. {--------}
  537. procedure TaaRegexCompiler.rcClear;
  538. var
  539.   i : integer;
  540.   StateData : PaaNFAState;
  541. begin
  542.   {free all items in the state transition table}
  543.   for i := 0 to pred(FTable.Count) do begin
  544.     StateData := PaaNFAState(FTable.List^[i]);
  545.     if (StateData <> nil) then begin
  546.       with StateData^ do begin
  547.         if (sdMatchType = mtClass) or
  548.            (sdMatchType = mtNegClass) then
  549.           if (sdClass <> nil) then
  550.             FreeMem(StateData^.sdClass);
  551.         sdNextList.Free; 
  552.       end;
  553.       Dispose(StateData);
  554.     end;
  555.   end;
  556.   {clear the state transition table}
  557.   FTable.Clear;
  558.   FTable.Capacity := 64;
  559.   FAnchorStart := false;
  560.   FAnchorEnd := false;
  561. end;
  562. {--------}
  563. {$IFDEF LogParse}
  564. procedure TaaRegexCompiler.rcDumpTable;
  565. var
  566.   i, j : integer;
  567. begin
  568.   if (FTable.Count = 0) then
  569.     writeln(Log, 'No transition table to dump!')
  570.   else begin
  571.     writeln(Log, 'Transition table dump for "', FRegexStr, '"');
  572.     if FAnchorStart then
  573.       writeln(Log, 'anchored at start of string');
  574.     if FAnchorEnd then
  575.       writeln(Log, 'anchored at end of string');
  576.     writeln(Log, 'start state: ', FStartState:3);
  577.     for i := 0 to pred(FTable.Count) do begin
  578.       write(Log, i:3);
  579.       with PaaNFAState(FTable[i])^ do begin
  580.         case sdMatchType of
  581.           mtNone    : write(Log, '  no match');
  582.           mtAnyChar : write(Log, '  any char');
  583.           mtChar    : write(Log, '    char:', sdChar);
  584.           mtClass   : write(Log, '     class');
  585.           mtNegClass: write(Log, ' neg class');
  586.           mtTerminal: write(Log, '*******END');
  587.           mtUnused  : write(Log, '        --');
  588.         else
  589.           write(Log, ' **error**');
  590.         end;
  591.         if (sdNextList <> nil) then begin
  592.           write(Log, ' next:');
  593.           for j := 0 to pred(sdNextList.Count) do
  594.             write(Log, ' ', sdNextList[j]);
  595.         end
  596.         else begin
  597.           if (sdMatchType <> mtTerminal) and
  598.              (sdMatchType <> mtUnused) then begin
  599.             write(Log, ' next1: ', sdNextState1:3);
  600.             if (sdNextState2 <> UnusedState) then
  601.               write(Log, ' next2: ', sdNextState2:3);
  602.           end;
  603.         end;
  604.       end;
  605.       writeln(Log);
  606.     end;
  607.   end;
  608.   writeln(Log);
  609. end;
  610. {$ENDIF}
  611. {--------}
  612. procedure TaaRegexCompiler.rcLevel1Optimize;
  613. var
  614.   i : integer;
  615.   Walker : PaaNFAState;
  616. begin
  617.   {level 1 optimization removes all states that have only a single
  618.    no-cost move to another state}
  619.  
  620.   {cycle through all the state records, except for the last one}
  621.   for i := 0 to (FTable.Count - 2) do begin
  622.     {get this state}
  623.     with PaaNFAState(FTable.List^[i])^ do begin
  624.       {walk the chain pointed to by the first next state, unlinking
  625.        the states that are simple single no-cost moves}
  626.       Walker := PaaNFAState(FTable.List^[sdNextState1]);
  627.       while (Walker^.sdMatchType = mtNone) and
  628.             (Walker^.sdNextState2 = UnusedState) do begin
  629.         sdNextState1 := Walker^.sdNextState1;
  630.         Walker := PaaNFAState(FTable.List^[sdNextState1]);
  631.       end;
  632.       {walk the chain pointed to by the first next state, unlinking
  633.        the states that are simple single no-cost moves}
  634.       if (sdNextState2 <> UnusedState) then begin
  635.         Walker := PaaNFAState(FTable.List^[sdNextState2]);
  636.         while (Walker^.sdMatchType = mtNone) and
  637.               (Walker^.sdNextState2 = UnusedState) do begin
  638.           sdNextState2 := Walker^.sdNextState1;
  639.           Walker := PaaNFAState(FTable.List^[sdNextState2]);
  640.         end;
  641.       end;
  642.     end;
  643.   end;
  644. end;
  645. {--------}
  646. procedure TaaRegexCompiler.rcLevel2Optimize;
  647. var
  648.   i : integer;
  649. begin
  650.   {level 2 optimization removes all no-cost moves, except for those
  651.    from the start state, if that is a no-cost move state}
  652.  
  653.   {cycle through all the state records, except for the last one}
  654.   for i := 0 to (FTable.Count - 2) do begin
  655.     {get this state}
  656.     with PaaNFAState(FTable.List^[i])^ do begin
  657.       {if it's not a no-cost move state or it's the start state...}
  658.       if (sdMatchType <> mtNone) or (i = FStartState) then begin
  659.         {create the state list}
  660.         sdNextList := TaaIntList.Create;
  661.         {walk the chain pointed to by the first next state, adding
  662.          the non-no-cost states to the list}
  663.         rcWalkNoCostTree(sdNextList, sdNextState1);
  664.         {if this is the start state, and it's a no-cost move state
  665.          walk the chain pointed to by the second next state, adding
  666.          the non-no-cost states to the list}
  667.         if (sdMatchType = mtNone) then
  668.           rcWalkNoCostTree(sdNextList, sdNextState2);
  669.       end;
  670.     end;
  671.   end;
  672.  
  673.   {cycle through all the state records, except for the last one,
  674.    marking unused ones--not strictly necessary but good for debugging}
  675.   for i := 0 to (FTable.Count - 2) do begin
  676.     if (i <> FStartState) then
  677.       with PaaNFAState(FTable.List^[i])^ do begin
  678.         if (sdMatchType = mtNone) then
  679.           sdMatchType := mtUnused;
  680.       end;
  681.   end;
  682. end;
  683. {--------}
  684. function TaaRegexCompiler.rcMatchSubString(const S   : string;
  685.                                            StartPosn : integer)
  686.                                                             : boolean;
  687. var
  688.   i      : integer;
  689.   Ch     : char;
  690.   State  : integer;
  691.   Deque  : TaaIntDeque;
  692.   StrInx : integer;
  693. begin
  694.   {assume we fail to match}
  695.   Result := false;
  696.   {create the deque}
  697.   Deque := TaaIntDeque.Create(64);
  698.   try
  699.     {enqueue the special value to start scanning}
  700.     Deque.Enqueue(MustScan);
  701.     {enqueue the first state}
  702.     Deque.Enqueue(FStartState);
  703.     {prepare the string index}
  704.     StrInx := StartPosn - 1;
  705.     Ch := #0; //just to fool the compiler
  706.     {loop until the deque is empty or we run out of string}
  707.     while (StrInx <= length(S)) and not Deque.IsEmpty do begin
  708.       {pop the top state from the deque}
  709.       State := Deque.Pop;
  710.       {process the "must scan" state first}
  711.       if (State = MustScan) then begin
  712.         {if the deque is empty at this point, we might as well give up
  713.          since there are no states left to process new characters}
  714.         if not Deque.IsEmpty then begin
  715.           {if we haven't run out of string, get the character, and
  716.            enqueue the "must scan" state again}
  717.           inc(StrInx);
  718.           if (StrInx <= length(S)) then begin
  719.             if IgnoreCase then
  720.               Ch := Upcase(S[StrInx])
  721.             else
  722.               Ch := S[StrInx];
  723.             Deque.Enqueue(MustScan);
  724.           end;
  725.         end;
  726.       end
  727.       {otherwise, process the state}
  728.       else with PaaNFAState(FTable.List^[State])^ do begin
  729.         case sdMatchType of
  730.           mtNone :
  731.             begin
  732.               if (State <> FStartState) then
  733.                 Assert(false, 'no-cost states shouldn''t be seen');
  734.               for i := 0 to pred(sdNextList.Count) do
  735.                 Deque.Push(sdNextList[i]);
  736.             end;
  737.           mtAnyChar :
  738.             begin
  739.               {for a match of any character, enqueue the next states}
  740.               for i := 0 to pred(sdNextList.Count) do
  741.                 Deque.Enqueue(sdNextList[i]);
  742.             end;
  743.           mtChar :
  744.             begin
  745.               {for a match of a character, enqueue the next states}
  746.               if (Ch = sdChar) then
  747.                 for i := 0 to pred(sdNextList.Count) do
  748.                   Deque.Enqueue(sdNextList[i]);
  749.             end;
  750.           mtClass :
  751.             begin
  752.               {for a match within a class, enqueue the next states}
  753.               if (Ch in sdClass^) then
  754.                 for i := 0 to pred(sdNextList.Count) do
  755.                   Deque.Enqueue(sdNextList[i]);
  756.             end;
  757.           mtNegClass :
  758.             begin
  759.               {for a match not within a class, enqueue the next states}
  760.               if not (Ch in sdClass^) then
  761.                 for i := 0 to pred(sdNextList.Count) do
  762.                   Deque.Enqueue(sdNextList[i]);
  763.             end;
  764.           mtTerminal :
  765.             begin
  766.               {for a terminal state, the string successfully matched
  767.                if the regex had no end anchor, or we're at the end
  768.                of the string}
  769.               if (not FAnchorEnd) or (StrInx > length(S)) then begin
  770.                 Result := true;
  771.                 Exit;
  772.               end;
  773.             end;
  774.           mtUnused :
  775.             begin
  776.               Assert(false, 'unused states shouldn''t be seen');
  777.             end;
  778.         end;
  779.       end;
  780.     end;
  781.     {if we reach this point we've either exhausted the deque or we've
  782.      run out of string; if the former, the substring did not match
  783.      since there are no more states. If the latter, we need to check
  784.      the states left on the deque to see if one is the terminating
  785.      state; if so the string matched the regular expression defined by
  786.      the transition table}
  787.     while not Deque.IsEmpty do begin
  788.       State := Deque.Pop;
  789.       with PaaNFAState(FTable.List^[State])^ do begin
  790.         case sdMatchType of
  791.           mtTerminal :
  792.             begin
  793.               {for a terminal state, the string successfully matched
  794.                if the regex had no end anchor, or we're at the end
  795.                of the string}
  796.               if (not FAnchorEnd) or (StrInx > length(S)) then begin
  797.                 Result := true;
  798.                 Exit;
  799.               end;
  800.             end;
  801.         end;{case}
  802.       end;
  803.     end;
  804.   finally
  805.     Deque.Free;
  806.   end;
  807. end;
  808. {--------}
  809. function TaaRegexCompiler.rcParseAnchorExpr : integer;
  810. begin
  811.   {check for an initial '^'}
  812.   if (FPosn^ = '^') then begin
  813.     FAnchorStart := true;
  814.     inc(FPosn);
  815.  
  816.     {$IFDEF LogParse}
  817.     writeln(Log, 'parsed start anchor');
  818.     {$ENDIF}
  819.   end;
  820.  
  821.   {parse an expression}
  822.   Result := rcParseExpr;
  823.  
  824.   {if we were successful, check for the final '$'}
  825.   if (Result <> ErrorState) then begin
  826.     if (FPosn^ = '$') then begin
  827.       FAnchorEnd := true;
  828.       inc(FPosn);
  829.  
  830.       {$IFDEF LogParse}
  831.       writeln(Log, 'parsed end anchor');
  832.       {$ENDIF}
  833.     end;
  834.   end;
  835. end;
  836. {--------}
  837. function TaaRegexCompiler.rcParseAtom : integer;
  838. var
  839.   MatchType : TaaNFAMatchType;
  840.   CharClass : PaaCharSet;
  841. begin
  842.   case FPosn^ of
  843.     '(' :
  844.       begin
  845.         {move past the open parenthesis}
  846.         inc(FPosn);
  847.  
  848.         {$IFDEF LogParse}
  849.         writeln(Log, 'parsed open paren');
  850.         {$ENDIF}
  851.  
  852.         {parse a complete regex between the parentheses}
  853.         Result := rcParseExpr;
  854.         if (Result = ErrorState) then
  855.           Exit;
  856.         {if the current character is not a close parenthesis,
  857.          there's an error}
  858.         if (FPosn^ <> ')') then begin
  859.           FErrorCode := recNoCloseParen;
  860.           Result := ErrorState;
  861.           Exit;
  862.         end;
  863.         {move past the close parenthesis}
  864.         inc(FPosn);
  865.  
  866.         {$IFDEF LogParse}
  867.         writeln(Log, 'parsed close paren');
  868.         {$ENDIF}
  869.       end;
  870.     '[' :
  871.       begin
  872.         {move past the open square bracket}
  873.         inc(FPosn);
  874.  
  875.         {$IFDEF LogParse}
  876.         writeln(Log, 'parsed open square bracket (start of class)');
  877.         {$ENDIF}
  878.  
  879.         {if the first character in the class is a '^' then the
  880.          class if negated, otherwise it's a normal one}
  881.         if (FPosn^ = '^') then begin
  882.           inc(FPosn);
  883.           MatchType := mtNegClass;
  884.  
  885.           {$IFDEF LogParse}
  886.           writeln(Log, 'it is a negated class');
  887.           {$ENDIF}
  888.         end
  889.         else begin
  890.           MatchType := mtClass;
  891.  
  892.           {$IFDEF LogParse}
  893.           writeln(Log, 'it is a normal class');
  894.           {$ENDIF}
  895.         end;
  896.         {allocate the class character set and parse the character
  897.          class; this will return either with an error, or when the
  898.          closing square bracket is encountered}
  899.         New(CharClass);
  900.         CharClass^ := [];
  901.         if not rcParseCharClass(CharClass) then begin
  902.           Dispose(CharClass);
  903.           Result := ErrorState;
  904.           Exit;
  905.         end;
  906.         {move past the closing square bracket}
  907.         Assert(FPosn^ = ']',
  908.                'the rcParseCharClass terminated without finding a "]"');
  909.         inc(FPosn);
  910.  
  911.         {$IFDEF LogParse}
  912.         writeln(Log, 'parsed close square bracket (end of class)');
  913.         {$ENDIF}
  914.  
  915.         {add a new state for the character class}
  916.         Result := rcAddState(MatchType, #0, CharClass,
  917.                              NewFinalState, UnusedState);
  918.       end;
  919.     '.' :
  920.       begin
  921.         {move past the period metacharacter}
  922.         inc(FPosn);
  923.  
  924.         {$IFDEF LogParse}
  925.         writeln(Log, 'parsed anychar operator "."');
  926.         {$ENDIF}
  927.  
  928.         {add a new state for the 'any character' token}
  929.         Result := rcAddState(mtAnyChar, #0, nil,
  930.                              NewFinalState, UnusedState);
  931.       end;
  932.   else
  933.     {otherwise parse a single character}
  934.     Result := rcParseChar;
  935.   end;{case}
  936. end;
  937. {--------}
  938. function TaaRegexCompiler.rcParseCCChar : char;
  939. begin
  940.   {if we hit the end of the string, it's an error}
  941.   if (FPosn^ = #0) then begin
  942.     FErrorCode := recSuddenEnd;
  943.     Result := #0;
  944.     Exit;
  945.   end;
  946.   {if the current char is a metacharacter (at least in terms of a
  947.    character class), it's an error}
  948.   if FPosn^ in [']', '-'] then begin
  949.     FErrorCode := recMetaChar;
  950.     Result := #0;
  951.     Exit;
  952.   end;
  953.   {otherwise return the character and advance past it}
  954.   if (FPosn^ = '\') then
  955.     {..it's an escaped character: get the next character instead}
  956.     inc(FPosn);
  957.   Result := FPosn^;
  958.   inc(FPosn);
  959.  
  960.   {$IFDEF LogParse}
  961.   writeln(Log, 'parsed charclass char: "', Result, '"');
  962.   {$ENDIF}
  963. end;
  964. {--------}
  965. function TaaRegexCompiler.rcParseChar : integer;
  966. var
  967.   Ch : char;
  968. begin
  969.   {if we hit the end of the string, it's an error}
  970.   if (FPosn^ = #0) then begin
  971.     Result := ErrorState;
  972.     FErrorCode := recSuddenEnd;
  973.     Exit;
  974.   end;
  975.   {if the current char is one of the metacharacters, it's an error}
  976.   if FPosn^ in MetaCharacters then begin
  977.     Result := ErrorState;
  978.     FErrorCode := recMetaChar;
  979.     Exit;
  980.   end;
  981.   {otherwise add a state for the character}
  982.   {..if it's an escaped character: get the next character instead}
  983.   if (FPosn^ = '\') then
  984.     inc(FPosn);
  985.   if IgnoreCase then
  986.     Ch := Upcase(FPosn^)
  987.   else
  988.     Ch := FPosn^;
  989.   Result := rcAddState(mtChar, Ch, nil, NewFinalState, UnusedState);
  990.   inc(FPosn);
  991.  
  992.   {$IFDEF LogParse}
  993.   writeln(Log, 'parsed char: "', Ch, '"');
  994.   {$ENDIF}
  995. end;
  996. {--------}
  997. function TaaRegexCompiler.rcParseCharClass(aClass : PaaCharSet) : boolean;
  998. begin
  999.   {assume we can't parse a character class properly}
  1000.   Result := false;
  1001.   {parse a character range; if we can't there was an error and the
  1002.    caller will take care of it}
  1003.   if not rcParseCharRange(aClass) then
  1004.     Exit;
  1005.   {if the current character was not the right bracket, parse another
  1006.    character class (note: we're removing the tail recursion here)}
  1007.   while (FPosn^ <> ']') do begin
  1008.     if not rcParseCharRange(aClass) then
  1009.       Exit;
  1010.   end;
  1011.   {if we reach here we were successful}
  1012.   Result := true;
  1013. end;
  1014. {--------}
  1015. function TaaRegexCompiler.rcParseCharRange(aClass : PaaCharSet) : boolean;
  1016. var
  1017.   StartChar : char;
  1018.   EndChar   : char;
  1019.   Ch        : char;
  1020. begin
  1021.   {assume we can't parse a character range properly}
  1022.   Result := false;
  1023.   {parse a single character; if it's null there was an error}
  1024.   StartChar := rcParseCCChar;
  1025.   if (StartChar = #0) then
  1026.     Exit;
  1027.   {if the current character is not a dash, the range consisted of a
  1028.    single character}
  1029.   if (FPosn^ <> '-') then begin
  1030.     if IgnoreCase then
  1031.       Include(aClass^, Upcase(StartChar))
  1032.     else
  1033.       Include(aClass^, StartChar)
  1034.   end
  1035.   {otherwise it's a real range, so get the character at the end of the
  1036.    range; if that's null, there was an error}
  1037.   else begin
  1038.  
  1039.     {$IFDEF LogParse}
  1040.     writeln(Log, '-range to-');
  1041.     {$ENDIF}
  1042.  
  1043.     inc(FPosn); {move past the '-'}
  1044.     EndChar := rcParseCCChar;
  1045.     if (EndChar = #0) then
  1046.       Exit;
  1047.     {build the range as a character set}
  1048.     if (StartChar > EndChar) then begin
  1049.       Ch := StartChar;
  1050.       StartChar := EndChar;
  1051.       EndChar := Ch;
  1052.     end;
  1053.     for Ch := StartChar to EndChar do begin
  1054.       Include(aClass^, Ch);
  1055.       if IgnoreCase then
  1056.         Include(aClass^, Upcase(Ch));
  1057.     end;
  1058.   end;
  1059.   {if we reach here we were successful}
  1060.   Result := true;
  1061. end;
  1062. {--------}
  1063. function TaaRegexCompiler.rcParseExpr : integer;
  1064. var
  1065.   StartState1 : integer;
  1066.   StartState2 : integer;
  1067.   EndState1   : integer;
  1068.   OverallStartState : integer;
  1069. begin
  1070.   {assume the worst}
  1071.   Result := ErrorState;
  1072.   {parse an initial term}
  1073.   StartState1 := rcParseTerm;
  1074.   if (StartState1 = ErrorState) then
  1075.     Exit;
  1076.   {if the current character is *not* a pipe character, no alternation
  1077.    is present so return the start state of the initial term as our
  1078.    start state}
  1079.   if (FPosn^ <> '|') then
  1080.     Result := StartState1
  1081.   {otherwise, we need to parse another expr and join the two together
  1082.    in the transition table}
  1083.   else begin
  1084.  
  1085.     {$IFDEF LogParse}
  1086.     writeln(Log, 'OR (alternation)');
  1087.     {$ENDIF}
  1088.  
  1089.     {advance past the pipe}
  1090.     inc(FPosn);
  1091.     {the initial term's end state does not exist yet (although there
  1092.      is a state in the term that points to it), so create it}
  1093.     EndState1 := rcAddState(mtNone, #0, nil, UnusedState, UnusedState);
  1094.     {for the OR construction we need a new initial state: it will
  1095.      point to the initial term and the second just-about-to-be-parsed
  1096.      expr}
  1097.     OverallStartState := rcAddState(mtNone, #0, nil,
  1098.                                     UnusedState, UnusedState);
  1099.     {parse another expr}
  1100.     StartState2 := rcParseExpr;
  1101.     if (StartState2 = ErrorState) then
  1102.       Exit;
  1103.     {alter the state state for the overall expr so that the second
  1104.      link points to the start of the second expr}
  1105.     Result := rcSetState(OverallStartState, StartState1, StartState2);
  1106.     {now set the end state for the initial term to point to the final
  1107.      end state for the second expr and the overall expr}
  1108.     rcSetState(EndState1, FTable.Count, UnusedState);
  1109.   end;
  1110. end;
  1111. {--------}
  1112. function TaaRegexCompiler.rcParseFactor : integer;
  1113. var
  1114.   StartStateAtom : integer;
  1115.   EndStateAtom   : integer;
  1116. begin
  1117.   {assume the worst}
  1118.   Result := ErrorState;
  1119.   {first parse an atom}
  1120.   StartStateAtom := rcParseAtom;
  1121.   if (StartStateAtom = ErrorState) then
  1122.     Exit;
  1123.   {check for a closure operator}
  1124.   case FPosn^ of
  1125.     '?' : begin
  1126.             {$IFDEF LogParse}
  1127.             writeln(Log, 'zero or one closure');
  1128.             {$ENDIF}
  1129.  
  1130.             {move past the ? operator}
  1131.             inc(FPosn);
  1132.             {the atom's end state doesn't exist yet, so create one}
  1133.             EndStateAtom := rcAddState(mtNone, #0, nil,
  1134.                                        UnusedState, UnusedState);
  1135.             {create a new start state for the overall regex}
  1136.             Result := rcAddState(mtNone, #0, nil,
  1137.                                  StartStateAtom, EndStateAtom);
  1138.             {make sure the new end state points to the next unused
  1139.              state}
  1140.             rcSetState(EndStateAtom, FTable.Count, UnusedState);
  1141.           end;
  1142.     '*' : begin
  1143.             {$IFDEF LogParse}
  1144.             writeln(Log, 'zero or more closure');
  1145.             {$ENDIF}
  1146.  
  1147.             {move past the * operator}
  1148.             inc(FPosn);
  1149.             {the atom's end state doesn't exist yet, so create one;
  1150.              it'll be the start of the overall regex subexpression}
  1151.             Result := rcAddState(mtNone, #0, nil,
  1152.                                  NewFinalState, StartStateAtom);
  1153.           end;
  1154.     '+' : begin
  1155.             {$IFDEF LogParse}
  1156.             writeln(Log, 'one or more closure');
  1157.             {$ENDIF}
  1158.  
  1159.             {move past the + operator}
  1160.             inc(FPosn);
  1161.             {the atom's end state doesn't exist yet, so create one}
  1162.             rcAddState(mtNone, #0, nil, NewFinalState, StartStateAtom);
  1163.             {the start of the overall regex subexpression will be the
  1164.              atom's start state}
  1165.             Result := StartStateAtom;
  1166.           end;
  1167.   else
  1168.     Result := StartStateAtom;
  1169.   end;{case}
  1170. end;
  1171. {--------}
  1172. function TaaRegexCompiler.rcParseTerm : integer;
  1173. var
  1174.   StartState2 : integer;
  1175.   EndState1   : integer;
  1176. begin
  1177.   {parse an initial factor, the state number returned will also be our
  1178.    return state number}
  1179.   Result := rcParseFactor;
  1180.   if (Result = ErrorState) then
  1181.     Exit;
  1182.   {Note: we have to "break the grammar" here. We've parsed a regular
  1183.          subexpression and we're possibly following on with another
  1184.          regular subexpression. There's no nice operator to key off
  1185.          for concatenation: we just have to know that for
  1186.          concatenating two subexpressions, the current character will
  1187.          be
  1188.            - an open parenthesis
  1189.            - an open square bracket
  1190.            - an any char operator
  1191.            - a character that's not a metacharacter
  1192.          i.e., the three possibilities for the start of an "atom" in
  1193.          our grammar}
  1194.   if (FPosn^ = '(') or
  1195.      (FPosn^ = '[') or
  1196.      (FPosn^ = '.') or
  1197.      ((FPosn^ <> #0) and not (FPosn^ in MetaCharacters)) then begin
  1198.     {$IFDEF LogParse}
  1199.     writeln(Log, 'concatenation');
  1200.     {$ENDIF}
  1201.  
  1202.     {the initial factor's end state does not exist yet (although there
  1203.      is a state in the term that points to it), so create it}
  1204.     EndState1 := rcAddState(mtNone, #0, nil, UnusedState, UnusedState);
  1205.     {parse another term}
  1206.     StartState2 := rcParseTerm;
  1207.     if (StartState2 = ErrorState) then begin
  1208.       Result := ErrorState;
  1209.       Exit;
  1210.     end;
  1211.     {join the first factor to the second term}
  1212.     rcSetState(EndState1, StartState2, UnusedState);
  1213.   end;
  1214. end;
  1215. {--------}
  1216. procedure TaaRegexCompiler.rcSetIgnoreCase(aValue : boolean);
  1217. begin
  1218.   if (aValue <> FIgnoreCase) then begin
  1219.     rcClear;
  1220.     FIgnoreCase := aValue;
  1221.   end;
  1222. end;
  1223. {--------}
  1224. procedure TaaRegexCompiler.rcSetRegexStr(const aRegexStr : string);
  1225. begin
  1226.   if (aRegexStr <> FRegexStr) then begin
  1227.     rcClear;
  1228.     FRegexStr := aRegexStr;
  1229.   end;
  1230. end;
  1231. {--------}
  1232. function TaaRegexCompiler.rcSetState(aState     : integer;
  1233.                                      aNextState1: integer;
  1234.                                      aNextState2: integer) : integer;
  1235. var
  1236.   StateData : PaaNFAState;
  1237. begin
  1238.   Assert((0 <= aState) and (aState < FTable.Count),
  1239.          'trying to change an invalid state');
  1240.  
  1241.   {get the state record and change the transition information}
  1242.   StateData := PaaNFAState(FTable.List^[aState]);
  1243.   StateData^.sdNextState1 := aNextState1;
  1244.   StateData^.sdNextState2 := aNextState2;
  1245.   Result := aState;
  1246. end;
  1247. {--------}
  1248. procedure TaaRegexCompiler.rcSetUpcase(aValue : TaaUpcaseChar);
  1249. begin
  1250.   if not Assigned(aValue) then
  1251.     FUpcase := System.Upcase
  1252.   else
  1253.     FUpcase := aValue;
  1254. end;
  1255. {--------}
  1256. procedure TaaRegexCompiler.rcWalkNoCostTree(aList  : TaaIntList;
  1257.                                             aState : integer);
  1258. begin
  1259.   {look at this state's record...}
  1260.   with PaaNFAState(FTable.List^[aState])^ do begin
  1261.     {if it's a no-cost state, recursively walk the
  1262.      first, then the second chain}
  1263.     if (sdMatchType = mtNone) then begin
  1264.       rcWalkNoCostTree(aList, sdNextState1);
  1265.       rcWalkNoCostTree(aList, sdNextState2);
  1266.     end
  1267.     {otherwise, add it to the list}
  1268.     else 
  1269.       aList.Add(aState);
  1270.   end;
  1271. end;
  1272. {====================================================================}
  1273.  
  1274. {800 800-1234}
  1275. {(800) 800-1234}
  1276. {800-1234}
  1277. { 800-1234}
  1278. {80 800-1234}
  1279. {800 80-1234}
  1280. {800 800-124}
  1281. {1 10}
  1282. {10}
  1283.  
  1284.  
  1285. end.
  1286.