GCC Code Coverage Report


Directory: src/
File: src/frontend/parse-literal-int.cpp
Date: 2025-10-24 11:14:59
Exec Total Coverage
Lines: 119 192 62.0%
Functions: 1 1 100.0%
Branches: 70 99 70.7%

Line Branch Exec Source
1 /* Copyright 2025 Jonathan S. Arney
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * https://github.com/jarney/gyoji/blob/master/LICENSE
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include <gyoji-frontend.hpp>
16 #include <gyoji-misc/jstring.hpp>
17
18 using namespace Gyoji::frontend::integers;
19 using namespace Gyoji::mir;
20
21 static std::string u8_type("u8");
22 static std::string u16_type("u16");
23 static std::string u32_type("u32");
24 static std::string u64_type("u64");
25
26 static std::string i8_type("i8");
27 static std::string i16_type("i16");
28 static std::string i32_type("i32");
29 static std::string i64_type("i64");
30
31 242 bool Gyoji::frontend::integers::parse_literal_int(
32 const Gyoji::context::CompilerContext & compiler_context,
33 const Gyoji::mir::Types & types,
34 const Gyoji::frontend::tree::Terminal & literal_int_token,
35 ParseLiteralIntResult & result
36 )
37 {
38 242 size_t radix = 10;
39 // Extremely ineficcient, but we need to resolve
40 // the specific type of integer here
41 // so that the MIR layer knows what we mean.
42 // Actually, the whole of this logic
43 // should probably move down one notch to the
44 // construction OP_LITERAL in the FunctionLowering
45 242 result.parsed_type = nullptr;
46
47 242 std::string integer_part;
48 242 Type* type_part = nullptr;
49
50 242 bool sign_positive = true;
51
52 242 const std::string & token_value = literal_int_token.get_value();
53 242 size_t len = token_value.size();
54
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 230 times.
242 if (Gyoji::misc::endswith(token_value, u8_type)) {
55 12 integer_part = token_value.substr(0, len - u8_type.size());
56 24 type_part = types.get_type("u8");
57 }
58
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 216 times.
230 else if (Gyoji::misc::endswith(token_value, u16_type)) {
59 14 integer_part = token_value.substr(0, len - u16_type.size());
60 28 type_part = types.get_type("u16");
61 }
62
2/2
✓ Branch 1 taken 38 times.
✓ Branch 2 taken 178 times.
216 else if (Gyoji::misc::endswith(token_value, u32_type)) {
63 38 integer_part = token_value.substr(0, len - u32_type.size());
64 76 type_part = types.get_type("u32");
65 }
66
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 160 times.
178 else if (Gyoji::misc::endswith(token_value, u64_type)) {
67 18 integer_part = token_value.substr(0, len - u64_type.size());
68 36 type_part = types.get_type("u64");
69 }
70
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 150 times.
160 else if (Gyoji::misc::endswith(token_value, i8_type)) {
71 10 integer_part = token_value.substr(0, len - i8_type.size());
72 20 type_part = types.get_type("i8");
73 }
74
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 144 times.
150 else if (Gyoji::misc::endswith(token_value, i16_type)) {
75 6 integer_part = token_value.substr(0, len - i16_type.size());
76 12 type_part = types.get_type("i16");
77 }
78
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 138 times.
144 else if (Gyoji::misc::endswith(token_value, i32_type)) {
79 6 integer_part = token_value.substr(0, len - i32_type.size());
80 12 type_part = types.get_type("i32");
81 }
82
2/2
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 128 times.
138 else if (Gyoji::misc::endswith(token_value, i64_type)) {
83 10 integer_part = token_value.substr(0, len - i64_type.size());
84 20 type_part = types.get_type("i64");
85 }
86 else {
87 // If not specified, we assume u32 or i32 depending
88 // on whether there's a '-' at the start.
89 128 integer_part = token_value;
90
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 126 times.
128 if (integer_part[0] == '-') {
91 2 type_part = types.get_type("i32");
92 2 sign_positive = false;
93 }
94 else {
95 126 type_part = types.get_type("u32");
96 126 sign_positive = true;
97 }
98 }
99
100 // If the remaining part of the number
101 // starts with a '-' then we flip the sign bit
102 // to make it negative and remove the '-' so
103 // we can parse the radix part.
104
2/2
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 230 times.
484 if (Gyoji::misc::startswith(integer_part, "-")) {
105 12 integer_part = integer_part.substr(1);
106 12 sign_positive = false;
107 }
108 else {
109 230 sign_positive = true;
110 }
111
112 // Next, we need to check that
113 // if it's a negative number, it is
114 // consistent with the signedness of the type.
115
4/6
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 230 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 242 times.
242 if (!sign_positive && type_part->is_unsigned()) {
116 compiler_context
117 .get_errors()
118 .add_simple_error(
119 literal_int_token.get_source_ref(),
120 "Integer literal type mismatch",
121 std::string("Type of integer literal was unsigned, but a negative value was given")
122 );
123 return false;
124 }
125
126
127 // Remove the superfluous '_' which are only
128 // supported for readability purposes and play
129 // no semantic role.
130 // Hex is supported
131 484 integer_part = Gyoji::misc::string_remove(integer_part, "_");
132
2/2
✓ Branch 3 taken 28 times.
✓ Branch 4 taken 214 times.
484 if (Gyoji::misc::startswith(integer_part, "0x")) {
133 28 integer_part = Gyoji::misc::string_remove(integer_part, "0x");
134 28 radix = 16;
135 }
136 // Binary is supported
137
2/2
✓ Branch 3 taken 18 times.
✓ Branch 4 taken 196 times.
428 else if (Gyoji::misc::startswith(integer_part, "0b")) {
138 18 integer_part = Gyoji::misc::string_remove(integer_part, "0b");
139 18 radix = 2;
140 }
141 // Octal is supported.
142
2/2
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 192 times.
392 else if (Gyoji::misc::startswith(integer_part, "0o")) {
143 4 integer_part = Gyoji::misc::string_remove(integer_part, "0o");
144 4 radix = 8;
145 }
146 // If radix is not specified, assume decimal.
147
148 242 const char *source_cstring = integer_part.c_str();
149 242 size_t length = integer_part.size();
150
151 242 char *endptr = nullptr;
152 242 errno = 0;
153
154
155
8/9
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 14 times.
✓ Branch 3 taken 164 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 10 times.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 8 times.
✓ Branch 8 taken 10 times.
✗ Branch 9 not taken.
242 switch (type_part->get_type()) {
156 12 case Type::TYPE_PRIMITIVE_u8:
157 {
158 12 unsigned long number = strtoul(source_cstring, &endptr, radix);
159
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (endptr != (source_cstring + length)) {
160 compiler_context
161 .get_errors()
162 .add_simple_error(
163 literal_int_token.get_source_ref(),
164 "Invalid integer literal",
165 std::string("Literal value contained extraneous characters: ") + integer_part
166 );
167 return false;
168 }
169
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (errno == ERANGE || number >= 256) {
170 compiler_context
171 .get_errors()
172 .add_simple_error(
173 literal_int_token.get_source_ref(),
174 "Invalid integer literal",
175 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in a u8")
176 );
177 return false;
178 }
179 12 result.parsed_type = type_part;
180 12 result.u8_value = (unsigned char)number;
181 12 return true;
182 }
183 14 case Type::TYPE_PRIMITIVE_u16:
184 {
185 14 unsigned long number = strtoul(source_cstring, &endptr, radix);
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14 times.
14 if (endptr != (source_cstring + length)) {
187 compiler_context
188 .get_errors()
189 .add_simple_error(
190 literal_int_token.get_source_ref(),
191 "Invalid integer literal",
192 std::string("Literal value contained extraneous characters: ") + integer_part
193 );
194 return false;
195 }
196
2/4
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 14 times.
14 if (errno == ERANGE || number > 0xffff) {
197 compiler_context
198 .get_errors()
199 .add_simple_error(
200 literal_int_token.get_source_ref(),
201 "Invalid integer literal",
202 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in a u16")
203 );
204 return false;
205 }
206 14 result.parsed_type = type_part;
207 14 result.u16_value = (unsigned short)number;
208 14 return true;
209 }
210 break;
211 164 case Type::TYPE_PRIMITIVE_u32:
212 {
213 164 unsigned long number = strtoull(source_cstring, &endptr, radix);
214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 164 times.
164 if (endptr != (source_cstring + length)) {
215 compiler_context
216 .get_errors()
217 .add_simple_error(
218 literal_int_token.get_source_ref(),
219 "Invalid integer literal",
220 std::string("Literal value contained extraneous characters: ") + integer_part
221 );
222 return false;
223 }
224
2/4
✓ Branch 0 taken 164 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 164 times.
164 if (errno == ERANGE || number > 0xffffffff) {
225 compiler_context
226 .get_errors()
227 .add_simple_error(
228 literal_int_token.get_source_ref(),
229 "Invalid integer literal",
230 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in a u32")
231 );
232 return false;
233 }
234 164 result.parsed_type = type_part;
235 164 result.u32_value = (unsigned int)number;
236 164 return true;
237 }
238 break;
239 18 case Type::TYPE_PRIMITIVE_u64:
240 {
241 18 unsigned long number = strtoull(source_cstring, &endptr, radix);
242
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (endptr != (source_cstring + length)) {
243 compiler_context
244 .get_errors()
245 .add_simple_error(
246 literal_int_token.get_source_ref(),
247 "Invalid integer literal",
248 std::string("Literal value contained extraneous characters: ") + integer_part
249 );
250 return false;
251 }
252
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (errno == ERANGE) {
253 compiler_context
254 .get_errors()
255 .add_simple_error(
256 literal_int_token.get_source_ref(),
257 "Invalid integer literal",
258 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in a u64")
259 );
260 return false;
261 }
262 18 result.parsed_type = type_part;
263 18 result.u64_value = (unsigned long)number;
264 18 return true;
265 }
266 break;
267
268 // Signed
269 10 case Type::TYPE_PRIMITIVE_i8:
270 {
271 10 long number = strtol(source_cstring, &endptr, radix);
272
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (endptr != (source_cstring + length)) {
273 compiler_context
274 .get_errors()
275 .add_simple_error(
276 literal_int_token.get_source_ref(),
277 "Invalid integer literal",
278 std::string("Literal value contained extraneous characters: ") + integer_part
279 );
280 return false;
281 }
282
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 number = sign_positive ? number : -number;
283
3/6
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 10 times.
10 if (errno == ERANGE || number < -128 || number > 127) {
284 compiler_context
285 .get_errors()
286 .add_simple_error(
287 literal_int_token.get_source_ref(),
288 "Invalid integer literal",
289 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in a i8")
290 );
291 return false;
292 }
293 10 result.parsed_type = type_part;
294 10 result.i8_value = (char)number;
295 10 return true;
296 }
297 6 case Type::TYPE_PRIMITIVE_i16:
298 {
299 6 long number = strtol(source_cstring, &endptr, radix);
300
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (endptr != (source_cstring + length)) {
301 compiler_context
302 .get_errors()
303 .add_simple_error(
304 literal_int_token.get_source_ref(),
305 "Invalid integer literal",
306 std::string("Literal value contained extraneous characters: ") + integer_part
307 );
308 return false;
309 }
310
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 number = sign_positive ? number : -number;
311
3/6
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
6 if (errno == ERANGE || number < -32768 || number > 32767) {
312 compiler_context
313 .get_errors()
314 .add_simple_error(
315 literal_int_token.get_source_ref(),
316 "Invalid integer literal",
317 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in an i16")
318 );
319 return false;
320 }
321 6 result.parsed_type = type_part;
322 6 result.i16_value = (short)number;
323 6 return true;
324 }
325 break;
326 8 case Type::TYPE_PRIMITIVE_i32:
327 {
328 8 long number = strtoll(source_cstring, &endptr, radix);
329
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (endptr != (source_cstring + length)) {
330 compiler_context
331 .get_errors()
332 .add_simple_error(
333 literal_int_token.get_source_ref(),
334 "Invalid integer literal",
335 std::string("Literal value contained extraneous characters: ") + integer_part
336 );
337 return false;
338 }
339
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2 times.
8 number = sign_positive ? number : -number;
340
3/6
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
8 if (errno == ERANGE || number < -2147483648 || number > 2147483647) {
341 compiler_context
342 .get_errors()
343 .add_simple_error(
344 literal_int_token.get_source_ref(),
345 "Invalid integer literal",
346 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in an i32")
347 );
348 return false;
349 }
350 8 result.parsed_type = type_part;
351 8 result.i32_value = (int)number;
352 8 return true;
353 }
354 break;
355 10 case Type::TYPE_PRIMITIVE_i64:
356 {
357 10 long number = strtoll(source_cstring, &endptr, radix);
358
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (endptr != (source_cstring + length)) {
359 compiler_context
360 .get_errors()
361 .add_simple_error(
362 literal_int_token.get_source_ref(),
363 "Invalid integer literal",
364 std::string("Literal value contained extraneous characters: ") + integer_part
365 );
366 return false;
367 }
368
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 number = sign_positive ? number : -number;
369
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (errno == ERANGE) {
370 compiler_context
371 .get_errors()
372 .add_simple_error(
373 literal_int_token.get_source_ref(),
374 "Invalid integer literal",
375 std::string("Literal value ") + integer_part + std::string(" is outside the range of values allowed in a u64")
376 );
377 return false;
378 }
379 10 result.parsed_type = type_part;
380 10 result.i64_value = (long)number;
381 10 return true;
382 }
383 break;
384
385 default:
386 compiler_context
387 .get_errors()
388 .add_simple_error(
389 literal_int_token.get_source_ref(),
390 "Compiler Bug! Invalid integer literal",
391 std::string("Unsupported primitive literal type ") + type_part->get_name()
392 );
393 return false;
394 }
395 /* Unreachable */
396 242 }
397
398