Linux GNU 11.4.0 Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 20.0% 16 / 0 / 80
Functions: -% 0 / 1 / 1
Branches: 12.5% 10 / 0 / 80

OMCompiler/Compiler/Util/StringUtil.mo
Line Branch Exec Source
1 /*
2 * This file is part of OpenModelica.
3 *
4 * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC),
5 * c/o Linköpings universitet, Department of Computer and Information Science,
6 * SE-58183 Linköping, Sweden.
7 *
8 * All rights reserved.
9 *
10 * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR
11 * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8.
12 * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES
13 * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL
14 * VERSION 3, ACCORDING TO RECIPIENTS CHOICE.
15 *
16 * The OpenModelica software and the OSMC (Open Source Modelica Consortium)
17 * Public License (OSMC-PL) are obtained from OSMC, either from the above
18 * address, from the URLs:
19 * http://www.openmodelica.org or
20 * https://github.com/OpenModelica/ or
21 * http://www.ida.liu.se/projects/OpenModelica,
22 * and in the OpenModelica distribution.
23 *
24 * GNU AGPL version 3 is obtained from:
25 * https://www.gnu.org/licenses/licenses.html#GPL
26 *
27 * This program is distributed WITHOUT ANY WARRANTY; without
28 * even the implied warranty of MERCHANTABILITY or FITNESS
29 * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH
30 * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL.
31 *
32 * See the full OSMC Public License conditions for more details.
33 *
34 */
35
36 encapsulated package StringUtil
37 " file: StringUtil.mo
38 package: StringUtil
39 description: String utility functions.
40
41
42 This package contains various utility functions for handling the builtin
43 MetaModelica String type."
44
45 protected import System;
46
47 protected import MetaModelica.Dangerous.{listReverseInPlace, stringGetNoBoundsChecking};
48
49 public
50 constant Integer NO_POS = 0;
51 constant Integer CHAR_NEWLINE = 10;
52 constant Integer CHAR_SPACE = 32;
53 constant Integer CHAR_DASH = 45;
54 constant Integer CHAR_DOT = 46;
55
56 public function headline_1
57 input String title;
58 output String header;
59 algorithm
60 30 header := repeat("#", stringLength(title) + 8) + "\n\n " + title + "\n\n" + repeat("#", stringLength(title) + 8) + "\n";
61 end headline_1;
62
63 public function headline_2
64 input String title;
65 output String header;
66 algorithm
67 117 header := repeat("=", stringLength(title) + 4) + "\n " + title + "\n" + repeat("=", stringLength(title) + 4) + "\n";
68 end headline_2;
69
70 public function headline_3
71 input String title;
72 output String header;
73 algorithm
74 116 header := title + "\n" + repeat("-", stringLength(title) + 2) + "\n";
75 end headline_3;
76
77 public function headline_4
78 input String title;
79 output String header;
80 algorithm
81 211 header := title + "\n" + repeat("*", stringLength(title) + 2) + "\n";
82 end headline_4;
83
84 public function findChar
85 "Searches for a given character in the given string, returning the index of
86 the character if found. If not found returns NO_POS. The start and end
87 position determines the section of the string to search in, and if not
88 specified they are set to the start and end of the string."
89 input String inString;
90 input Integer inChar;
91 input Integer inStartPos = 1;
92 input Integer inEndPos = 0;
93 output Integer outIndex = NO_POS;
94 protected
95 constant Integer len = stringLength(inString);
96 Integer start_pos, end_pos;
97 algorithm
98 start_pos := max(inStartPos, 1);
99
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 end_pos := if inEndPos > 0 then min(inEndPos, len) else len;
100
101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
436 for i in start_pos:end_pos loop
102
1/2
✓ Branch 0 taken 405 times.
✗ Branch 1 not taken.
405 if stringGetNoBoundsChecking(inString, i) == inChar then
103 outIndex := i;
104 break;
105 end if;
106 end for;
107 end findChar;
108
109 public function rfindChar
110 "Searches backwards for a given character in the given string, returning the
111 index of the character if found. If not found returns NO_POS. The start and
112 end position determines the section of the string to search in, and if not
113 specified they are set to the start and end of the string."
114 input String inString;
115 input Integer inChar;
116 input Integer inStartPos = 0;
117 input Integer inEndPos = 1;
118 output Integer outIndex = NO_POS;
119 protected
120 constant Integer len = stringLength(inString);
121 Integer start_pos, end_pos;
122 algorithm
123 ✗ start_pos := if inStartPos > 0 then min(inStartPos, len) else len;
124 end_pos := max(inEndPos, 1);
125
126 ✗ for i in start_pos:-1:end_pos loop
127 ✗ if stringGetNoBoundsChecking(inString, i) == inChar then
128 outIndex := i;
129 break;
130 end if;
131 end for;
132 end rfindChar;
133
134 public function findCharNot
135 "Searches for a character not matching the given character in the given
136 string, returning the index of the character if found. If not found returns
137 NO_POS. The start and end position determines the section of the string to
138 search in, and if not specified they are set to the start and end of the
139 string."
140 input String inString;
141 input Integer inChar;
142 input Integer inStartPos = 1;
143 input Integer inEndPos = 0;
144 output Integer outIndex = NO_POS;
145 protected
146 constant Integer len = stringLength(inString);
147 Integer start_pos, end_pos;
148 algorithm
149 start_pos := max(inStartPos, 1);
150 ✗ end_pos := if inEndPos > 0 then min(inEndPos, len) else len;
151
152 ✗ for i in start_pos:end_pos loop
153 ✗ if stringGetNoBoundsChecking(inString, i) <> inChar then
154 outIndex := i;
155 break;
156 end if;
157 end for;
158 end findCharNot;
159
160 public function rfindCharNot
161 "Searches backwards for a character not matching the given character in the
162 given string, returning the index of the character if found. If not found
163 returns NO_POS. The start and end position determines the section of the
164 string to search in, and if not specified they are set to the start and end
165 of the string."
166 input String inString;
167 input Integer inChar;
168 input Integer inStartPos = 0;
169 input Integer inEndPos = 1;
170 output Integer outIndex = NO_POS;
171 protected
172 constant Integer len = stringLength(inString);
173 Integer start_pos, end_pos;
174 algorithm
175 ✗ start_pos := if inStartPos > 0 then min(inStartPos, len) else len;
176 end_pos := max(inEndPos, 1);
177
178 ✗ for i in start_pos:-1:end_pos loop
179 ✗ if stringGetNoBoundsChecking(inString, i) <> inChar then
180 outIndex := i;
181 break;
182 end if;
183 end for;
184 end rfindCharNot;
185
186 public function isAlpha
187 "Returns true if the given character represented by it's ASCII decimal number
188 is an alphabetic character."
189 input Integer inChar;
190 output Boolean outIsAlpha = (inChar >= 65 and inChar <= 90) or
191 (inChar >= 97 and inChar <= 122);
192 end isAlpha;
193
194 public function wordWrap
195 "Breaks the given string into lines which are no longer than the given wrap
196 length. The function tries to break lines at word boundaries, i.e. at spaces,
197 so that words are not split. It also wraps the string at any newline
198 characters it finds. The function also takes two optional parameters to set
199 the delimiter and raggedness.
200
201 inDelimiter sets the delimiter which is prefixed to all lines except for the
202 first one. The length of this delimiter is taken into account when wrapping
203 the string, so it must be shorter than the wrap length. Otherwise the string
204 will be returned unwrapped. The default is an empty string.
205
206 inRaggedness determines the allowed raggedness of the lines, given as a ratio
207 between 0 and 1. A raggedness of e.g. 0.2 means that each segment may be at
208 most 20% smaller than the max line length. If a line would be shorter than
209 this, due to a long word, then the function instead hyphenates the last word.
210 This is not done according to any grammatical rules, the words are just
211 broken so that the line is as long as allowed. The default is 0.3.
212
213 This function operates on ASCII strings, and does not handle UTF-8 strings
214 correctly."
215 input String inString;
216 input Integer inWrapLength;
217 input String inDelimiter = "";
218 input Real inRaggedness = 0.3;
219 output list<String> outStrings = {};
220 protected
221 Integer start_pos = 1, end_pos = inWrapLength;
222 Integer line_len, pos, next_char, char, gap_size;
223 String str, delim = "";
224 list<String> lines;
225 algorithm
226 // Check that the wrap length is larger than the delimiter, otherwise just
227 // return the string as it is.
228 ✗ if stringLength(inDelimiter) >= inWrapLength - 1 then
229 outStrings := {inString};
230 ✗ return;
231 end if;
232
233 // Split the string at newlines.
234 ✗ lines := System.strtok(inString, "\n");
235 // Calculate the length of each line, excluding the delimiter.
236 ✗ line_len := inWrapLength - stringLength(inDelimiter) - 1;
237 // The gap size is how many characters a line may be shorter than the sought
238 // after line length.
239 ✗ gap_size := max(realInt(realMul(line_len, inRaggedness)), 0);
240
241 // Wrap each line separately.
242 ✗ for line in lines loop
243 ✗ while end_pos < stringLength(line) loop
244 ✗ next_char := stringGetNoBoundsChecking(line, end_pos + 1);
245
246 ✗ if next_char <> CHAR_SPACE and next_char <> CHAR_DASH then
247 // If the next character isn't a space or dash, search backwards for a space.
248 ✗ pos := rfindChar(line, CHAR_SPACE, end_pos, end_pos - gap_size);
249
250 ✗ if pos <> NO_POS then
251 // A space was found, break the string here.
252 ✗ str := substring(line, start_pos, pos - 1);
253 ✗ start_pos := pos + 1;
254 else
255 // No space was found, search for a dash instead.
256 ✗ pos := rfindChar(line, CHAR_DASH, end_pos, start_pos + gap_size);
257
258 ✗ if pos > 1 then
259 // A dash was found, check that the previous character is alphabetic.
260 ✗ char := stringGetNoBoundsChecking(line, pos - 1);
261 ✗ pos := if isAlpha(char) and isAlpha(next_char) then pos else NO_POS;
262 end if;
263
264 ✗ if pos <> NO_POS then
265 // A dash was found, break the string here.
266 ✗ str := substring(line, start_pos, pos);
267 ✗ start_pos := pos + 1;
268 else
269 // No dash was found, break the word and hyphenate it.
270 ✗ str := substring(line, start_pos, end_pos - 1) + "-";
271 start_pos := end_pos;
272 end if;
273 end if;
274 else
275 // The next character is a space or dash, split the string here.
276 ✗ str := substring(line, start_pos, end_pos);
277 // Skip the space.
278 ✗ start_pos := end_pos + (if next_char == CHAR_SPACE then 2 else 1);
279 end if;
280
281 // Add the string to the list and continue with the rest of the line.
282 ✗ outStrings := (delim + str) :: outStrings;
283 ✗ end_pos := start_pos + line_len;
284 delim := inDelimiter;
285 end while;
286
287 // Add any remainder of the line to the list.
288 ✗ if start_pos < stringLength(line) then
289 ✗ str := delim + substring(line, start_pos, stringLength(line));
290 outStrings := str :: outStrings;
291 end if;
292
293 // Continue with the next line.
294 start_pos := 1;
295 end_pos := line_len;
296 delim := inDelimiter;
297 end for;
298
299 ✗ outStrings := listReverseInPlace(outStrings);
300 end wordWrap;
301
302 function repeat
303 "Repeat str n times"
304 input String str;
305 input Integer n;
306 output String res="";
307 protected
308 Integer len = stringLength(str);
309 System.StringAllocator ext = System.StringAllocator(len*n);
310 algorithm
311
2/2
✓ Branch 0 taken 695 times.
✓ Branch 1 taken 6 times.
27305 for i in 0:n-1 loop
312 26604 System.stringAllocatorStringCopy(ext, str, len*i);
313 end for;
314 701 res := System.stringAllocatorResult(ext, res);
315 end repeat;
316
317 function quote
318 "Adds quotation marks to the beginning and end of a string."
319 input String inString;
320 output String outString = stringAppendList({"\"", inString, "\""});
321 annotation(__OpenModelica_EarlyInline = true);
322 end quote;
323
324 function equalIgnoreSpace
325 input String s1;
326 input String s2;
327 output Boolean b;
328 protected
329 Integer j=1;
330 algorithm
331 b := true;
332 ✗ for i in 1:stringLength(s1) loop
333 ✗ if MetaModelica.Dangerous.stringGetNoBoundsChecking(s1, i) <> stringCharInt(" ") then
334 b := false;
335 ✗ for j2 in j:stringLength(s2) loop
336 ✗ if MetaModelica.Dangerous.stringGetNoBoundsChecking(s2, j2) <> stringCharInt(" ") then
337 ✗ if MetaModelica.Dangerous.stringGetNoBoundsChecking(s2, j2) <>
338 MetaModelica.Dangerous.stringGetNoBoundsChecking(s1, i) then
339 ✗ return;
340 end if;
341 ✗ j := j2+1;
342 b := true;
343 break;
344 end if;
345 end for;
346 if not b then
347 ✗ return;
348 end if;
349 end if;
350 end for;
351 ✗ for j2 in j:stringLength(s2) loop
352 ✗ if MetaModelica.Dangerous.stringGetNoBoundsChecking(s2, j2) <> stringCharInt(" ") then
353 b := false;
354 ✗ return;
355 end if;
356 end for;
357 end equalIgnoreSpace;
358
359 function bytesToReadableUnit
360 input Real bytes;
361 input Integer significantDigits=4;
362 input Real maxSizeInUnit=500 "If it is 1000, we print up to 1000GB before changing to X TB";
363 output String str;
364 protected
365 constant Real TB = 1024^4, GB=1024^3, MB=1024^2, kB=1024;
366 algorithm
367 ✗ if bytes > maxSizeInUnit*GB then
368 ✗ str := String(bytes/TB, significantDigits=significantDigits)+" TB";
369 elseif bytes > maxSizeInUnit*MB then
370 ✗ str := String(bytes/GB, significantDigits=significantDigits)+" GB";
371 elseif bytes > maxSizeInUnit*kB then
372 ✗ str := String(bytes/MB, significantDigits=significantDigits)+" MB";
373 elseif bytes > maxSizeInUnit then
374 ✗ str := String(bytes/kB, significantDigits=significantDigits)+" kB";
375 else
376 ✗ str := String(integer(bytes));
377 end if;
378 end bytesToReadableUnit;
379
380 function startsWith
381 input String str;
382 input String prefix;
383 output Boolean startsWith = (0 == System.strncmp(str, prefix, stringLength(prefix)));
384 end startsWith;
385
386 function endsWith
387 input String str;
388 input String suffix;
389 output Boolean endsWith = false;
390 protected
391 Integer str_len = stringLength(str);
392 Integer suf_len = stringLength(suffix);
393 algorithm
394
2/2
✓ Branch 0 taken 26638 times.
✓ Branch 1 taken 582639 times.
609277 if str_len >= suf_len then
395 582639 endsWith := 0 == System.strcmp_offset(str, str_len - suf_len + 1, str_len, suffix, 1, suf_len);
396 end if;
397 end endsWith;
398
399 function endsWithNewline
400 input String str;
401 output Boolean b;
402 algorithm
403 3197265 b := CHAR_NEWLINE == MetaModelica.Dangerous.stringGetNoBoundsChecking(str, stringLength(str));
404 end endsWithNewline;
405
406 function convertCharNonAsciiToHex "Converts a single character string to a hex representation if it is not valid unicode"
407 input output String s;
408 protected
409 Integer i;
410 constant array<String> hex = MetaModelica.Dangerous.listArrayLiteral({"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"});
411 algorithm
412 ✗ i := stringCharInt(s);
413 ✗ if i < 128 then
414 ✗ return;
415 end if;
416 ✗ s := "0x" + arrayGet(hex, intDiv(i, 16)+1) + arrayGet(hex, intMod(i, 16)+1);
417 end convertCharNonAsciiToHex;
418
419 function stripBOM
420 input output String s;
421 output String bom = "";
422 algorithm
423
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 92 times.
92 if stringLength(s) < 3 then
424 ✗ return;
425 end if;
426
1/6
✓ Branch 1 taken 92 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
92 if stringGet(s,1) == 239 and
427 stringGet(s,2) == 187 and
428 stringGet(s,3) == 191 then
429 ✗ bom := substring(s, 1, 3);
430 ✗ s := substring(s, 4, stringLength(s));
431 end if;
432 end stripBOM;
433
434 function stripFileExtension
435 input output String filename;
436 protected
437 Integer pos;
438 algorithm
439 ✗ pos := rfindChar(filename, CHAR_DOT);
440
441 ✗ if pos <> NO_POS then
442 ✗ filename := substring(filename, 1, pos-1);
443 end if;
444 end stripFileExtension;
445
446 public function rest
447 "Returns all but the first character of a string."
448 input String str;
449 output String rest;
450 algorithm
451 // TODO: Change substring to allow returning the last 0 characters at the end of a string?
452
1/2
✓ Branch 0 taken 1547 times.
✗ Branch 1 not taken.
1547 rest := if stringLength(str) == 1 then "" else substring(str, 2, stringLength(str));
453 end rest;
454
455 annotation(__OpenModelica_Interface="util");
456 end StringUtil;
457