DEADSOFTWARE

some fixes for recursive grid queries (grid doesn't support recursive queries, but...
[d2df-sdl.git] / src / shared / utils.pas
1 (* Copyright (C) DooM 2D:Forever Developers
2 *
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.
7 *
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.
12 *
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/>.
15 *)
16 {$INCLUDE a_modes.inc}
17 unit utils;
19 interface
21 uses
22 SysUtils, Classes;
25 // ////////////////////////////////////////////////////////////////////////// //
26 type
27 TUtf8DecoderFast = packed record
28 public
29 const Replacement = $FFFD; // replacement char for invalid unicode
30 const Accept = 0;
31 const Reject = 12;
33 private
34 state: LongWord;
36 public
37 codepoint: LongWord; // decoded codepoint (valid only when decoder is in "complete" state)
39 public
40 constructor Create (v: Boolean{fuck you, fpc});
42 procedure reset (); inline;
44 function complete (): Boolean; inline; // is current character complete? take `codepoint` then
45 function invalid (): Boolean; inline;
46 function completeOrInvalid (): Boolean; inline;
48 // process one byte, return `true` if codepoint is ready
49 function decode (b: Byte): Boolean; inline; overload;
50 function decode (c: AnsiChar): Boolean; inline; overload;
51 end;
54 // ////////////////////////////////////////////////////////////////////////// //
55 // does filename have one of ".wad", ".pk3", ".zip" extensions?
56 function hasWadExtension (fn: AnsiString): Boolean;
58 // does filepath have ".XXX:\" in it?
59 function isWadPath (fn: AnsiString): Boolean;
61 // adds ".wad" extension if filename doesn't have one of ".wad", ".pk3", ".zip"
62 function addWadExtension (fn: AnsiString): AnsiString;
64 // convert number to strig with nice commas
65 function Int64ToStrComma (i: Int64): AnsiString;
67 function UpCase1251 (ch: Char): Char;
68 function LoCase1251 (ch: Char): Char;
70 // `true` if strings are equal; ignoring case for cp1251
71 function StrEquCI1251 (const s0, s1: AnsiString): Boolean;
73 function utf8Valid (const s: AnsiString): Boolean;
75 function utf8to1251 (s: AnsiString): AnsiString;
77 // `pathname` will be modified if path is valid
78 // `lastIsDir` should be `true` if we are searching for directory
79 // nobody cares about shitdoze, so i'll use the same code path for it
80 function findFileCI (var pathname: AnsiString; lastIsDir: Boolean=false): Boolean;
82 // they throws
83 function openDiskFileRO (pathname: AnsiString): TStream;
84 function createDiskFile (pathname: AnsiString): TStream;
86 // little endian
87 procedure writeInt (st: TStream; v: Byte); overload;
88 procedure writeInt (st: TStream; v: ShortInt); overload;
89 procedure writeInt (st: TStream; v: Word); overload;
90 procedure writeInt (st: TStream; v: SmallInt); overload;
91 procedure writeInt (st: TStream; v: LongWord); overload;
92 procedure writeInt (st: TStream; v: LongInt); overload;
93 procedure writeInt (st: TStream; v: Int64); overload;
94 procedure writeInt (st: TStream; v: UInt64); overload;
96 function readByte (st: TStream): Byte;
97 function readShortInt (st: TStream): ShortInt;
98 function readWord (st: TStream): Word;
99 function readSmallInt (st: TStream): SmallInt;
100 function readLongWord (st: TStream): LongWord;
101 function readLongInt (st: TStream): LongInt;
102 function readInt64 (st: TStream): Int64;
103 function readUInt64 (st: TStream): UInt64;
105 // big endian
106 procedure writeIntBE (st: TStream; v: Byte); overload;
107 procedure writeIntBE (st: TStream; v: ShortInt); overload;
108 procedure writeIntBE (st: TStream; v: Word); overload;
109 procedure writeIntBE (st: TStream; v: SmallInt); overload;
110 procedure writeIntBE (st: TStream; v: LongWord); overload;
111 procedure writeIntBE (st: TStream; v: LongInt); overload;
112 procedure writeIntBE (st: TStream; v: Int64); overload;
113 procedure writeIntBE (st: TStream; v: UInt64); overload;
115 function readByteBE (st: TStream): Byte;
116 function readShortIntBE (st: TStream): ShortInt;
117 function readWordBE (st: TStream): Word;
118 function readSmallIntBE (st: TStream): SmallInt;
119 function readLongWordBE (st: TStream): LongWord;
120 function readLongIntBE (st: TStream): LongInt;
121 function readInt64BE (st: TStream): Int64;
122 function readUInt64BE (st: TStream): UInt64;
125 type
126 TFormatStrFCallback = procedure (constref buf; len: SizeUInt);
128 // returns formatted string if `writerCB` is `nil`, empty string otherwise
129 function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
131 function wchar2win (wc: WideChar): AnsiChar; inline;
132 function utf2win (const s: AnsiString): AnsiString;
133 function digitInBase (ch: AnsiChar; base: Integer): Integer;
135 // returns string in single or double quotes
136 // single quotes supports only pascal-style '' for single quote char
137 // double quotes supports c-style escapes
138 // function will select quote mode automatically
139 function quoteStr (const s: AnsiString): AnsiString;
142 type
143 generic TSimpleList<ItemT> = class
144 private
145 type PItemT = ^ItemT;
147 public
148 type
149 TEnumerator = record
150 private
151 mItems: PItemT;
152 mCount: Integer;
153 mCurrent: Integer;
154 public
155 constructor Create (aitems: PItemT; acount: Integer);
156 function MoveNext: Boolean;
157 function getCurrent (): ItemT;
158 property Current: ItemT read getCurrent;
159 end;
161 private
162 mItems: array of ItemT;
163 mCount: Integer; // can be less than `mItems` size
165 private
166 function getAt (idx: Integer): ItemT; inline;
168 public
169 constructor Create ();
170 destructor Destroy (); override;
172 function GetEnumerator (): TEnumerator;
174 procedure reset (); inline; // won't resize `mItems`
175 procedure clear (); inline;
177 procedure append (constref it: ItemT); inline;
179 public
180 property count: Integer read mCount;
181 property at[idx: Integer]: ItemT read getAt; default;
182 end;
185 implementation
188 // ////////////////////////////////////////////////////////////////////////// //
189 constructor TSimpleList.TEnumerator.Create (aitems: PItemT; acount: Integer);
190 begin
191 mItems := aitems;
192 mCount := acount;
193 mCurrent := -1;
194 end;
196 function TSimpleList.TEnumerator.MoveNext: Boolean;
197 begin
198 Inc(mCurrent);
199 result := (mCurrent < mCount);
200 end;
202 function TSimpleList.TEnumerator.getCurrent (): ItemT;
203 begin
204 result := mItems[mCurrent];
205 end;
208 // ////////////////////////////////////////////////////////////////////////// //
209 constructor TSimpleList.Create ();
210 begin
211 mItems := nil;
212 mCount := 0;
213 end;
216 destructor TSimpleList.Destroy ();
217 begin
218 mItems := nil;
219 inherited;
220 end;
223 function TSimpleList.GetEnumerator (): TEnumerator;
224 begin
225 if (Length(mItems) > 0) then result := TEnumerator.Create(@mItems[0], mCount)
226 else result := TEnumerator.Create(nil, -1);
227 end;
230 procedure TSimpleList.reset (); inline;
231 begin
232 mCount := 0;
233 end;
236 procedure TSimpleList.clear (); inline;
237 begin
238 mItems := nil;
239 mCount := 0;
240 end;
243 function TSimpleList.getAt (idx: Integer): ItemT; inline;
244 begin
245 if (idx >= 0) and (idx < mCount) then result := mItems[idx] else result := Default(ItemT);
246 end;
249 procedure TSimpleList.append (constref it: ItemT); inline;
250 begin
251 if (mCount = Length(mItems)) then
252 begin
253 if (mCount = 0) then SetLength(mItems, 128) else SetLength(mItems, mCount*2);
254 end;
255 mItems[mCount] := it;
256 Inc(mCount);
257 end;
260 // ////////////////////////////////////////////////////////////////////////// //
261 var
262 wc2shitmap: array[0..65535] of AnsiChar;
263 wc2shitmapInited: Boolean = false;
266 // ////////////////////////////////////////////////////////////////////////// //
267 procedure initShitMap ();
268 const
269 cp1251: array[0..127] of Word = (
270 $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
271 $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
272 $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
273 $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
274 $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
275 $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
276 $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
277 $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
278 );
279 var
280 f: Integer;
281 begin
282 for f := 0 to High(wc2shitmap) do wc2shitmap[f] := '?';
283 for f := 0 to 127 do wc2shitmap[f] := AnsiChar(f);
284 for f := 0 to 127 do wc2shitmap[cp1251[f]] := AnsiChar(f+128);
285 wc2shitmapInited := true;
286 end;
289 // ////////////////////////////////////////////////////////////////////////// //
290 // fast state-machine based UTF-8 decoder; using 8 bytes of memory
291 // code points from invalid range will never be valid, this is the property of the state machine
292 const
293 // see http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
294 utf8dfa: array[0..$16c-1] of Byte = (
295 // maps bytes to character classes
296 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 00-0f
297 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 10-1f
298 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 20-2f
299 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 30-3f
300 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 40-4f
301 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 50-5f
302 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 60-6f
303 $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00, // 70-7f
304 $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01, // 80-8f
305 $09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09,$09, // 90-9f
306 $07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07, // a0-af
307 $07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07, // b0-bf
308 $08,$08,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02, // c0-cf
309 $02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02, // d0-df
310 $0a,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$04,$03,$03, // e0-ef
311 $0b,$06,$06,$06,$05,$08,$08,$08,$08,$08,$08,$08,$08,$08,$08,$08, // f0-ff
312 // maps a combination of a state of the automaton and a character class to a state
313 $00,$0c,$18,$24,$3c,$60,$54,$0c,$0c,$0c,$30,$48,$0c,$0c,$0c,$0c, // 100-10f
314 $0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$00,$0c,$0c,$0c,$0c,$0c,$00, // 110-11f
315 $0c,$00,$0c,$0c,$0c,$18,$0c,$0c,$0c,$0c,$0c,$18,$0c,$18,$0c,$0c, // 120-12f
316 $0c,$0c,$0c,$0c,$0c,$0c,$0c,$18,$0c,$0c,$0c,$0c,$0c,$18,$0c,$0c, // 130-13f
317 $0c,$0c,$0c,$0c,$0c,$18,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$24, // 140-14f
318 $0c,$24,$0c,$0c,$0c,$24,$0c,$0c,$0c,$0c,$0c,$24,$0c,$24,$0c,$0c, // 150-15f
319 $0c,$24,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c,$0c);
322 // ////////////////////////////////////////////////////////////////////////// //
323 constructor TUtf8DecoderFast.Create (v: Boolean{fuck you, fpc}); begin state := Accept; codepoint := 0; end;
325 procedure TUtf8DecoderFast.reset (); inline; begin state := Accept; codepoint := 0; end;
327 function TUtf8DecoderFast.complete (): Boolean; inline; begin result := (state = Accept); end;
328 function TUtf8DecoderFast.invalid (): Boolean; inline; begin result := (state = Reject); end;
329 function TUtf8DecoderFast.completeOrInvalid (): Boolean; inline; begin result := (state = Accept) or (state = Reject); end;
331 function TUtf8DecoderFast.decode (c: AnsiChar): Boolean; inline; overload; begin result := decode(Byte(c)); end;
333 function TUtf8DecoderFast.decode (b: Byte): Boolean; inline; overload;
334 var
335 tp: LongWord;
336 begin
337 if (state = Reject) then begin state := Accept; codepoint := 0; end;
338 tp := utf8dfa[b];
339 if (state <> Accept) then codepoint := (b and $3f) or (codepoint shl 6) else codepoint := ($ff shr tp) and b;
340 state := utf8dfa[256+state+tp];
341 if (state = Reject) then begin codepoint := Replacement; state := Accept; end;
342 result := (state = Accept);
343 end;
346 // ////////////////////////////////////////////////////////////////////////// //
347 function wchar2win (wc: WideChar): AnsiChar; inline;
348 begin
349 if not wc2shitmapInited then initShitMap();
350 if (LongWord(wc) > 65535) then result := '?' else result := wc2shitmap[LongWord(wc)];
351 end;
354 // ////////////////////////////////////////////////////////////////////////// //
355 function utf2win (const s: AnsiString): AnsiString;
356 var
357 f, c: Integer;
358 ud: TUtf8DecoderFast;
359 begin
360 for f := 1 to Length(s) do
361 begin
362 if (Byte(s[f]) > 127) then
363 begin
364 ud := TUtf8DecoderFast.Create(true);
365 result := '';
366 for c := 1 to Length(s) do
367 begin
368 if ud.decode(s[c]) then result += wchar2win(WideChar(ud.codepoint));
369 end;
370 exit;
371 end;
372 end;
373 result := s;
374 end;
377 // ////////////////////////////////////////////////////////////////////////// //
378 function digitInBase (ch: AnsiChar; base: Integer): Integer;
379 begin
380 result := -1;
381 if (base < 1) or (base > 36) then exit;
382 if (ch < '0') then exit;
383 if (base <= 10) then
384 begin
385 if (Integer(ch) >= 48+base) then exit;
386 result := Integer(ch)-48;
387 end
388 else
389 begin
390 if (ch >= '0') and (ch <= '9') then begin result := Integer(ch)-48; exit; end;
391 if (ch >= 'a') and (ch <= 'z') then Dec(ch, 32); // poor man's tolower()
392 if (ch < 'A') or (Integer(ch) >= 65+(base-10)) then exit;
393 result := Integer(ch)-65+10;
394 end;
395 end;
398 // ////////////////////////////////////////////////////////////////////////// //
399 function quoteStr (const s: AnsiString): AnsiString;
401 function squote (const s: AnsiString): AnsiString;
402 var
403 f: Integer;
404 begin
405 result := '''';
406 for f := 1 to Length(s) do
407 begin
408 if (s[f] = '''') then result += '''';
409 result += s[f];
410 end;
411 result += '''';
412 end;
414 function dquote (const s: AnsiString): AnsiString;
415 var
416 f: Integer;
417 ch: AnsiChar;
418 begin
419 result := '"';
420 for f := 1 to Length(s) do
421 begin
422 ch := s[f];
423 if (ch = #0) then result += '\z'
424 else if (ch = #9) then result += '\t'
425 else if (ch = #10) then result += '\n'
426 else if (ch = #13) then result += '\r'
427 else if (ch = #27) then result += '\e'
428 else if (ch < ' ') or (ch = #127) then
429 begin
430 result += '\x';
431 result += LowerCase(IntToHex(Integer(ch), 2));
432 end
433 else if (ch = '"') or (ch = '\') then
434 begin
435 result += '\';
436 result += ch;
437 end
438 else
439 begin
440 result += ch;
441 end;
442 end;
443 result += '"';
444 end;
446 var
447 needSingle: Boolean = false;
448 f: Integer;
449 begin
450 for f := 1 to Length(s) do
451 begin
452 if (s[f] = '''') then begin needSingle := true; continue; end;
453 if (s[f] < ' ') or (s[f] = #127) then begin result := dquote(s); exit; end;
454 end;
455 if needSingle then result := squote(s) else result := ''''+s+'''';
456 end;
459 // ////////////////////////////////////////////////////////////////////////// //
460 function hasWadExtension (fn: AnsiString): Boolean;
461 begin
462 fn := ExtractFileExt(fn);
463 result := StrEquCI1251(fn, '.wad') or StrEquCI1251(fn, '.pk3') or StrEquCI1251(fn, '.zip');
464 end;
467 function addWadExtension (fn: AnsiString): AnsiString;
468 begin
469 result := fn;
470 if not hasWadExtension(result) then result := result+'.wad';
471 end;
474 function isWadPath (fn: AnsiString): Boolean;
475 var
476 p: Integer;
477 s: AnsiString;
478 begin
479 result := false;
480 while true do
481 begin
482 p := Pos(':', fn);
483 if (p = 0) or (length(fn)-p < 1) then break;
484 if (p-4 > 1) and (fn[p-4] = '.') and ((fn[p+1] = '\') or (fn[p+1] = '/')) then
485 begin
486 s := Copy(fn, p-4, 4);
487 if StrEquCI1251(s, '.wad') or StrEquCI1251(s, '.pk3') or StrEquCI1251(s, '.zip') then
488 begin
489 result := true;
490 exit;
491 end;
492 end;
493 Delete(fn, 1, p);
494 end;
495 end;
498 function Int64ToStrComma (i: Int64): AnsiString;
499 var
500 f: Integer;
501 begin
502 Str(i, result);
503 f := Length(result)+1;
504 while f > 4 do
505 begin
506 Dec(f, 3); Insert(',', result, f);
507 end;
508 end;
511 function UpCase1251 (ch: Char): Char;
512 begin
513 if ch < #128 then
514 begin
515 if (ch >= 'a') and (ch <= 'z') then Dec(ch, 32);
516 end
517 else
518 begin
519 if (ch >= #224) and (ch <= #255) then
520 begin
521 Dec(ch, 32);
522 end
523 else
524 begin
525 case ch of
526 #184, #186, #191: Dec(ch, 16);
527 #162, #179: Dec(ch);
528 end;
529 end;
530 end;
531 result := ch;
532 end;
535 function LoCase1251 (ch: Char): Char;
536 begin
537 if ch < #128 then
538 begin
539 if (ch >= 'A') and (ch <= 'Z') then Inc(ch, 32);
540 end
541 else
542 begin
543 if (ch >= #192) and (ch <= #223) then
544 begin
545 Inc(ch, 32);
546 end
547 else
548 begin
549 case ch of
550 #168, #170, #175: Inc(ch, 16);
551 #161, #178: Inc(ch);
552 end;
553 end;
554 end;
555 result := ch;
556 end;
559 function StrEquCI1251 (const s0, s1: AnsiString): Boolean;
560 var
561 i: Integer;
562 begin
563 result := false;
564 if length(s0) <> length(s1) then exit;
565 for i := 1 to length(s0) do if UpCase1251(s0[i]) <> UpCase1251(s1[i]) then exit;
566 result := true;
567 end;
570 // ////////////////////////////////////////////////////////////////////////// //
571 // utils
572 // `ch`: utf8 start
573 // -1: invalid utf8
574 function utf8CodeLen (ch: Word): Integer;
575 begin
576 if ch < $80 then begin result := 1; exit; end;
577 if (ch and $FE) = $FC then begin result := 6; exit; end;
578 if (ch and $FC) = $F8 then begin result := 5; exit; end;
579 if (ch and $F8) = $F0 then begin result := 4; exit; end;
580 if (ch and $F0) = $E0 then begin result := 3; exit; end;
581 if (ch and $E0) = $C0 then begin result := 2; exit; end;
582 result := -1; // invalid
583 end;
586 function utf8Valid (const s: AnsiString): Boolean;
587 var
588 pos, len: Integer;
589 begin
590 result := false;
591 pos := 1;
592 while pos <= length(s) do
593 begin
594 len := utf8CodeLen(Byte(s[pos]));
595 if len < 1 then exit; // invalid sequence start
596 if pos+len-1 > length(s) then exit; // out of chars in string
597 Dec(len);
598 Inc(pos);
599 // check other sequence bytes
600 while len > 0 do
601 begin
602 if (Byte(s[pos]) and $C0) <> $80 then exit;
603 Dec(len);
604 Inc(pos);
605 end;
606 end;
607 result := true;
608 end;
611 // ////////////////////////////////////////////////////////////////////////// //
612 const
613 uni2wint: array [128..255] of Word = (
614 $0402,$0403,$201A,$0453,$201E,$2026,$2020,$2021,$20AC,$2030,$0409,$2039,$040A,$040C,$040B,$040F,
615 $0452,$2018,$2019,$201C,$201D,$2022,$2013,$2014,$003F,$2122,$0459,$203A,$045A,$045C,$045B,$045F,
616 $00A0,$040E,$045E,$0408,$00A4,$0490,$00A6,$00A7,$0401,$00A9,$0404,$00AB,$00AC,$00AD,$00AE,$0407,
617 $00B0,$00B1,$0406,$0456,$0491,$00B5,$00B6,$00B7,$0451,$2116,$0454,$00BB,$0458,$0405,$0455,$0457,
618 $0410,$0411,$0412,$0413,$0414,$0415,$0416,$0417,$0418,$0419,$041A,$041B,$041C,$041D,$041E,$041F,
619 $0420,$0421,$0422,$0423,$0424,$0425,$0426,$0427,$0428,$0429,$042A,$042B,$042C,$042D,$042E,$042F,
620 $0430,$0431,$0432,$0433,$0434,$0435,$0436,$0437,$0438,$0439,$043A,$043B,$043C,$043D,$043E,$043F,
621 $0440,$0441,$0442,$0443,$0444,$0445,$0446,$0447,$0448,$0449,$044A,$044B,$044C,$044D,$044E,$044F
622 );
625 function decodeUtf8Char (s: AnsiString; var pos: Integer): char;
626 var
627 b, c: Integer;
628 begin
629 (* The following encodings are valid, except for the 5 and 6 byte
630 * combinations:
631 * 0xxxxxxx
632 * 110xxxxx 10xxxxxx
633 * 1110xxxx 10xxxxxx 10xxxxxx
634 * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
635 * 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
636 * 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
637 *)
638 result := '?';
639 if pos > length(s) then exit;
641 b := Byte(s[pos]);
642 Inc(pos);
643 if b < $80 then begin result := char(b); exit; end;
645 // mask out unused bits
646 if (b and $FE) = $FC then b := b and $01
647 else if (b and $FC) = $F8 then b := b and $03
648 else if (b and $F8) = $F0 then b := b and $07
649 else if (b and $F0) = $E0 then b := b and $0F
650 else if (b and $E0) = $C0 then b := b and $1F
651 else exit; // invalid utf8
653 // now continue
654 while pos <= length(s) do
655 begin
656 c := Byte(s[pos]);
657 if (c and $C0) <> $80 then break; // no more
658 b := b shl 6;
659 b := b or (c and $3F);
660 Inc(pos);
661 end;
663 // done, try 1251
664 for c := 128 to 255 do if uni2wint[c] = b then begin result := char(c and $FF); exit; end;
665 // alas
666 end;
669 function utf8to1251 (s: AnsiString): AnsiString;
670 var
671 pos: Integer;
672 begin
673 if not utf8Valid(s) then begin result := s; exit; end;
674 pos := 1;
675 while pos <= length(s) do
676 begin
677 if Byte(s[pos]) >= $80 then break;
678 Inc(pos);
679 end;
680 if pos > length(s) then begin result := s; exit; end; // nothing to do here
681 result := '';
682 pos := 1;
683 while pos <= length(s) do result := result+decodeUtf8Char(s, pos);
684 end;
687 // ////////////////////////////////////////////////////////////////////////// //
688 // `pathname` will be modified if path is valid
689 // `lastIsDir` should be `true` if we are searching for directory
690 // nobody cares about shitdoze, so i'll use the same code path for it
691 function findFileCI (var pathname: AnsiString; lastIsDir: Boolean=false): Boolean;
692 var
693 sr: TSearchRec;
694 npt: AnsiString;
695 newname: AnsiString = '';
696 curname: AnsiString;
697 wantdir: Boolean;
698 attr: LongInt;
699 foundher: Boolean;
700 begin
701 npt := pathname;
702 result := (length(npt) > 0);
703 if (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) then newname := '/';
704 while length(npt) > 0 do
705 begin
706 // remove trailing slashes
707 while (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) do Delete(npt, 1, 1);
708 if length(npt) = 0 then break;
709 // extract name
710 curname := '';
711 while (length(npt) > 0) and (npt[1] <> '/') and (npt[1] <> '\') do
712 begin
713 curname := curname+npt[1];
714 Delete(npt, 1, 1);
715 end;
716 // remove trailing slashes again
717 while (length(npt) > 0) and ((npt[1] = '/') or (npt[1] = '\')) do Delete(npt, 1, 1);
718 wantdir := lastIsDir or (length(npt) > 0); // do we want directory here?
719 //writeln(Format('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)]));
720 // try the easiest case first
721 attr := FileGetAttr(newname+curname);
722 if attr <> -1 then
723 begin
724 if wantdir = ((attr and faDirectory) <> 0) then
725 begin
726 // i found her!
727 newname := newname+curname;
728 if wantdir then newname := newname+'/';
729 continue;
730 end;
731 end;
732 //writeln(Format('npt=[%s]; newname=[%s]; curname=[%s]; wantdir=%d', [npt, newname, curname, Integer(wantdir)]));
733 // alas, either not found, or invalid attributes
734 foundher := false;
735 try
736 if FindFirst(newname+'*', faAnyFile, sr) = 0 then
737 repeat
738 if (wantdir = ((sr.attr and faDirectory) <> 0)) and StrEquCI1251(sr.name, curname) then
739 begin
740 // i found her!
741 newname := newname+sr.name;
742 if wantdir then newname := newname+'/';
743 foundher := true;
744 break;
745 end;
746 until FindNext(sr) <> 0;
747 finally
748 FindClose(sr);
749 end;
750 if not foundher then begin newname := ''; result := false; break; end;
751 end;
752 if result then pathname := newname;
753 end;
756 function openDiskFileRO (pathname: AnsiString): TStream;
757 begin
758 if not findFileCI(pathname) then raise Exception.Create('can''t open file "'+pathname+'"');
759 result := TFileStream.Create(pathname, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
760 end;
762 function createDiskFile (pathname: AnsiString): TStream;
763 var
764 path: AnsiString;
765 begin
766 path := ExtractFilePath(pathname);
767 if length(path) > 0 then
768 begin
769 if not findFileCI(path, true) then raise Exception.Create('can''t create file "'+pathname+'"');
770 end;
771 result := TFileStream.Create(path+ExtractFileName(pathname), fmCreate);
772 end;
775 procedure writeIntegerLE (st: TStream; vp: Pointer; size: Integer);
776 {$IFDEF ENDIAN_LITTLE}
777 begin
778 st.writeBuffer(vp^, size);
779 end;
780 {$ELSE}
781 var
782 p: PByte;
783 begin
784 p := PByte(vp)+size-1;
785 while size > 0 do
786 begin
787 st.writeBuffer(p^, 1);
788 Dec(size);
789 Dec(p);
790 end;
791 end;
792 {$ENDIF}
794 procedure writeIntegerBE (st: TStream; vp: Pointer; size: Integer);
795 {$IFDEF ENDIAN_LITTLE}
796 var
797 p: PByte;
798 begin
799 p := PByte(vp)+size-1;
800 while size > 0 do
801 begin
802 st.writeBuffer(p^, 1);
803 Dec(size);
804 Dec(p);
805 end;
806 end;
807 {$ELSE}
808 begin
809 st.writeBuffer(vp^, size);
810 end;
811 {$ENDIF}
813 procedure writeInt (st: TStream; v: Byte); overload; begin writeIntegerLE(st, @v, 1); end;
814 procedure writeInt (st: TStream; v: ShortInt); overload; begin writeIntegerLE(st, @v, 1); end;
815 procedure writeInt (st: TStream; v: Word); overload; begin writeIntegerLE(st, @v, 2); end;
816 procedure writeInt (st: TStream; v: SmallInt); overload; begin writeIntegerLE(st, @v, 2); end;
817 procedure writeInt (st: TStream; v: LongWord); overload; begin writeIntegerLE(st, @v, 4); end;
818 procedure writeInt (st: TStream; v: LongInt); overload; begin writeIntegerLE(st, @v, 4); end;
819 procedure writeInt (st: TStream; v: Int64); overload; begin writeIntegerLE(st, @v, 8); end;
820 procedure writeInt (st: TStream; v: UInt64); overload; begin writeIntegerLE(st, @v, 8); end;
822 procedure writeIntBE (st: TStream; v: Byte); overload; begin writeIntegerBE(st, @v, 1); end;
823 procedure writeIntBE (st: TStream; v: ShortInt); overload; begin writeIntegerBE(st, @v, 1); end;
824 procedure writeIntBE (st: TStream; v: Word); overload; begin writeIntegerBE(st, @v, 2); end;
825 procedure writeIntBE (st: TStream; v: SmallInt); overload; begin writeIntegerBE(st, @v, 2); end;
826 procedure writeIntBE (st: TStream; v: LongWord); overload; begin writeIntegerBE(st, @v, 4); end;
827 procedure writeIntBE (st: TStream; v: LongInt); overload; begin writeIntegerBE(st, @v, 4); end;
828 procedure writeIntBE (st: TStream; v: Int64); overload; begin writeIntegerBE(st, @v, 8); end;
829 procedure writeIntBE (st: TStream; v: UInt64); overload; begin writeIntegerBE(st, @v, 8); end;
832 procedure readIntegerLE (st: TStream; vp: Pointer; size: Integer);
833 {$IFDEF ENDIAN_LITTLE}
834 begin
835 st.readBuffer(vp^, size);
836 end;
837 {$ELSE}
838 var
839 p: PByte;
840 begin
841 p := PByte(vp)+size-1;
842 while size > 0 do
843 begin
844 st.readBuffer(p^, 1);
845 Dec(size);
846 Dec(p);
847 end;
848 end;
849 {$ENDIF}
851 procedure readIntegerBE (st: TStream; vp: Pointer; size: Integer);
852 {$IFDEF ENDIAN_LITTLE}
853 var
854 p: PByte;
855 begin
856 p := PByte(vp)+size-1;
857 while size > 0 do
858 begin
859 st.readBuffer(p^, 1);
860 Dec(size);
861 Dec(p);
862 end;
863 end;
864 {$ELSE}
865 begin
866 st.readBuffer(vp^, size);
867 end;
868 {$ENDIF}
870 function readByte (st: TStream): Byte; begin readIntegerLE(st, @result, 1); end;
871 function readShortInt (st: TStream): ShortInt; begin readIntegerLE(st, @result, 1); end;
872 function readWord (st: TStream): Word; begin readIntegerLE(st, @result, 2); end;
873 function readSmallInt (st: TStream): SmallInt; begin readIntegerLE(st, @result, 2); end;
874 function readLongWord (st: TStream): LongWord; begin readIntegerLE(st, @result, 4); end;
875 function readLongInt (st: TStream): LongInt; begin readIntegerLE(st, @result, 4); end;
876 function readInt64 (st: TStream): Int64; begin readIntegerLE(st, @result, 8); end;
877 function readUInt64 (st: TStream): UInt64; begin readIntegerLE(st, @result, 8); end;
879 function readByteBE (st: TStream): Byte; begin readIntegerBE(st, @result, 1); end;
880 function readShortIntBE (st: TStream): ShortInt; begin readIntegerBE(st, @result, 1); end;
881 function readWordBE (st: TStream): Word; begin readIntegerBE(st, @result, 2); end;
882 function readSmallIntBE (st: TStream): SmallInt; begin readIntegerBE(st, @result, 2); end;
883 function readLongWordBE (st: TStream): LongWord; begin readIntegerBE(st, @result, 4); end;
884 function readLongIntBE (st: TStream): LongInt; begin readIntegerBE(st, @result, 4); end;
885 function readInt64BE (st: TStream): Int64; begin readIntegerBE(st, @result, 8); end;
886 function readUInt64BE (st: TStream): UInt64; begin readIntegerBE(st, @result, 8); end;
889 // ////////////////////////////////////////////////////////////////////////// //
890 {$IFDEF WINDOWS}
891 function snprintf (buf: PAnsiChar; bufsize: SizeUInt; const fmt: PAnsiChar): SizeUInt; cdecl; varargs; external 'msvcrt.dll' name '_snprintf';
892 {$ELSE}
893 function snprintf (buf: PAnsiChar; bufsize: SizeUInt; const fmt: PAnsiChar): SizeUInt; cdecl; varargs; external 'libc' name 'snprintf';
894 {$ENDIF}
897 (*
898 procedure conwriter (constref buf; len: SizeUInt);
899 var
900 ss: ShortString;
901 slen: Integer;
902 b: PByte;
903 begin
904 if (len < 1) then exit;
905 b := PByte(@buf);
906 while (len > 0) do
907 begin
908 if (len > 255) then slen := 255 else slen := Integer(len);
909 Move(b^, ss[1], len);
910 ss[0] := AnsiChar(slen);
911 write(ss);
912 b += slen;
913 len -= slen;
914 end;
915 end;
916 *)
919 function formatstrf (const fmt: AnsiString; args: array of const; writerCB: TFormatStrFCallback=nil): AnsiString;
920 const
921 PadSpaces: AnsiString = ' ';
922 PadZeroes: AnsiString = '00000000000000000000000000000000000000000000000000000000000000000000000';
923 var
924 curarg: Integer = 0; // current arg in `args`
925 sign, fmtch: AnsiChar;
926 zeropad: Boolean;
927 width, prec: Integer; // width and precision
928 spos, epos: Integer;
929 ch: AnsiChar;
930 strbuf: array[0..256] of AnsiChar;
931 strblen: SizeUInt;
932 fmtbuf: array[0..256] of AnsiChar;
933 fmtblen: Integer;
934 pclen: Integer;
935 pc: PAnsiChar;
937 procedure writer (constref buf; len: SizeUInt);
938 var
939 ss: ShortString;
940 slen: Integer;
941 b: PByte;
942 begin
943 if (len < 1) then exit;
944 b := PByte(@buf);
945 if assigned(writerCB) then
946 begin
947 writerCB(b^, len);
948 end
949 else
950 begin
951 while (len > 0) do
952 begin
953 if (len > 255) then slen := 255 else slen := Integer(len);
954 Move(b^, ss[1], len);
955 ss[0] := AnsiChar(slen);
956 result += ss;
957 b += slen;
958 len -= slen;
959 end;
960 end;
961 end;
963 procedure xwrite (const s: AnsiString);
964 begin
965 if (Length(s) > 0) then writer(PAnsiChar(s)^, Length(s));
966 end;
968 procedure putFmtChar (ch: AnsiChar);
969 begin
970 fmtbuf[fmtblen] := ch;
971 Inc(fmtblen);
972 end;
974 procedure putFmtInt (n: Integer);
975 var
976 len: SizeUInt;
977 begin
978 len := snprintf(@fmtbuf[fmtblen], Length(fmtbuf)-fmtblen, '%d', n);
979 if (len > 0) then Inc(fmtblen, len);
980 end;
982 procedure buildCFormat (const pfx: AnsiString='');
983 var
984 f: Integer;
985 begin
986 fmtblen := 0;
987 for f := 1 to Length(pfx) do putFmtChar(pfx[f]);
988 putFmtChar('%');
989 if (sign <> ' ') then putFmtChar(sign);
990 if (width >= 0) then
991 begin
992 if (zeropad) then putFmtChar('0');
993 putFmtInt(width);
994 if (prec >= 0) then
995 begin
996 putFmtChar('.');
997 putFmtInt(prec);
998 end;
999 end;
1000 putFmtChar(fmtch);
1001 fmtbuf[fmtblen] := #0;
1002 end;
1004 procedure writeStrBuf ();
1005 begin
1006 if (strblen > 0) then writer(strbuf, strblen);
1007 end;
1009 function i642str (n: Int64; hex: Boolean; hexup: Boolean): PAnsiChar;
1010 var
1011 neg: Boolean;
1012 xpos: Integer;
1013 begin
1014 if (n = $8000000000000000) then
1015 begin
1016 if hex then snprintf(@strbuf[0], Length(strbuf), '-8000000000000000')
1017 else snprintf(@strbuf[0], Length(strbuf), '-9223372036854775808');
1018 result := @strbuf[0];
1019 end
1020 else
1021 begin
1022 neg := (n < 0);
1023 if neg then n := -n;
1024 xpos := High(strbuf);
1025 strbuf[xpos] := #0; Dec(xpos);
1026 repeat
1027 if hex then
1028 begin
1029 strbuf[xpos] := AnsiChar((n mod 10)+48);
1030 Dec(xpos);
1031 n := n div 10;
1032 end
1033 else
1034 begin
1035 if (n mod 16 > 9) then
1036 begin
1037 strbuf[xpos] := AnsiChar((n mod 16)+48+7);
1038 if not hexup then Inc(strbuf[xpos], 32);
1039 end
1040 else strbuf[xpos] := AnsiChar((n mod 16)+48);
1041 Dec(xpos);
1042 n := n div 16;
1043 end;
1044 until (n = 0);
1045 if neg then begin strbuf[xpos] := '-'; Dec(xpos); end;
1046 result := @strbuf[xpos+1];
1047 end;
1048 end;
1050 function ui642str (n: UInt64; hex: Boolean; hexup: Boolean): PAnsiChar;
1051 var
1052 xpos: Integer;
1053 begin
1054 xpos := High(strbuf);
1055 strbuf[xpos] := #0; Dec(xpos);
1056 repeat
1057 if hex then
1058 begin
1059 strbuf[xpos] := AnsiChar((n mod 10)+48);
1060 Dec(xpos);
1061 n := n div 10;
1062 end
1063 else
1064 begin
1065 if (n mod 16 > 9) then
1066 begin
1067 strbuf[xpos] := AnsiChar((n mod 16)+48+7);
1068 if not hexup then Inc(strbuf[xpos], 32);
1069 end
1070 else strbuf[xpos] := AnsiChar((n mod 16)+48);
1071 Dec(xpos);
1072 n := n div 16;
1073 end;
1074 until (n = 0);
1075 result := @strbuf[xpos+1];
1076 end;
1078 procedure indent (len: Integer);
1079 var
1080 ilen: Integer;
1081 begin
1082 while (len > 0) do
1083 begin
1084 if (len > Length(PadSpaces)) then ilen := Length(PadSpaces) else ilen := len;
1085 writer(PAnsiChar(PadSpaces)^, ilen);
1086 Dec(len, ilen);
1087 end;
1088 end;
1090 procedure indent0 (len: Integer);
1091 var
1092 ilen: Integer;
1093 begin
1094 while (len > 0) do
1095 begin
1096 if (len > Length(PadZeroes)) then ilen := Length(PadZeroes) else ilen := len;
1097 writer(PAnsiChar(PadZeroes)^, ilen);
1098 Dec(len, ilen);
1099 end;
1100 end;
1102 begin
1103 result := '';
1104 spos := 1;
1105 while (spos <= Length(fmt)) do
1106 begin
1107 // print literal part
1108 epos := spos;
1109 while (epos <= Length(fmt)) and (fmt[epos] <> '%') do Inc(epos);
1110 // output literal part
1111 if (epos > spos) then
1112 begin
1113 if (epos > Length(fmt)) then
1114 begin
1115 writer((PAnsiChar(fmt)+spos-1)^, epos-spos);
1116 break;
1117 end;
1118 if (epos+1 > Length(fmt)) then Inc(epos) // last percent, output literally
1119 else if (fmt[epos+1] = '%') then // special case
1120 begin
1121 Inc(epos);
1122 writer((PAnsiChar(fmt)+spos-1)^, epos-spos);
1123 spos := epos+1;
1124 end
1125 else
1126 begin
1127 writer((PAnsiChar(fmt)+spos-1)^, epos-spos);
1128 spos := epos;
1129 end;
1130 continue;
1131 end;
1132 // check if we have argument for this format string
1133 if (curarg > High(args)) then
1134 begin
1135 xwrite('<OUT OF ARGS>');
1136 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1137 break;
1138 end;
1139 // skip percent
1140 if (spos+1 > Length(fmt)) then break; // oops
1141 assert(fmt[spos] = '%');
1142 Inc(spos);
1143 // parse format; check for sign
1144 if (fmt[spos] = '-') then begin sign := '-'; Inc(spos); end
1145 else if (fmt[spos] = '+') then begin sign := '+'; Inc(spos); end
1146 else sign := ' ';
1147 // parse width
1148 if (spos > Length(fmt)) then begin xwrite('<INVALID FORMAT>'); break; end;
1149 if (sign <> ' ') or ((fmt[spos] >= '0') and (fmt[spos] <= '9')) then
1150 begin
1151 if (fmt[spos] < '0') or (fmt[spos] > '9') then begin xwrite('<INVALID FORMAT>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1152 zeropad := (fmt[spos] = '0');
1153 width := 0;
1154 while (spos <= Length(fmt)) do
1155 begin
1156 ch := fmt[spos];
1157 if (ch < '0') or (ch > '9') then break;
1158 width := width*10+Integer(ch)-48;
1159 Inc(spos);
1160 end;
1161 end
1162 else
1163 begin
1164 width := -1;
1165 zeropad := false;
1166 end;
1167 // parse precision
1168 prec := -1;
1169 if (spos <= Length(fmt)) and (fmt[spos] = '.') then
1170 begin
1171 Inc(spos);
1172 if (spos > Length(fmt)) then begin xwrite('<INVALID FORMAT>'); break; end;
1173 if (fmt[spos] < '0') or (fmt[spos] > '9') then begin xwrite('<INVALID FORMAT>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1174 prec := 0;
1175 while (spos <= Length(fmt)) do
1176 begin
1177 ch := fmt[spos];
1178 if (ch < '0') or (ch > '9') then break;
1179 prec := prec*10+Integer(ch)-48;
1180 Inc(spos);
1181 end;
1182 end;
1183 // get format char
1184 if (spos > Length(fmt)) then begin xwrite('<INVALID FORMAT>'); break; end;
1185 fmtch := fmt[spos];
1186 Inc(spos);
1187 // done parsing format, check for valid format chars
1188 if not (fmtch in ['s','u','d','x','X','p','f','g','c']) then begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1189 // now write formatted string
1190 case args[curarg].VType of
1191 vtInteger: // args[curarg].VInteger
1192 begin
1193 if not (fmtch in ['s','u','d','x','X']) then begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1194 if (fmtch = 's') then fmtch := 'd';
1195 buildCFormat();
1196 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], args[curarg].VInteger);
1197 writeStrBuf();
1198 end;
1199 vtBoolean: // args[curarg].VBoolean
1200 case fmtch of
1201 's':
1202 begin
1203 buildCFormat();
1204 if args[curarg].VBoolean then strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], 'true')
1205 else strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], 'false');
1206 writeStrBuf();
1207 end;
1208 'c':
1209 begin
1210 buildCFormat();
1211 if args[curarg].VBoolean then strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], AnsiChar('t'))
1212 else strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], AnsiChar('f'));
1213 writeStrBuf();
1214 end;
1215 'u', 'd', 'x', 'X':
1216 begin
1217 buildCFormat();
1218 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Integer(args[curarg].VBoolean));
1219 writeStrBuf();
1220 end;
1221 else
1222 begin
1223 xwrite('<INVALID FORMAT CHAR>');
1224 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1225 break;
1226 end;
1227 end;
1228 vtChar: // args[curarg].VChar
1229 case fmtch of
1230 's', 'c':
1231 begin
1232 fmtch := 'c';
1233 buildCFormat();
1234 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], args[curarg].VChar);
1235 writeStrBuf();
1236 end;
1237 'u', 'd', 'x', 'X':
1238 begin
1239 buildCFormat();
1240 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Integer(args[curarg].VChar));
1241 writeStrBuf();
1242 end;
1243 else
1244 begin
1245 xwrite('<INVALID FORMAT CHAR>');
1246 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1247 break;
1248 end;
1249 end;
1250 //vtWideChar: begin end; // args[curarg].VWideChar (WideChar)
1251 vtExtended: // args[curarg].VExtended^
1252 case fmtch of
1253 's', 'g':
1254 begin
1255 fmtch := 'g';
1256 buildCFormat();
1257 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Double(args[curarg].VExtended^));
1258 writeStrBuf();
1259 end;
1260 'f':
1261 begin
1262 buildCFormat();
1263 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Double(args[curarg].VExtended^));
1264 writeStrBuf();
1265 end;
1266 'd':
1267 begin
1268 buildCFormat();
1269 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], Integer(trunc(args[curarg].VExtended^)));
1270 writeStrBuf();
1271 end;
1272 'u', 'x', 'X':
1273 begin
1274 buildCFormat();
1275 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], LongWord(trunc(args[curarg].VExtended^)));
1276 writeStrBuf();
1277 end;
1278 else
1279 begin
1280 xwrite('<INVALID FORMAT CHAR>');
1281 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1282 break;
1283 end;
1284 end;
1285 vtString: // args[curarg].VString^ (PShortString)
1286 begin
1287 if (sign <> '-') then indent(width-Length(args[curarg].VString^));
1288 writer(args[curarg].VString^[1], Length(args[curarg].VString^));
1289 if (sign = '-') then indent(width-Length(args[curarg].VString^));
1290 end;
1291 vtPointer: // args[curarg].VPointer
1292 case fmtch of
1293 's':
1294 begin
1295 fmtch := 'x';
1296 if (width < 8) then width := 8;
1297 zeropad := true;
1298 buildCFormat('0x');
1299 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], PtrUInt(args[curarg].VPointer));
1300 writeStrBuf();
1301 end;
1302 'u', 'd', 'x', 'p', 'X':
1303 begin
1304 if (fmtch = 'p') then fmtch := 'x';
1305 if (width < 8) then width := 8;
1306 zeropad := true;
1307 buildCFormat('0x');
1308 strblen := snprintf(@strbuf[0], Length(strbuf), @fmtbuf[0], PtrUInt(args[curarg].VPointer));
1309 writeStrBuf();
1310 end;
1311 else
1312 begin
1313 xwrite('<INVALID FORMAT CHAR>');
1314 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1315 break;
1316 end;
1317 end;
1318 vtPChar: // args[curarg].VPChar
1319 if (args[curarg].VPChar = nil) then
1320 begin
1321 if (sign <> '-') then indent(width-3);
1322 xwrite('nil');
1323 if (sign = '-') then indent(width-3);
1324 end
1325 else
1326 begin
1327 pclen := 0;
1328 while (args[curarg].VPChar[pclen] <> #0) do Inc(pclen);
1329 if (sign <> '-') then indent(width-pclen);
1330 writer(args[curarg].VPChar^, pclen);
1331 if (sign = '-') then indent(width-pclen);
1332 end;
1333 vtObject: // args[curarg].VObject.Classname (TObject)
1334 begin
1335 if (sign <> '-') then indent(width-Length(args[curarg].VObject.Classname));
1336 xwrite(args[curarg].VObject.Classname);
1337 if (sign = '-') then indent(width-Length(args[curarg].VObject.Classname));
1338 end;
1339 vtClass: // args[curarg].VClass.Classname (TClass)
1340 begin
1341 if (sign <> '-') then indent(width-Length(args[curarg].VClass.Classname));
1342 xwrite(args[curarg].VClass.Classname);
1343 if (sign = '-') then indent(width-Length(args[curarg].VClass.Classname));
1344 end;
1345 //vtPWideChar: begin end; // args[curarg].VPWideChar (PWideChar)
1346 vtAnsiString: // AnsiString(args[curarg].VAnsiString) (Pointer)
1347 begin
1348 if (sign <> '-') then indent(width-Length(AnsiString(args[curarg].VAnsiString)));
1349 xwrite(AnsiString(args[curarg].VAnsiString));
1350 if (sign = '-') then indent(width-Length(AnsiString(args[curarg].VAnsiString)));
1351 end;
1352 //vtCurrency: begin end; // args[curarg].VCurrency (PCurrency)
1353 //vtVariant: begin end; // args[curarg].VVariant^ (PVariant)
1354 //vtInterface: begin end; // args[curarg].VInterface (Pointer);
1355 //vtWideString: begin end; // args[curarg].VWideString (Pointer);
1356 vtInt64: // args[curarg].VInt64^ (PInt64)
1357 begin
1358 case fmtch of
1359 's','d','u': pc := i642str(args[curarg].VInt64^, false, false);
1360 'x': pc := i642str(args[curarg].VInt64^, true, false);
1361 'X': pc := i642str(args[curarg].VInt64^, true, true);
1362 else begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1363 end;
1364 pclen := 0;
1365 while (pc[pclen] <> #0) do Inc(pclen);
1366 if (sign <> '-') and (width > pclen) then
1367 begin
1368 if zeropad then
1369 begin
1370 if (pc[0] = '-') or (pc[0] = '+') then
1371 begin
1372 writer(pc^, 1);
1373 indent0(width-pclen-1);
1374 Inc(pc);
1375 Dec(pclen);
1376 end
1377 else
1378 begin
1379 indent0(width-pclen);
1380 end;
1381 end
1382 else
1383 begin
1384 indent(width-pclen);
1385 end;
1386 end;
1387 writer(pc^, pclen);
1388 if (sign = '-') then indent(width-pclen);
1389 end;
1390 vtQWord: // args[curarg].VQWord^ (PQWord)
1391 begin
1392 case fmtch of
1393 's','d','u': pc := ui642str(args[curarg].VInt64^, false, false);
1394 'x': pc := ui642str(args[curarg].VInt64^, true, false);
1395 'X': pc := ui642str(args[curarg].VInt64^, true, true);
1396 else begin xwrite('<INVALID FORMAT CHAR>'); writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1); break; end;
1397 end;
1398 pclen := 0;
1399 while (pc[pclen] <> #0) do Inc(pclen);
1400 if (sign <> '-') then begin if zeropad then indent0(width-pclen) else indent(width-pclen); end;
1401 writer(pc^, pclen);
1402 if (sign = '-') then indent(width-pclen);
1403 end;
1404 else
1405 begin
1406 xwrite('<INVALID TYPE>');
1407 writer((PAnsiChar(fmt)+spos-1)^, Length(fmt)-spos+1);
1408 break;
1409 end;
1410 end;
1411 Inc(curarg);
1412 end;
1413 end;
1416 (*
1417 var
1418 ss: ShortString;
1419 ls: AnsiString;
1420 i64: Int64 = -$A000000000;
1421 ui64: UInt64 = $A000000000;
1422 begin
1423 writef(conwriter, 'test int:<%s> bool:<%s:%02d:%c> bool:<%s:%02d:%c>; char:<%2s;%c;%d>!'#10, [42, true, true, true, false, false, false, 'A', 'A', 'A']);
1424 writef(conwriter, 'test float:<%s;%u;%f;%g>'#10, [666.6942, 666.6942, 666.6942, 666.6942]);
1425 ss := 'fuckit';
1426 ls := 'FUCKIT';
1427 writef(conwriter, 'test ss:<%5s;%040s>'#10, [ss, ss]);
1428 writef(conwriter, 'test ls:<%5s;%040s>'#10, [ls, ls]);
1429 writef(conwriter, 'test pointer:<%s;%x;%p>'#10, [@ss, @ss, @ss]);
1430 writef(conwriter, 'test i64:<%s;%x;%015d;%u;%X>'#10, [i64, i64, i64, i64, i64]);
1431 writef(conwriter, 'test ui64:<%s;%x;%15d;%015u;%X>'#10, [ui64, ui64, ui64, ui64, ui64]);
1432 *)
1433 end.