summaryrefslogtreecommitdiff
path: root/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis')
-rw-r--r--src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Analyzer.java167
-rw-r--r--src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Symbol.java23
2 files changed, 174 insertions, 16 deletions
diff --git a/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Analyzer.java b/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Analyzer.java
index b7cc7af..a01c6bf 100644
--- a/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Analyzer.java
+++ b/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Analyzer.java
@@ -1,17 +1,22 @@
package no.eliashaugsbakk.kompilator.semanticAnalysis;
-import static no.eliashaugsbakk.kompilator.tokenization.Keywords.KEYWORDS;
-
+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 Map<String, Symbol> symbolTable = new HashMap<>();
public Analyzer(AST ast) {
this.ast = ast;
@@ -27,32 +32,162 @@ public class Analyzer {
private void analyzeStatement(Statement stmt) throws SemanticException {
if (stmt instanceof ExpressionStatement exprStmt) {
- analyzeExpression(exprStmt.expression);
+ typeOf(exprStmt.expression);
+ } else if (stmt instanceof Assignment assignment) {
+ analyzeAssignment(assignment);
+ } else if (stmt instanceof IdentifierDeclaration decl) {
+ analyzeIdentifierDeclaration(decl);
} else {
- // Analyze the statement
- // NO other statements implemented
+ throw new SemanticException("Unrecognized statement");
}
}
- private void analyzeExpression(Expression expr) throws SemanticException {
- if (expr instanceof FunctionCall call) {
- checkFunctionCall(call);
- } else {
- // analyze the expression
+ /**
+ * 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.type.equals("string")) {
+ throw new SemanticException("Unknown type declaration: " + declaredType.type);
+ }
+
+ // 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;
}
- private void checkFunctionCall(FunctionCall call) throws SemanticException {
- if (!KEYWORDS.contains(call.functionName)) {
- throw new SemanticException("unknown function: " + call.functionName);
+ /**
+ * Analyzes identifier usage (variable reference).
+ */
+ private void analyzeIdentifier(Identifier identifier) throws SemanticException {
+ // Check if identifier is declared
+ if (!symbolTable.containsKey(identifier.name)) {
+ throw new SemanticException("Identifier does not exist: " + identifier.name);
}
+ // Check if identifier is initialized
+ if (!symbolTable.get(identifier.name).initialized) {
+ throw new SemanticException("Identifier is not initialized: " + identifier.name);
+ }
+ }
+
+ /**
+ * 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("print expects 1 argument, got " + call.arguments.size());
+ throw new SemanticException("skriv() supports only one argument");
}
- if (!(call.arguments.getFirst() instanceof StringLiteral)) {
- throw new SemanticException("print expects String argument only");
+ // Validate argument types
+ for (Expression argument : call.arguments) {
+ Type argType = typeOf(argument);
+
+ if (!argType.type.equals("string")) {
+ throw new SemanticException("skriv() only supports string literals");
+ }
+
+ if (argType.nullable) {
+ throw new SemanticException("Cannot print nullable string: " + argType.type + "?.");
+ }
}
}
+
+ private void checkAssignable(Type target, Type value) throws SemanticException {
+ if (!target.type.equals(value.type)) {
+ throw new SemanticException(
+ "Type mismatch: expected " + target.type + ", found " + value.type);
+ }
+ if (value.nullable && !target.nullable) {
+ throw new SemanticException(
+ "Cannot assign nullable " + value.type + " to non-nullable " + target.type);
+ }
+ }
+
+ /**
+ * 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 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());
+ }
}
diff --git a/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Symbol.java b/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Symbol.java
new file mode 100644
index 0000000..c86d2f7
--- /dev/null
+++ b/src/main/java/no/eliashaugsbakk/kompilator/semanticAnalysis/Symbol.java
@@ -0,0 +1,23 @@
+package no.eliashaugsbakk.kompilator.semanticAnalysis;
+
+import no.eliashaugsbakk.kompilator.parsing.Type;
+
+class Symbol {
+ final String name;
+ final Type type;
+ boolean mutable;
+ boolean initialized;
+
+ Symbol(String name, Type type, boolean mutable, boolean initialized) {
+ this.name = name;
+ this.type = type;
+ this.mutable = mutable;
+ this.initialized = initialized;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Symbol{name='%s', type=%s, mutable=%b, initialized=%b}",
+ name, type, mutable, initialized);
+ }
+}