You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
5.6 KiB

  1. unit uutlSScanf;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5. Classes, SysUtils;
  6. function utlSScanf(const s: string; const fmt: string; const Pointers: array of Pointer): Integer;
  7. function utlSScanf(const s: string; const fmt: string; const Pointers: array of Pointer; const FormatSettings: TFormatSettings): Integer;
  8. implementation
  9. function utlSScanf(const s: string; const fmt: string; const Pointers: array of Pointer): Integer;
  10. begin
  11. Result:= utlSScanf(s, fmt, Pointers, DefaultFormatSettings);
  12. end;
  13. {
  14. 0 on success (perfect match)
  15. +N if fmt was consumed without error, but there are characters left in s. First unparsed character is N
  16. -N if there was an error consuming pattern character N
  17. %s: string, must be terminated by a fixed string in fmt
  18. %d: integer
  19. %D: int64
  20. %f: double, supports scientific notation
  21. %P: hex PtrUInt (fixed length)
  22. %x: hex integer
  23. %X: hex int64
  24. }
  25. function utlSScanf(const s: string; const fmt: string; const Pointers: array of Pointer; const FormatSettings: TFormatSettings): Integer;
  26. var
  27. pt, fpos, tokfpos, spos: integer;
  28. token, limit: string;
  29. limitChar: Char;
  30. success: Boolean;
  31. function nextToken(advance: boolean): string;
  32. var
  33. oldfpos: integer;
  34. begin
  35. Result:= '';
  36. oldfpos:= fpos;
  37. try
  38. if fpos > Length(fmt) then
  39. Exit;
  40. Result:= fmt[fpos];
  41. inc(fpos);
  42. if Result = '%' then begin
  43. if fpos > Length(fmt) then
  44. Exit; // incomplete token, treat as literal %
  45. Result += fmt[fpos];
  46. inc(fpos);
  47. if Result = '%%' then
  48. Result:= '%';
  49. end;
  50. finally
  51. if not advance then
  52. fpos:= oldfpos
  53. else
  54. tokfpos:= oldfpos;
  55. end;
  56. end;
  57. function GetString: boolean;
  58. var
  59. tmp: string;
  60. begin
  61. tmp:= '';
  62. Result:= false;
  63. if limitChar = #0 then begin
  64. tmp:= Copy(s, spos, MaxInt);
  65. inc(spos, length(tmp));
  66. Result:= true;
  67. end else begin
  68. while spos <= Length(s) do begin
  69. if s[spos] = limitChar then begin
  70. dec(spos); // consume again
  71. Result:= true;
  72. break;
  73. end;
  74. tmp:= tmp + s[spos];
  75. inc(spos);
  76. end;
  77. end;
  78. PString(Pointers[pt])^:= tmp;
  79. end;
  80. function GetInteger(i64: boolean): Boolean;
  81. var
  82. tmp: string;
  83. v: int64;
  84. begin
  85. tmp:= '';
  86. Result:= false;
  87. while spos <= Length(s) do begin
  88. if s[spos] = limitChar then begin
  89. dec(spos); // consume again
  90. break;
  91. end;
  92. tmp:= tmp + s[spos];
  93. if not TryStrToInt64(tmp, v) then begin
  94. dec(spos);
  95. SetLength(tmp, Length(tmp) - 1);
  96. Break;
  97. end;
  98. inc(spos);
  99. end;
  100. if TryStrToInt64(tmp, v) then begin
  101. if i64 then
  102. PInt64(Pointers[pt])^:= v
  103. else
  104. PInteger(Pointers[pt])^:= v;
  105. Result:= true;
  106. end;
  107. end;
  108. function GetFloat: Boolean;
  109. var
  110. tmp: string;
  111. v: double;
  112. lastChar: char;
  113. firstFailure, fflen: Integer;
  114. begin
  115. tmp:= '';
  116. Result:= false;
  117. firstFailure:= 0;
  118. while spos <= Length(s) do begin
  119. if s[spos] = limitChar then begin
  120. dec(spos); // consume again
  121. break;
  122. end;
  123. tmp:= tmp + s[spos];
  124. if not TryStrToFloat(tmp, v, FormatSettings) then begin
  125. lastChar:= tmp[length(tmp)];
  126. if (firstFailure = 0) and
  127. ((lastChar = FormatSettings.ThousandSeparator) or (lastChar in ['-','E'])) then begin
  128. firstFailure:= spos;
  129. fflen:= Length(tmp);
  130. end else begin
  131. Break;
  132. end;
  133. end else
  134. firstFailure:= 0;
  135. inc(spos);
  136. end;
  137. if firstFailure > 0 then begin
  138. spos:= firstFailure;
  139. dec(spos);
  140. SetLength(tmp, fflen - 1);
  141. end;
  142. if TryStrToFloat(tmp, v, FormatSettings) then begin
  143. PDouble(Pointers[pt])^:= v;
  144. Result:= true;
  145. end;
  146. end;
  147. function GetHex(mode: byte): Boolean;
  148. const
  149. ptrlen = Sizeof(PtrUInt) * 2;
  150. var
  151. tmp: string;
  152. v: int64;
  153. begin
  154. tmp:= '$';
  155. Result:= false;
  156. if mode = 0 then begin
  157. tmp += Copy(s, spos, ptrlen);
  158. inc(spos, length(tmp));
  159. if Length(tmp) <> ptrlen+1 then
  160. Exit;
  161. end else
  162. while spos <= Length(s) do begin
  163. if s[spos] = limitChar then begin
  164. dec(spos); // consume again
  165. break;
  166. end;
  167. tmp:= tmp + s[spos];
  168. if not TryStrToInt64(tmp, v) then begin
  169. dec(spos);
  170. SetLength(tmp, Length(tmp) - 1);
  171. Break;
  172. end;
  173. inc(spos);
  174. end;
  175. if TryStrToInt64(tmp, v) then begin
  176. case mode of
  177. 0: PPtrUInt(Pointers[pt])^:= v;
  178. 1: PInteger(Pointers[pt])^:= v;
  179. 2: PInt64(Pointers[pt])^:= v;
  180. end;
  181. Result:= true;
  182. end;
  183. end;
  184. begin
  185. pt:= 0;
  186. fpos:= 1;
  187. spos:= 1;
  188. while fpos <= length(fmt) do begin
  189. if spos > Length(s) then
  190. Exit(-fpos);
  191. token:= nextToken(true);
  192. if token = '' then
  193. Exit(-tokfpos);
  194. if Length(token) = 1 then begin
  195. if token <> s[spos] then
  196. Exit(-tokfpos);
  197. end else begin
  198. limit:= nextToken(false);
  199. if Length(limit) = 1 then
  200. limitChar:= limit[1]
  201. else
  202. limitChar:= #0;
  203. case token[2] of
  204. 's': success:= GetString;
  205. 'd': success:= GetInteger(false);
  206. 'D': success:= GetInteger(True);
  207. 'f': success:= GetFloat;
  208. 'P': success:= GetHex(0);
  209. 'x': success:= GetHex(1);
  210. 'X': success:= GetHex(2);
  211. else
  212. Exit(-tokfpos);
  213. end;
  214. if not success then
  215. Exit(-tokfpos);
  216. inc(pt);
  217. end;
  218. inc(spos);
  219. end;
  220. if spos <= Length(s) then
  221. Exit(spos)
  222. else
  223. Exit(0);
  224. end;
  225. end.