X-Git-Url: https://deadsoftware.ru/gitweb?a=blobdiff_plain;f=src%2Fshared%2Fxparser.pas;h=5332f0afe7c4af022019799a6d2eb4ce796f40c0;hb=6cfc4749e77a32dc356f8dc4b4f26788626bbb4e;hp=7d7a9ed5d3bbdeb237376833203c68374a90986c;hpb=56ec1dee6d63a32353f94eac7e87d6a42b801a25;p=d2df-sdl.git diff --git a/src/shared/xparser.pas b/src/shared/xparser.pas index 7d7a9ed..5332f0a 100644 --- a/src/shared/xparser.pas +++ b/src/shared/xparser.pas @@ -1,4 +1,5 @@ -(* Copyright (C) DooM 2D:Forever Developers +(* coded by Ketmar // Invisible Vector + * Understanding is not required. Only obedience. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -11,7 +12,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . *) {$INCLUDE a_modes.inc} {.$DEFINE XPARSER_DEBUG} @@ -20,11 +21,22 @@ unit xparser; interface uses - Classes{$IFDEF USE_MEMPOOL}, mempool{$ENDIF}; + SysUtils, Classes{$IFDEF USE_MEMPOOL}, mempool{$ENDIF}; // ////////////////////////////////////////////////////////////////////////// // type + TTextParser = class; + + TParserException = class(Exception) + public + tokLine, tokCol: Integer; + + public + constructor Create (pr: TTextParser; const amsg: AnsiString); + constructor CreateFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const); + end; + TTextParser = class{$IFDEF USE_MEMPOOL}(TPoolObject){$ENDIF} public const @@ -53,6 +65,7 @@ type 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 + DashIsId, // '-' can be part of identifier (but identifier cannot start with '-') PascalComments // allow `{}` pascal comments ); TOptions = set of TOption; @@ -81,6 +94,9 @@ type constructor Create (aopts: TOptions=[TOption.SignedNumbers]); destructor Destroy (); override; + procedure error (const amsg: AnsiString); noreturn; + procedure errorfmt (const afmt: AnsiString; const args: array of const); noreturn; + function isEOF (): Boolean; inline; function skipChar (): Boolean; // returns `false` on eof @@ -92,14 +108,18 @@ type function skipToken1 (): Boolean; {$ENDIF} + function isIdOrStr (): Boolean; inline; + function expectId (): AnsiString; - procedure expectId (const aid: AnsiString); - function eatId (const aid: AnsiString): Boolean; + procedure expectId (const aid: AnsiString; caseSens: Boolean=true); + function eatId (const aid: AnsiString; caseSens: Boolean=true): Boolean; + function eatIdOrStr (const aid: AnsiString; caseSens: Boolean=true): Boolean; + function eatIdOrStrCI (const aid: AnsiString): Boolean; inline; function expectStr (allowEmpty: Boolean=false): AnsiString; function expectInt (): Integer; - function expectStrOrId (allowEmpty: Boolean=false): AnsiString; + function expectIdOrStr (allowEmpty: Boolean=false): AnsiString; procedure expectTT (ttype: Integer); function eatTT (ttype: Integer): Boolean; @@ -235,7 +255,21 @@ type implementation uses - SysUtils, utils; + utils; + + +// ////////////////////////////////////////////////////////////////////////// // +constructor TParserException.Create (pr: TTextParser; const amsg: AnsiString); +begin + if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end; + inherited Create(amsg); +end; + +constructor TParserException.CreateFmt (pr: TTextParser; const afmt: AnsiString; const args: array of const); +begin + if (pr <> nil) then begin tokLine := pr.tokLine; tokCol := pr.tokCol; end; + inherited Create(formatstrf(afmt, args)); +end; // ////////////////////////////////////////////////////////////////////////// // @@ -261,7 +295,19 @@ begin end; -function TTextParser.isEOF (): Boolean; inline; begin result := (mCurChar = #0); end; +procedure TTextParser.error (const amsg: AnsiString); noreturn; +begin + raise TParserException.Create(self, amsg); +end; + + +procedure TTextParser.errorfmt (const afmt: AnsiString; const args: array of const); noreturn; +begin + raise TParserException.CreateFmt(self, afmt, args); +end; + + +function TTextParser.isEOF (): Boolean; inline; begin {result := (mCurChar = #0);} result := (mTokType = TTEOF); end; procedure TTextParser.warmup (); @@ -294,26 +340,26 @@ function TTextParser.skipBlanks (): Boolean; var level: Integer; begin - while not isEOF do + while (mCurChar <> #0) do begin - if (curChar = '/') then + if (mCurChar = '/') then begin // single-line comment - if (nextChar = '/') then + if (mNextChar = '/') then begin - while not isEOF and (curChar <> #10) do skipChar(); + while (mCurChar <> #0) and (mCurChar <> #10) do skipChar(); skipChar(); // skip EOL continue; end; // multline comment - if (nextChar = '*') then + if (mNextChar = '*') then begin // skip comment start skipChar(); skipChar(); - while not isEOF do + while (mCurChar <> #0) do begin - if (curChar = '*') and (nextChar = '/') then + if (mCurChar = '*') and (mNextChar = '/') then begin // skip comment end skipChar(); @@ -325,15 +371,15 @@ begin continue; end; // nesting multline comment - if (nextChar = '+') then + if (mNextChar = '+') then begin // skip comment start skipChar(); skipChar(); level := 1; - while not isEOF do + while (mCurChar <> #0) do begin - if (curChar = '+') and (nextChar = '/') then + if (mCurChar = '+') and (mNextChar = '/') then begin // skip comment end skipChar(); @@ -342,7 +388,7 @@ begin if (level = 0) then break; continue; end; - if (curChar = '/') and (nextChar = '+') then + if (mCurChar = '/') and (mNextChar = '+') then begin // skip comment start skipChar(); @@ -355,14 +401,14 @@ begin continue; end; end - else if (curChar = '(') and (nextChar = '*') then + else if (mCurChar = '(') and (mNextChar = '*') then begin // pascal comment; skip comment start skipChar(); skipChar(); - while not isEOF do + while (mCurChar <> #0) do begin - if (curChar = '*') and (nextChar = ')') then + if (mCurChar = '*') and (mNextChar = ')') then begin // skip comment end skipChar(); @@ -373,13 +419,13 @@ begin end; continue; end - else if (curChar = '{') and (TOption.PascalComments in mOptions) then + else if (mCurChar = '{') and (TOption.PascalComments in mOptions) then begin // pascal comment; skip comment start skipChar(); - while not isEOF do + while (mCurChar <> #0) do begin - if (curChar = '}') then + if (mCurChar = '}') then begin // skip comment end skipChar(); @@ -389,10 +435,10 @@ begin end; continue; end; - if (curChar > ' ') then break; + if (mCurChar > ' ') then break; skipChar(); // skip blank end; - result := not isEOF; + result := (mCurChar <> #0); end; @@ -416,11 +462,11 @@ function TTextParser.skipToken (): Boolean; begin if (TOption.SignedNumbers in mOptions) then begin - if (curChar = '+') or (curChar = '-') then + if (mCurChar = '+') or (mCurChar = '-') then begin - neg := (curChar = '-'); + neg := (mCurChar = '-'); skipChar(); - if (curChar < '0') or (curChar > '9') then + if (mCurChar < '0') or (mCurChar > '9') then begin mTokType := TTDelim; if (neg) then mTokChar := '-' else mTokChar := '+'; @@ -428,9 +474,9 @@ function TTextParser.skipToken (): Boolean; end; end; end; - if (curChar = '0') then + if (mCurChar = '0') then begin - case nextChar of + case mNextChar of 'b','B': base := 2; 'o','O': base := 8; 'd','D': base := 10; @@ -445,12 +491,12 @@ function TTextParser.skipToken (): Boolean; end; // default base if (base < 0) then base := 10; - if (digitInBase(curChar, base) < 0) then raise Exception.Create('invalid number'); + if (digitInBase(mCurChar, base) < 0) then raise Exception.Create('invalid number'); mTokType := TTInt; mTokInt := 0; // just in case - while not isEOF do + while (mCurChar <> #0) do begin - n := digitInBase(curChar, base); + n := digitInBase(mCurChar, base); if (n < 0) then break; n := mTokInt*10+n; if (n < 0) or (n < mTokInt) then raise Exception.Create('integer overflow'); @@ -458,10 +504,10 @@ function TTextParser.skipToken (): Boolean; skipChar(); end; // check for valid number end - if not isEOF then + if (mCurChar <> #0) then begin - if (curChar = '.') then raise Exception.Create('floating numbers aren''t supported yet'); - if (curChar = '_') or ((curChar >= 'A') and (curChar <= 'Z')) or ((curChar >= 'a') and (curChar <= 'z')) or (curChar >= #128) then + if (mCurChar = '.') then raise Exception.Create('floating numbers aren''t supported yet'); + if (mCurChar = '_') or ((mCurChar >= 'A') and (mCurChar <= 'Z')) or ((mCurChar >= 'a') and (mCurChar <= 'z')) or (mCurChar >= #128) then begin raise Exception.Create('invalid number'); end; @@ -476,15 +522,15 @@ function TTextParser.skipToken (): Boolean; begin mTokType := TTStr; mTokStr := ''; // just in case - qch := curChar; + qch := mCurChar; skipChar(); // skip starting quote - while not isEOF do + while (mCurChar <> #0) do begin // escape - if (qch = '"') and (curChar = '\') then + if (qch = '"') and (mCurChar = '\') then begin - if (nextChar = #0) then raise Exception.Create('unterminated string escape'); - ch := nextChar; + if (mNextChar = #0) then raise Exception.Create('unterminated string escape'); + ch := mNextChar; // skip backslash and escape type skipChar(); skipChar(); @@ -496,12 +542,12 @@ function TTextParser.skipToken (): Boolean; 'e': mTokStr += #27; 'x', 'X': // hex escape begin - n := digitInBase(curChar, 16); + n := digitInBase(mCurChar, 16); if (n < 0) then raise Exception.Create('invalid hexstr escape'); skipChar(); - if (digitInBase(curChar, 16) > 0) then + if (digitInBase(mCurChar, 16) > 0) then begin - n := n*16+digitInBase(curChar, 16); + n := n*16+digitInBase(mCurChar, 16); skipChar(); end; mTokStr += AnsiChar(n); @@ -511,7 +557,7 @@ function TTextParser.skipToken (): Boolean; continue; end; // duplicate single quote (pascal style) - if (qch = '''') and (curChar = '''') and (nextChar = '''') then + if (qch = '''') and (mCurChar = '''') and (mNextChar = '''') then begin // skip both quotes skipChar(); @@ -519,12 +565,12 @@ function TTextParser.skipToken (): Boolean; mTokStr += ''''; continue; end; - if (curChar = qch) then + if (mCurChar = qch) then begin skipChar(); // skip ending quote break; end; - mTokStr += curChar; + mTokStr += mCurChar; skipChar(); end; end; @@ -533,20 +579,21 @@ function TTextParser.skipToken (): Boolean; begin mTokType := TTId; mTokStr := ''; // just in case - while (curChar = '_') or ((curChar >= '0') and (curChar <= '9')) or - ((curChar >= 'A') and (curChar <= 'Z')) or - ((curChar >= 'a') and (curChar <= 'z')) or - (curChar >= #128) or - ((TOption.DollarIsId in mOptions) and (curChar = '$')) or - ((TOption.DotIsId in mOptions) and (curChar = '.') and (nextChar <> '.')) do + while (mCurChar = '_') or ((mCurChar >= '0') and (mCurChar <= '9')) or + ((mCurChar >= 'A') and (mCurChar <= 'Z')) or + ((mCurChar >= 'a') and (mCurChar <= 'z')) or + (mCurChar >= #128) or + ((TOption.DollarIsId in mOptions) and (mCurChar = '$')) or + ((TOption.DotIsId in mOptions) and (mCurChar = '.') and (mNextChar <> '.')) or + ((TOption.DashIsId in mOptions) and (mCurChar = '-')) do begin - mTokStr += curChar; + mTokStr += mCurChar; skipChar(); end; end; begin - mTokType := TTEOF; + mTokType := TTNone; mTokStr := ''; mTokChar := #0; mTokInt := 0; @@ -554,6 +601,7 @@ begin if not skipBlanks() then begin result := false; + mTokType := TTEOF; mTokLine := mLine; mTokCol := mCol; exit; @@ -565,22 +613,22 @@ begin result := true; // number? - 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; + if (TOption.SignedNumbers in mOptions) and ((mCurChar = '+') or (mCurChar = '-')) then begin parseInt(); exit; end; + if (mCurChar >= '0') and (mCurChar <= '9') then begin parseInt(); exit; end; // string? - if (curChar = '"') or (curChar = '''') then begin parseString(); exit; end; + if (mCurChar = '"') or (mCurChar = '''') then begin parseString(); exit; end; // 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; + if (mCurChar = '_') or ((mCurChar >= 'A') and (mCurChar <= 'Z')) or ((mCurChar >= 'a') and (mCurChar <= 'z')) or (mCurChar >= #128) then begin parseId(); exit; end; + if (TOption.DollarIsId in mOptions) and (mCurChar = '$') then begin parseId(); exit; end; + if (TOption.DotIsId in mOptions) and (mCurChar = '.') and (mNextChar <> '.') then begin parseId(); exit; end; // known delimiters? - mTokChar := curChar; + mTokChar := mCurChar; mTokType := TTDelim; skipChar(); - if (curChar = '=') then + if (mCurChar = '=') then begin case mTokChar of '<': begin mTokType := TTLessEqu; mTokStr := '<='; skipChar(); exit; end; @@ -590,7 +638,7 @@ begin ':': begin mTokType := TTAss; mTokStr := ':='; skipChar(); exit; end; end; end - else if (mTokChar = curChar) then + else if (mTokChar = mCurChar) then begin case mTokChar of '<': begin mTokType := TTShl; mTokStr := '<<'; skipChar(); exit; end; @@ -602,13 +650,19 @@ begin 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; + '<': if (mCurChar = '>') then begin mTokType := TTNotEqu; mTokStr := '<>'; skipChar(); exit; end; + '.': if (mCurChar = '.') then begin mTokType := TTDotDot; mTokStr := '..'; skipChar(); exit; end; end; end; end; +function TTextParser.isIdOrStr (): Boolean; inline; +begin + result := (mTokType = TTId) or (mTokType = TTStr); +end; + + function TTextParser.expectId (): AnsiString; begin if (mTokType <> TTId) then raise Exception.Create('identifier expected'); @@ -617,20 +671,56 @@ begin end; -procedure TTextParser.expectId (const aid: AnsiString); +procedure TTextParser.expectId (const aid: AnsiString; caseSens: Boolean=true); begin - if (mTokType <> TTId) or (mTokStr <> aid) then raise Exception.Create('identifier '''+aid+''' expected'); + if caseSens then + begin + if (mTokType <> TTId) or (mTokStr <> aid) then raise Exception.Create('identifier '''+aid+''' expected'); + end + else + begin + if (mTokType <> TTId) or (not strEquCI1251(mTokStr, aid)) then raise Exception.Create('identifier '''+aid+''' expected'); + end; skipToken(); end; -function TTextParser.eatId (const aid: AnsiString): Boolean; +function TTextParser.eatId (const aid: AnsiString; caseSens: Boolean=true): Boolean; +begin + if caseSens then + begin + result := (mTokType = TTId) and (mTokStr = aid); + end + else + begin + result := (mTokType = TTId) and strEquCI1251(mTokStr, aid); + end; + if result then skipToken(); +end; + + +function TTextParser.eatIdOrStr (const aid: AnsiString; caseSens: Boolean=true): Boolean; begin - result := (mTokType = TTId) and (mTokStr = aid); + if caseSens then + begin + result := (mTokType = TTId) and (mTokStr = aid); + if not result then result := (mTokType = TTStr) and (mTokStr = aid); + end + else + begin + result := (mTokType = TTId) and strEquCI1251(mTokStr, aid); + if not result then result := (mTokType = TTStr) and strEquCI1251(mTokStr, aid); + end; if result then skipToken(); end; +function TTextParser.eatIdOrStrCI (const aid: AnsiString): Boolean; inline; +begin + result := eatIdOrStr(aid, false); +end; + + function TTextParser.expectStr (allowEmpty: Boolean=false): AnsiString; begin if (mTokType <> TTStr) then raise Exception.Create('string expected'); @@ -640,7 +730,7 @@ begin end; -function TTextParser.expectStrOrId (allowEmpty: Boolean=false): AnsiString; +function TTextParser.expectIdOrStr (allowEmpty: Boolean=false): AnsiString; begin case mTokType of TTStr: