1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
package no.eliashaugsbakk.kompilator.semanticAnalysis;
import java.util.HashMap;
import java.util.Map;
import no.eliashaugsbakk.kompilator.parsing.AST;
import no.eliashaugsbakk.kompilator.parsing.Type;
import no.eliashaugsbakk.kompilator.parsing.node.Program;
import no.eliashaugsbakk.kompilator.parsing.node.expression.Expression;
import no.eliashaugsbakk.kompilator.parsing.node.expression.FunctionCall;
import no.eliashaugsbakk.kompilator.parsing.node.expression.Identifier;
import no.eliashaugsbakk.kompilator.parsing.node.expression.literal.StringLiteral;
import no.eliashaugsbakk.kompilator.parsing.node.statement.Assignment;
import no.eliashaugsbakk.kompilator.parsing.node.statement.ExpressionStatement;
import no.eliashaugsbakk.kompilator.parsing.node.statement.IdentifierDeclaration;
import no.eliashaugsbakk.kompilator.parsing.node.statement.Statement;
public class Analyzer {
private final AST ast;
private final Map<String, Symbol> symbolTable = new HashMap<>();
public Analyzer(AST ast) {
this.ast = ast;
}
public void analyze() throws SemanticException {
Program program = (Program) ast.root();
for (Statement stmt : program.statements) {
analyzeStatement(stmt);
}
}
private void analyzeStatement(Statement stmt) throws SemanticException {
switch (stmt) {
case ExpressionStatement exprStmt -> typeOf(exprStmt.expression);
case Assignment assignment -> analyzeAssignment(assignment);
case IdentifierDeclaration decl -> analyzeIdentifierDeclaration(decl);
case null, default -> throw new SemanticException("Unrecognized statement");
}
}
/**
* Analyzes identifier declaration: set/mut x: type [= value];
*/
private void analyzeIdentifierDeclaration(IdentifierDeclaration decl) throws SemanticException {
boolean initialized = decl.initializer != null;
boolean nullable = decl.type.nullable();
Type declaredType = decl.type;
// Ensure the type exists
// string is the only type implemented, but should look in a type table or something in the future
if (!declaredType.name().equals("string")) {
throw new SemanticException("Unknown type declaration: " + declaredType.name());
}
// Immutable variables must always be initialized on declaration
if (!decl.mutable && !initialized) {
throw new SemanticException(
"Immutable variable " + decl.identifier + " must be initialized upon declaration.");
}
// If no initializer, ensure type is nullable
if (!initialized && !nullable) {
throw new SemanticException("Non-nullable type requires initialization");
}
// If initializer exists, validate type matches declared type
if (initialized) {
Type implementedType = typeOf(decl.initializer);
checkAssignable(declaredType, implementedType);
}
// Shadowing is allowed; do not check to see if the symbol already exists
symbolTable.put(decl.identifier,
new Symbol(decl.identifier, declaredType, decl.mutable, initialized));
}
/**
* Analyzes assignment: x = value;
*/
private void analyzeAssignment(Assignment assignment) throws SemanticException {
Symbol symbol = symbolTable.get(assignment.identifier);
// Check existence
if (symbol == null) {
throw new SemanticException("Variable not declared: " + assignment.identifier);
}
// Check mutability
if (!symbol.mutable) {
throw new SemanticException("Cannot assign to immutable value: " + assignment.identifier);
}
// Type and Nullability Check
Type assignedType = typeOf(assignment.expression);
checkAssignable(symbol.type, assignedType);
// Update symbol state
symbol.initialized = true;
}
/**
* Analyzes function call.
*/
private void analyzeFunctionCall(FunctionCall call) throws SemanticException {
// Validate function exists
// skriv() is the only implemented function
// Should ref. function table in the future
if (!call.functionName.equals("skriv")) {
throw new SemanticException("Function calls are not supported: " + call.functionName);
}
// Validate argument count
if (call.arguments.size() != 1) {
throw new SemanticException("skriv() supports only one argument");
}
// Validate argument types
for (Expression argument : call.arguments) {
Type argType = typeOf(argument);
if (!argType.name().equals("string")) {
throw new SemanticException("skriv() only supports string literals");
}
if (argType.nullable()) {
throw new SemanticException("Cannot print nullable string: " + argType.name() + "?.");
}
}
}
private void checkAssignable(Type target, Type value) throws SemanticException {
if (!target.name().equals(value.name())) {
throw new SemanticException(
"Type mismatch: expected " + target.name() + ", found " + value.name());
}
if (value.nullable() && !target.nullable()) {
throw new SemanticException(
"Cannot assign nullable " + value.name() + " to non-nullable " + target.name());
}
}
/**
* Analyzes an expression, ensures all identifiers are declared and initialized, recursively
* validates sub-expressions, and returns the resulting Type.
*/
private Type typeOf(Expression expr) throws SemanticException {
switch (expr) {
case null -> throw new SemanticException("Expression cannot be null");
case StringLiteral _ -> {
return new Type("string", false);
}
case Identifier id -> {
Symbol symbol = symbolTable.get(id.name);
if (symbol == null) {
throw new SemanticException("Undeclared identifier: " + id.name);
}
if (!symbol.initialized) {
throw new SemanticException("Identifier is not initialized: " + id.name);
}
return symbol.type;
}
case FunctionCall call -> {
analyzeFunctionCall(call);
return call.getReturnType();
}
default -> {
}
}
throw new SemanticException("Unknown expression type: " + expr.getClass().getSimpleName());
}
}
|