1 (* Copyright (C) DooM 2D:Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE a_modes.inc}
25 // ////////////////////////////////////////////////////////////////////////// //
34 //TTFloat = 3; // not yet
39 TTBegin
= 8; // left curly
40 TTEnd
= 9; // right curly
41 TTDelim
= 10; // other delimiters
45 mCurChar
, mNextChar
: AnsiChar;
47 mAllowSignedNumbers
: Boolean; // internal control
49 mTokLine
, mTokCol
: Integer; // token start
51 mTokStr
: AnsiString; // string or identifier
52 mTokChar
: AnsiChar; // for delimiters
56 procedure warmup (); virtual; // called in constructor to warm up the system
57 procedure loadNextChar (); virtual; abstract; // loads next char into mNextChar; #0 means 'eof'
60 constructor Create (loadToken
: Boolean=true);
61 destructor Destroy (); override;
63 function isEOF (): Boolean; inline;
65 function skipChar (): Boolean; // returns `false` on eof
67 function skipBlanks (): Boolean; // ...and comments; returns `false` on eof
69 function skipToken (): Boolean; // returns `false` on eof
71 function expectId (): AnsiString;
72 procedure expectId (const aid
: AnsiString);
73 function eatId (const aid
: AnsiString): Boolean;
75 function expectStr (allowEmpty
: Boolean=false): AnsiString;
76 function expectInt (): Integer;
78 procedure expectTT (ttype
: Integer);
79 function eatTT (ttype
: Integer): Boolean;
81 function expectDelim (const ch
: AnsiChar): AnsiChar;
82 function eatDelim (const ch
: AnsiChar): Boolean;
85 property col
: Integer read mCol
;
86 property line
: Integer read mLine
;
88 property curChar
: AnsiChar read mCurChar
;
89 property nextChar
: AnsiChar read mNextChar
;
92 property tokCol
: Integer read mTokCol
;
93 property tokLine
: Integer read mTokLine
;
95 property tokType
: Integer read mTokType
; // see TTXXX constants
96 property tokStr
: AnsiString read mTokStr
; // string or identifier
97 property tokChar
: AnsiChar read mTokChar
; // for delimiters
98 property tokInt
: Integer read mTokInt
;
102 // ////////////////////////////////////////////////////////////////////////// //
104 TFileTextParser
= class(TTextParser
)
106 const BufSize
= 65536;
115 procedure loadNextChar (); override; // loads next char into mNextChar; #0 means 'eof'
118 constructor Create (const fname
: AnsiString; loadToken
: Boolean=true);
119 constructor Create (st
: TStream
; loadToken
: Boolean=true); // will take ownership on st
120 destructor Destroy (); override;
123 TStrTextParser
= class(TTextParser
)
129 procedure loadNextChar (); override; // loads next char into mNextChar; #0 means 'eof'
132 constructor Create (const astr
: AnsiString; loadToken
: Boolean=true);
133 destructor Destroy (); override;
137 // ////////////////////////////////////////////////////////////////////////// //
144 procedure putBuf (constref buf
; len
: SizeUInt
); virtual; abstract;
147 constructor Create ();
149 procedure put (const s
: AnsiString); overload
;
150 procedure put (v
: Byte); overload
;
151 procedure put (v
: Integer); overload
;
152 procedure put (const fmt
: AnsiString; args
: array of const); overload
;
153 procedure putIndent ();
155 procedure unindent ();
159 // ////////////////////////////////////////////////////////////////////////// //
161 TFileTextWriter
= class(TTextWriter
)
166 procedure putBuf (constref buf
; len
: SizeUInt
); override;
169 constructor Create (const fname
: AnsiString);
170 destructor Destroy (); override;
180 // ////////////////////////////////////////////////////////////////////////// //
181 function StrEqu (const a
, b
: AnsiString): Boolean; inline; begin result
:= (a
= b
); end;
184 // ////////////////////////////////////////////////////////////////////////// //
186 wc2shitmap
: array[0..65535] of AnsiChar;
187 wc2shitmapInited
: Boolean = false;
190 // ////////////////////////////////////////////////////////////////////////// //
191 procedure initShitMap ();
193 cp1251
: array[0..127] of Word = (
194 $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
195 $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
196 $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
197 $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
198 $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
199 $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
200 $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
201 $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
206 for f
:= 0 to High(wc2shitmap
) do wc2shitmap
[f
] := '?';
207 for f
:= 0 to 127 do wc2shitmap
[f
] := AnsiChar(f
);
208 for f
:= 0 to 127 do wc2shitmap
[cp1251
[f
]] := AnsiChar(f
+128);
209 wc2shitmapInited
:= true;
213 // ////////////////////////////////////////////////////////////////////////// //
214 // TODO: make a hash or something
215 function wcharTo1251 (wc
: WideChar): AnsiChar; inline;
217 if not wc2shitmapInited
then initShitMap();
218 if (LongWord(wc
) > 65535) then result
:= '?' else result
:= wc2shitmap
[LongWord(wc
)];
222 // ////////////////////////////////////////////////////////////////////////// //
223 constructor TTextParser
.Create (loadToken
: Boolean=true);
233 mAllowSignedNumbers
:= true;
234 warmup(); // change `mAllowSignedNumbers` there, if necessary
235 if loadToken
then skipToken();
239 destructor TTextParser
.Destroy ();
245 function TTextParser
.isEOF (): Boolean; inline; begin result
:= (mCurChar
= #0); end;
248 procedure TTextParser
.warmup ();
252 mCurChar
:= mNextChar
;
253 if (mNextChar
<> #0) then loadNextChar();
257 function TTextParser
.skipChar (): Boolean;
259 if (mCurChar
= #0) then begin result
:= false; exit
; end;
260 if (mCurChar
= #10) then begin mCol
:= 1; Inc(mLine
); end else Inc(mCol
);
261 mCurChar
:= mNextChar
;
262 if (mCurChar
= #0) then begin result
:= false; exit
; end;
265 if (mCurChar
= #13) then
267 if (mNextChar
= #10) then loadNextChar();
274 function TTextParser
.skipBlanks (): Boolean;
280 if (curChar
= '/') then
282 // single-line comment
283 if (nextChar
= '/') then
285 while not isEOF
and (curChar
<> #10) do skipChar();
286 skipChar(); // skip EOL
290 if (nextChar
= '*') then
292 // skip comment start
297 if (curChar
= '*') and (nextChar
= '/') then
308 // nesting multline comment
309 if (nextChar
= '+') then
311 // skip comment start
317 if (curChar
= '+') and (nextChar
= '/') then
323 if (level
= 0) then break
;
326 if (curChar
= '/') and (nextChar
= '+') then
328 // skip comment start
339 if (curChar
> ' ') then break
;
340 skipChar(); // skip blank
346 function TTextParser
.skipToken (): Boolean;
348 procedure parseInt ();
350 neg
: Boolean = false;
354 if mAllowSignedNumbers
then
356 if (curChar
= '+') or (curChar
= '-') then
358 neg
:= (curChar
= '-');
360 if (curChar
< '0') or (curChar
> '9') then
363 if (neg
) then mTokChar
:= '-' else mTokChar
:= '+';
368 if (curChar
= '0') then
384 if (base
< 0) then base
:= 10;
385 if (digitInBase(curChar
, base
) < 0) then raise Exception
.Create('invalid number');
387 mTokInt
:= 0; // just in case
390 n
:= digitInBase(curChar
, base
);
391 if (n
< 0) then break
;
393 if (n
< 0) or (n
< mTokInt
) then raise Exception
.Create('integer overflow');
397 // check for valid number end
400 if (curChar
= '.') then raise Exception
.Create('floating numbers aren''t supported yet');
401 if (curChar
= '_') or ((curChar
>= 'A') and (curChar
<= 'Z')) or ((curChar
>= 'a') and (curChar
<= 'z')) or (curChar
>= #128) then
403 raise Exception
.Create('invalid number');
406 if neg
then mTokInt
:= -mTokInt
;
409 procedure parseString ();
415 mTokStr
:= ''; // just in case
417 skipChar(); // skip starting quote
421 if (qch
= '"') and (curChar
= '\') then
423 if (nextChar
= #0) then raise Exception
.Create('unterminated string escape');
425 // skip backslash and escape type
434 'x', 'X': // hex escape
436 n
:= digitInBase(curChar
, 16);
437 if (n
< 0) then raise Exception
.Create('invalid hexstr escape');
439 if (digitInBase(curChar
, 16) > 0) then
441 n
:= n
*16+digitInBase(curChar
, 16);
444 mTokStr
+= AnsiChar(n
);
450 // duplicate single quote (pascal style)
451 if (qch
= '''') and (curChar
= '''') and (nextChar
= '''') then
459 if (curChar
= qch
) then
461 skipChar(); // skip ending quote
469 procedure parseId ();
472 mTokStr
:= ''; // just in case
473 while (curChar
= '_') or ((curChar
>= '0') and (curChar
<= '9')) or
474 ((curChar
>= 'A') and (curChar
<= 'Z')) or
475 ((curChar
>= 'a') and (curChar
<= 'z')) or
489 if not skipBlanks() then
503 if mAllowSignedNumbers
and ((curChar
= '+') or (curChar
= '-')) then begin parseInt(); exit
; end;
504 if (curChar
>= '0') and (curChar
<= '9') then begin parseInt(); exit
; end;
507 if (curChar
= '"') or (curChar
= '''') then begin parseString(); exit
; end;
510 if (curChar
= '_') or ((curChar
>= 'A') and (curChar
<= 'Z')) or ((curChar
>= 'a') and (curChar
<= 'z')) or (curChar
>= #128) then begin parseId(); exit
; end;
514 ',': mTokType
:= TTComma
;
515 ':': mTokType
:= TTColon
;
516 ';': mTokType
:= TTSemi
;
517 '{': mTokType
:= TTBegin
;
518 '}': mTokType
:= TTEnd
;
519 else mTokType
:= TTDelim
;
526 function TTextParser
.expectId (): AnsiString;
528 if (mTokType
<> TTId
) then raise Exception
.Create('identifier expected');
534 procedure TTextParser
.expectId (const aid
: AnsiString);
536 if (mTokType
<> TTId
) or (not StrEqu(mTokStr
, aid
)) then raise Exception
.Create('identifier '''+aid
+''' expected');
541 function TTextParser
.eatId (const aid
: AnsiString): Boolean;
544 if (mTokType
<> TTId
) or (not StrEqu(mTokStr
, aid
)) then exit
;
550 function TTextParser
.expectStr (allowEmpty
: Boolean=false): AnsiString;
552 if (mTokType
<> TTStr
) then raise Exception
.Create('string expected');
553 if (not allowEmpty
) and (Length(mTokStr
) = 0) then raise Exception
.Create('non-empty string expected');
559 function TTextParser
.expectInt (): Integer;
561 if (mTokType
<> TTInt
) then raise Exception
.Create('string expected');
567 procedure TTextParser
.expectTT (ttype
: Integer);
569 if (mTokType
<> ttype
) then raise Exception
.Create('unexpected token');
574 function TTextParser
.eatTT (ttype
: Integer): Boolean;
576 result
:= (mTokType
= ttype
);
577 if result
then skipToken();
581 function TTextParser
.expectDelim (const ch
: AnsiChar): AnsiChar;
583 if (mTokType
<> TTDelim
) then raise Exception
.Create(Format('delimiter ''%s'' expected', [ch
]));
589 function TTextParser
.eatDelim (const ch
: AnsiChar): Boolean;
592 if (mTokType
<> TTDelim
) or (mTokChar
<> ch
) then exit
;
598 // ////////////////////////////////////////////////////////////////////////// //
599 constructor TFileTextParser
.Create (const fname
: AnsiString; loadToken
: Boolean=true);
602 mFile
:= openDiskFileRO(fname
);
603 GetMem(mBuffer
, BufSize
);
605 mBufLen
:= mFile
.Read(mBuffer
^, BufSize
);
606 if (mBufLen
< 0) then raise Exception
.Create('TFileTextParser: read error');
607 inherited Create(loadToken
);
611 constructor TFileTextParser
.Create (st
: TStream
; loadToken
: Boolean=true);
613 if (st
= nil) then raise Exception
.Create('cannot create parser for nil stream');
615 GetMem(mBuffer
, BufSize
);
617 mBufLen
:= mFile
.Read(mBuffer
^, BufSize
);
618 if (mBufLen
< 0) then raise Exception
.Create('TFileTextParser: read error');
619 inherited Create(loadToken
);
623 destructor TFileTextParser
.Destroy ();
625 if (mBuffer
<> nil) then FreeMem(mBuffer
);
631 procedure TFileTextParser
.loadNextChar ();
633 if (mBufLen
= 0) then begin mNextChar
:= #0; exit
; end;
634 if (mBufPos
>= mBufLen
) then
636 mBufLen
:= mFile
.Read(mBuffer
^, BufSize
);
637 if (mBufLen
< 0) then raise Exception
.Create('TFileTextParser: read error');
638 if (mBufLen
= 0) then begin mNextChar
:= #0; exit
; end;
641 assert(mBufPos
< mBufLen
);
642 mNextChar
:= mBuffer
[mBufPos
];
644 if (mNextChar
= #0) then mNextChar
:= ' ';
648 // ////////////////////////////////////////////////////////////////////////// //
649 constructor TStrTextParser
.Create (const astr
: AnsiString; loadToken
: Boolean=true);
653 inherited Create(loadToken
);
657 destructor TStrTextParser
.Destroy ();
664 procedure TStrTextParser
.loadNextChar ();
667 if (mPos
> Length(mStr
)) then exit
;
668 mNextChar
:= mStr
[mPos
]; Inc(mPos
);
669 if (mNextChar
= #0) then mNextChar
:= ' ';
673 // ////////////////////////////////////////////////////////////////////////// //
674 constructor TTextWriter
.Create (); begin mIndent
:= 0; end;
675 procedure TTextWriter
.put (const s
: AnsiString); overload
; begin if (Length(s
) > 0) then putBuf((@(s
[1]))^, Length(s
)); end;
676 procedure TTextWriter
.put (v
: Byte); overload
; begin put('%d', [v
]); end;
677 procedure TTextWriter
.put (v
: Integer); overload
; begin put('%d', [v
]); end;
678 procedure TTextWriter
.put (const fmt
: AnsiString; args
: array of const); overload
; begin put(formatstrf(fmt
, args
)); end;
679 procedure TTextWriter
.putIndent (); var f
: Integer; begin for f
:= 1 to mIndent
do put(' '); end;
680 procedure TTextWriter
.indent (); begin Inc(mIndent
, 2); end;
681 procedure TTextWriter
.unindent (); begin Dec(mIndent
, 2); end;
684 // ////////////////////////////////////////////////////////////////////////// //
685 constructor TFileTextWriter
.Create (const fname
: AnsiString);
687 mFile
:= createDiskFile(fname
);
692 destructor TFileTextWriter
.Destroy ();
699 procedure TFileTextWriter
.putBuf (constref buf
; len
: SizeUInt
);
706 mFile
.WriteBuffer(pc
^, len
);