DEADSOFTWARE

mempool is optional now
[d2df-sdl.git] / src / shared / xparser.pas
index 76ac3c0165483ef90d4066904540d1eef5b7a2dd..7d7a9ed5d3bbdeb237376833203c68374a90986c 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *)
 {$INCLUDE a_modes.inc}
+{.$DEFINE XPARSER_DEBUG}
 unit xparser;
 
 interface
 
 uses
-  Classes;
+  Classes{$IFDEF USE_MEMPOOL}, mempool{$ENDIF};
 
 
 // ////////////////////////////////////////////////////////////////////////// //
 type
-  TTextParser = class
+  TTextParser = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF}
   public
     const
       TTNone = -1;
@@ -33,18 +34,38 @@ type
       TTInt = 2;
       //TTFloat = 3; // not yet
       TTStr = 4; // string
-      TTComma = 5; // ','
-      TTColon = 6; // ':'
-      TTSemi = 7; // ';'
-      TTBegin = 8; // left curly
-      TTEnd = 9; // right curly
-      TTDelim = 10; // other delimiters
+      TTDelim = 5; // one-char delimiters
+      //
+      TTLogAnd = 11; // &&
+      TTLogOr = 12; // ||
+      TTLessEqu = 13; // <=
+      TTGreatEqu = 14; // >=
+      TTNotEqu = 15; // !=
+      TTEqu = 16; // == or <>
+      TTAss = 17; // :=
+      TTShl = 18; // <<
+      TTShr = 19; // >>
+      TTDotDot = 19; // ..
+
+  public
+    type
+      TOption = (
+        SignedNumbers, // allow signed numbers; otherwise sign will be TTDelim
+        DollarIsId, // allow dollar in identifiers; otherwise dollar will be TTDelim
+        DotIsId, // allow dot in identifiers; otherwise dot will be TTDelim
+        PascalComments // allow `{}` pascal comments
+      );
+      TOptions = set of TOption;
+
+  private
+    type
+      TAnsiCharSet = set of AnsiChar;
 
   private
     mLine, mCol: Integer;
     mCurChar, mNextChar: AnsiChar;
 
-    mAllowSignedNumbers: Boolean; // internal control
+    mOptions: TOptions;
 
     mTokLine, mTokCol: Integer; // token start
     mTokType: Integer;
@@ -53,11 +74,11 @@ type
     mTokInt: Integer;
 
   protected
-    procedure warmup (); virtual; // called in constructor to warm up the system
+    procedure warmup (); // called in constructor to warm up the system
     procedure loadNextChar (); virtual; abstract; // loads next char into mNextChar; #0 means 'eof'
 
   public
-    constructor Create ();
+    constructor Create (aopts: TOptions=[TOption.SignedNumbers]);
     destructor Destroy (); override;
 
     function isEOF (): Boolean; inline;
@@ -67,6 +88,9 @@ type
     function skipBlanks (): Boolean; // ...and comments; returns `false` on eof
 
     function skipToken (): Boolean; // returns `false` on eof
+    {$IFDEF XPARSER_DEBUG}
+    function skipToken1 (): Boolean;
+    {$ENDIF}
 
     function expectId (): AnsiString;
     procedure expectId (const aid: AnsiString);
@@ -75,12 +99,20 @@ type
     function expectStr (allowEmpty: Boolean=false): AnsiString;
     function expectInt (): Integer;
 
+    function expectStrOrId (allowEmpty: Boolean=false): AnsiString;
+
     procedure expectTT (ttype: Integer);
     function eatTT (ttype: Integer): Boolean;
 
-    function expectDelim (const ch: AnsiChar): AnsiChar;
+    procedure expectDelim (const ch: AnsiChar);
+    function expectDelims (const ch: TAnsiCharSet): AnsiChar;
     function eatDelim (const ch: AnsiChar): Boolean;
 
+    function isDelim (const ch: AnsiChar): Boolean; inline;
+
+  public
+    property options: TOptions read mOptions write mOptions;
+
   public
     property col: Integer read mCol;
     property line: Integer read mLine;
@@ -116,8 +148,8 @@ type
     procedure loadNextChar (); override; // loads next char into mNextChar; #0 means 'eof'
 
   public
-    constructor Create (const fname: AnsiString);
-    constructor Create (st: TStream; astOwned: Boolean=true); // will take ownership on st by default
+    constructor Create (const fname: AnsiString; aopts: TOptions=[TOption.SignedNumbers]);
+    constructor Create (st: TStream; astOwned: Boolean=true; aopts: TOptions=[TOption.SignedNumbers]);
     destructor Destroy (); override;
   end;
 
@@ -130,7 +162,7 @@ type
     procedure loadNextChar (); override; // loads next char into mNextChar; #0 means 'eof'
 
   public
-    constructor Create (const astr: AnsiString);
+    constructor Create (const astr: AnsiString; aopts: TOptions=[TOption.SignedNumbers]);
     destructor Destroy (); override;
   end;
 
@@ -156,6 +188,9 @@ type
     procedure putIndent ();
     procedure indent ();
     procedure unindent ();
+
+  public
+    property curIndent: Integer read mIndent;
   end;
 
 
@@ -204,11 +239,7 @@ uses
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-function StrEqu (const a, b: AnsiString): Boolean; inline; begin result := (a = b); end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-constructor TTextParser.Create ();
+constructor TTextParser.Create (aopts: TOptions=[TOption.SignedNumbers]);
 begin
   mLine := 1;
   mCol := 1;
@@ -218,8 +249,8 @@ begin
   mTokStr := '';
   mTokChar := #0;
   mTokInt := 0;
-  mAllowSignedNumbers := true;
-  warmup(); // change `mAllowSignedNumbers` there, if necessary
+  mOptions := aopts;
+  warmup();
   skipToken();
 end;
 
@@ -323,6 +354,40 @@ begin
         end;
         continue;
       end;
+    end
+    else if (curChar = '(') and (nextChar = '*') then
+    begin
+      // pascal comment; skip comment start
+      skipChar();
+      skipChar();
+      while not isEOF do
+      begin
+        if (curChar = '*') and (nextChar = ')') then
+        begin
+          // skip comment end
+          skipChar();
+          skipChar();
+          break;
+        end;
+        skipChar();
+      end;
+      continue;
+    end
+    else if (curChar = '{') and (TOption.PascalComments in mOptions) then
+    begin
+      // pascal comment; skip comment start
+      skipChar();
+      while not isEOF do
+      begin
+        if (curChar = '}') then
+        begin
+          // skip comment end
+          skipChar();
+          break;
+        end;
+        skipChar();
+      end;
+      continue;
     end;
     if (curChar > ' ') then break;
     skipChar(); // skip blank
@@ -331,15 +396,25 @@ begin
 end;
 
 
+{$IFDEF XPARSER_DEBUG}
 function TTextParser.skipToken (): Boolean;
+begin
+  writeln('getting token...');
+  result := skipToken1();
+  writeln('  got token: ', mTokType, ' <', mTokStr, '> : <', mTokChar, '>');
+end;
 
+function TTextParser.skipToken1 (): Boolean;
+{$ELSE}
+function TTextParser.skipToken (): Boolean;
+{$ENDIF}
   procedure parseInt ();
   var
     neg: Boolean = false;
     base: Integer = -1;
     n: Integer;
   begin
-    if mAllowSignedNumbers then
+    if (TOption.SignedNumbers in mOptions) then
     begin
       if (curChar = '+') or (curChar = '-') then
       begin
@@ -461,7 +536,9 @@ function TTextParser.skipToken (): Boolean;
     while (curChar = '_') or ((curChar >= '0') and (curChar <= '9')) or
           ((curChar >= 'A') and (curChar <= 'Z')) or
           ((curChar >= 'a') and (curChar <= 'z')) or
-          (curChar >= #128) do
+          (curChar >= #128) or
+          ((TOption.DollarIsId in mOptions) and (curChar = '$')) or
+          ((TOption.DotIsId in mOptions) and (curChar = '.') and (nextChar <> '.')) do
     begin
       mTokStr += curChar;
       skipChar();
@@ -488,7 +565,7 @@ begin
   result := true;
 
   // number?
-  if mAllowSignedNumbers and ((curChar = '+') or (curChar = '-')) then begin parseInt(); exit; end;
+  if (TOption.SignedNumbers in mOptions) and ((curChar = '+') or (curChar = '-')) then begin parseInt(); exit; end;
   if (curChar >= '0') and (curChar <= '9') then begin parseInt(); exit; end;
 
   // string?
@@ -496,18 +573,39 @@ begin
 
   // identifier?
   if (curChar = '_') or ((curChar >= 'A') and (curChar <= 'Z')) or ((curChar >= 'a') and (curChar <= 'z')) or (curChar >= #128) then begin parseId(); exit; end;
+  if (TOption.DollarIsId in mOptions) and (curChar = '$') then begin parseId(); exit; end;
+  if (TOption.DotIsId in mOptions) and (curChar = '.') and (nextChar <> '.') then begin parseId(); exit; end;
 
   // known delimiters?
-  case curChar of
-    ',': mTokType := TTComma;
-    ':': mTokType := TTColon;
-    ';': mTokType := TTSemi;
-    '{': mTokType := TTBegin;
-    '}': mTokType := TTEnd;
-    else mTokType := TTDelim;
-  end;
   mTokChar := curChar;
+  mTokType := TTDelim;
   skipChar();
+  if (curChar = '=') then
+  begin
+    case mTokChar of
+      '<': begin mTokType := TTLessEqu; mTokStr := '<='; skipChar(); exit; end;
+      '>': begin mTokType := TTGreatEqu; mTokStr := '>='; skipChar(); exit; end;
+      '!': begin mTokType := TTNotEqu; mTokStr := '!='; skipChar(); exit; end;
+      '=': begin mTokType := TTEqu; mTokStr := '=='; skipChar(); exit; end;
+      ':': begin mTokType := TTAss; mTokStr := ':='; skipChar(); exit; end;
+    end;
+  end
+  else if (mTokChar = curChar) then
+  begin
+    case mTokChar of
+      '<': begin mTokType := TTShl; mTokStr := '<<'; skipChar(); exit; end;
+      '>': begin mTokType := TTShr; mTokStr := '>>'; skipChar(); exit; end;
+      '&': begin mTokType := TTLogAnd; mTokStr := '&&'; skipChar(); exit; end;
+      '|': begin mTokType := TTLogOr; mTokStr := '||'; skipChar(); exit; end;
+    end;
+  end
+  else
+  begin
+    case mTokChar of
+      '<': if (curChar = '>') then begin mTokType := TTNotEqu; mTokStr := '<>'; skipChar(); exit; end;
+      '.': if (curChar = '.') then begin mTokType := TTDotDot; mTokStr := '..'; skipChar(); exit; end;
+    end;
+  end;
 end;
 
 
@@ -521,17 +619,15 @@ end;
 
 procedure TTextParser.expectId (const aid: AnsiString);
 begin
-  if (mTokType <> TTId) or (not StrEqu(mTokStr, aid)) then raise Exception.Create('identifier '''+aid+''' expected');
+  if (mTokType <> TTId) or (mTokStr <> aid) then raise Exception.Create('identifier '''+aid+''' expected');
   skipToken();
 end;
 
 
 function TTextParser.eatId (const aid: AnsiString): Boolean;
 begin
-  result := false;
-  if (mTokType <> TTId) or (not StrEqu(mTokStr, aid)) then exit;
-  result := true;
-  skipToken();
+  result := (mTokType = TTId) and (mTokStr = aid);
+  if result then skipToken();
 end;
 
 
@@ -544,6 +640,21 @@ begin
 end;
 
 
+function TTextParser.expectStrOrId (allowEmpty: Boolean=false): AnsiString;
+begin
+  case mTokType of
+    TTStr:
+      if (not allowEmpty) and (Length(mTokStr) = 0) then raise Exception.Create('non-empty string expected');
+    TTId:
+      begin end;
+    else
+      raise Exception.Create('string or identifier expected');
+  end;
+  result := mTokStr;
+  skipToken();
+end;
+
+
 function TTextParser.expectInt (): Integer;
 begin
   if (mTokType <> TTInt) then raise Exception.Create('string expected');
@@ -566,9 +677,17 @@ begin
 end;
 
 
-function TTextParser.expectDelim (const ch: AnsiChar): AnsiChar;
+procedure TTextParser.expectDelim (const ch: AnsiChar);
 begin
-  if (mTokType <> TTDelim) then raise Exception.Create(Format('delimiter ''%s'' expected', [ch]));
+  if (mTokType <> TTDelim) or (mTokChar <> ch) then raise Exception.CreateFmt('delimiter ''%s'' expected', [ch]);
+  skipToken();
+end;
+
+
+function TTextParser.expectDelims (const ch: TAnsiCharSet): AnsiChar;
+begin
+  if (mTokType <> TTDelim) then raise Exception.Create('delimiter expected');
+  if not (mTokChar in ch) then raise Exception.Create('delimiter expected');
   result := mTokChar;
   skipToken();
 end;
@@ -576,15 +695,19 @@ end;
 
 function TTextParser.eatDelim (const ch: AnsiChar): Boolean;
 begin
-  result := false;
-  if (mTokType <> TTDelim) or (mTokChar <> ch) then exit;
-  result := true;
-  skipToken();
+  result := (mTokType = TTDelim) and (mTokChar = ch);
+  if result then skipToken();
+end;
+
+
+function TTextParser.isDelim (const ch: AnsiChar): Boolean; inline;
+begin
+  result := (mTokType = TTDelim) and (mTokChar = ch);
 end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-constructor TFileTextParser.Create (const fname: AnsiString);
+constructor TFileTextParser.Create (const fname: AnsiString; aopts: TOptions=[TOption.SignedNumbers]);
 begin
   mBuffer := nil;
   mFile := openDiskFileRO(fname);
@@ -593,11 +716,11 @@ begin
   mBufPos := 0;
   mBufLen := mFile.Read(mBuffer^, BufSize);
   if (mBufLen < 0) then raise Exception.Create('TFileTextParser: read error');
-  inherited Create();
+  inherited Create(aopts);
 end;
 
 
-constructor TFileTextParser.Create (st: TStream; astOwned: Boolean=true);
+constructor TFileTextParser.Create (st: TStream; astOwned: Boolean=true; aopts: TOptions=[TOption.SignedNumbers]);
 begin
   if (st = nil) then raise Exception.Create('cannot create parser for nil stream');
   mFile := st;
@@ -606,7 +729,7 @@ begin
   mBufPos := 0;
   mBufLen := mFile.Read(mBuffer^, BufSize);
   if (mBufLen < 0) then raise Exception.Create('TFileTextParser: read error');
-  inherited Create();
+  inherited Create(aopts);
 end;
 
 
@@ -640,11 +763,11 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-constructor TStrTextParser.Create (const astr: AnsiString);
+constructor TStrTextParser.Create (const astr: AnsiString; aopts: TOptions=[TOption.SignedNumbers]);
 begin
   mStr := astr;
   mPos := 1;
-  inherited Create();
+  inherited Create(aopts);
 end;