GCC Code Coverage Report


src/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
110 / 111
99.1%
Branches:
34 / 42, 20 excl
81.0%
Functions:
12 / 13
92.3%

reader/code-loc.spice
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 // Imports
4 import "std/type/type-conversion";
5 import "std/data/vector";
6 import "std/io/filepath";
7 import "std/text/string-ext";
8 import "std/os/os";
9
10 // Own imports
11 import "@/util/test-util";
12
13 // Path of the root source file of the compilation. A CodeLoc only carries the path of its source file, so the path of the root
14 // source file is registered here by the GlobalResourceManager, when it creates the root source file.
15 string ROOT_SOURCE_FILE_PATH = ""; // Empty as long as no root source file is registered
16
17 /**
18 * Register the path of the root source file, which toPrettyFilePath() makes the paths relative to
19 *
20 * @param rootSourceFilePath Path of the root source file. Has to stay valid for the whole compilation
21 */
22 1054 public p setRootSourceFilePath(string rootSourceFilePath) {
23 1054 ROOT_SOURCE_FILE_PATH = rootSourceFilePath;
24 1054 }
25
26 public type CodeLoc struct {
27 public unsigned long line
28 public unsigned long col
29 public string sourceFilePath
30 // Interval of the located source code as offsets of its first and last char (both inclusive). The default is an empty
31 // interval.
32 3975 public long sourceIntervalStart = -1l
33 3975 public long sourceIntervalStop = -2l
34 }
35
36 3975 public p CodeLoc.ctor(unsigned long line, unsigned long col, string sourceFilePath = "") {
37 3975 this.line = line;
38 3975 this.col = col;
39 3975 this.sourceFilePath = sourceFilePath;
40 3975 }
41
42 /**
43 * Returns the code location as a string for using it as a map key or similar
44 *
45 * @return Code location string
46 */
47 3277350 public f<String> CodeLoc.toString() {
48 6554700 return "L" + toString(this.line) + "C" + toString(this.col);
49 3277350 }
50
51 /**
52 * Returns the code location in a pretty form
53 *
54 * @return Pretty code location
55 */
56 8847 public f<String> CodeLoc.toPrettyString() {
57 8847 const String filePath = this.toPrettyFilePath();
58
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8847 times.
8847 const String prefix = filePath.isEmpty() ? String() : filePath + ":";
59 17694 return prefix + toString(this.line) + ":" + toString(this.col);
60 8847 }
61
62 /**
63 * Returns the path of the source file this code location points into, relative to the root source file
64 *
65 * @return Pretty file path
66 */
67 8858 public f<String> CodeLoc.toPrettyFilePath() {
68 // Without a registered root source file (e.g. in unit tests), the path is returned as is
69
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 8858 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 8858 times.
8858 if len(ROOT_SOURCE_FILE_PATH) == 0 || len(this.sourceFilePath) == 0 {
70 ✗ return String(this.sourceFilePath);
71 }
72 8858 String relativePath = getRelativePath(String(this.sourceFilePath), String(ROOT_SOURCE_FILE_PATH));
73
2/2
✓ Branch 0 taken 8176 times.
✓ Branch 1 taken 682 times.
8858 if relativePath == "." {
74 682 const FilePath path = FilePath(this.sourceFilePath);
75 682 relativePath = "./" + path.getFileName();
76 682 }
77 17716 return relativePath;
78 8858 }
79
80 /**
81 * Make the given path relative to the given base path, like std::filesystem::relative(path, base) in C++. Both paths are
82 * resolved to their canonical form first. If this is not possible, the path is returned as is.
83 *
84 * @param path Path to make relative
85 * @param base Base path
86 * @return Relative path in generic format
87 */
88 18136 public f<String> getRelativePath(const String& path, const String& base) {
89 18136 const FilePath originalPath = FilePath(path);
90 18136 const FilePath originalBase = FilePath(base);
91 18136 FilePath canonicalPath = originalPath.canonical();
92 18136 FilePath canonicalBase = originalBase.canonical();
93
4/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 18135 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 18135 times.
18136 if canonicalPath.isEmpty() || canonicalBase.isEmpty() {
94 2 return path;
95 }
96 18135 const Vector<String> pathFragments = split(String(canonicalPath.toGenericString()), '/');
97 18135 const Vector<String> baseFragments = split(String(canonicalBase.toGenericString()), '/');
98
99 // Skip the common prefix
100 18135 unsigned long commonCount = 0l;
101
4/4
✓ Branch 0 taken 1444 times.
✓ Branch 1 taken 135052 times.
✓ Branch 2 taken 1444 times.
✓ Branch 3 taken 135052 times.
136496 while commonCount < pathFragments.getSize() && commonCount < baseFragments.getSize() {
102
2/2
✓ Branch 0 taken 16691 times.
✓ Branch 1 taken 118361 times.
135052 if pathFragments.get(commonCount) != baseFragments.get(commonCount) {
103 16691 break;
104 }
105 118361 commonCount++;
106 118361 }
107
108 // Go up for each remaining fragment of the base and then down along the remaining fragments of the path
109 18135 String relativePath;
110
2/2
✓ Branch 0 taken 98582 times.
✓ Branch 1 taken 18135 times.
116717 for unsigned long i = commonCount; i < baseFragments.getSize(); i++ {
111
2/2
✓ Branch 0 taken 16691 times.
✓ Branch 1 taken 81891 times.
98582 if !relativePath.isEmpty() {
112 81891 relativePath += '/';
113 81891 }
114 98582 relativePath += "..";
115 98582 }
116
2/2
✓ Branch 0 taken 49989 times.
✓ Branch 1 taken 18135 times.
68124 for unsigned long i = commonCount; i < pathFragments.getSize(); i++ {
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49989 times.
49989 if !relativePath.isEmpty() {
118 49989 relativePath += '/';
119 49989 }
120 49989 relativePath += pathFragments.get(i);
121 49989 }
122
2/2
✓ Branch 0 taken 1444 times.
✓ Branch 1 taken 16691 times.
18135 return relativePath.isEmpty() ? String(".") : relativePath;
123 18136 }
124
125 /**
126 * Returns the line number in a pretty form
127 *
128 * @return Pretty line number
129 */
130 38615 public f<String> CodeLoc.toPrettyLine() {
131 77230 return "L" + toString(this.line);
132 38615 }
133
134 /**
135 * Returns the line and column number in a pretty form
136 */
137 7294 public f<String> CodeLoc.toPrettyLineAndColumn() {
138 7294 return this.toString();
139 }
140
141 /**
142 * Check if this code location is located before the given one. Spice does not support overloading '<', so this is a method.
143 *
144 * @param other Other code location
145 * @return Before or not
146 */
147 1628 public f<bool> CodeLoc.isBefore(const CodeLoc& other) {
148
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1604 times.
1628 return this.line == other.line ? this.col < other.col : this.line < other.line;
149 }
150
151 /**
152 * Check if this code location is located after the given one.
153 *
154 * @param other Other code location
155 * @return After or not
156 */
157 102652 public f<bool> CodeLoc.isAfter(const CodeLoc& other) {
158
2/2
✓ Branch 0 taken 597 times.
✓ Branch 1 taken 102055 times.
102652 return this.line == other.line ? this.col > other.col : this.line > other.line;
159 }
160
161 73533 public f<bool> operator==(const CodeLoc &lhs, const CodeLoc &rhs) {
162
3/4
✓ Branch 0 taken 73533 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 72873 times.
✓ Branch 3 taken 660 times.
73533 return lhs.sourceFilePath == rhs.sourceFilePath && lhs.line == rhs.line && lhs.col == rhs.col;
163 }
164
165 // ─── tests ────────────────────────────────────────────────────────────────────
166
167 1 #[test]
168 f<bool> testGetRelativePath() {
169 1 const FilePath testDir = makeUniqueTestDir("spice-code-loc-test");
170 1 const FilePath realDir = testDir / "real" / "nested";
171 1 createTestDirs(realDir);
172 1 const FilePath realFile = realDir / "file.txt";
173 1 const FilePath otherFile = testDir / "other.txt";
174 1 writeTestFile(realFile, "content");
175 1 writeTestFile(otherFile, "content");
176
177 // Like std::filesystem::relative, the base is treated as a dir, even if it is a file
178 1 const String upwards = getRelativePath(String(otherFile.toString()), String(realFile.toString()));
179 1 const String downwards = getRelativePath(String(realFile.toString()), String(otherFile.toString()));
180 1 const String same = getRelativePath(String(otherFile.toString()), String(otherFile.toString()));
181
182 // A path, that can't be resolved, is returned as is
183 1 const FilePath missingFile = testDir / "non-existing.txt";
184 1 const FilePath canonicalMissingFile = missingFile.canonical();
185 1 const String missing = getRelativePath(String(missingFile.toString()), String(otherFile.toString()));
186
187 // Paths are resolved through symlinks. Creating symlinks requires special privileges on Windows, so this part is skipped
188 // there. Through the symlink, the file lives three levels below the test dir, not two
189 1 const FilePath linkDir = testDir / "link-parent" / "link";
190 1 createTestDirs(testDir / "link-parent");
191 1 const bool hasSymlink = createTestDirSymlink(realDir, linkDir);
192 1 String upwardsFromLink = String("../../../other.txt");
193 1 String downwardsToLink = String("../real/nested/file.txt");
194 1 bool linkResolvesToRealFile = true;
195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if hasSymlink {
196 1 const FilePath linkFile = linkDir / "file.txt";
197 1 const FilePath linkFileWithDot = linkDir / "." / "file.txt";
198 1 const FilePath canonicalRealFile = realFile.canonical();
199 1 const FilePath canonicalLinkFile = linkFile.canonical();
200 1 const FilePath canonicalLinkFileWithDot = linkFileWithDot.canonical();
201
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
1 linkResolvesToRealFile = canonicalRealFile.isAbsolute() && canonicalRealFile == canonicalLinkFile && canonicalRealFile == canonicalLinkFileWithDot;
202 1 upwardsFromLink = getRelativePath(String(otherFile.toString()), String(linkFile.toString()));
203 1 downwardsToLink = getRelativePath(String(linkFile.toString()), String(otherFile.toString()));
204 1 }
205
206 // Clean up before asserting, so that a failure does not leave the files behind
207 1 removeTestDir(testDir);
208 1 assert upwards == "../../../other.txt";
209 1 assert downwards == "../real/nested/file.txt";
210 1 assert same == ".";
211 1 assert canonicalMissingFile.isEmpty();
212 1 assert missing == missingFile.toString();
213 1 assert hasSymlink || isWindows();
214 1 assert linkResolvesToRealFile;
215 1 assert upwardsFromLink == "../../../other.txt";
216 1 assert downwardsToLink == "../real/nested/file.txt";
217 1 return true;
218 1 }
219