Linux GNU 11.4.0 Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 86.8% 322 / 0 / 371
Functions: -% 0 / 1 / 1
Branches: 73.3% 374 / 0 / 510

OMCompiler/Compiler/NFFrontEnd/NFDefUseChains.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 NFDefUseChains
37 "Serializes the def-use chains found by NFUsedElements.collectUses as JSON,
38 see getDefUseChains.
39
40 The names in the abstract syntax don't have a source position, only the
41 element, equation or statement they're used in has. The position of a name
42 is found by reading the source file and finding the name among the
43 identifiers in the span of what it's used in. Each identifier is only given
44 to one use, the uses in the smallest spans first, and the names in a span in
45 the order they're looked up. Names written the same way in the same span
46 refer to the same definition, so the order they're given out doesn't matter.
47 The declared names are taken first, so a use can't be given the name of a
48 declaration. A use whose name isn't found keeps the span it's used in and
49 is marked as not exact."
50
51 import Absyn;
52 import NFUsedElements;
53 import NFUsedElements.Definition;
54 import NFUsedElements.Site;
55 import NFUsedElements.Use;
56
57 protected
58 import AbsynUtil;
59 import JSON;
60 import List;
61 import System;
62 import UnorderedMap;
63 import UnorderedSet;
64 import Util;
65
66 constant Integer IDENT = 1;
67 constant Integer DOT = 2;
68 constant Integer LBRACKET = 3;
69 constant Integer RBRACKET = 4;
70 constant Integer LBRACE = 5 "Only the one of a group import, A.{b, c}.";
71 constant Integer RBRACE = 6;
72
73 uniontype Token
74 record TOKEN
75 Integer kind;
76 String text;
77 Integer line;
78 Integer column "The column of the first character, starting at 1.";
79 Integer endColumn "The column of the last character.";
80 end TOKEN;
81 end Token;
82
83 uniontype SourceFile
84 record SOURCE_FILE
85 array<Token> tokens;
86 array<Boolean> taken;
87 end SOURCE_FILE;
88 end SourceFile;
89
90 uniontype Position
91 record POSITION
92 String fileName;
93 Integer lineStart;
94 Integer columnStart;
95 Integer lineEnd;
96 Integer columnEnd;
97 Boolean exact;
98 end POSITION;
99 end Position;
100
101 type OptSourceFile = Option<SourceFile>;
102 type Files = UnorderedMap<String, OptSourceFile>;
103 type Placed = tuple<Use, Position>;
104 type PlacedList = list<Placed>;
105 type OptLines = Option<array<String>>;
106 type InfoList = list<SourceInfo>;
107 type StringSet = UnorderedSet<String>;
108
109 // The moduli of the hashes of the source of a class, primes small enough that
110 // h * 263 + 255 stays below 2^30, so the hashes are the same on 32-bit targets
111 // (wasm) as on 64-bit ones.
112 constant Integer HASH_MOD1 = 4082651;
113 constant Integer HASH_MOD2 = 4082629;
114
115 public
116 function toJSON
117 "Returns the def-use chains of the names used in or declared in a class,
118 or of a single definition, as JSON."
119 input String className "The class, or the full name of a component.";
120 input list<Definition> definitions;
121 input list<Use> uses;
122 input list<Use> unresolved;
123 input String scope "The class the uses are reported in, all if empty.";
124 input Boolean prettyPrint;
125 output String json;
126 protected
127 UnorderedMap<String, Definition> defs;
128 UnorderedMap<String, list<tuple<Use, Position>>> def_uses;
129 UnorderedMap<String, Position> names;
130 list<Definition> reported = {};
131 list<Use> reported_uses;
132 list<tuple<Use, Position>> placed;
133 Files files;
134 UnorderedSet<String> resolved;
135 Boolean single;
136 Definition def;
137 JSON jdefs, jdef, juses, obj;
138 list<JSON> jlist;
139 Use u;
140 Position pos;
141 algorithm
142 18 defs := UnorderedMap.new<Definition>(stringHashDjb2, stringEq);
143
2/2
✓ Branch 0 taken 385 times.
✓ Branch 1 taken 18 times.
403 for d in definitions loop
144 385 UnorderedMap.add(d.key, d, defs);
145 end for;
146
147 // A component name gives only its own chain.
148 single := false;
149
2/2
✓ Branch 0 taken 182 times.
✓ Branch 1 taken 5 times.
187 for d in definitions loop
150
7/8
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 131 times.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 35 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 13 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 3 times.
182 if d.name == className and d.kind <> "class" then
151 reported := {d};
152 single := true;
153 break;
154 end if;
155 end for;
156
157 if not single then
158
8/8
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 31 times.
✓ Branch 6 taken 5 times.
✓ Branch 7 taken 14 times.
✓ Branch 8 taken 5 times.
36 reported := list(d for d guard d.declared and isInClass(d.name, className) in definitions);
159
2/2
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 5 times.
24 for u in uses loop
160
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 5 times.
19 if isInClass(u.site.scope, className) then
161 14 def := UnorderedMap.getOrFail(u.key, defs);
162
4/4
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1 time.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 12 times.
14 if not (def.declared and isInClass(def.name, className)) then
163 reported := def :: reported;
164 end if;
165 end if;
166 end for;
167 5 reported := uniqueDefinitions(reported);
168 end if;
169
170 18 reported := List.sort(reported, definitionGt);
171 18 def_uses := UnorderedMap.new<PlacedList>(stringHashDjb2, stringEq);
172
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 18 times.
47 for d in reported loop
173 29 UnorderedMap.add(d.key, {}, def_uses);
174 end for;
175 // The class itself is walked also when it isn't in the scope.
176
11/12
✓ Branch 1 taken 45 times.
✓ Branch 2 taken 504 times.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 30 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 14 times.
✓ Branch 9 taken 1 time.
✗ Branch 10 not taken.
✓ Branch 11 taken 549 times.
✓ Branch 12 taken 18 times.
✓ Branch 13 taken 44 times.
✓ Branch 14 taken 18 times.
567 reported_uses := list(u for u guard UnorderedMap.contains(u.key, def_uses) and
177 (stringEmpty(scope) or isInClass(u.site.scope, scope) or isInClass(u.site.scope, className)) in uses);
178
179 // Place the declared names first, then the uses, the ones in the smallest spans first.
180 18 files := UnorderedMap.new<OptSourceFile>(stringHashDjb2, stringEq);
181 18 names := UnorderedMap.new<Position>(stringHashDjb2, stringEq);
182
2/2
✓ Branch 0 taken 385 times.
✓ Branch 1 taken 18 times.
403 for d in definitions loop
183
6/6
✓ Branch 2 taken 28 times.
✓ Branch 3 taken 357 times.
✓ Branch 5 taken 26 times.
✓ Branch 6 taken 2 times.
✓ Branch 9 taken 14 times.
✓ Branch 10 taken 12 times.
385 if UnorderedMap.contains(d.info.fileName, files) or UnorderedMap.contains(d.key, def_uses) or
184 List.any(reported_uses, function useInFile(fileName = d.info.fileName)) then
185 373 UnorderedMap.add(d.key, placeDefinition(d, files), names);
186 end if;
187 end for;
188
189 18 placed := placeUses(reported_uses, defs, files);
190
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 18 times.
62 for p in placed loop
191 44 (u, _) := p;
192 88 UnorderedMap.add(u.key, p :: UnorderedMap.getOrFail(u.key, def_uses), def_uses);
193 end for;
194
195 // The name after end refers to the class too.
196
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 18 times.
47 for d in reported loop
197
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 26 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
29 if d.kind == "class" then
198 3 pos := placeEnd(d, files);
199
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 if pos.exact then
200 2 u := NFUsedElements.USE(d.key, Absyn.IDENT(lastIdent(d.name)), 1,
201 NFUsedElements.SITE(d.info, "end", d.name), false);
202 4 UnorderedMap.add(d.key, (u, pos) :: UnorderedMap.getOrFail(d.key, def_uses), def_uses);
203 end if;
204 end if;
205 end for;
206
207 jlist := {};
208
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 18 times.
47 for d in reported loop
209 29 jdef := definitionJSON(d, UnorderedMap.getOrDefault(d.key, names, POSITION("", 0, 0, 0, 0, false)));
210
4/4
✓ Branch 2 taken 46 times.
✓ Branch 3 taken 29 times.
✓ Branch 4 taken 46 times.
✓ Branch 5 taken 29 times.
75 juses := JSON.makeArray(list(useJSON(p) for p in List.sort(UnorderedMap.getOrFail(d.key, def_uses), placedGt)));
211 29 jdef := JSON.addPair("uses", juses, jdef);
212 jlist := jdef :: jlist;
213 end for;
214
215 18 obj := JSON.emptyListObject();
216 18 obj := JSON.addPair("class", JSON.makeString(className), obj);
217 18 obj := JSON.addPair("definitions", JSON.makeArray(listReverse(jlist)), obj);
218 // A name that is only found in the classes a replaceable class is redeclared as isn't unresolved.
219 18 resolved := UnorderedSet.new<String>(stringHashDjb2, stringEq);
220
2/2
✓ Branch 0 taken 549 times.
✓ Branch 1 taken 18 times.
567 for u in uses loop
221
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 549 times.
549 if u.candidate then
222 ✗ UnorderedSet.add(siteSignature(u), resolved);
223 end if;
224 end for;
225
11/12
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 18 times.
✓ Branch 9 taken 1 time.
✓ Branch 10 taken 18 times.
✓ Branch 12 taken 1 time.
✓ Branch 13 taken 18 times.
✓ Branch 14 taken 1 time.
✓ Branch 15 taken 18 times.
23 obj := JSON.addPair("unresolved", JSON.makeArray(list(useJSON(p) for p in
226 placeUses(list(u for u guard isInClass(u.site.scope, className) and
227 not UnorderedSet.contains(siteSignature(u), resolved) in unresolved), defs, files))), obj);
228 18 json := JSON.toString(obj, prettyPrint);
229 end toJSON;
230
231 function dependencyGraphJSON
232 "Returns the classes that were walked or used, the hashes of their source and
233 the classes they use, as JSON."
234 input String scope;
235 input list<Definition> definitions;
236 input list<Use> uses;
237 input Boolean prettyPrint;
238 output String json;
239 protected
240 UnorderedMap<String, Definition> defs = UnorderedMap.new<Definition>(stringHashDjb2, stringEq);
241 UnorderedMap<String, Definition> classes = UnorderedMap.new<Definition>(stringHashDjb2, stringEq);
242 UnorderedMap<String, StringSet> deps = UnorderedMap.new<StringSet>(stringHashDjb2, stringEq);
243 UnorderedMap<String, InfoList> nested = UnorderedMap.new<InfoList>(stringHashDjb2, stringEq);
244 UnorderedMap<String, OptLines> texts = UnorderedMap.new<OptLines>(stringHashDjb2, stringEq);
245 Definition def;
246 String to, parent;
247 StringSet used;
248 JSON jclasses = JSON.emptyListObject(), jcls, obj;
249 algorithm
250
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 3 times.
51 for d in definitions loop
251 48 UnorderedMap.add(d.key, d, defs);
252
253 // A class is identified by its name, the one declared in the walked classes is kept.
254
4/8
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 18 times.
✓ Branch 3 taken 30 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 30 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
48 if d.kind == "class" and (d.declared or not UnorderedMap.contains(d.name, classes)) then
255 30 UnorderedMap.add(d.name, d, classes);
256 end if;
257 end for;
258
259 // The classes declared in a class in the same file, which aren't part of its source.
260
2/2
✓ Branch 1 taken 30 times.
✓ Branch 2 taken 3 times.
33 for d in UnorderedMap.valueList(classes) loop
261 30 parent := ownerName(d.name);
262
263
2/2
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 3 times.
30 if UnorderedMap.contains(parent, classes) then
264 27 def := UnorderedMap.getOrFail(parent, classes);
265
266
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 27 times.
✗ Branch 4 not taken.
27 if def.info.fileName == d.info.fileName then
267 27 UnorderedMap.add(parent, d.info :: UnorderedMap.getOrDefault(parent, nested, {}), nested);
268 end if;
269 end if;
270 end for;
271
272
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 3 times.
51 for u in uses loop
273
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 if not stringEmpty(u.key) then
274 48 def := UnorderedMap.getOrFail(u.key, defs);
275
276 // A class a name is only looked up through, e.g. P in P.f, isn't used by it.
277
6/10
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 33 times.
✓ Branch 6 taken 15 times.
✓ Branch 8 taken 33 times.
✗ Branch 9 not taken.
✓ Branch 13 taken 6 times.
✓ Branch 14 taken 27 times.
48 if def.kind == "iterator" or (def.kind == "class" and u.index < listLength(
278 AbsynUtil.pathToStringList(AbsynUtil.makeNotFullyQualified(u.written)))) then
279 6 continue;
280 end if;
281
282 // A component, constant or enumeration literal is part of the class it's declared in.
283
3/4
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 15 times.
✓ Branch 3 taken 27 times.
✗ Branch 4 not taken.
42 to := if def.kind == "class" then def.name else ownerName(def.name);
284
285
5/6
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 33 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 3 times.
42 if not stringEmpty(to) and to <> u.site.scope then
286 39 used := UnorderedMap.addUpdate(u.site.scope, newStringSet, deps);
287 39 UnorderedSet.add(to, used);
288 end if;
289 end if;
290 end for;
291
292
2/2
✓ Branch 2 taken 30 times.
✓ Branch 3 taken 3 times.
33 for d in List.sort(UnorderedMap.valueList(classes), definitionGt) loop
293 30 jcls := JSON.emptyListObject();
294 30 jcls := JSON.addPair("restriction", JSON.makeString(d.detail), jcls);
295 30 jcls := addInfo(d.info, jcls);
296 30 jcls := JSON.addPair("hash", JSON.makeString(sourceHash(d.info,
297 List.sort(UnorderedMap.getOrDefault(d.name, nested, {}), infoGt), texts)), jcls);
298 // Only the classes that are in the graph, not builtin ones like StateSelect.
299
5/6
✗ Branch 5 not taken.
✓ Branch 6 taken 30 times.
✓ Branch 7 taken 30 times.
✓ Branch 8 taken 30 times.
✓ Branch 9 taken 30 times.
✓ Branch 10 taken 30 times.
60 jcls := JSON.addPair("uses", JSON.makeArray(list(JSON.makeString(n) for n guard UnorderedMap.contains(n, classes) in
300 List.sort(UnorderedSet.toList(UnorderedMap.getOrDefault(d.name, deps, newStringSet())), stringGt))), jcls);
301 30 jclasses := JSON.addPair(d.name, jcls, jclasses);
302 end for;
303
304 3 obj := JSON.emptyListObject();
305 3 obj := JSON.addPair("scope", JSON.makeString(scope), obj);
306 3 obj := JSON.addPair("classes", jclasses, obj);
307 3 json := JSON.toString(obj, prettyPrint);
308 end dependencyGraphJSON;
309
310 function definitionAtJSON
311 "Returns the definition of the name at a position in a file as JSON: the
312 definition, and the use if the name at the position isn't the declared name."
313 input String fileName;
314 input Integer line;
315 input Integer column;
316 input list<Definition> definitions;
317 input list<Use> uses;
318 input Boolean prettyPrint;
319 output String json;
320 protected
321 UnorderedMap<String, Definition> defs = UnorderedMap.new<Definition>(stringHashDjb2, stringEq);
322 Files files = UnorderedMap.new<OptSourceFile>(stringHashDjb2, stringEq);
323 Option<Definition> found = NONE();
324 Option<Placed> found_use = NONE();
325 UnorderedMap<String, Position> names = UnorderedMap.new<Position>(stringHashDjb2, stringEq);
326 list<Definition> candidates = {};
327 Position pos;
328 Use u;
329 Definition def;
330 JSON obj, jdef;
331 algorithm
332
2/2
✓ Branch 0 taken 75 times.
✓ Branch 1 taken 11 times.
86 for d in definitions loop
333 75 UnorderedMap.add(d.key, d, defs);
334 end for;
335
336 // The declared names are placed first, like in toJSON, then the uses.
337
2/2
✓ Branch 0 taken 75 times.
✓ Branch 1 taken 11 times.
86 for d in definitions loop
338
4/6
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 75 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 28 times.
✓ Branch 7 taken 47 times.
75 if d.info.fileName == fileName and isInSpan(d.info, line, column) then
339 28 pos := placeDefinition(d, files);
340 28 UnorderedMap.add(d.key, pos, names);
341
342
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 27 times.
28 if isAt(pos, fileName, line, column) then
343 found := SOME(d);
344 elseif d.kind == "class" then
345 24 pos := placeEnd(d, files);
346
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 23 times.
24 if isAt(pos, fileName, line, column) then
347 found := SOME(d);
348 end if;
349 end if;
350 end if;
351 end for;
352
353
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 2 times.
11 if isNone(found) then
354
10/12
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 60 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 36 times.
✓ Branch 7 taken 24 times.
✓ Branch 8 taken 60 times.
✓ Branch 9 taken 9 times.
✓ Branch 10 taken 24 times.
✓ Branch 11 taken 9 times.
✓ Branch 13 taken 22 times.
✓ Branch 14 taken 9 times.
91 for p in placeUses(list(u for u guard u.site.info.fileName == fileName and
355 isInSpan(u.site.info, line, column) in uses), defs, files) loop
356 22 (u, pos) := p;
357
358
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 14 times.
22 if isAt(pos, fileName, line, column) then
359 8 def := UnorderedMap.getOrFail(u.key, defs);
360
361
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 7 times.
8 if u.candidate then
362 candidates := def :: candidates;
363
364 // The use of a name only found in redeclared classes.
365
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 if isNone(found_use) then
366 found_use := SOME(p);
367 end if;
368 elseif isNone(found) then
369 found := SOME(def);
370 found_use := SOME(p);
371 end if;
372 end if;
373 end for;
374 end if;
375
376 11 obj := JSON.emptyListObject();
377 11 obj := JSON.addPair("file", JSON.makeString(fileName), obj);
378 11 obj := JSON.addPair("line", JSON.makeInteger(line), obj);
379 11 obj := JSON.addPair("column", JSON.makeInteger(column), obj);
380
381 obj := JSON.addPair("definition", match found
382 9 case SOME(def) then definitionJSON(def, namePosition(def, names, files));
383 2 else JSON.makeNull();
384 end match, obj);
385
386 obj := JSON.addPair("use", match found_use
387 local
388 Placed placed;
389 8 case SOME(placed) then useJSON(placed);
390 3 else JSON.makeNull();
391 end match, obj);
392
393
4/4
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 11 times.
12 obj := JSON.addPair("candidates", JSON.makeArray(list(definitionJSON(c,
394 namePosition(c, names, files)) for c in listReverse(candidates))), obj);
395 11 json := JSON.toString(obj, prettyPrint);
396 end definitionAtJSON;
397
398 protected
399 function definitionJSON
400 input Definition d;
401 input Position name "The position of the declared name.";
402 output JSON jdef = JSON.emptyListObject();
403 algorithm
404 39 jdef := JSON.addPair("name", JSON.makeString(d.name), jdef);
405 39 jdef := JSON.addPair("kind", JSON.makeString(d.kind), jdef);
406
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 6 times.
39 if not stringEmpty(d.detail) then
407
3/4
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 26 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
33 jdef := JSON.addPair(if d.kind == "class" then "restriction" else "type", JSON.makeString(d.detail), jdef);
408 end if;
409 39 jdef := addInfo(d.info, jdef);
410
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 35 times.
39 if name.exact then
411 35 jdef := JSON.addPair("nameLine", JSON.makeInteger(name.lineStart), jdef);
412 35 jdef := JSON.addPair("nameColumnStart", JSON.makeInteger(name.columnStart), jdef);
413 35 jdef := JSON.addPair("nameColumnEnd", JSON.makeInteger(name.columnEnd), jdef);
414 end if;
415 end definitionJSON;
416
417 function namePosition
418 "Returns the position of the declared name of a definition, placing it if it wasn't."
419 input Definition d;
420 input UnorderedMap<String, Position> names;
421 input Files files;
422 output Position pos;
423 algorithm
424
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 7 times.
10 if UnorderedMap.contains(d.key, names) then
425 3 pos := UnorderedMap.getOrFail(d.key, names);
426 else
427 7 pos := placeDefinition(d, files);
428 7 UnorderedMap.add(d.key, pos, names);
429 end if;
430 end namePosition;
431
432 function isInSpan
433 input SourceInfo info;
434 input Integer line;
435 input Integer column;
436 output Boolean res = (line > info.lineNumberStart or (line == info.lineNumberStart and column >= info.columnNumberStart)) and
437 (line < info.lineNumberEnd or (line == info.lineNumberEnd and column <= info.columnNumberEnd));
438 end isInSpan;
439
440 function isAt
441 "Returns true if the exact position of a name covers the given position."
442 input Position pos;
443 input String fileName;
444 input Integer line;
445 input Integer column;
446 output Boolean res = pos.exact and pos.fileName == fileName and pos.lineStart == line and
447 column >= pos.columnStart and column <= pos.columnEnd;
448 end isAt;
449
450 function newStringSet
451 input Option<StringSet> old = NONE();
452 output StringSet set;
453 algorithm
454 set := match old
455 case SOME(set) then set;
456 48 else UnorderedSet.new<String>(stringHashDjb2, stringEq);
457 end match;
458 end newStringSet;
459
460 function stringGt
461 input String s1;
462 input String s2;
463 output Boolean res = stringCompare(s1, s2) > 0;
464 end stringGt;
465
466 function infoGt
467 input SourceInfo i1;
468 input SourceInfo i2;
469 output Boolean res = i1.lineNumberStart > i2.lineNumberStart or
470 (i1.lineNumberStart == i2.lineNumberStart and i1.columnNumberStart > i2.columnNumberStart);
471 end infoGt;
472
473 function ownerName
474 "Returns the name of the class something is declared in, P.A for P.A.x."
475 input String name;
476 output String owner = "";
477 algorithm
478
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 45 times.
129 for i in stringLength(name):-1:1 loop
479
2/2
✓ Branch 1 taken 42 times.
✓ Branch 2 taken 84 times.
126 if stringGet(name, i) == 46 then
480 42 owner := substring(name, 1, i - 1);
481 42 return;
482 end if;
483 end for;
484 end ownerName;
485
486 function sourceHash
487 "Hashes the source of a class without the classes declared in it, its
488 comments and its whitespace, so it only changes when the class itself does."
489 input SourceInfo info;
490 input list<SourceInfo> nested "Sorted by position.";
491 input UnorderedMap<String, OptLines> texts;
492 output String hash = "";
493 protected
494 array<String> lines;
495 Integer line, col, len = 0, h1 = 0, h2 = 0, i, n, c, last = 0;
496 Boolean in_string = false, in_line_comment = false, in_block_comment = false,
497 space = false;
498 list<String> pieces = {};
499 String p;
500 algorithm
501
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 27 times.
30 if not UnorderedMap.contains(info.fileName, texts) then
502 3 UnorderedMap.add(info.fileName, readLines(info.fileName), texts);
503 end if;
504
505 () := match UnorderedMap.getOrFail(info.fileName, texts)
506 case SOME(lines) then ();
507 ✗ else algorithm return; then ();
508 end match;
509
510 // The pieces of the source between the nested classes.
511 30 line := info.lineNumberStart;
512 30 col := info.columnNumberStart;
513
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 30 times.
57 for n in nested loop
514 27 pieces := textRange(lines, line, col, n.lineNumberStart, n.columnNumberStart - 1) :: pieces;
515 27 line := n.lineNumberEnd;
516 27 col := n.columnNumberEnd + 1;
517 end for;
518 60 pieces := listReverse(textRange(lines, line, col, info.lineNumberEnd, info.columnNumberEnd) :: pieces);
519
520 // Hash the source without comments and whitespace, except for a space between
521 // two identifiers or numbers.
522
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 30 times.
87 for p in pieces loop
523 57 n := stringLength(p);
524 i := 1;
525
526
2/2
✓ Branch 0 taken 1551 times.
✓ Branch 1 taken 57 times.
1608 while i <= n loop
527 1551 c := stringGet(p, i);
528
529
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 1538 times.
1551 if in_line_comment then
530
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 12 times.
13 if c == 10 then
531 in_line_comment := false;
532 space := true;
533 end if;
534 13 i := i + 1;
535 elseif in_block_comment then
536 ✗ if c == 42 and i < n and stringGet(p, i + 1) == 47 then
537 in_block_comment := false;
538 space := true;
539 ✗ i := i + 2;
540 else
541 ✗ i := i + 1;
542 end if;
543 elseif in_string then
544 ✗ (h1, h2, len) := hashChar(c, h1, h2, len);
545
546 ✗ if c == 92 and i < n then // \
547 ✗ (h1, h2, len) := hashChar(stringGet(p, i + 1), h1, h2, len);
548 i := i + 1;
549 elseif c == 34 then
550 in_string := false;
551 end if;
552 ✗ i := i + 1;
553 elseif c == 47 and i < n and stringGet(p, i + 1) == 47 then
554 in_line_comment := true;
555 1 i := i + 2;
556 elseif c == 47 and i < n and stringGet(p, i + 1) == 42 then
557 in_block_comment := true;
558 ✗ i := i + 2;
559 elseif c == 32 or c == 9 or c == 10 or c == 13 then
560 space := true;
561 521 i := i + 1;
562 else
563
6/6
✓ Branch 0 taken 211 times.
✓ Branch 1 taken 805 times.
✓ Branch 3 taken 164 times.
✓ Branch 4 taken 47 times.
✓ Branch 6 taken 144 times.
✓ Branch 7 taken 20 times.
1016 if space and isIdentChar(last) and isIdentChar(c) then
564 144 (h1, h2, len) := hashChar(32, h1, h2, len);
565 end if;
566
567 space := false;
568 1016 (h1, h2, len) := hashChar(c, h1, h2, len);
569 last := c;
570 1016 in_string := c == 34;
571 1016 i := i + 1;
572 end if;
573 end while;
574 end for;
575
576 30 hash := intString(len) + "-" + intString(h1) + "-" + intString(h2);
577 end sourceHash;
578
579 function hashChar
580 input Integer c;
581 input output Integer h1;
582 input output Integer h2;
583 input output Integer len;
584 algorithm
585
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1160 times.
1160 h1 := mod(h1 * 257 + c, HASH_MOD1);
586
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1160 times.
1160 h2 := mod(h2 * 263 + c, HASH_MOD2);
587
1/2
✓ Branch 0 taken 1160 times.
✗ Branch 1 not taken.
1160 len := len + 1;
588 end hashChar;
589
590 function readLines
591 input String fileName;
592 output Option<array<String>> lines = NONE();
593 algorithm
594 try
595
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if System.regularFileExists(fileName) then
596 3 lines := SOME(listArray(Util.stringSplitAtChar(System.readFile(fileName), "\n")));
597 end if;
598 else
599 end try;
600 end readLines;
601
602 function textRange
603 "Returns the source from one position to another, both included."
604 input array<String> lines;
605 input Integer lineStart;
606 input Integer colStart;
607 input Integer lineEnd;
608 input Integer colEnd;
609 output String text;
610 protected
611 list<String> parts = {};
612 algorithm
613
2/4
✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 57 times.
114 if lineStart > lineEnd or lineStart < 1 or lineEnd > arrayLength(lines) then
614 text := "";
615 ✗ return;
616 end if;
617
618
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 54 times.
57 if lineStart == lineEnd then
619 3 text := lineRange(arrayGet(lines, lineStart), colStart, colEnd);
620 3 return;
621 end if;
622
623 54 parts := {lineRange(arrayGet(lines, lineStart), colStart, stringLength(arrayGet(lines, lineStart)))};
624
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 30 times.
84 for l in lineStart + 1:lineEnd - 1 loop
625 30 parts := arrayGet(lines, l) :: parts;
626 end for;
627 54 parts := lineRange(arrayGet(lines, lineEnd), 1, colEnd) :: parts;
628 54 text := stringDelimitList(listReverse(parts), "\n");
629 end textRange;
630
631 function lineRange
632 input String line;
633 input Integer colStart;
634 input Integer colEnd;
635 output String text;
636 protected
637 Integer stop = min(colEnd, stringLength(line));
638 algorithm
639
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 27 times.
111 text := if colStart < 1 or colStart > stop then "" else substring(line, colStart, stop);
640 end lineRange;
641
642 function isInClass
643 "Returns true if the name is the class or declared in it."
644 input String name;
645 input String className;
646 output Boolean res;
647 protected
648 Integer len = stringLength(className);
649 algorithm
650
9/10
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 43 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 32 times.
✓ Branch 6 taken 24 times.
✓ Branch 7 taken 26 times.
✓ Branch 9 taken 24 times.
✗ Branch 10 not taken.
✓ Branch 13 taken 23 times.
✓ Branch 14 taken 1 time.
82 res := name == className or
651 (stringLength(name) > len and substring(name, 1, len + 1) == className + ".");
652 end isInClass;
653
654 function lastIdent
655 input String name;
656 output String id = List.last(Util.stringSplitAtChar(name, "."));
657 end lastIdent;
658
659 function uniqueDefinitions
660 input list<Definition> defs;
661 output list<Definition> unique = {};
662 protected
663 UnorderedSet<String> keys = UnorderedSet.new<String>(stringHashDjb2, stringEq);
664 algorithm
665
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 5 times.
21 for d in defs loop
666
1/2
✓ Branch 1 taken 16 times.
✗ Branch 2 not taken.
16 if not UnorderedSet.contains(d.key, keys) then
667 16 UnorderedSet.add(d.key, keys);
668 unique := d :: unique;
669 end if;
670 end for;
671 end uniqueDefinitions;
672
673 function definitionGt
674 input Definition d1;
675 input Definition d2;
676 output Boolean res;
677 protected
678 Integer c = stringCompare(d1.name, d2.name);
679 algorithm
680
5/6
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
79 res := c > 0 or (c == 0 and stringCompare(d1.key, d2.key) > 0);
681 end definitionGt;
682
683 function useInFile
684 input Use u;
685 input String fileName;
686 output Boolean res = u.site.info.fileName == fileName;
687 end useInFile;
688
689 function placedGt
690 input tuple<Use, Position> p1;
691 input tuple<Use, Position> p2;
692 output Boolean res;
693 protected
694 Position a, b;
695 Integer c;
696 algorithm
697 26 (_, a) := p1;
698 26 (_, b) := p2;
699 26 c := stringCompare(a.fileName, b.fileName);
700
7/10
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 12 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 8 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 6 times.
26 res := c > 0 or (c == 0 and (a.lineStart > b.lineStart or
701 (a.lineStart == b.lineStart and a.columnStart > b.columnStart)));
702 end placedGt;
703
704 function addInfo
705 input SourceInfo info;
706 input output JSON obj;
707 algorithm
708 69 obj := JSON.addPair("file", JSON.makeString(info.fileName), obj);
709 69 obj := JSON.addPair("lineStart", JSON.makeInteger(info.lineNumberStart), obj);
710 69 obj := JSON.addPair("columnStart", JSON.makeInteger(info.columnNumberStart), obj);
711 69 obj := JSON.addPair("lineEnd", JSON.makeInteger(info.lineNumberEnd), obj);
712 69 obj := JSON.addPair("columnEnd", JSON.makeInteger(info.columnNumberEnd), obj);
713 end addInfo;
714
715 function useJSON
716 input tuple<Use, Position> placed;
717 output JSON obj = JSON.emptyListObject();
718 protected
719 Use u;
720 Position p;
721 algorithm
722 55 (u, p) := placed;
723 55 obj := JSON.addPair("file", JSON.makeString(p.fileName), obj);
724 55 obj := JSON.addPair("lineStart", JSON.makeInteger(p.lineStart), obj);
725 55 obj := JSON.addPair("columnStart", JSON.makeInteger(p.columnStart), obj);
726 55 obj := JSON.addPair("lineEnd", JSON.makeInteger(p.lineEnd), obj);
727 55 obj := JSON.addPair("columnEnd", JSON.makeInteger(p.columnEnd), obj);
728 55 obj := JSON.addPair("exact", JSON.makeBoolean(p.exact), obj);
729 55 obj := JSON.addPair("text", JSON.makeString(writtenString(u.written)), obj);
730
4/4
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 17 times.
✓ Branch 5 taken 34 times.
55 if not p.exact or AbsynUtil.pathIsQual(AbsynUtil.makeNotFullyQualified(u.written)) then
731 21 obj := JSON.addPair("part", JSON.makeInteger(u.index), obj);
732 end if;
733 55 obj := JSON.addPair("in", JSON.makeString(u.site.scope), obj);
734 55 obj := JSON.addPair("role", JSON.makeString(u.site.role), obj);
735
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 1 time.
55 if u.candidate then
736 1 obj := JSON.addPair("candidate", JSON.makeBoolean(true), obj);
737 end if;
738 end useJSON;
739
740 function writtenString
741 input Absyn.Path path;
742 output String str = (if AbsynUtil.pathIsFullyQualified(path) then "." else "") +
743 AbsynUtil.pathString(AbsynUtil.makeNotFullyQualified(path));
744 end writtenString;
745
746 function placeUses
747 "Finds the positions of the uses, the ones in the smallest spans first. A
748 candidate is at the positions of the uses it was found through."
749 input list<Use> uses;
750 input UnorderedMap<String, Definition> defs;
751 input Files files;
752 output list<tuple<Use, Position>> placed = {};
753 protected
754 list<tuple<Integer, Integer, Use>> order = {};
755 list<Use> candidates = {};
756 Integer i = 0;
757 Use u;
758 Position pos;
759 String sig;
760 UnorderedSet<String> found = UnorderedSet.new<String>(stringHashDjb2, stringEq);
761 UnorderedMap<String, PlacedList> primaries =
762 UnorderedMap.new<PlacedList>(stringHashDjb2, stringEq);
763 Use pu, cu;
764 Position pp;
765 Site site;
766 algorithm
767
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 45 times.
114 for u in uses loop
768 69 i := i + 1;
769
770
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 67 times.
69 if u.candidate then
771 candidates := u :: candidates;
772 else
773 67 order := (spanSize(u.site.info), i, u) :: order;
774 end if;
775 end for;
776
777 // A name can be looked up more than once, e.g. the type of each component
778 // in Res r1, r2. A use that isn't found again is dropped if an identical
779 // one was.
780 45 order := List.sort(order, orderGt);
781
2/2
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 45 times.
112 for o in order loop
782 67 (_, _, u) := o;
783 67 pos := placeUse(u, files);
784 67 sig := useSignature(u);
785
786 // A prefix shared by several names, e.g. A in import A.{b, c}, is a single use.
787
4/4
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 62 times.
67 if pos.exact and UnorderedSet.contains(positionSignature(u.key, pos), found) then
788 1 continue;
789 end if;
790
791
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 4 times.
66 if pos.exact then
792 62 UnorderedSet.add(sig, found);
793 62 UnorderedSet.add(positionSignature(u.key, pos), found);
794 124 UnorderedMap.add(siteSignature(u),
795 (u, pos) :: UnorderedMap.getOrDefault(siteSignature(u), primaries, {}), primaries);
796 62 placed := (u, pos) :: placed;
797 elseif not UnorderedSet.contains(sig, found) then
798 4 placed := (u, pos) :: placed;
799 end if;
800 end for;
801
802 // A candidate that is what the name refers to anyway, e.g. an inherited
803 // element, is left out.
804
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 45 times.
47 for u in listReverse(candidates) loop
805 // The name wasn't found without the redeclares, the first candidate finds its position.
806
2/2
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 1 time.
2 if not UnorderedMap.contains(siteSignature(u), primaries) then
807 1 pos := placeUse(u, files);
808 2 UnorderedMap.add(siteSignature(u), {(u, pos)}, primaries);
809 end if;
810
811
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 for p in UnorderedMap.getOrFail(siteSignature(u), primaries) loop
812 2 (pu, pp) := p;
813 2 sig := positionSignature(u.key, pp);
814
815
5/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 1 time.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
2 if not (pu.key == u.key and not pu.candidate) and not UnorderedSet.contains(sig, found) then
816 1 UnorderedSet.add(sig, found);
817 cu := u;
818 1 site := cu.site;
819 1 site.role := pu.site.role;
820 1 cu.site := site;
821 1 placed := (cu, pp) :: placed;
822 end if;
823 end for;
824 end for;
825 end placeUses;
826
827 function positionSignature
828 input String key;
829 input Position p;
830 output String sig = stringAppendList({key, "|", p.fileName, ":", intString(p.lineStart),
831 ":", intString(p.columnStart), "-", intString(p.lineEnd), ":", intString(p.columnEnd)});
832 end positionSignature;
833
834 function siteSignature
835 "Identifies where a name is used, but not what it refers to."
836 input Use u;
837 output String sig = stringAppendList({writtenString(u.written), "|",
838 intString(u.index), "|", u.site.info.fileName, ":",
839 intString(u.site.info.lineNumberStart), ":", intString(u.site.info.columnNumberStart), "-",
840 intString(u.site.info.lineNumberEnd), ":", intString(u.site.info.columnNumberEnd)});
841 end siteSignature;
842
843 function useSignature
844 input Use u;
845 output String sig = stringAppendList({u.key, "|", writtenString(u.written), "|",
846 intString(u.index), "|", u.site.role, "|", u.site.info.fileName, ":",
847 intString(u.site.info.lineNumberStart), ":", intString(u.site.info.columnNumberStart), "-",
848 intString(u.site.info.lineNumberEnd), ":", intString(u.site.info.columnNumberEnd)});
849 end useSignature;
850
851 function orderGt
852 input tuple<Integer, Integer, Use> o1;
853 input tuple<Integer, Integer, Use> o2;
854 output Boolean res;
855 protected
856 Integer s1, s2, i1, i2;
857 algorithm
858 61 (s1, i1, _) := o1;
859 61 (s2, i2, _) := o2;
860
3/4
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
61 res := s1 > s2 or (s1 == s2 and i1 > i2);
861 end orderGt;
862
863 function spanSize
864 input SourceInfo info;
865 output Integer size = (info.lineNumberEnd - info.lineNumberStart) * 1000000 +
866 info.columnNumberEnd - info.columnNumberStart + 1000;
867 end spanSize;
868
869 function spanPosition
870 input SourceInfo info;
871 output Position pos = POSITION(info.fileName, info.lineNumberStart,
872 info.columnNumberStart, info.lineNumberEnd, info.columnNumberEnd, false);
873 end spanPosition;
874
875 function tokenPosition
876 input String fileName;
877 input Token t;
878 output Position pos = POSITION(fileName, t.line, t.column, t.line, t.endColumn, true);
879 end tokenPosition;
880
881 function placeUse
882 input Use u;
883 input Files files;
884 output Position pos = spanPosition(u.site.info);
885 protected
886 SourceFile file;
887 Integer first, last, i;
888 list<String> parts;
889 algorithm
890 try
891
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 64 times.
68 SOME(file) := getFile(u.site.info.fileName, files);
892 64 (first, last) := tokenRange(file, u.site.info);
893
894 // What a class extends or a redeclare replaces is referred to by the name it declares.
895
4/8
✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✓ Branch 6 taken 56 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 8 times.
64 if u.site.role == "classExtends" or u.site.role == "redeclare" then
896 ✗ i := findName(file, first, last, AbsynUtil.pathFirstIdent(u.written), ignoreTaken = true);
897 else
898 64 parts := AbsynUtil.pathToStringList(u.written);
899 64 i := findWritten(file, first, last, parts, u.index, AbsynUtil.pathIsFullyQualified(u.written));
900 end if;
901
902
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 if i > 0 then
903 64 arrayUpdate(file.taken, i, true);
904 64 pos := tokenPosition(u.site.info.fileName, arrayGet(file.tokens, i));
905 end if;
906 else
907 end try;
908 end placeUse;
909
910 function placeDefinition
911 "Finds the declared name of a definition and takes it."
912 input Definition def;
913 input Files files;
914 output Position pos = spanPosition(def.info);
915 protected
916 SourceFile file;
917 Integer first, last, i;
918 String name = lastIdent(def.name);
919 algorithm
920 try
921
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 408 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 396 times.
408 SOME(file) := getFile(def.info.fileName, files);
922 396 (first, last) := tokenRange(file, def.info);
923
924
3/4
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 344 times.
✓ Branch 3 taken 52 times.
✗ Branch 4 not taken.
396 if def.kind == "iterator" then
925 52 i := findAfter(file, first, last, "for", name);
926 else
927 344 i := findName(file, first, last, name);
928 end if;
929
930
1/2
✓ Branch 0 taken 396 times.
✗ Branch 1 not taken.
396 if i > 0 then
931 396 arrayUpdate(file.taken, i, true);
932 396 pos := tokenPosition(def.info.fileName, arrayGet(file.tokens, i));
933 end if;
934 else
935 end try;
936 end placeDefinition;
937
938 function placeEnd
939 "Finds the name after end at the end of a class."
940 input Definition def;
941 input Files files;
942 output Position pos = spanPosition(def.info);
943 protected
944 SourceFile file;
945 Integer first, last;
946 Token t;
947 algorithm
948 try
949
3/4
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 26 times.
27 SOME(file) := getFile(def.info.fileName, files);
950 26 (first, last) := tokenRange(file, def.info);
951
952
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if last > first then
953 26 t := arrayGet(file.tokens, last);
954
5/10
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 26 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 26 times.
✗ Branch 15 not taken.
26 if t.kind == IDENT and t.text == lastIdent(def.name) and
955 tokenText(file, last - 1) == "end" then
956 26 pos := tokenPosition(def.info.fileName, t);
957 end if;
958 end if;
959 else
960 end try;
961 end placeEnd;
962
963 function findName
964 "Returns the index of the first identifier in the range with the given
965 name that isn't part of a qualified name, or 0."
966 input SourceFile file;
967 input Integer first;
968 input Integer last;
969 input String name;
970 input Boolean ignoreTaken = false;
971 output Integer index = 0;
972 protected
973 Token t;
974 algorithm
975
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 344 times.
878 for i in first:last loop
976 878 t := arrayGet(file.tokens, i);
977
8/12
✓ Branch 0 taken 878 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 419 times.
✓ Branch 3 taken 459 times.
✓ Branch 5 taken 344 times.
✓ Branch 6 taken 75 times.
✓ Branch 8 taken 344 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 344 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 344 times.
✗ Branch 14 not taken.
878 if t.kind == IDENT and t.text == name and not isAfterDot(file, i, first) and
978 (ignoreTaken or not arrayGet(file.taken, i)) then
979 index := i;
980 344 return;
981 end if;
982 end for;
983 end findName;
984
985 function findAfter
986 "Returns the index of the first free identifier with the given name that
987 follows the given keyword, or 0."
988 input SourceFile file;
989 input Integer first;
990 input Integer last;
991 input String keyword;
992 input String name;
993 output Integer index = 0;
994 protected
995 Token t;
996 algorithm
997
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
221 for i in first + 1:last loop
998 221 t := arrayGet(file.tokens, i);
999
10/12
✓ Branch 0 taken 169 times.
✓ Branch 1 taken 52 times.
✓ Branch 2 taken 130 times.
✓ Branch 3 taken 39 times.
✓ Branch 5 taken 78 times.
✓ Branch 6 taken 52 times.
✓ Branch 8 taken 78 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 52 times.
✓ Branch 12 taken 26 times.
✓ Branch 15 taken 52 times.
✗ Branch 16 not taken.
221 if t.kind == IDENT and t.text == name and not arrayGet(file.taken, i) and
1000 tokenText(file, i - 1) == keyword then
1001 index := i;
1002 52 return;
1003 end if;
1004 end for;
1005 end findAfter;
1006
1007 function tokenKind
1008 input SourceFile file;
1009 input Integer i;
1010 output Integer kind;
1011 protected
1012 Token t = arrayGet(file.tokens, i);
1013 algorithm
1014 511 kind := t.kind;
1015 end tokenKind;
1016
1017 function tokenText
1018 input SourceFile file;
1019 input Integer i;
1020 output String text;
1021 protected
1022 Token t = arrayGet(file.tokens, i);
1023 algorithm
1024 264 text := t.text;
1025 end tokenText;
1026
1027 function isAfterDot
1028 input SourceFile file;
1029 input Integer i;
1030 input Integer first;
1031 output Boolean res = i > first and tokenKind(file, i - 1) == DOT;
1032 end isAfterDot;
1033
1034 function findWritten
1035 "Returns the index of the identifier that is the given part of the first
1036 free occurrence of a name in the range, or 0. Subscripts between the parts
1037 are skipped."
1038 input SourceFile file;
1039 input Integer first;
1040 input Integer last;
1041 input list<String> parts;
1042 input Integer part;
1043 input Boolean fullyQualified;
1044 output Integer index = 0;
1045 protected
1046 Token t;
1047 Integer j, k, depth, n, prefix;
1048 Boolean matched;
1049 algorithm
1050
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
243 for i in first:last loop
1051 243 t := arrayGet(file.tokens, i);
1052
1053
7/8
✓ Branch 0 taken 211 times.
✓ Branch 1 taken 32 times.
✓ Branch 3 taken 132 times.
✓ Branch 4 taken 79 times.
✓ Branch 7 taken 75 times.
✓ Branch 8 taken 57 times.
✓ Branch 10 taken 75 times.
✗ Branch 11 not taken.
243 if t.kind == IDENT and t.text == listHead(parts) and
1054 isAfterDot(file, i, first) == fullyQualified then
1055 j := i;
1056 k := i;
1057 matched := true;
1058 n := 1;
1059 // The parts before the braces of a group import.
1060 prefix := 0;
1061
1062
2/2
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 69 times.
109 for p in listRest(parts) loop
1063 // Skip the subscripts, e.g. a[i].b.
1064
2/4
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 40 times.
40 if j < last and tokenKind(file, j + 1) == LBRACKET then
1065 depth := 0;
1066 j := j + 1;
1067 ✗ while j <= last loop
1068 ✗ t := arrayGet(file.tokens, j);
1069 ✗ depth := if t.kind == LBRACKET then depth + 1 elseif t.kind == RBRACKET then depth - 1 else depth;
1070 ✗ if depth == 0 then
1071 break;
1072 end if;
1073 ✗ j := j + 1;
1074 end while;
1075 end if;
1076
1077
7/8
✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 38 times.
✓ Branch 4 taken 2 times.
✓ Branch 6 taken 34 times.
✓ Branch 7 taken 4 times.
✓ Branch 10 taken 4 times.
✓ Branch 11 taken 30 times.
40 if j + 2 <= last and tokenKind(file, j + 1) == DOT and
1078 tokenText(file, j + 2) == p then
1079 j := j + 2;
1080 elseif j + 2 <= last and tokenKind(file, j + 1) == DOT and
1081 tokenKind(file, j + 2) == LBRACE then
1082 // A group import, A.{b, c}.
1083 prefix := n;
1084 4 k := j + 3;
1085 matched := false;
1086
2/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 6 times.
✗ Branch 4 not taken.
6 while k <= last and tokenKind(file, k) <> RBRACE loop
1087
3/4
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
6 if tokenText(file, k) == p then
1088 j := k;
1089 matched := true;
1090 break;
1091 end if;
1092 2 k := k + 1;
1093 end while;
1094
1095 if not matched then
1096 break;
1097 end if;
1098 else
1099 matched := false;
1100 break;
1101 end if;
1102
1103 34 n := n + 1;
1104 end for;
1105
1106
2/2
✓ Branch 0 taken 69 times.
✓ Branch 1 taken 6 times.
75 if matched then
1107 // The last part is where the name ends, the names before it in a group import aren't parts of it.
1108
2/2
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 54 times.
69 k := if part == listLength(parts) then j else partIndex(file, i, j, part);
1109 // The prefix of a group import is part of each of its names.
1110
5/6
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 67 times.
✓ Branch 3 taken 2 times.
✓ Branch 5 taken 62 times.
✓ Branch 6 taken 5 times.
69 if k > 0 and (part <= prefix or not arrayGet(file.taken, k)) then
1111 index := k;
1112 64 return;
1113 end if;
1114 end if;
1115 end if;
1116 end for;
1117 end findWritten;
1118
1119 function partIndex
1120 "Returns the index of the n:th identifier from i to j outside subscripts."
1121 input SourceFile file;
1122 input Integer i;
1123 input Integer j;
1124 input Integer part;
1125 output Integer index = 0;
1126 protected
1127 Integer n = 0, depth = 0;
1128 Token t;
1129 algorithm
1130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
19 for k in i:j loop
1131 19 t := arrayGet(file.tokens, k);
1132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if t.kind == LBRACKET then
1133 ✗ depth := depth + 1;
1134 elseif t.kind == RBRACKET then
1135 ✗ depth := depth - 1;
1136 elseif t.kind == IDENT and depth == 0 then
1137 17 n := n + 1;
1138
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 2 times.
17 if n == part then
1139 index := k;
1140 15 return;
1141 end if;
1142 end if;
1143 end for;
1144 end partIndex;
1145
1146 function tokenRange
1147 "Returns the indices of the first and last token in a span, last < first if none."
1148 input SourceFile file;
1149 input SourceInfo info;
1150 output Integer first;
1151 output Integer last;
1152 protected
1153 Integer lo = 1, hi = arrayLength(file.tokens), mid;
1154 Token t;
1155 algorithm
1156 // The first token that doesn't begin before the span.
1157
2/2
✓ Branch 0 taken 3614 times.
✓ Branch 1 taken 486 times.
4100 while lo <= hi loop
1158 3614 mid := intDiv(lo + hi, 2);
1159 3614 t := arrayGet(file.tokens, mid);
1160
5/6
✓ Branch 0 taken 1854 times.
✓ Branch 1 taken 1760 times.
✓ Branch 2 taken 771 times.
✓ Branch 3 taken 1083 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 771 times.
3614 if t.line < info.lineNumberStart or
1161 (t.line == info.lineNumberStart and t.column < info.columnNumberStart) then
1162 1760 lo := mid + 1;
1163 else
1164 1854 hi := mid - 1;
1165 end if;
1166 end while;
1167
1168 first := lo;
1169 486 last := first - 1;
1170
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 11740 times.
11774 while last < arrayLength(file.tokens) loop
1171 11740 t := arrayGet(file.tokens, last + 1);
1172
5/6
✓ Branch 0 taken 452 times.
✓ Branch 1 taken 11288 times.
✓ Branch 2 taken 9129 times.
✓ Branch 3 taken 2159 times.
✓ Branch 4 taken 2159 times.
✗ Branch 5 not taken.
11740 if t.line > info.lineNumberEnd or
1173 (t.line == info.lineNumberEnd and t.endColumn > info.columnNumberEnd) then
1174 break;
1175 end if;
1176 last := last + 1;
1177 end while;
1178 end tokenRange;
1179
1180 function getFile
1181 input String fileName;
1182 input Files files;
1183 output Option<SourceFile> file;
1184 algorithm
1185
2/2
✓ Branch 1 taken 476 times.
✓ Branch 2 taken 27 times.
503 if UnorderedMap.contains(fileName, files) then
1186 476 file := UnorderedMap.getOrFail(fileName, files);
1187 else
1188 27 file := readSourceFile(fileName);
1189 27 UnorderedMap.add(fileName, file, files);
1190 end if;
1191 end getFile;
1192
1193 function readSourceFile
1194 input String fileName;
1195 output Option<SourceFile> file = NONE();
1196 protected
1197 array<Token> tokens;
1198 algorithm
1199 try
1200
2/2
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 2 times.
27 if System.regularFileExists(fileName) then
1201 25 tokens := listArray(tokenize(System.readFile(fileName)));
1202 25 file := SOME(SOURCE_FILE(tokens, arrayCreate(arrayLength(tokens), false)));
1203 end if;
1204 else
1205 end try;
1206 end readSourceFile;
1207
1208 function tokenize
1209 "Splits Modelica source into identifiers, dots and brackets, skipping
1210 comments, strings and everything else. Columns count bytes, like the
1211 parser does."
1212 input String source;
1213 output list<Token> tokens = {};
1214 protected
1215 Integer len = stringLength(source), i = 1, line = 1, col = 1, c, start, start_col;
1216 Boolean in_group = false;
1217 Token t;
1218 algorithm
1219
2/2
✓ Branch 0 taken 17117 times.
✓ Branch 1 taken 25 times.
17142 while i <= len loop
1220 17117 c := stringGet(source, i);
1221
1222
2/2
✓ Branch 0 taken 1523 times.
✓ Branch 1 taken 15594 times.
17117 if c == 10 then // \n
1223 1523 line := line + 1;
1224 col := 1;
1225 1523 i := i + 1;
1226 elseif c == 47 and i < len and stringGet(source, i + 1) == 47 then // //
1227 ✗ while i <= len and stringGet(source, i) <> 10 loop
1228 ✗ i := i + 1;
1229 ✗ col := col + 1;
1230 end while;
1231 elseif c == 47 and i < len and stringGet(source, i + 1) == 42 then // /*
1232 ✗ i := i + 2;
1233 ✗ col := col + 2;
1234 ✗ while i <= len and not (stringGet(source, i) == 42 and i < len and stringGet(source, i + 1) == 47) loop
1235 ✗ if stringGet(source, i) == 10 then
1236 ✗ line := line + 1;
1237 col := 1;
1238 else
1239 ✗ col := col + 1;
1240 end if;
1241 ✗ i := i + 1;
1242 end while;
1243 ✗ i := i + 2;
1244 ✗ col := col + 2;
1245 elseif c == 34 or c == 39 then // " or '
1246 start := i;
1247 start_col := col;
1248 58 i := i + 1;
1249 58 col := col + 1;
1250
3/4
✓ Branch 0 taken 410 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 352 times.
✓ Branch 4 taken 58 times.
410 while i <= len and stringGet(source, i) <> c loop
1251
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 352 times.
352 if stringGet(source, i) == 92 then // \
1252 ✗ i := i + 1;
1253 ✗ col := col + 1;
1254 end if;
1255
2/4
✓ Branch 0 taken 352 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 352 times.
352 if i <= len and stringGet(source, i) == 10 then
1256 ✗ line := line + 1;
1257 ✗ col := 1;
1258 else
1259 352 col := col + 1;
1260 end if;
1261 352 i := i + 1;
1262 end while;
1263
1264 // A quoted identifier is kept with its quotes, like the parser does.
1265
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 58 times.
58 if c == 39 and i <= len then
1266 ✗ tokens := TOKEN(IDENT, substring(source, start, i), line, start_col, col) :: tokens;
1267 end if;
1268 58 i := i + 1;
1269 58 col := col + 1;
1270 elseif isIdentStart(c) then
1271 start := i;
1272 start_col := col;
1273
3/4
✓ Branch 0 taken 20297 times.
✗ Branch 1 not taken.
✓ Branch 4 taken 16040 times.
✓ Branch 5 taken 4257 times.
20297 while i <= len and isIdentChar(stringGet(source, i)) loop
1274 16040 i := i + 1;
1275 16040 col := col + 1;
1276 end while;
1277 4257 tokens := TOKEN(IDENT, substring(source, start, i - 1), line, start_col, col - 1) :: tokens;
1278 elseif isDigit(c) then
1279 // A number, including its fraction and exponent.
1280
3/4
✓ Branch 0 taken 646 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 323 times.
✓ Branch 4 taken 323 times.
1292 while i <= len and isDigit(stringGet(source, i)) loop
1281 323 i := i + 1;
1282 323 col := col + 1;
1283 end while;
1284
2/4
✓ Branch 0 taken 323 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 323 times.
323 if i <= len and stringGet(source, i) == 46 then
1285 ✗ i := i + 1;
1286 ✗ col := col + 1;
1287 ✗ while i <= len and isDigit(stringGet(source, i)) loop
1288 ✗ i := i + 1;
1289 ✗ col := col + 1;
1290 end while;
1291 end if;
1292
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 323 times.
✓ Branch 3 taken 323 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 323 times.
✗ Branch 7 not taken.
323 if i <= len and (stringGet(source, i) == 101 or stringGet(source, i) == 69) then
1293 ✗ i := i + 1;
1294 ✗ col := col + 1;
1295 ✗ if i <= len and (stringGet(source, i) == 43 or stringGet(source, i) == 45) then
1296 ✗ i := i + 1;
1297 ✗ col := col + 1;
1298 end if;
1299 ✗ while i <= len and isDigit(stringGet(source, i)) loop
1300 ✗ i := i + 1;
1301 ✗ col := col + 1;
1302 end while;
1303 end if;
1304 else
1305 // An element-wise operator, e.g. .* or ./, isn't a dot in a name.
1306
5/6
✓ Branch 0 taken 225 times.
✓ Branch 1 taken 10731 times.
✓ Branch 2 taken 225 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 214 times.
✓ Branch 7 taken 11 times.
10956 if c == 46 and not (i < len and listMember(stringGet(source, i + 1), {42, 47, 43, 45, 94})) then
1307 428 tokens := TOKEN(DOT, ".", line, col, col) :: tokens;
1308 elseif c == 91 then
1309 67 tokens := TOKEN(LBRACKET, "[", line, col, col) :: tokens;
1310 elseif c == 93 then
1311 67 tokens := TOKEN(RBRACKET, "]", line, col, col) :: tokens;
1312 elseif c == 123 and not listEmpty(tokens) then
1313 47 t := listHead(tokens);
1314
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 36 times.
47 if t.kind == DOT then
1315 11 tokens := TOKEN(LBRACE, "{", line, col, col) :: tokens;
1316 in_group := true;
1317 end if;
1318 elseif c == 125 and in_group then
1319 11 tokens := TOKEN(RBRACE, "}", line, col, col) :: tokens;
1320 in_group := false;
1321 end if;
1322 10956 i := i + 1;
1323 10956 col := col + 1;
1324 end if;
1325 end while;
1326
1327 25 tokens := listReverse(tokens);
1328 end tokenize;
1329
1330 function isIdentStart
1331 input Integer c;
1332 output Boolean res = (c >= 65 and c <= 90) or (c >= 97 and c <= 122) or c == 95;
1333 end isIdentStart;
1334
1335 function isIdentChar
1336 input Integer c;
1337 output Boolean res = isIdentStart(c) or isDigit(c);
1338 end isIdentChar;
1339
1340 function isDigit
1341 input Integer c;
1342 output Boolean res = c >= 48 and c <= 57;
1343 end isDigit;
1344
1345 annotation(__OpenModelica_Interface="nf_frontend");
1346 end NFDefUseChains;
1347