Strings.Mod 31 KB

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