Strings.Mod 32 KB

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