summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedNode.java7
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelExpressionNode.java7
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelReferenceNode.java11
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java51
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java25
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndeclaredVariableError.java17
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndefinedVariableError.java17
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UnexpectedTypeError.java25
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/typechecker/Variable.java11
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/types/ScalarType.java8
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/types/Type.java4
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java5
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValueType.java8
-rw-r--r--src/main/java/org/zwobble/hobgoblin/compiler/types/TypeSet.java19
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedArb.java12
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java10
-rw-r--r--src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java75
17 files changed, 312 insertions, 0 deletions
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedNode.java b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedNode.java
new file mode 100644
index 0000000..6baa8d6
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedNode.java
@@ -0,0 +1,7 @@
+package org.zwobble.hobgoblin.compiler.ast.typed;
+
+import org.zwobble.hobgoblin.compiler.sources.Source;
+
+public interface TypedNode {
+ Source source();
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelExpressionNode.java b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelExpressionNode.java
new file mode 100644
index 0000000..c588f95
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelExpressionNode.java
@@ -0,0 +1,7 @@
+package org.zwobble.hobgoblin.compiler.ast.typed;
+
+import org.zwobble.hobgoblin.compiler.types.TypeLevelValue;
+
+public sealed interface TypedTypeLevelExpressionNode<T extends TypeLevelValue> extends TypedNode permits TypedTypeLevelReferenceNode {
+ T value();
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelReferenceNode.java b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelReferenceNode.java
new file mode 100644
index 0000000..bc2c50d
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/ast/typed/TypedTypeLevelReferenceNode.java
@@ -0,0 +1,11 @@
+package org.zwobble.hobgoblin.compiler.ast.typed;
+
+import org.zwobble.hobgoblin.compiler.sources.Source;
+import org.zwobble.hobgoblin.compiler.types.TypeLevelValue;
+
+public record TypedTypeLevelReferenceNode<T extends TypeLevelValue>(
+ String name,
+ T value,
+ Source source
+) implements TypedTypeLevelExpressionNode<T> {
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
new file mode 100644
index 0000000..56d4645
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeChecker.java
@@ -0,0 +1,51 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.ast.typed.TypedTypeLevelExpressionNode;
+import org.zwobble.hobgoblin.compiler.ast.typed.TypedTypeLevelReferenceNode;
+import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedTypeLevelExpressionNode;
+import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedTypeLevelReferenceNode;
+import org.zwobble.hobgoblin.compiler.types.TypeLevelValue;
+import org.zwobble.hobgoblin.compiler.types.TypeLevelValueType;
+import org.zwobble.hobgoblin.compiler.types.TypeSet;
+
+public class TypeChecker {
+ private TypeChecker() {
+ }
+
+ public static TypedTypeLevelExpressionNode<? extends TypeLevelValue> typeCheckTypeLevelExpression(
+ UntypedTypeLevelExpressionNode untyped,
+ TypeCheckerContext context
+ ) {
+ return switch (untyped) {
+ case UntypedTypeLevelReferenceNode untypedTypeLevelReference ->
+ typeCheckTypeLevelReference(untypedTypeLevelReference, context);
+ };
+ }
+
+ private static TypedTypeLevelExpressionNode<? extends TypeLevelValue> typeCheckTypeLevelReference(
+ UntypedTypeLevelReferenceNode untyped,
+ TypeCheckerContext context
+ ) {
+ var variable = context.lookup(untyped.name())
+ .orElseThrow(() -> new UndeclaredVariableError(untyped.name(), untyped.source()));
+
+ var type = switch (variable) {
+ case Variable.Declared declared ->
+ throw new UndefinedVariableError(untyped.name(), untyped.source());
+
+ case Variable.Defined defined ->
+ defined.type();
+ };
+
+ // TODO: handle not a type-level value
+ if (!(type instanceof TypeLevelValueType(TypeLevelValue value))) {
+ throw new UnexpectedTypeError(new TypeSet.MetaType(), type, untyped.source());
+ }
+
+ return new TypedTypeLevelReferenceNode<>(
+ untyped.name(),
+ value,
+ untyped.source()
+ );
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java
new file mode 100644
index 0000000..e861c30
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContext.java
@@ -0,0 +1,25 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.types.Type;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class TypeCheckerContext {
+ private final Map<String, Variable> variables = new HashMap<>();
+
+ public void declare(String name) {
+ // TODO: Check variable is not already declared.
+ this.variables.put(name, new Variable.Declared());
+ }
+
+ public void define(String name, Type type) {
+ // TODO: Check variable is not already defined.
+ this.variables.put(name, new Variable.Defined(type));
+ }
+
+ public Optional<Variable> lookup(String name) {
+ return Optional.ofNullable(this.variables.get(name));
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndeclaredVariableError.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndeclaredVariableError.java
new file mode 100644
index 0000000..c6130fa
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndeclaredVariableError.java
@@ -0,0 +1,17 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.errors.SourceError;
+import org.zwobble.hobgoblin.compiler.sources.Source;
+
+public class UndeclaredVariableError extends SourceError {
+ private final String variableName;
+
+ public UndeclaredVariableError(String variableName, Source source) {
+ super("Undeclared variable: " + variableName, source);
+ this.variableName = variableName;
+ }
+
+ public String variableName() {
+ return variableName;
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndefinedVariableError.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndefinedVariableError.java
new file mode 100644
index 0000000..95be5a6
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UndefinedVariableError.java
@@ -0,0 +1,17 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.errors.SourceError;
+import org.zwobble.hobgoblin.compiler.sources.Source;
+
+public class UndefinedVariableError extends SourceError {
+ private final String variableName;
+
+ public UndefinedVariableError(String variableName, Source source) {
+ super("Variable has been declared but not defined: " + variableName, source);
+ this.variableName = variableName;
+ }
+
+ public String variableName() {
+ return variableName;
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UnexpectedTypeError.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UnexpectedTypeError.java
new file mode 100644
index 0000000..c2ca4b6
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/UnexpectedTypeError.java
@@ -0,0 +1,25 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.errors.SourceError;
+import org.zwobble.hobgoblin.compiler.sources.Source;
+import org.zwobble.hobgoblin.compiler.types.Type;
+import org.zwobble.hobgoblin.compiler.types.TypeSet;
+
+public class UnexpectedTypeError extends SourceError {
+ private final TypeSet expected;
+ private final Type actual;
+
+ public UnexpectedTypeError(TypeSet expected, Type actual, Source source) {
+ super("Expected " + expected.describe() + " but was " + actual.describe(), source);
+ this.expected = expected;
+ this.actual = actual;
+ }
+
+ public TypeSet expected() {
+ return expected;
+ }
+
+ public Type actual() {
+ return actual;
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/Variable.java b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/Variable.java
new file mode 100644
index 0000000..1d3a4b6
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/typechecker/Variable.java
@@ -0,0 +1,11 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.zwobble.hobgoblin.compiler.types.Type;
+
+public sealed interface Variable {
+ record Declared() implements Variable {
+ }
+
+ record Defined(Type type) implements Variable {
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/ScalarType.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/ScalarType.java
new file mode 100644
index 0000000..22032a1
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/ScalarType.java
@@ -0,0 +1,8 @@
+package org.zwobble.hobgoblin.compiler.types;
+
+public record ScalarType(String name) implements Type {
+ @Override
+ public String describe() {
+ return name;
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/Type.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/Type.java
new file mode 100644
index 0000000..2692261
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/Type.java
@@ -0,0 +1,4 @@
+package org.zwobble.hobgoblin.compiler.types;
+
+public sealed interface Type extends TypeLevelValue permits ScalarType, TypeLevelValueType {
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java
new file mode 100644
index 0000000..9740cf9
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValue.java
@@ -0,0 +1,5 @@
+package org.zwobble.hobgoblin.compiler.types;
+
+public sealed interface TypeLevelValue permits Type {
+ String describe();
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValueType.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValueType.java
new file mode 100644
index 0000000..b885c25
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeLevelValueType.java
@@ -0,0 +1,8 @@
+package org.zwobble.hobgoblin.compiler.types;
+
+public record TypeLevelValueType(TypeLevelValue value) implements Type {
+ @Override
+ public String describe() {
+ return "TypeLevelValue[" + value.describe() + "]";
+ }
+}
diff --git a/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeSet.java b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeSet.java
new file mode 100644
index 0000000..f587897
--- /dev/null
+++ b/src/main/java/org/zwobble/hobgoblin/compiler/types/TypeSet.java
@@ -0,0 +1,19 @@
+package org.zwobble.hobgoblin.compiler.types;
+
+public sealed interface TypeSet {
+ String describe();
+
+ record SingleType(Type type) implements TypeSet {
+ @Override
+ public String describe() {
+ return type.describe();
+ }
+ }
+
+ record MetaType() implements TypeSet {
+ @Override
+ public String describe() {
+ return "metatype";
+ }
+ }
+}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedArb.java b/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedArb.java
new file mode 100644
index 0000000..5a011d7
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/ast/untyped/UntypedArb.java
@@ -0,0 +1,12 @@
+package org.zwobble.hobgoblin.compiler.ast.untyped;
+
+import org.zwobble.hobgoblin.compiler.sources.NullSource;
+
+public class UntypedArb {
+ private UntypedArb() {
+ }
+
+ public static UntypedTypeLevelReferenceNode typeLevelReference(String name) {
+ return new UntypedTypeLevelReferenceNode(name, NullSource.INSTANCE);
+ }
+}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java
new file mode 100644
index 0000000..98d8064
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerContextArb.java
@@ -0,0 +1,10 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+public class TypeCheckerContextArb {
+ private TypeCheckerContextArb() {
+ }
+
+ public static TypeCheckerContext context() {
+ return new TypeCheckerContext();
+ }
+}
diff --git a/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
new file mode 100644
index 0000000..82704fc
--- /dev/null
+++ b/src/test/java/org/zwobble/hobgoblin/compiler/typechecker/TypeCheckerTypeLevelReferenceTests.java
@@ -0,0 +1,75 @@
+package org.zwobble.hobgoblin.compiler.typechecker;
+
+import org.junit.jupiter.api.Test;
+import org.zwobble.hobgoblin.compiler.ast.typed.TypedTypeLevelReferenceNode;
+import org.zwobble.hobgoblin.compiler.ast.untyped.UntypedArb;
+import org.zwobble.hobgoblin.compiler.types.ScalarType;
+import org.zwobble.hobgoblin.compiler.types.TypeLevelValueType;
+import org.zwobble.hobgoblin.compiler.types.TypeSet;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.zwobble.precisely.AssertThat.assertThat;
+import static org.zwobble.precisely.Matchers.*;
+
+public class TypeCheckerTypeLevelReferenceTests {
+ @Test
+ public void whenNameIsUnrecognisedThenErrorIsThrown() {
+ var untyped = UntypedArb.typeLevelReference("X");
+ var context = TypeCheckerContextArb.context();
+
+ var error = assertThrows(
+ UndeclaredVariableError.class,
+ () -> TypeChecker.typeCheckTypeLevelExpression(untyped, context)
+ );
+
+ assertThat(error.variableName(), equalTo("X"));
+ }
+
+ @Test
+ public void whenNameIsUndefinedThenErrorIsThrown() {
+ var untyped = UntypedArb.typeLevelReference("X");
+ var context = TypeCheckerContextArb.context();
+ context.declare("X");
+
+ var error = assertThrows(
+ UndefinedVariableError.class,
+ () -> TypeChecker.typeCheckTypeLevelExpression(untyped, context)
+ );
+
+ assertThat(error.variableName(), equalTo("X"));
+ }
+
+ @Test
+ public void whenVariableIsNotTypeLevelValueThenErrorIsThrown() {
+ var untyped = UntypedArb.typeLevelReference("X");
+ var context = TypeCheckerContextArb.context();
+ context.declare("X");
+ var scalarType = new ScalarType("Int");
+ context.define("X", scalarType);
+
+ var error = assertThrows(
+ UnexpectedTypeError.class,
+ () -> TypeChecker.typeCheckTypeLevelExpression(untyped, context)
+ );
+
+ assertThat(error.expected(), equalTo(new TypeSet.MetaType()));
+ assertThat(error.actual(), equalTo(scalarType));
+ }
+
+ @Test
+ public void whenNameIsMetaTypeThenValueIsType() {
+ var untyped = UntypedArb.typeLevelReference("X");
+ var context = TypeCheckerContextArb.context();
+ context.declare("X");
+ var scalarType = new ScalarType("Int");
+ context.define("X", new TypeLevelValueType(scalarType));
+
+ var typed = TypeChecker.typeCheckTypeLevelExpression(untyped, context);
+
+ assertThat(typed, instanceOf(
+ TypedTypeLevelReferenceNode.class,
+ has("name", TypedTypeLevelReferenceNode::name, equalTo("X")),
+ has("value", TypedTypeLevelReferenceNode::value, equalTo(scalarType))
+ ));
+ }
+}