• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rieske / trans / 29902648358

22 Jul 2026 08:04AM UTC coverage: 91.671% (+0.5%) from 91.142%
29902648358

Pull #53

github

rieske
Fuzz-test and fix the backend
Pull Request #53: Fuzz-test and fix the backend

128 of 134 new or added lines in 10 files covered. (95.52%)

5514 of 6015 relevant lines covered (91.67%)

379841.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.11
/src/semantic_analyzer/SemanticAnalysisVisitor.cpp
1
#include "SemanticAnalysisVisitor.h"
2

3
#include <algorithm>
4
#include <cctype>
5
#include <stdexcept>
6
#include <string>
7

8
#include "translation_unit/Context.h"
9
#include "types/Type.h"
10
#include "util/Logger.h"
11
#include "util/LogManager.h"
12

13
namespace semantic_analyzer {
14

15
static const translation_unit::Context EXTERNAL_CONTEXT {"external", 0};
16

17
static Logger& err = LogManager::getErrorLogger();
18

19
// Locals are stored as `$s<scopeId><name>` in the symbol table; strip that prefix for diagnostics
20
// and for looking up file-scope functions under their source names.
21
static std::string unscopedSymbolName(const std::string& name) {
998✔
22
    if (name.size() > 2 && name[0] == '$' && name[1] == 's') {
998✔
23
        std::size_t i = 2;
4✔
24
        while (i < name.size() && std::isdigit(static_cast<unsigned char>(name[i]))) {
8✔
25
            ++i;
4✔
26
        }
27
        if (i > 2 && i < name.size()) {
4✔
28
            return name.substr(i);
4✔
29
        }
30
    }
31
    return name;
994✔
32
}
33

34
SemanticAnalysisVisitor::SemanticAnalysisVisitor() {
448✔
35
    type::Type functionType = type::function(type::signedInteger());
448✔
36
    symbolTable.insertFunction("printf", functionType.getFunction(), EXTERNAL_CONTEXT);
1,344✔
37
    symbolTable.insertFunction("scanf", functionType.getFunction(), EXTERNAL_CONTEXT);
1,344✔
38
}
448✔
39

40
SemanticAnalysisVisitor::~SemanticAnalysisVisitor() {
448✔
41
}
448✔
42

43
void SemanticAnalysisVisitor::printSymbolTable() const {
416✔
44
    symbolTable.printTable();
416✔
45
}
416✔
46

47
void SemanticAnalysisVisitor::visit(ast::DeclarationSpecifiers& declarationSpecifiers) {
1,296✔
48
    // FIXME: this would look so much better
49
    /*for (std::string error : declarationSpecifiers.getSemanticErrors()) {
50
     semanticError(error, globalContext);
51
     }*/
52
    if (declarationSpecifiers.getStorageSpecifiers().size() > 1) {
1,296✔
53
        semanticError("multiple storage classes in declaration specifiers",
×
54
                declarationSpecifiers.getStorageSpecifiers().at(1).getContext());
×
55
    }
56
}
1,296✔
57

58
void SemanticAnalysisVisitor::visit(ast::Declaration& declaration) {
540✔
59
    declaration.visitChildren(*this);
540✔
60

61
    auto baseType = declaration.getDeclarationSpecifiers().getTypeSpecifiers().at(0).getType();
540✔
62
    for (const auto& declarator : declaration.getDeclarators()) {
1,202✔
63
        auto type = declarator->getFundamentalType(baseType);
662✔
64
        if (type.isVoid()) {
662✔
65
            semanticError("variable `" + declarator->getName() + "` declared void", declarator->getContext());
2✔
66
        } else if (symbolTable.isAtFileScope() && symbolTable.hasFunction(declarator->getName())) {
660✔
67
            semanticError("symbol `" + declarator->getName() + "` declaration conflicts with function of the same name",
×
68
                    declarator->getContext());
×
69
        } else if (symbolTable.insertSymbol(declarator->getName(), type, declarator->getContext())) {
660✔
70
            declarator->setHolder(symbolTable.lookup(declarator->getName()));
658✔
71
            // TODO: type check initializers
72
            if (declarator->hasInitializer() && symbolTable.isAtFileScope()) {
658✔
73
                long initValue = 0;
28✔
74
                if (declarator->getInitializer()->evaluateConstant(initValue)) {
28✔
75
                    symbolTable.setGlobalInitializer(declarator->getName(), initValue);
28✔
76
                } else {
77
                    semanticError("global initializer is not a constant expression", declarator->getContext());
×
78
                }
79
            }
80
        } else {
81
            semanticError(
2✔
82
                    "symbol `" + declarator->getName() +
4✔
83
                            "` declaration conflicts with previous declaration on " +
4✔
84
                            to_string(symbolTable.lookup(declarator->getName()).getContext()),
4✔
85
                    declarator->getContext());
4✔
86
        }
87
    }
662✔
88
}
540✔
89

90
void SemanticAnalysisVisitor::visit(ast::Declarator& declarator) {
1,420✔
91
    declarator.visitChildren(*this);
1,420✔
92
}
1,420✔
93

94
void SemanticAnalysisVisitor::visit(ast::InitializedDeclarator& declarator) {
662✔
95
    declarator.visitChildren(*this);
662✔
96
}
662✔
97

98
void SemanticAnalysisVisitor::visit(ast::ArrayAccess& arrayAccess) {
2✔
99
    arrayAccess.visitLeftOperand(*this);
2✔
100
    arrayAccess.visitRightOperand(*this);
2✔
101

102
    // Operand failed to resolve (e.g. undeclared identifier) — error already reported.
103
    if (!arrayAccess.hasLeftOperandSymbol() || !arrayAccess.hasRightOperandSymbol()) {
2✔
104
        return;
2✔
105
    }
106

107
    auto type = arrayAccess.leftOperandType();
×
108
    if (type.isPointer()) {
×
109
        arrayAccess.setLvalue(symbolTable.createTemporarySymbol(type.dereference()));
×
110
        arrayAccess.setResultSymbol(symbolTable.createTemporarySymbol(type.dereference()));
×
111
    } else {
112
        semanticError("invalid type for operator[]\n", arrayAccess.getContext());
×
113
    }
114
}
×
115

116
void SemanticAnalysisVisitor::visit(ast::FunctionCall& functionCall) {
1,002✔
117
    functionCall.visitOperand(*this);
1,002✔
118
    functionCall.visitArguments(*this);
1,002✔
119

120
    // Operand failed to resolve (e.g. undeclared identifier) — error already reported.
121
    if (!functionCall.hasOperandSymbol()) {
1,002✔
122
        return;
8✔
123
    }
124

125
    // ValueEntry names for locals are scope-prefixed (e.g. `$s1a`); functions are stored under
126
    // the source identifier. A mangled name is always a local (or temp), never a function entry —
127
    // do not look up the demangled form, or a local would incorrectly call a same-named function.
128
    const auto symbolName = functionCall.operandSymbol()->getName();
998✔
129
    const auto displayName = unscopedSymbolName(symbolName);
998✔
130
    if (symbolName != displayName || !symbolTable.hasFunction(displayName)) {
998✔
131
        semanticError("called object `" + displayName + "` is not a function", functionCall.getContext());
4✔
132
        return;
4✔
133
    }
134

135
    auto functionSymbol = symbolTable.findFunction(displayName);
994✔
136

137
    functionCall.setSymbol(functionSymbol);
994✔
138

139
    auto& arguments = functionCall.getArgumentList();
994✔
140
    // Reject function designators as values (e.g. printf("%d", main)) — codegen has no address.
141
    for (auto& argument : arguments) {
3,130✔
142
        if (argument->hasResultSymbol()) {
2,136✔
143
            rejectFunctionValue(argument->getResultSymbol()->getType(), functionCall.getContext());
2,134✔
144
        }
145
    }
146

147
    if (arguments.size() == functionSymbol.argumentCount()) {
994✔
148
        auto declaredArguments = functionSymbol.arguments();
128✔
149
        for (std::size_t i { 0 }; i < arguments.size(); ++i) {
348✔
150
            if (!arguments.at(i)->hasResultSymbol()) {
220✔
151
                return;
×
152
            }
153
            const auto& declaredArgument = declaredArguments.at(i);
220✔
154
            const auto& actualArgument = arguments.at(i)->getResultSymbol();
220✔
155
            typeCheck(actualArgument->getType(), declaredArgument, functionCall.getContext());
220✔
156
        }
157

158
        auto returnType = functionSymbol.returnType();
128✔
159
        if (!returnType.isVoid()) {
128✔
160
            functionCall.setResultSymbol(symbolTable.createTemporarySymbol(returnType));
128✔
161
        }
162
    } else if (functionSymbol.getContext() == EXTERNAL_CONTEXT) {
994✔
163
    // FIXME: using EXTERNAL_CONTEXT as a workaround for printf/scanf external functions until varargs are properly implemented
164
        auto returnType = functionSymbol.returnType();
866✔
165
        if (!returnType.isVoid()) {
866✔
166
            functionCall.setResultSymbol(symbolTable.createTemporarySymbol(returnType));
866✔
167
        }
168
    } else {
866✔
169
        semanticError("no match for function " + functionSymbol.getType().to_string(), functionCall.getContext());
×
170
    }
171
}
1,002✔
172

173
void SemanticAnalysisVisitor::visit(ast::IdentifierExpression& identifier) {
2,940✔
174
    if (symbolTable.hasSymbol(identifier.getIdentifier())) {
2,940✔
175
        identifier.setResultSymbol(symbolTable.lookup(identifier.getIdentifier()));
2,928✔
176
    } else {
177
        semanticError("symbol `" + identifier.getIdentifier() + "` is not defined", identifier.getContext());
12✔
178
    }
179
}
2,940✔
180

181
void SemanticAnalysisVisitor::visit(ast::ConstantExpression& constant) {
1,480✔
182
    constant.setResultSymbol(symbolTable.createTemporarySymbol(constant.getType()));
1,480✔
183
}
1,480✔
184

185
void SemanticAnalysisVisitor::visit(ast::StringLiteralExpression& stringLiteral) {
870✔
186
    std::string constantSymbol = symbolTable.newConstant(stringLiteral.getValue());
870✔
187
    stringLiteral.setConstantSymbol(constantSymbol);
870✔
188
    stringLiteral.setResultSymbol(symbolTable.createTemporarySymbol(stringLiteral.getType()));
870✔
189
}
870✔
190

191
void SemanticAnalysisVisitor::visit(ast::PostfixExpression& expression) {
30✔
192
    expression.visitOperand(*this);
30✔
193
    if (!expression.hasOperandSymbol()) {
30✔
194
        return;
×
195
    }
196
    rejectFunctionValue(expression.operandType(), expression.getContext());
30✔
197

198
    expression.setType(expression.operandType());
30✔
199
    auto operandSymbol = *expression.operandSymbol();
30✔
200
    expression.setResultSymbol(operandSymbol);
30✔
201

202
    auto preOperationSymbolName = operandSymbol.getName() + "_pre";
30✔
203
    symbolTable.insertSymbol(preOperationSymbolName, operandSymbol.getType(), operandSymbol.getContext());
30✔
204
    expression.setPreOperationSymbol(symbolTable.lookup(preOperationSymbolName));
30✔
205

206
    if (!expression.isLval()) {
30✔
207
        semanticError("lvalue required as increment operand", expression.getContext());
×
208
    }
209
}
30✔
210

211
void SemanticAnalysisVisitor::visit(ast::PrefixExpression& expression) {
28✔
212
    expression.visitOperand(*this);
28✔
213
    if (!expression.hasOperandSymbol()) {
28✔
214
        return;
×
215
    }
216
    rejectFunctionValue(expression.operandType(), expression.getContext());
28✔
217

218
    expression.setType(expression.operandType());
28✔
219
    expression.setResultSymbol(*expression.operandSymbol());
28✔
220

221
    if (!expression.isLval()) {
28✔
222
        semanticError("lvalue required as increment operand", expression.getContext());
×
223
    }
224
}
225

226
void SemanticAnalysisVisitor::visit(ast::UnaryExpression& expression) {
570✔
227
    expression.visitOperand(*this);
570✔
228
    if (!expression.hasOperandSymbol()) {
570✔
229
        return;
4✔
230
    }
231
    rejectFunctionValue(expression.operandType(), expression.getContext());
566✔
232

233
    switch (expression.getOperator()->getLexeme().front()) {
566✔
234
    case '&':
328✔
235
        expression.setResultSymbol(symbolTable.createTemporarySymbol(type::pointer(expression.operandType())));
328✔
236
        break;
328✔
237
    case '*':
92✔
238
        if (expression.operandType().isPointer()) {
92✔
239
            expression.setResultSymbol(symbolTable.createTemporarySymbol(expression.operandType().dereference()));
92✔
240
            expression.setLvalueSymbol(symbolTable.createTemporarySymbol(expression.operandType()));
92✔
241
        } else {
242
            semanticError("invalid type argument of ‘unary *’ :" + expression.operandType().to_string(), expression.getContext());
×
243
        }
244
        break;
92✔
245
    case '+':
4✔
246
        expression.setResultSymbol(*expression.operandSymbol());
4✔
247
        break;
4✔
248
    case '-':
94✔
249
        expression.setResultSymbol(symbolTable.createTemporarySymbol(expression.operandType()));
94✔
250
        break;
94✔
251
    case '~':
18✔
252
        expression.setResultSymbol(symbolTable.createTemporarySymbol(expression.operandType()));
18✔
253
        break;
18✔
254
    case '!':
30✔
255
        expression.setResultSymbol(symbolTable.createTemporarySymbol(type::signedInteger()));
30✔
256
        expression.setTruthyLabel(symbolTable.newLabel());
30✔
257
        expression.setFalsyLabel(symbolTable.newLabel());
30✔
258
        break;
30✔
259
    default:
×
NEW
260
        throw std::runtime_error { "Unidentified unary operator: " + expression.getOperator()->getLexeme() };
×
261
    }
262
}
263

264
void SemanticAnalysisVisitor::visit(ast::TypeCast& expression) {
×
265
    expression.visitOperand(*this);
×
266
    if (!expression.hasOperandSymbol()) {
×
267
        return;
×
268
    }
269

270
    expression.setResultSymbol(symbolTable.createTemporarySymbol(expression.getType().getType()));
×
271
}
272

273
void SemanticAnalysisVisitor::visit(ast::ArithmeticExpression& expression) {
134✔
274
    expression.visitLeftOperand(*this);
134✔
275
    expression.visitRightOperand(*this);
134✔
276
    if (!expression.hasLeftOperandSymbol() || !expression.hasRightOperandSymbol()) {
134✔
277
        return;
×
278
    }
279
    rejectFunctionValue(expression.leftOperandType(), expression.getContext());
134✔
280
    rejectFunctionValue(expression.rightOperandType(), expression.getContext());
134✔
281

282
    typeCheck(
134✔
283
            expression.leftOperandType(),
268✔
284
            expression.rightOperandType(),
268✔
285
            expression.getContext());
268✔
286
    // FIXME: type conversion
287
    expression.setResultSymbol(symbolTable.createTemporarySymbol(expression.leftOperandType()));
134✔
288
}
289

290
void SemanticAnalysisVisitor::visit(ast::ShiftExpression& expression) {
30✔
291
    expression.visitLeftOperand(*this);
30✔
292
    expression.visitRightOperand(*this);
30✔
293
    if (!expression.hasLeftOperandSymbol() || !expression.hasRightOperandSymbol()) {
30✔
294
        return;
×
295
    }
296
    rejectFunctionValue(expression.leftOperandType(), expression.getContext());
30✔
297
    rejectFunctionValue(expression.rightOperandType(), expression.getContext());
30✔
298

299
    if (expression.rightOperandType().isPrimitive() && !expression.rightOperandType().getPrimitive().isFloating()) {
30✔
300
        expression.setResultSymbol(symbolTable.createTemporarySymbol(expression.leftOperandType()));
30✔
301
    } else {
302
        semanticError("argument of type int required for shift expression", expression.getContext());
×
303
    }
304
}
305

306
void SemanticAnalysisVisitor::visit(ast::ComparisonExpression& expression) {
198✔
307
    expression.visitLeftOperand(*this);
198✔
308
    expression.visitRightOperand(*this);
198✔
309
    if (!expression.hasLeftOperandSymbol() || !expression.hasRightOperandSymbol()) {
198✔
310
        return;
×
311
    }
312
    rejectFunctionValue(expression.leftOperandType(), expression.getContext());
198✔
313
    rejectFunctionValue(expression.rightOperandType(), expression.getContext());
198✔
314

315
    typeCheck(
198✔
316
            expression.leftOperandType(),
396✔
317
            expression.rightOperandType(),
396✔
318
            expression.getContext());
396✔
319

320
    expression.setResultSymbol(symbolTable.createTemporarySymbol(type::signedInteger()));
198✔
321
    expression.setTruthyLabel(symbolTable.newLabel());
198✔
322
    expression.setFalsyLabel(symbolTable.newLabel());
198✔
323
}
324

325
void SemanticAnalysisVisitor::visit(ast::BitwiseExpression& expression) {
16✔
326
    expression.visitLeftOperand(*this);
16✔
327
    expression.visitRightOperand(*this);
16✔
328
    if (!expression.hasLeftOperandSymbol() || !expression.hasRightOperandSymbol()) {
16✔
329
        return;
×
330
    }
331
    rejectFunctionValue(expression.leftOperandType(), expression.getContext());
16✔
332
    rejectFunctionValue(expression.rightOperandType(), expression.getContext());
16✔
333
    expression.setType(expression.leftOperandType());
16✔
334

335
    typeCheck(
16✔
336
            expression.leftOperandType(),
32✔
337
            expression.rightOperandType(),
32✔
338
            expression.getContext());
32✔
339

340
    expression.setResultSymbol(
16✔
341
            symbolTable.createTemporarySymbol(expression.getType()));
32✔
342
}
343

344
void SemanticAnalysisVisitor::visit(ast::LogicalAndExpression& expression) {
12✔
345
    expression.visitLeftOperand(*this);
12✔
346
    expression.visitRightOperand(*this);
12✔
347
    if (!expression.hasLeftOperandSymbol() || !expression.hasRightOperandSymbol()) {
12✔
348
        return;
×
349
    }
350
    rejectFunctionValue(expression.leftOperandType(), expression.getContext());
12✔
351
    rejectFunctionValue(expression.rightOperandType(), expression.getContext());
12✔
352

353
    typeCheck(
12✔
354
            expression.leftOperandType(),
24✔
355
            expression.rightOperandType(),
24✔
356
            expression.getContext());
24✔
357

358
    expression.setResultSymbol(symbolTable.createTemporarySymbol(type::signedInteger()));
12✔
359
    expression.setExitLabel(symbolTable.newLabel());
12✔
360
}
361

362
void SemanticAnalysisVisitor::visit(ast::LogicalOrExpression& expression) {
12✔
363
    expression.visitLeftOperand(*this);
12✔
364
    expression.visitRightOperand(*this);
12✔
365
    if (!expression.hasLeftOperandSymbol() || !expression.hasRightOperandSymbol()) {
12✔
366
        return;
×
367
    }
368
    rejectFunctionValue(expression.leftOperandType(), expression.getContext());
12✔
369
    rejectFunctionValue(expression.rightOperandType(), expression.getContext());
12✔
370

371
    typeCheck(
12✔
372
            expression.leftOperandType(),
24✔
373
            expression.rightOperandType(),
24✔
374
            expression.getContext());
24✔
375

376
    expression.setResultSymbol(symbolTable.createTemporarySymbol(type::signedInteger()));
12✔
377
    expression.setExitLabel(symbolTable.newLabel());
12✔
378
}
379

380
void SemanticAnalysisVisitor::visit(ast::AssignmentExpression& expression) {
524✔
381
    expression.visitLeftOperand(*this);
524✔
382
    expression.visitRightOperand(*this);
524✔
383
    if (!expression.hasLeftOperandSymbol() || !expression.hasRightOperandSymbol()) {
524✔
384
        return;
4✔
385
    }
386

387
    if (expression.isLval()) {
520✔
388
        rejectFunctionValue(expression.leftOperandType(), expression.getContext());
516✔
389
        rejectFunctionValue(expression.rightOperandType(), expression.getContext());
516✔
390
        typeCheck(
516✔
391
                expression.leftOperandType(),
1,032✔
392
                expression.rightOperandType(),
1,032✔
393
                expression.getContext());
1,032✔
394

395
        expression.setResultSymbol(*expression.leftOperandSymbol());
516✔
396
    } else {
397
        semanticError("lvalue required on the left side of assignment", expression.getContext());
12✔
398
    }
399
}
400

401
void SemanticAnalysisVisitor::visit(ast::ExpressionList& expression) {
2✔
402
    expression.visitLeftOperand(*this);
2✔
403
    expression.visitRightOperand(*this);
2✔
404
    if (!expression.hasRightOperandSymbol()) {
2✔
405
        return;
×
406
    }
407
    // Comma operator: value and type of the right operand
408
    expression.setType(expression.rightOperandType());
2✔
409
    expression.setResultSymbol(*expression.rightOperandSymbol());
2✔
410
}
411

412
void SemanticAnalysisVisitor::visit(ast::Operator&) {
×
413
}
×
414

415
void SemanticAnalysisVisitor::visit(ast::JumpStatement& statement) {
10✔
416
    if (loopStack.empty()) {
10✔
417
        semanticError("`" + statement.jumpKeyword.type + "` statement not in loop", statement.jumpKeyword.context);
2✔
418
        return;
2✔
419
    }
420
    const auto& loop = loopStack.back();
8✔
421
    if (statement.jumpKeyword.type == "break") {
8✔
422
        statement.setJumpTo(*loop.exit);
4✔
423
    } else if (statement.jumpKeyword.type == "continue") {
4✔
424
        statement.setJumpTo(*loop.cont);
4✔
425
    } else {
NEW
426
        semanticError("unsupported jump statement `" + statement.jumpKeyword.type + "`", statement.jumpKeyword.context);
×
427
    }
428
}
429

430
void SemanticAnalysisVisitor::visit(ast::ReturnStatement& statement) {
544✔
431
    statement.returnExpression->accept(*this);
544✔
432
    if (statement.returnExpression->hasResultSymbol()) {
544✔
433
        rejectFunctionValue(statement.returnExpression->getResultSymbol()->getType(),
544✔
434
                statement.returnExpression->getContext());
1,088✔
435
    }
436
}
544✔
437

438
void SemanticAnalysisVisitor::visit(ast::VoidReturnStatement& statement) {
22✔
439
}
22✔
440

441
void SemanticAnalysisVisitor::visit(ast::IfStatement& statement) {
38✔
442
    statement.testExpression->accept(*this);
38✔
443
    if (statement.testExpression->hasResultSymbol()) {
38✔
444
        rejectFunctionValue(statement.testExpression->getResultSymbol()->getType(),
38✔
445
                statement.testExpression->getContext());
76✔
446
    }
447
    statement.body->accept(*this);
38✔
448

449
    statement.setFalsyLabel(symbolTable.newLabel());
38✔
450
}
38✔
451

452
void SemanticAnalysisVisitor::visit(ast::IfElseStatement& statement) {
18✔
453
    statement.testExpression->accept(*this);
18✔
454
    if (statement.testExpression->hasResultSymbol()) {
18✔
455
        rejectFunctionValue(statement.testExpression->getResultSymbol()->getType(),
18✔
456
                statement.testExpression->getContext());
36✔
457
    }
458
    statement.truthyBody->accept(*this);
18✔
459
    statement.falsyBody->accept(*this);
18✔
460

461
    statement.setFalsyLabel(symbolTable.newLabel());
18✔
462
    statement.setExitLabel(symbolTable.newLabel());
18✔
463
}
18✔
464

465
void SemanticAnalysisVisitor::visit(ast::LoopStatement& loop) {
66✔
466
    loop.header->accept(*this);
66✔
467
    // while/for-without-increment: continue → entry; for-with-increment: separate continue label.
468
    if (loop.header->increment) {
66✔
469
        loop.header->setLoopContinue(symbolTable.newLabel());
28✔
470
    } else {
471
        loop.header->setLoopContinue(*loop.header->getLoopEntry());
38✔
472
    }
473
    loopStack.push_back({ loop.header->getLoopEntry(), loop.header->getLoopContinue(), loop.header->getLoopExit() });
66✔
474
    loop.body->accept(*this);
66✔
475
    loopStack.pop_back();
66✔
476
}
66✔
477

478
void SemanticAnalysisVisitor::visit(ast::ForLoopHeader& loopHeader) {
40✔
479
    if (loopHeader.initialization) {
40✔
480
        loopHeader.initialization->accept(*this);
26✔
481
    }
482
    if (loopHeader.clause) {
40✔
483
        loopHeader.clause->accept(*this);
30✔
484
    }
485
    if (loopHeader.increment) {
40✔
486
        loopHeader.increment->accept(*this);
28✔
487
    }
488

489
    loopHeader.setLoopEntry(symbolTable.newLabel());
40✔
490
    loopHeader.setLoopExit(symbolTable.newLabel());
40✔
491
}
40✔
492

493
void SemanticAnalysisVisitor::visit(ast::WhileLoopHeader& loopHeader) {
26✔
494
    loopHeader.clause->accept(*this);
26✔
495

496
    loopHeader.setLoopEntry(symbolTable.newLabel());
26✔
497
    loopHeader.setLoopExit(symbolTable.newLabel());
26✔
498
}
26✔
499

500
void SemanticAnalysisVisitor::visit(ast::Pointer&) {
70✔
501
}
70✔
502

503
void SemanticAnalysisVisitor::visit(ast::Identifier&) {
850✔
504
}
850✔
505

506
void SemanticAnalysisVisitor::visit(ast::ArrayDeclarator& declaration) {
×
507
    declaration.subscriptExpression->accept(*this);
×
508
    throw std::runtime_error { "not implemented" };
×
509
}
510

511
void SemanticAnalysisVisitor::visit(ast::FunctionDeclarator& declarator) {
562✔
512
    declarator.visitFormalArguments(*this);
562✔
513

514
    argumentNames.clear();
562✔
515
    std::vector<type::Type> arguments;
562✔
516
    for (auto& argumentDeclaration : declarator.getFormalArguments()) {
758✔
517
        arguments.push_back(argumentDeclaration.getType());
196✔
518
        argumentNames.push_back(argumentDeclaration.getName());
196✔
519
    }
520

521
    // FIXME: return type is not known at this point!
522
    type::Type functionType = type::function(type::signedInteger(), arguments);
562✔
523
    if (symbolTable.hasGlobalVariable(declarator.getName())) {
562✔
524
        semanticError("function `" + declarator.getName() + "` conflicts with global variable of the same name",
2✔
525
                declarator.getContext());
4✔
526
        return;
2✔
527
    }
528
    FunctionEntry functionEntry = symbolTable.insertFunction(
529
            declarator.getName(),
1,120✔
530
            functionType.getFunction(),
1,120✔
531
            declarator.getContext());
1,680✔
532

533
    if (functionEntry.getContext() != declarator.getContext()) {
560✔
534
        semanticError("function `" + declarator.getName() + "` definition conflicts with previous one on "
×
535
                + to_string(functionEntry.getContext()), declarator.getContext());
×
536
    }
537
}
564✔
538

539
void SemanticAnalysisVisitor::visit(ast::FormalArgument& argument) {
196✔
540
    argument.visitSpecifiers(*this);
196✔
541
    argument.visitDeclarator(*this);
196✔
542
    if (argument.getType().isVoid()) {
196✔
543
        semanticError("function argument ‘" + argument.getName() + "’ declared void", argument.getDeclarationContext());
×
544
    }
545
}
196✔
546

547
void SemanticAnalysisVisitor::visit(ast::FunctionDefinition& function) {
560✔
548
    function.visitReturnType(*this);
560✔
549
    function.visitDeclarator(*this);
560✔
550

551
    if (!symbolTable.hasFunction(function.getName())) {
560✔
552
        return;
2✔
553
    }
554
    function.setSymbol(symbolTable.findFunction(function.getName()));
558✔
555
    symbolTable.startFunction(function.getName(), argumentNames);
558✔
556
    // Parameters and outermost body declarations share one scope (C); do not enterBlockScope.
557
    function.visitBodyChildren(*this);
558✔
558
    function.setArguments(symbolTable.getCurrentScopeArguments());
558✔
559
    function.setLocalVariables(symbolTable.getCurrentScopeSymbols());
558✔
560
    symbolTable.endFunction();
558✔
561
}
562

563
void SemanticAnalysisVisitor::visit(ast::Block& block) {
158✔
564
    symbolTable.enterBlockScope();
158✔
565
    block.visitChildren(*this);
158✔
566
    symbolTable.exitBlockScope();
158✔
567
}
158✔
568

569
void SemanticAnalysisVisitor::typeCheck(const type::Type& typeFrom, const type::Type& typeTo,
1,108✔
570
        const translation_unit::Context& context)
571
{
572
    if (!typeTo.canAssignFrom(typeFrom)) {
1,108✔
573
        semanticError("type mismatch: can't convert " + typeFrom.to_string() + " to " + typeTo.to_string(), context);
×
574
    }
575
}
1,108✔
576

577
void SemanticAnalysisVisitor::rejectFunctionValue(const type::Type& type, const translation_unit::Context& context) {
5,194✔
578
    // Pointer-to-function types are stored as function types with indirection > 0, so they
579
    // also report isFunction(). Only bare function designators (no pointer) are rejected.
580
    if (type.isFunction() && !type.isPointer()) {
5,194✔
581
        semanticError("function designator used as a value is not supported", context);
8✔
582
    }
583
}
5,194✔
584

585
void SemanticAnalysisVisitor::semanticError(std::string message, const translation_unit::Context& context) {
32✔
586
    containsSemanticErrors = true;
32✔
587
    err << context << ": error: " << message << "\n";
32✔
588
}
32✔
589

590
bool SemanticAnalysisVisitor::successfulSemanticAnalysis() const {
448✔
591
    return !containsSemanticErrors;
448✔
592
}
593

594
std::map<std::string, std::string> SemanticAnalysisVisitor::getConstants() const {
416✔
595
    return symbolTable.getConstants();
416✔
596
}
597

598
std::vector<ValueEntry> SemanticAnalysisVisitor::getGlobalVariables() const {
416✔
599
    return symbolTable.getGlobalVariables();
416✔
600
}
601

602
} // namespace semantic_analyzer
603

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc