Strings.Mod 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  1. MODULE Strings; (** AUTHOR "be,tf, staubesv"; PURPOSE "String functions" *)
  2. IMPORT SYSTEM, Streams, Reals, Dates;
  3. CONST
  4. Ok* = 0;
  5. TYPE
  6. String* = POINTER TO ARRAY OF CHAR;
  7. StringArray* = POINTER TO ARRAY OF String;
  8. VAR
  9. DateFormat*, TimeFormat*: ARRAY 32 OF CHAR; (** date and time format strings used by DateToStr/TimeToStr *)
  10. TYPE
  11. (** The stringmaker creates an automatically growing character array from the input with an Streams writer *)
  12. Buffer* = OBJECT
  13. VAR
  14. length : LONGINT;
  15. data : String;
  16. w : Streams.Writer;
  17. PROCEDURE &Init*(initialSize : LONGINT);
  18. BEGIN
  19. IF initialSize < 16 THEN initialSize := 256 END;
  20. NEW(data, initialSize); length := 0;
  21. END Init;
  22. PROCEDURE Add*(CONST buf: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: WORD);
  23. VAR newSize, i : LONGINT; n : String;
  24. BEGIN
  25. IF length + len + 1 >= LEN(data) THEN
  26. newSize := Max(LEN(data) * 2, length + len + 1);
  27. NEW(n, newSize);
  28. FOR i := 0 TO length - 1 DO n[i] := data[i] END;
  29. data := n;
  30. END;
  31. WHILE len > 0 DO
  32. data[length] := buf[ofs];
  33. INC(ofs); INC(length); DEC(len);
  34. END;
  35. data[length] := 0X;
  36. res := Ok;
  37. END Add;
  38. (** resets the length of the string to 0. The buffer is reused*)
  39. PROCEDURE Clear*;
  40. BEGIN
  41. data[0] := 0X;
  42. length := 0
  43. END Clear;
  44. (** returns an Streams.Writer to the string *)
  45. PROCEDURE GetWriter*() : Streams.Writer;
  46. BEGIN
  47. IF w = NIL THEN NEW(w, SELF.Add, 256) END;
  48. RETURN w
  49. END GetWriter;
  50. (** returns the number of bytes written to the string. The Streams.Writer is updated *)
  51. PROCEDURE GetLength*() : LONGINT;
  52. BEGIN
  53. IF w # NIL THEN w.Update END;
  54. RETURN length
  55. END GetLength;
  56. (** returns the current string buffer. If the string maker is reused, the content of the string may or may not
  57. vary. The application might need to copy the returned string. The Streams.Writer is updated *)
  58. PROCEDURE GetString*() : String;
  59. BEGIN
  60. IF w # NIL THEN w.Update END;
  61. RETURN data
  62. END GetString;
  63. PROCEDURE Write*(out : Streams.Writer);
  64. BEGIN
  65. IF w # NIL THEN w.Update END;
  66. out.Bytes(data^, 0, length)
  67. END Write;
  68. END Buffer;
  69. (** useful functions *)
  70. PROCEDURE Min*(a,b: LONGINT): LONGINT;
  71. BEGIN IF (a < b) THEN RETURN a ELSE RETURN b END
  72. END Min;
  73. PROCEDURE Max*(a,b: LONGINT): LONGINT;
  74. BEGIN IF (a > b) THEN RETURN a ELSE RETURN b END
  75. END Max;
  76. (** string handling *)
  77. (** returns the length of a string *)
  78. PROCEDURE Length* (CONST string: ARRAY OF CHAR): LONGINT;
  79. VAR len: LONGINT;
  80. BEGIN
  81. len := 0; WHILE (string[len] # 0X) DO INC(len) END;
  82. RETURN len
  83. END Length;
  84. (** Find position of character, returns -1 if not found*)
  85. PROCEDURE Find* (CONST string: ARRAY OF CHAR; pos: LONGINT; ch: CHAR): LONGINT;
  86. BEGIN
  87. WHILE (string[pos] # 0X ) & (string[pos] # ch) DO INC(pos) END;
  88. IF string[pos] = 0X THEN pos := -1 END;
  89. RETURN pos
  90. END Find;
  91. (** returns the number of occurences of ch within string *)
  92. PROCEDURE Count* (CONST string: ARRAY OF CHAR; ch: CHAR): LONGINT;
  93. VAR count, pos: LONGINT;
  94. BEGIN
  95. count := 0; pos := Find (string, 0, ch);
  96. WHILE pos # -1 DO INC (count); pos := Find (string, pos + 1, ch) END;
  97. RETURN count
  98. END Count;
  99. (** truncates string to length *)
  100. PROCEDURE Truncate* (VAR string: ARRAY OF CHAR; length: SIZE);
  101. BEGIN
  102. IF LEN(string) > length THEN string[length] := 0X END;
  103. END Truncate;
  104. (**
  105. * Returns the position of the first occurrence of pattern in the string or -1 if no occurrence is found.
  106. * Rabin-Karp algorithm, adopted from Sedgewick.
  107. *)
  108. PROCEDURE Pos*(CONST pattern, string: ARRAY OF CHAR): LONGINT;
  109. CONST
  110. q = 8204957; (* prime number, {(d+1) * q <= MAX(LONGINT)} *)
  111. d = 256; (* number of different characters *)
  112. VAR h1, h2, dM, i, j, m, n: LONGINT; found : BOOLEAN;
  113. BEGIN
  114. m := Length(pattern); n := Length(string);
  115. IF (m > n) THEN RETURN -1 END;
  116. dM := 1; FOR i := 0 TO m-2 DO dM := (d*dM) MOD q END;
  117. h1 := 0; FOR i := 0 TO m-1 DO h1 := (h1*d + ORD(pattern[i])) MOD q END;
  118. h2 := 0; FOR i := 0 TO m-1 DO h2 := (h2*d + ORD(string[i])) MOD q END;
  119. i := 0; found := FALSE;
  120. IF (h1 = h2) THEN (* verify *)
  121. j := 0; found := TRUE;
  122. WHILE (j < m) DO
  123. IF (string[j] # pattern[j]) THEN found := FALSE; j := m; END; (* hash values are equal, but strings are not *)
  124. INC(j);
  125. END;
  126. END;
  127. WHILE ~found & (i < n-m) DO
  128. h2 := (h2 + d*q - ORD(string[i])*dM) MOD q;
  129. h2 := (h2*d + ORD(string[i+m])) MOD q;
  130. INC(i);
  131. IF (h1 = h2) THEN (* verify *)
  132. j := 0; found := TRUE;
  133. WHILE (j < m) DO
  134. IF (string[i + j] # pattern[j]) THEN found := FALSE; j := m; END; (* hash values are equal, but strings are not *)
  135. INC(j);
  136. END
  137. END;
  138. END;
  139. IF found THEN
  140. RETURN i;
  141. ELSE
  142. RETURN -1
  143. END
  144. END Pos;
  145. (** More generic version of Pos. Basically the same search algorithm, but can also perform case-insensitive searching and/or
  146. * backwards directed searching.
  147. * Returns the position of the first character of the first occurence of 'pattern' in 'text' in search direction or -1 if pattern not found *)
  148. PROCEDURE GenericPos*(CONST pattern: ARRAY OF CHAR; from : LONGINT; CONST string: ARRAY OF CHAR; ignoreCase, backwards : BOOLEAN): LONGINT;
  149. CONST
  150. q = 8204957; (* prime number, {(d+1) * q <= MAX(LONGINT)} *)
  151. d = 256; (* number of different characters *)
  152. VAR ch, chp : CHAR; h1, h2, dM, i, j, patternLength, stringLength: LONGINT; found : BOOLEAN;
  153. BEGIN
  154. patternLength := Length(pattern); stringLength := Length(string);
  155. (* check whether the search pattern can be contained in the text regarding the search direction *)
  156. IF backwards THEN
  157. IF (patternLength > from + 1) THEN RETURN -1; END;
  158. ELSE
  159. IF (from + patternLength > stringLength) THEN RETURN -1; END;
  160. END;
  161. dM := 1; FOR i := 0 TO patternLength-2 DO dM := (d*dM) MOD q END;
  162. (* calculate hash value for search pattern string *)
  163. h1 := 0; FOR i := 0 TO patternLength-1 DO
  164. IF backwards THEN
  165. ch := pattern[patternLength-1-i];
  166. ELSE
  167. ch := pattern[i];
  168. END;
  169. IF ignoreCase THEN UpperCaseChar(ch); END;
  170. h1 := (h1*d + ORD(ch)) MOD q;
  171. END;
  172. (* calculate hash value for the first 'patternLength' characters of the text to be searched *)
  173. h2 := 0; FOR i := 0 TO patternLength-1 DO
  174. IF backwards THEN
  175. ch := string[from - i];
  176. ELSE
  177. ch := string[from + i];
  178. END;
  179. IF ignoreCase THEN UpperCaseChar(ch); END;
  180. h2 := (h2*d + ORD(ch)) MOD q;
  181. END;
  182. i := from; found := FALSE;
  183. IF (h1 = h2) THEN (* Hash values match, compare strings *)
  184. j := 0; found := TRUE;
  185. WHILE (j < patternLength) DO
  186. ch := string[from + j];
  187. chp := pattern[j];
  188. IF ignoreCase THEN UpperCaseChar(ch); UpperCaseChar(chp); END;
  189. IF (ch # chp) THEN found := FALSE; j := patternLength; END; (* hash values are equal, but strings are not *)
  190. INC(j);
  191. END;
  192. END;
  193. LOOP
  194. (* check wether we're finished *)
  195. IF found THEN EXIT; END;
  196. IF backwards THEN
  197. IF (i < patternLength) THEN EXIT; END;
  198. ELSE
  199. IF (i >= stringLength-patternLength) THEN EXIT; END;
  200. END;
  201. (* remove last character from hash value *)
  202. ch := string[i];
  203. IF ignoreCase THEN UpperCaseChar(ch); END;
  204. h2 := (h2 + d*q - ORD(ch)*dM) MOD q;
  205. (* add next character to hash value *)
  206. IF backwards THEN
  207. ch := string[i-patternLength];
  208. ELSE
  209. ch := string[i+patternLength];
  210. END;
  211. IF ignoreCase THEN UpperCaseChar(ch); END;
  212. h2 := (h2*d + ORD(ch)) MOD q;
  213. IF backwards THEN DEC(i); ELSE INC(i); END;
  214. IF (h1 = h2) THEN (* verify *)
  215. j := 0; found := TRUE;
  216. WHILE (j < patternLength) DO
  217. IF backwards THEN
  218. ch := string[i - patternLength + 1 + j];
  219. ELSE
  220. ch := string[i + j];
  221. END;
  222. chp := pattern[j];
  223. IF ignoreCase THEN UpperCaseChar(ch); UpperCaseChar(chp); END;
  224. IF (ch # chp) THEN found := FALSE; j := patternLength; END; (* hash values are equal, but strings are not *)
  225. INC(j);
  226. END
  227. END;
  228. END;
  229. IF found THEN
  230. IF backwards THEN RETURN i - patternLength + 1;
  231. ELSE RETURN i;
  232. END;
  233. ELSE
  234. RETURN -1;
  235. END;
  236. END GenericPos;
  237. (** Simple pattern matching with support for "*" and "?" wildcards - returns TRUE if name matches mask. Patent pending ;-) *)
  238. PROCEDURE Match*(CONST mask, name: ARRAY OF CHAR): BOOLEAN;
  239. VAR m,n, om, on: SIZE; f: BOOLEAN;
  240. BEGIN
  241. m := 0; n := 0; om := -1;
  242. f := TRUE;
  243. LOOP
  244. IF (mask[m] = "*") THEN
  245. om := m; INC(m);
  246. WHILE (name[n] # 0X) & (name[n] # mask[m]) DO INC(n) END;
  247. on := n
  248. ELSIF (mask[m] = "?") THEN
  249. IF (name[n] = 0X) THEN f := FALSE; EXIT END;
  250. INC(m); INC(n)
  251. ELSE
  252. IF (mask[m] # name[n]) THEN
  253. IF (om = -1) THEN f := FALSE; EXIT
  254. ELSIF (name[n] # 0X) THEN (* try the next position *)
  255. m := om; n := on + 1;
  256. IF (name[n] = 0X) THEN f := FALSE; EXIT END
  257. ELSE
  258. f := FALSE; EXIT
  259. END
  260. ELSE INC(m); INC(n)
  261. END
  262. END;
  263. IF (mask[m] = 0X) & ((name[n] = 0X) OR (om=-1)) THEN EXIT END
  264. END;
  265. RETURN f & (name[n] = 0X)
  266. END Match;
  267. (** copies src[soff ... soff + len - 1] to dst[doff ... doff + len - 1] *)
  268. PROCEDURE Move* (CONST src: ARRAY OF CHAR; soff, len: SIZE; VAR dst: ARRAY OF CHAR; doff: SIZE);
  269. BEGIN
  270. (* reverse copy direction in case src and dst denote the same string *)
  271. IF soff < doff THEN
  272. INC (soff, len - 1); INC (doff, len - 1);
  273. WHILE len > 0 DO dst[doff] := src[soff]; DEC (soff); DEC (doff); DEC (len) END
  274. ELSE
  275. WHILE len > 0 DO dst[doff] := src[soff]; INC (soff); INC (doff); DEC (len) END
  276. END;
  277. END Move;
  278. (** concatenates s1 and s2: s := s1 || s2 *)
  279. PROCEDURE Concat* (CONST s1, s2: ARRAY OF CHAR; VAR s: ARRAY OF CHAR);
  280. VAR len1, len2 : SIZE;
  281. BEGIN
  282. len1 := Length (s1); len2 := Length (s2);
  283. Move(s2, 0, len2, s, len1);
  284. Move (s1, 0, len1, s, 0);
  285. Truncate (s, len1 + len2);
  286. END Concat;
  287. (** concatenates s1 and s2: s := s1 || s2. The resulting string is truncated to the length of s if necessary *)
  288. PROCEDURE ConcatX*(CONST s1, s2 : ARRAY OF CHAR; VAR s : ARRAY OF CHAR);
  289. VAR len1, len2 : SIZE;
  290. BEGIN
  291. len1 := Length (s1); len2 := Length (s2);
  292. IF (len1 + 1 >= LEN(s)) THEN
  293. COPY(s1, s);
  294. ELSE
  295. IF (len1 + len2 + 1 > LEN(s)) THEN
  296. len2 := LEN(s) - 1 - len1;
  297. END;
  298. Move(s2, 0, len2, s, len1);
  299. Move (s1, 0, len1, s, 0);
  300. Truncate (s, len1 + len2);
  301. END;
  302. END ConcatX;
  303. (** appends appendix to s: s := s || appendix *)
  304. PROCEDURE Append* (VAR s: ARRAY OF CHAR; CONST appendix: ARRAY OF CHAR);
  305. BEGIN Concat (s, appendix, s)
  306. END Append;
  307. (** appends appendix to s: s := s || appendix. The resulting string is truncated to the length of s if necessary *)
  308. PROCEDURE AppendX* (VAR s: ARRAY OF CHAR; CONST appendix: ARRAY OF CHAR);
  309. BEGIN ConcatX (s, appendix, s)
  310. END AppendX;
  311. (** appends an integer number to a string *)
  312. PROCEDURE AppendInt*(VAR s: ARRAY OF CHAR; num: HUGEINT);
  313. VAR number: ARRAY 21 OF CHAR;
  314. BEGIN
  315. IntToStr(num,number); Append(s,number);
  316. END AppendInt;
  317. (** appends a character to a string s := s || char *)
  318. PROCEDURE AppendChar*(VAR s: ARRAY OF CHAR; ch: CHAR);
  319. VAR cs: ARRAY 2 OF CHAR;
  320. BEGIN
  321. cs[0] := ch; cs[1] := 0X; Append(s,cs);
  322. END AppendChar;
  323. (** copies src[index ... index + len-1] to dst *)
  324. PROCEDURE Copy* (CONST src: ARRAY OF CHAR; index, len: SIZE; VAR dst: ARRAY OF CHAR);
  325. BEGIN
  326. Move (src, index, len, dst, 0);
  327. Truncate (dst, len);
  328. END Copy;
  329. (** deletes positions index ... index + count - 1 from 's' *)
  330. PROCEDURE Delete* (VAR s: ARRAY OF CHAR; index, count: SIZE);
  331. VAR len: SIZE;
  332. BEGIN
  333. len := Length (s);
  334. Move (s, index + count, len - index - count, s, index);
  335. Truncate (s, len - count);
  336. END Delete;
  337. (** inserts 'src' at position 'index' into 'dst' *)
  338. PROCEDURE Insert* (CONST src: ARRAY OF CHAR; VAR dst: ARRAY OF CHAR; index: SIZE);
  339. VAR slen, dlen: SIZE;
  340. BEGIN
  341. slen := Length (src); dlen := Length (dst);
  342. Move (dst, index, dlen-index, dst, index+slen);
  343. Move (src, 0, slen, dst, index);
  344. Truncate (dst, slen + dlen);
  345. END Insert;
  346. (** removes all occurrences of 'c' at the head of 'string' *)
  347. PROCEDURE TrimLeft* (VAR string: ARRAY OF CHAR; c: CHAR);
  348. VAR len, index: SIZE;
  349. BEGIN
  350. len := Length (string); index := 0;
  351. WHILE (index # len) & (string[index] = c) DO INC (index) END;
  352. Delete (string, 0, index);
  353. END TrimLeft;
  354. (** removes all occurrences of 'c' at the end of 'string' *)
  355. PROCEDURE TrimRight* (VAR string: ARRAY OF CHAR; c: CHAR);
  356. VAR len, index: SIZE;
  357. BEGIN
  358. len := Length (string); index := len;
  359. WHILE (index # 0) & (string[index - 1] = c) DO DEC (index) END;
  360. Delete (string, index, len - index);
  361. END TrimRight;
  362. (** removes all occurrences of 'c' at both ends of 'string' *)
  363. PROCEDURE Trim* (VAR string: ARRAY OF CHAR; c: CHAR);
  364. BEGIN
  365. TrimLeft(string, c);
  366. TrimRight(string, c)
  367. END Trim;
  368. (**
  369. * Splits 'string' into multiple strings separated by 'separator'.
  370. * Result properties:
  371. * separator = 0X: LEN(StringArray) = 1
  372. * separator # 0X: LEN(StringArray) = 1 + <Number of occurences of 'ch' in 'string'>
  373. * StringArray[i] # NIL (0 <= i <= LEN(StringArray)-1)
  374. *)
  375. PROCEDURE Split*(CONST string : ARRAY OF CHAR; separator : CHAR) : StringArray;
  376. VAR count, index, pos, next: LONGINT; result : StringArray;
  377. BEGIN
  378. count := Count (string, separator);
  379. NEW (result, count + 1); pos := 0;
  380. FOR index := 0 TO count DO
  381. next := Find (string, pos, separator);
  382. IF next = -1 THEN next := Length (string) END;
  383. NEW (result[index], next - pos + 1);
  384. Copy (string, pos, next - pos, result[index]^);
  385. pos := next + 1;
  386. END;
  387. RETURN result;
  388. END Split;
  389. PROCEDURE Join*(CONST strings : StringArray; startIndex, endIndex : LONGINT; separator : CHAR) : String;
  390. VAR string : String; length, pos, i : LONGINT;
  391. BEGIN
  392. ASSERT((strings # NIL) & (LEN(strings) >= 1));
  393. ASSERT((0 <= startIndex) & (startIndex <= endIndex) & (endIndex < LEN(strings)));
  394. length := 1; (* 0X termination *)
  395. IF (separator # 0X) THEN length := length + (endIndex - startIndex); END;
  396. FOR i := startIndex TO endIndex DO
  397. length := length + Length(strings[i]^);
  398. END;
  399. pos := 0;
  400. NEW(string, length);
  401. FOR i := startIndex TO endIndex DO
  402. length := Length(strings[i]^);
  403. Move(strings[i]^, 0, length, string^, pos);
  404. pos := pos + length;
  405. IF (i < endIndex) & (separator # 0X) THEN string[pos] := separator; INC(pos); END;
  406. END;
  407. string^[LEN(string)-1] := 0X;
  408. ASSERT((string # NIL) & (LEN(string) > 0) & (string^[LEN(string)-1] = 0X));
  409. RETURN string;
  410. END Join;
  411. (** returns the corresponding lower-case letter for "A" <= ch <= "Z" *)
  412. PROCEDURE LOW*(ch: CHAR): CHAR;
  413. BEGIN
  414. IF (ch >= "A") & (ch <= "Z") THEN RETURN CHR(ORD(ch) - ORD("A") + ORD("a"))
  415. ELSE RETURN ch
  416. END
  417. END LOW;
  418. (** converts s to lower-case letters *)
  419. PROCEDURE LowerCase*(VAR s: ARRAY OF CHAR);
  420. VAR i: SIZE;
  421. BEGIN
  422. i := 0;
  423. WHILE (s[i] # 0X) DO
  424. s[i] := LOW(s[i]);
  425. INC(i)
  426. END
  427. END LowerCase;
  428. (** returns the corresponding upper-case letter for "a" <= ch <= "z" *)
  429. PROCEDURE UP*(ch : CHAR) : CHAR;
  430. BEGIN
  431. IF ("a" <= ch) & (ch <= "z") THEN ch := CAP(ch); END;
  432. RETURN ch;
  433. END UP;
  434. PROCEDURE UpperCaseChar*(VAR ch : CHAR);
  435. BEGIN
  436. IF ("a" <= ch) & (ch <= "z") THEN ch := CAP(ch); END;
  437. END UpperCaseChar;
  438. (** converts s to upper-case letters *)
  439. PROCEDURE UpperCase*(VAR s: ARRAY OF CHAR);
  440. VAR i: SIZE; c : CHAR;
  441. BEGIN
  442. i := 0;
  443. WHILE (s[i] # 0X) DO
  444. c := s[i];
  445. IF ('a' <= c) & (c <= 'z') THEN s[i] := CAP(c) END;
  446. INC(i)
  447. END
  448. END UpperCase;
  449. (* ASCII printable characters *)
  450. PROCEDURE IsPrintable*(ch:CHAR):BOOLEAN;
  451. BEGIN
  452. RETURN (ch>=20X) & (ch<=7EX)
  453. END IsPrintable;
  454. (** conversion functions *)
  455. (** converts a boolean value to a string *)
  456. PROCEDURE BoolToStr*(b: BOOLEAN; VAR s: ARRAY OF CHAR);
  457. CONST True = "True"; False = "False";
  458. BEGIN
  459. IF b THEN COPY(True, s)
  460. ELSE COPY(False, s)
  461. END
  462. END BoolToStr;
  463. (** converts a string to a boolean value: b := CAP(s[0]) = "T" *)
  464. PROCEDURE StrToBool*(CONST s: ARRAY OF CHAR; VAR b: BOOLEAN);
  465. BEGIN b := CAP(s[0]) = "T"
  466. END StrToBool;
  467. (** converts an integer value to a string *)
  468. PROCEDURE IntToStr*(x: HUGEINT; VAR s: ARRAY OF CHAR);
  469. VAR i, j: SIZE; x0: HUGEINT; digits: ARRAY 21 OF CHAR;
  470. BEGIN
  471. IF x < 0 THEN
  472. IF x = MIN( HUGEINT ) THEN
  473. COPY("-9223372036854775808", s) ;
  474. RETURN;
  475. ELSE
  476. x0 := -x; s[0] := "-"; j := 1;
  477. END;
  478. ELSE
  479. x0 := x; j := 0;
  480. END;
  481. i := 0;
  482. REPEAT digits[i] := CHR( x0 MOD 10 + 30H ); x0 := x0 DIV 10; INC( i ) UNTIL x0 = 0;
  483. REPEAT DEC( i ); s[j] := digits[i]; INC(j) UNTIL i = 0;
  484. s[j] := 0X;
  485. END IntToStr;
  486. (** converts a string to an integer. Leading whitespace is ignored *)
  487. (* adopted from Strings.Mod *)
  488. PROCEDURE StrToInt*(CONST str: ARRAY OF CHAR; VAR val: LONGINT);
  489. VAR i: SIZE; d: LONGINT; neg: BOOLEAN;
  490. BEGIN
  491. i := 0; WHILE (str[i] # 0X) & (str[i] <= " ") DO INC(i) END;
  492. neg := FALSE;
  493. IF (str[i] = "+") THEN INC(i)
  494. ELSIF (str[i] = "-") THEN neg := TRUE; INC(i)
  495. END;
  496. val := 0;
  497. WHILE (str[i] >= "0") & (str[i] <= "9") DO
  498. d := ORD(str[i])-ORD("0");
  499. IF (val <= ((MAX(LONGINT)-d) DIV 10)) THEN val := 10*val+d
  500. ELSIF neg & (val = 214748364) & (d = 8) & ((str[i+1] < "0") OR (str[i+1] > "9")) THEN
  501. (* LONGINT range: -2147483648 ... 2147483647 _> need special handling for -2147483648 here *)
  502. val := MIN(LONGINT); neg := FALSE
  503. ELSE
  504. HALT(99)
  505. END;
  506. INC(i)
  507. END;
  508. IF neg THEN val := -val END
  509. END StrToInt;
  510. (** Convert the substring beginning at position i in str into an integer. Leading whitespace is ignored.
  511. After the conversion i points to the first character after the integer. *)
  512. (* adopted from Strings.Mod *)
  513. PROCEDURE StrToIntPos*(CONST str: ARRAY OF CHAR; VAR val, i: LONGINT);
  514. VAR noStr: ARRAY 16 OF CHAR;
  515. BEGIN
  516. WHILE (str[i] # 0X) & (str[i] <= " ") DO INC(i) END;
  517. val := 0;
  518. IF str[i] = "-" THEN
  519. noStr[val] := str[i]; INC(val); INC(i);
  520. WHILE (str[i] # 0X) & (str[i] <= " ") DO INC(i) END
  521. END;
  522. WHILE (str[i] >= "0") & (str[i] <= "9") DO noStr[val] := str[i]; INC(val); INC(i) END;
  523. noStr[val] := 0X;
  524. StrToInt(noStr, val)
  525. END StrToIntPos;
  526. (** converts an integer value to a hex string *)
  527. PROCEDURE IntToHexStr*(h : HUGEINT; width: WORD; VAR s: ARRAY OF CHAR);
  528. VAR c: CHAR;
  529. BEGIN
  530. IF (width <= 0) THEN width := 8 END;
  531. DEC(width); (* opov *)
  532. s[width+1] := 0X;
  533. WHILE (width >= 0) DO
  534. c := CHR(h MOD 10H + ORD("0"));
  535. IF (c > "9") THEN c := CHR((h MOD 10H - 10) + ORD("A")) END;
  536. s[width] := c; h := h DIV 10H; DEC(width)
  537. END
  538. END IntToHexStr;
  539. (** converts a hex string to an integer. Leading whitespace is ignored. res=Ok indicates success, val=0 on failure. *)
  540. PROCEDURE HexStrToInt*(CONST string: ARRAY OF CHAR; VAR val, res: LONGINT);
  541. VAR length, i : LONGINT; ch: CHAR; negative : BOOLEAN;
  542. BEGIN
  543. length := LEN(string); val := 0; res := -1;
  544. (* skip whitespace *)
  545. i := 0; WHILE (i < length) & (string[i] # 0X) & (string[i] <= " ") DO INC(i); END;
  546. IF (i < length) THEN
  547. IF (string[i] = "+") OR (string[i] = "-") THEN
  548. negative := (string[i] = "-"); INC(i);
  549. ELSE
  550. negative := FALSE;
  551. END;
  552. LOOP
  553. IF (i >= length) OR (string[i] = 0X) THEN EXIT; END;
  554. ch := string[i];
  555. IF (ch >= "0") & (ch <= "9") THEN val := 16 * val + ORD(ch) - ORD("0");
  556. ELSIF (CAP(ch) >= "A") & (CAP(ch) <= "F") THEN val := 16 * val + ORD(CAP(ch)) - ORD("A") + 10;
  557. ELSE EXIT;
  558. END;
  559. INC(i);
  560. END;
  561. IF (i < length) & (string[i] = "H") THEN INC(i); END; (* skip optional "H" *)
  562. IF (i < length) & (string[i] = 0X) THEN
  563. IF negative THEN val := -val END;
  564. res := Ok;
  565. END;
  566. END;
  567. END HexStrToInt;
  568. (** converts a real value to a string *)
  569. (* adopted from Strings.Mod *)
  570. PROCEDURE FloatToStr*(x: LONGREAL; n, f, D: LONGINT; VAR str: ARRAY OF CHAR);
  571. VAR pos, len, e, i, h, l: LONGINT; r, z: LONGREAL; d: ARRAY 16 OF CHAR; s: CHAR;
  572. PROCEDURE Wr(ch: CHAR);
  573. BEGIN IF pos < len THEN str[pos] := ch; INC(pos) END;
  574. END Wr;
  575. BEGIN
  576. len := LEN(str)-1; pos := 0;
  577. e := Reals.ExpoL(x);
  578. IF (e = 2047) OR (ABS(D) > 308) THEN
  579. Wr("N"); Wr("a"); Wr("N")
  580. ELSE
  581. IF D = 0 THEN DEC(n, 2) ELSE DEC(n, 7) END;
  582. IF n < 2 THEN n := 2 END;
  583. IF f < 0 THEN f := 0 END;
  584. IF n < f + 2 THEN n := f + 2 END;
  585. DEC(n, f);
  586. IF (e # 0) & (x < 0) THEN s := "-"; x := - x ELSE s := " " END;
  587. IF e = 0 THEN
  588. h := 0; l := 0; DEC(e, D-1) (* no denormals *)
  589. ELSE
  590. e := (e - 1023) * 301029 DIV 1000000; (* ln(2)/ln(10) = 0.301029996 *)
  591. z := Reals.Ten(e+1);
  592. IF x >= z THEN x := x/z; INC(e) ELSE x:= x * Reals.Ten(-e) END;
  593. DEC(e, D-1); i := -(e+f);
  594. IF i <= 0 THEN r := 5 * Reals.Ten(i) ELSE r := 0 END;
  595. IF x >= 10 THEN
  596. x := x * Reals.Ten(-1) + r; INC(e)
  597. ELSE
  598. x := x + r;
  599. IF x >= 10 THEN x := x * Reals.Ten(-1); INC(e) END
  600. END;
  601. x := x * Reals.Ten(7); h:= ENTIER(x); x := (x-h) * Reals.Ten(8); l := ENTIER(x)
  602. END;
  603. i := 15;
  604. WHILE i > 7 DO d[i] := CHR(l MOD 10 + ORD("0")); l := l DIV 10; DEC(i) END;
  605. WHILE i >= 0 DO d[i] := CHR(h MOD 10 + ORD("0")); h := h DIV 10; DEC(i) END;
  606. IF n <= e THEN n := e + 1 END;
  607. IF e > 0 THEN
  608. WHILE n > e DO Wr(" "); DEC(n) END;
  609. Wr(s); e:= 0;
  610. WHILE n > 0 DO
  611. DEC(n);
  612. IF e < 16 THEN Wr(d[e]); INC(e) ELSE Wr("0") END
  613. END;
  614. Wr(".")
  615. ELSE
  616. WHILE n > 1 DO Wr(" "); DEC(n) END;
  617. Wr(s); Wr("0"); Wr(".");
  618. WHILE (0 < f) & (e < 0) DO Wr("0"); DEC(f); INC(e) END
  619. END;
  620. WHILE f > 0 DO
  621. DEC(f);
  622. IF e < 16 THEN Wr(d[e]); INC(e) ELSE Wr("0") END
  623. END;
  624. IF D # 0 THEN
  625. IF D < 0 THEN Wr("D"); Wr("-"); D := - D
  626. ELSE Wr("D"); Wr("+")
  627. END;
  628. Wr(CHR(D DIV 100 + ORD("0"))); D := D MOD 100;
  629. Wr(CHR(D DIV 10 + ORD("0"))); Wr(CHR(D MOD 10 + ORD("0")))
  630. END
  631. END;
  632. str[pos] := 0X
  633. END FloatToStr;
  634. PROCEDURE AddressToStr*(adr : ADDRESS; VAR str : ARRAY OF CHAR);
  635. BEGIN
  636. IntToHexStr(adr, 2*SIZEOF(ADDRESS), str);
  637. END AddressToStr;
  638. (** converts a string to a real value *)
  639. (* adopted from Strings.Mod *)
  640. PROCEDURE StrToFloat*(CONST s: ARRAY OF CHAR; VAR r: LONGREAL);
  641. VAR p, e: INTEGER; y, g: LONGREAL; neg, negE: BOOLEAN;
  642. BEGIN
  643. p := 0;
  644. WHILE (s[p] = " ") OR (s[p] = "0") DO INC(p) END;
  645. IF s[p] = "-" THEN neg := TRUE; INC(p) ELSE neg := FALSE END;
  646. WHILE (s[p] = " ") OR (s[p] = "0") DO INC(p) END;
  647. y := 0;
  648. WHILE ("0" <= s[p]) & (s[p] <= "9") DO
  649. y := y * 10 + (ORD(s[p]) - 30H);
  650. INC(p);
  651. END;
  652. IF s[p] = "." THEN
  653. INC(p); g := 1;
  654. WHILE ("0" <= s[p]) & (s[p] <= "9") DO
  655. g := g / 10; y := y + g * (ORD(s[p]) - 30H);
  656. INC(p);
  657. END;
  658. END;
  659. IF (s[p] = "d") OR (s[p] = "D") OR (s[p] = "e") OR (s[p] = "E") THEN
  660. INC(p); e := 0;
  661. IF s[p] = "-" THEN negE := TRUE; INC(p)
  662. ELSIF s[p] = "+" THEN negE := FALSE; INC(p)
  663. ELSE negE := FALSE
  664. END;
  665. WHILE (s[p] = "0") DO INC(p) END;
  666. WHILE ("0" <= s[p]) & (s[p] <= "9") DO
  667. e := e * 10 + (ORD(s[p]) - 30H);
  668. INC(p);
  669. END;
  670. IF negE THEN y := y / Reals.Ten(e)
  671. ELSE y := y * Reals.Ten(e) END;
  672. END;
  673. IF neg THEN y := -y END;
  674. r := y
  675. END StrToFloat;
  676. (** converts a set to a string *)
  677. (* adopted from Strings.Mod *)
  678. PROCEDURE SetToStr*(set: SET; VAR s: ARRAY OF CHAR);
  679. VAR i, j, k: INTEGER; noFirst: BOOLEAN;
  680. BEGIN
  681. s[0] := "{"; i := 0; k := 1; noFirst := FALSE;
  682. WHILE i <= MAX(SET) DO
  683. IF i IN set THEN
  684. IF noFirst THEN s[k] := ","; INC(k) ELSE noFirst := TRUE END;
  685. IF i >= 10 THEN s[k] := CHR(i DIV 10 + 30H); INC(k) END;
  686. s[k] := CHR(i MOD 10 + 30H); INC(k);
  687. j := i; INC(i);
  688. WHILE (i <= MAX(SET)) & (i IN set) DO INC(i) END;
  689. IF i-2 > j THEN
  690. s[k] := "."; s[k+1] := "."; INC(k, 2); j := i - 1;
  691. IF j >= 10 THEN s[k] := CHR(j DIV 10 + 30H); INC(k) END;
  692. s[k] := CHR(j MOD 10 + 30H); INC(k)
  693. ELSE i := j
  694. END
  695. END;
  696. INC(i)
  697. END;
  698. s[k] := "}"; s[k+1] := 0X
  699. END SetToStr;
  700. (** converts a string to a set *)
  701. (* adopted from Strings.Mod *)
  702. PROCEDURE StrToSet*(CONST str: ARRAY OF CHAR; VAR set: SET);
  703. VAR i, d, d1: INTEGER; dot: BOOLEAN;
  704. BEGIN
  705. set := {}; dot := FALSE;
  706. i := 0;
  707. WHILE (str[i] # 0X) & (str[i] # "}") DO
  708. WHILE (str[i] # 0X) & ((str[i] < "0") OR ("9" < str[i])) DO INC(i) END;
  709. d := 0; WHILE ("0" <= str[i]) & (str[i] <= "9") DO d := d*10 + ORD(str[i]) - 30H; INC(i) END;
  710. IF (str[i] = 0X) THEN RETURN; END;
  711. IF d <= MAX(SET) THEN INCL(set, d) END;
  712. IF dot THEN
  713. WHILE (d1 <= MAX(SET)) & (d1 < d) DO INCL(set, d1); INC(d1) END;
  714. dot := FALSE
  715. END;
  716. WHILE (str[i] = " ") DO INC(i) END;
  717. IF (str[i] = ".") THEN d1 := d + 1; dot := TRUE END
  718. END
  719. END StrToSet;
  720. (** converts a time to a string, using the 'TimeFormat' format. C.f. FormatDateTime *)
  721. PROCEDURE TimeToStr*(time: Dates.DateTime; VAR s: ARRAY OF CHAR);
  722. BEGIN FormatDateTime(TimeFormat, time, s)
  723. END TimeToStr;
  724. (** converts a string to a time *)
  725. (* adopted from Strings.Mod *)
  726. PROCEDURE StrToTime*(CONST str: ARRAY OF CHAR; VAR dt: Dates.DateTime);
  727. VAR i: LONGINT;
  728. BEGIN
  729. i := 0;
  730. WHILE (str[i] # 0X) & ((str[i] < "0") OR (str[i] > "9")) DO INC(i) END;
  731. StrToIntPos(str, dt.hour, i);
  732. WHILE (str[i] # 0X) & ((str[i] < "0") OR (str[i] > "9")) DO INC(i) END;
  733. StrToIntPos(str, dt.minute, i);
  734. WHILE (str[i] # 0X) & ((str[i] < "0") OR (str[i] > "9")) DO INC(i) END;
  735. StrToIntPos(str, dt.second, i);
  736. ASSERT(Dates.ValidDateTime(dt));
  737. END StrToTime;
  738. (** converts a date to a string, using the 'DateFormat' format. C.f. FormatDateTime *)
  739. PROCEDURE DateToStr*(date: Dates.DateTime; VAR s: ARRAY OF CHAR);
  740. BEGIN FormatDateTime(DateFormat, date, s)
  741. END DateToStr;
  742. (** Convert a string of the form 'day month year' into an date value. Leading whitespace is ignored. *)
  743. PROCEDURE StrToDate*(CONST str: ARRAY OF CHAR; VAR dt: Dates.DateTime);
  744. VAR i: LONGINT;
  745. BEGIN
  746. i := 0;
  747. WHILE (str[i] # 0X) & ((str[i] < "0") OR (str[i] > "9")) DO INC(i) END;
  748. StrToIntPos(str, dt.day, i);
  749. WHILE (str[i] # 0X) & ((str[i] < "0") OR (str[i] > "9")) DO INC(i) END;
  750. StrToIntPos(str, dt.month, i);
  751. WHILE (str[i] # 0X) & ((str[i] < "0") OR (str[i] > "9")) DO INC(i) END;
  752. StrToIntPos(str, dt.year, i);
  753. ASSERT(Dates.ValidDateTime(dt));
  754. END StrToDate;
  755. (** converts a TDateTime into a string.
  756. Format rules:
  757. yyyy -> four-digit year, e.g. 2001
  758. yy -> two-digit year, e.g. 01
  759. mmmm -> clear-text month, e.g. May
  760. mmm -> clear-text month, abbreviated, e.g. Sep
  761. mm -> two-digit month, e.g. 05
  762. m -> month, e.g. 5
  763. dd -> two-digit day, e.g. 02
  764. d -> day, e.g. 2 or 15
  765. wwww -> clear-text week-day, e.g. Monday
  766. www -> clear-text week-day, e.g. Mon
  767. hh -> two-digit hour, e.g. 08
  768. h -> hour, e.g. 8
  769. nn -> two-digit minute, e.g. 03
  770. n -> minute, e.g. 3
  771. ss -> two-digit second, e.g. 00
  772. s -> second, e.g. 0
  773. any other characters will be copied 1:1 to the result string
  774. Examples:
  775. "yyyy.mm.dd hh:nn:ss" -> "2002.01.01 17:08:00"
  776. "yyyyyy.m.ddd" -> "002002.1.001"
  777. "wwww, mmmm d, yyyy" -> "Tuesday, September 11, 2001"
  778. *)
  779. PROCEDURE FormatDateTime*(CONST format: ARRAY OF CHAR; dt: Dates.DateTime; VAR result: ARRAY OF CHAR);
  780. VAR i,k,l,len,n,m,y,w,dw: LONGINT;
  781. PROCEDURE IntToStr(v, len: LONGINT; VAR s: ARRAY OF CHAR; VAR pos: LONGINT);
  782. VAR i: LONGINT;
  783. BEGIN
  784. FOR i := 1 TO len DO s[pos+len-i] := CHR(ORD("0") + v MOD 10); v := v DIV 10 END;
  785. INC(pos, len)
  786. END IntToStr;
  787. BEGIN
  788. k := 0;
  789. IF Dates.ValidDateTime(dt) THEN
  790. i := 0;
  791. WHILE (format[i] # 0X) DO
  792. n := 1; WHILE (format[i+n] = format[i]) DO INC(n) END;
  793. len := n;
  794. CASE format[i] OF
  795. |"w": Dates.WeekDate(dt, y, w, dw); DEC(dw);
  796. IF (len >= 4) THEN len := 10 END;
  797. l := 0; WHILE (l < len) & (Dates.Days[dw,l] # 0X) DO result[k] := Dates.Days[dw,l]; INC(k); INC(l) END;
  798. |"y": IntToStr(dt.year, n, result, k);
  799. |"m": IF (n >= 3) THEN
  800. m := dt.month-1; ASSERT((m>=0) & (m<12));
  801. IF (len > 3) THEN len := 12 END;
  802. l := 0; WHILE (l < len) & (Dates.Months[m,l] # 0X) DO result[k] := Dates.Months[m, l]; INC(k); INC(l) END
  803. ELSE
  804. IF (len=1) & (dt.month > 9) THEN len := 2; END;
  805. IntToStr(dt.month, len, result, k)
  806. END;
  807. |"d": IF (len=1) & (dt.day > 9) THEN len := 2 END;
  808. IntToStr(dt.day, len, result, k);
  809. |"h": IF (len=1) & (dt.hour > 9) THEN len := 2 END;
  810. IntToStr(dt.hour, len, result, k);
  811. |"n": IF (len=1) & (dt.minute > 9) THEN len := 2 END;
  812. IntToStr(dt.minute, len, result, k);
  813. |"s": IF (len=1) & (dt.second > 9) THEN len := 2 END;
  814. IntToStr(dt.second, len, result, k);
  815. ELSE result[k] := format[i]; INC(k); n := 1
  816. END;
  817. INC(i, n)
  818. END
  819. END;
  820. result[k] := 0X
  821. END FormatDateTime;
  822. PROCEDURE ShowTimeDifference*(t1, t2 : Dates.DateTime; out : Streams.Writer);
  823. VAR days, hours, minutes, seconds : LONGINT; show : BOOLEAN;
  824. BEGIN
  825. Dates.TimeDifference(t1, t2, days, hours, minutes, seconds);
  826. show := FALSE;
  827. IF (days > 0) THEN out.Int(days, 0); out.String("d "); show := TRUE; END;
  828. IF show OR (hours > 0) THEN out.Int(hours, 0); out.String("h "); show := TRUE; END;
  829. IF show OR (minutes > 0) THEN out.Int(minutes, 0); out.String("m "); show := TRUE; END;
  830. out.Int(seconds, 0); out.String("s");
  831. END ShowTimeDifference;
  832. PROCEDURE NewString*(CONST str : ARRAY OF CHAR) : String;
  833. VAR l : LONGINT; s : String;
  834. BEGIN
  835. l := Length(str) + 1;
  836. NEW(s, l);
  837. COPY(str, s^);
  838. RETURN s
  839. END NewString;
  840. PROCEDURE SetAOC*(CONST str: ARRAY OF CHAR; VAR s: String);
  841. VAR l: LONGINT;
  842. BEGIN
  843. l := Length(str) + 1;
  844. IF (s = NIL) OR (LEN(s^) < l) THEN
  845. NEW(s,l)
  846. END;
  847. COPY(str, s^);
  848. END SetAOC;
  849. (* Gets extension of the given name, returns file (without extension) and ext *)
  850. PROCEDURE GetExtension* (CONST name : ARRAY OF CHAR; VAR file, ext: ARRAY OF CHAR);
  851. VAR len, index: LONGINT;
  852. BEGIN
  853. len := Length (name); index := len;
  854. WHILE (index # 0) & (name[index- 1] # '.') DO DEC (index) END;
  855. IF index = 0 THEN
  856. Copy (name, 0, len, file);
  857. Truncate (ext, 0);
  858. ELSE
  859. Copy (name, 0, index - 1, file);
  860. Copy (name, index, len - index, ext);
  861. END
  862. END GetExtension;
  863. (* Returns a new string that is a concatenation of s1 and s2: s := s1 || s2 *)
  864. PROCEDURE ConcatToNew*(CONST s1, s2 : ARRAY OF CHAR) : String;
  865. VAR
  866. s : String;
  867. BEGIN
  868. NEW(s, Length(s1) + Length(s2) + 1);
  869. Concat(s1, s2, s^);
  870. RETURN s;
  871. END ConcatToNew;
  872. (* Tests if string s ends with the specified suffix *)
  873. PROCEDURE EndsWith*(CONST suffix, s : ARRAY OF CHAR) : BOOLEAN;
  874. BEGIN
  875. RETURN StartsWith(suffix, Length(s)-Length(suffix), s);
  876. END EndsWith;
  877. (* Tests if two strings are equal *)
  878. (* This procedure makes sense, because "proc(..)^ = proc(..)^" is not supported by the compiler! *)
  879. PROCEDURE Equal*(s1, s2 : String) : BOOLEAN;
  880. BEGIN
  881. ASSERT(s1 # NIL);
  882. ASSERT(s2 # NIL);
  883. RETURN s1^ = s2^;
  884. END Equal;
  885. (** Returns TRUE if the 0X-terminated string contains the character 'ch', FALSE otherwise. *)
  886. PROCEDURE ContainsChar*(CONST string : ARRAY OF CHAR; ch : CHAR; ignoreCase : BOOLEAN) : BOOLEAN;
  887. BEGIN
  888. IF ignoreCase THEN
  889. RETURN (Find (string, 0, LOW (ch)) # -1) & (Find (string, 0, UP (ch)) # -1)
  890. ELSE
  891. RETURN Find (string, 0, ch) # -1
  892. END
  893. END ContainsChar;
  894. (* Returns the index within string s of the first occurrence of the specified character *)
  895. PROCEDURE IndexOfByte2*(ch : CHAR; CONST s : ARRAY OF CHAR) : LONGINT;
  896. BEGIN
  897. RETURN IndexOfByte(ch, 0, s);
  898. END IndexOfByte2;
  899. (* Returns the index within string s of the first occurrence of the specified character, starting the search at the specified index *)
  900. PROCEDURE IndexOfByte*(ch : CHAR; fromIndex : LONGINT; CONST s : ARRAY OF CHAR) : LONGINT;
  901. VAR
  902. lenString, i : LONGINT;
  903. BEGIN
  904. lenString := Length(s);
  905. IF fromIndex < 0 THEN
  906. fromIndex := 0;
  907. ELSIF fromIndex >= lenString THEN
  908. RETURN -1;
  909. END;
  910. FOR i := fromIndex TO lenString-1 DO
  911. IF s[i] = ch THEN RETURN i; END;
  912. END;
  913. RETURN -1;
  914. END IndexOfByte;
  915. (* Returns the index within string s of the last occurrence of the specified character *)
  916. PROCEDURE LastIndexOfByte2*(ch : CHAR; CONST s : ARRAY OF CHAR) : LONGINT;
  917. BEGIN
  918. RETURN LastIndexOfByte(ch, Length(s)-1, s);
  919. END LastIndexOfByte2;
  920. (* Returns the index within string s of the last occurrence of the specified character, searching backward starting at the specified index *)
  921. PROCEDURE LastIndexOfByte*(ch : CHAR; fromIndex : LONGINT; CONST s : ARRAY OF CHAR) : LONGINT;
  922. VAR
  923. lenString, i : LONGINT;
  924. BEGIN
  925. lenString := Length(s);
  926. IF fromIndex >= lenString THEN
  927. fromIndex := lenString - 1;
  928. END;
  929. FOR i := fromIndex TO 0 BY -1 DO
  930. IF s[i] = ch THEN RETURN i; END;
  931. END;
  932. RETURN -1;
  933. END LastIndexOfByte;
  934. (* Returns a new string that is a copy of s in lower-case letters *)
  935. PROCEDURE LowerCaseInNew*(CONST s : ARRAY OF CHAR) : String;
  936. VAR
  937. n : String;
  938. BEGIN
  939. n := NewString(s);
  940. LowerCase(n^);
  941. RETURN n;
  942. END LowerCaseInNew;
  943. (* Tests if string s starts with the specified prefix *)
  944. PROCEDURE StartsWith2*(CONST prefix, s : ARRAY OF CHAR) : BOOLEAN;
  945. BEGIN
  946. RETURN StartsWith(prefix, 0, s);
  947. END StartsWith2;
  948. (* Tests if string s starts with the specified prefix beginning a specified index *)
  949. PROCEDURE StartsWith*(CONST prefix : ARRAY OF CHAR; toffset : LONGINT; CONST s : ARRAY OF CHAR) : BOOLEAN;
  950. VAR
  951. lenString, lenPrefix, i : LONGINT;
  952. BEGIN
  953. lenString := Length(s);
  954. lenPrefix := Length(prefix);
  955. IF (toffset < 0) OR (toffset > lenString - lenPrefix) THEN
  956. RETURN FALSE;
  957. END;
  958. FOR i := 0 TO lenPrefix-1 DO
  959. IF prefix[i] # s[toffset + i] THEN RETURN FALSE; END;
  960. END;
  961. RETURN TRUE;
  962. END StartsWith;
  963. (* Returns a new string that is a substring of string s *)
  964. PROCEDURE Substring2*(beginIndex : LONGINT; CONST s : ARRAY OF CHAR) : String;
  965. BEGIN
  966. RETURN Substring(beginIndex, Length(s), s);
  967. END Substring2;
  968. (* Returns a new string that is a substring of string s *)
  969. (* s[endIndex-1] is the last character of the new string *)
  970. PROCEDURE Substring*(beginIndex : LONGINT; endIndex : LONGINT; CONST s : ARRAY OF CHAR) : String;
  971. VAR
  972. lenString, lenNewString : LONGINT;
  973. st : String;
  974. BEGIN
  975. ASSERT(beginIndex >= 0);
  976. lenString := Length(s);
  977. ASSERT(endIndex <= lenString);
  978. lenNewString := endIndex - beginIndex;
  979. ASSERT(lenNewString >= 0);
  980. NEW(st, lenNewString + 1);
  981. Copy(s, beginIndex, lenNewString, st^);
  982. RETURN st;
  983. END Substring;
  984. (* Omitts leading and trailing whitespace of string s *)
  985. PROCEDURE TrimWS*(VAR s : ARRAY OF CHAR);
  986. VAR
  987. len, start, i : LONGINT;
  988. BEGIN
  989. len := Length(s);
  990. start := 0;
  991. WHILE (start < len) & (ORD(s[start]) < 33) DO
  992. INC(start);
  993. END;
  994. WHILE (start < len) & (ORD(s[len-1]) < 33) DO
  995. DEC(len);
  996. END;
  997. IF start > 0 THEN
  998. FOR i := 0 TO len - start - 1 DO
  999. s[i] := s[start + i];
  1000. END;
  1001. s[i] := 0X;
  1002. ELSE
  1003. s[len] := 0X;
  1004. END;
  1005. END TrimWS;
  1006. (* Returns a new string that is a copy of s in upper-case letters *)
  1007. PROCEDURE UpperCaseInNew*(CONST s : ARRAY OF CHAR) : String;
  1008. VAR n : String;
  1009. BEGIN
  1010. n := NewString(s);
  1011. UpperCase(n^);
  1012. RETURN n;
  1013. END UpperCaseInNew;
  1014. BEGIN
  1015. DateFormat := "dd.mmm.yyyy";
  1016. TimeFormat := "hh:nn:ss"
  1017. END Strings.
  1018. System.Free Utilities ~