Make it possible to use an enum in an expression (i.e. selecting one of the enum values: Foo.A)

This commit is contained in:
2023-03-15 19:14:04 +01:00
parent 3b928d621b
commit 6fd3f5a2e6
14 changed files with 107 additions and 94 deletions

View File

@@ -82,7 +82,7 @@ destroy_statement
expression
: atom #atomExpression
| IDENT (PERIOD IDENT)+ #structFieldAccessExpression
| IDENT (PERIOD IDENT)+ #memberAccessExpression
| OPAR expression CPAR #parenthesisExpression
| lhs=expression MUL rhs=expression #multiplicationExpression
| lhs=expression DIV rhs=expression #divisionExpression

View File

@@ -9,8 +9,7 @@ import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
import de.hsrm.compiler.Klang.nodes.statements.*;
import de.hsrm.compiler.Klang.types.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
public class ContextAnalysis extends KlangBaseVisitor<Node> {
Map<String, VariableDeclaration> vars = new HashMap<>();
@@ -319,43 +318,68 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
}
@Override
public Node visitStructFieldAccessExpression(KlangParser.StructFieldAccessExpressionContext ctx) {
String varName = ctx.IDENT(0).getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
String[] path = new String[ctx.IDENT().size() - 1];
public Node visitMemberAccessExpression(KlangParser.MemberAccessExpressionContext ctx) {
var baseName = ctx.IDENT(0).getText();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
// Create a list of member names. This excludes
// the first entry as it is the base name.
var path = new ArrayList<String>();
for (int i = 1; i < ctx.IDENT().size(); i++) {
path[i - 1] = ctx.IDENT(i).getText();
path.add(ctx.IDENT(i).getText());
}
// Determine if the base name points to an enum or a variable
var enumDef = enumDefs.get(baseName);
if (enumDef != null) {
if (path.size() != 1) {
var error = "Illegal access to enum " + enumDef.name + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var enumValueName = path.get(0);
if (Arrays.stream(enumDef.enums).noneMatch(e -> e.equals(enumValueName))) {
var error = "Unknown enum value " + enumValueName + " of enum " + enumDef.name + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var enumAccessExpression = new EnumAccessExpression(baseName, enumValueName);
enumAccessExpression.type = enumDef.type;
enumAccessExpression.line = line;
enumAccessExpression.col = col;
return enumAccessExpression;
}
// Get the referenced variable, make sure it is defined
var variableDef = this.vars.get(varName);
var variableDef = vars.get(baseName);
if (variableDef == null) {
String error = "Variable with name " + varName + " not defined.";
var error = "Variable with name " + baseName + " not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
// Make sure it references a struct
if (variableDef.type.isPrimitiveType()) {
String error = "Variable must reference a struct but references " + variableDef.type.getName() + ".";
var error = "Variable must reference a struct but references " + variableDef.type.getName() + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
// Get the type of the result of this expression
String structName = variableDef.type.getName();
var structName = variableDef.type.getName();
Type resultType;
try {
resultType = Helper.drillType(this.structDefs, structName, path, 0);
resultType = Helper.drillType(structDefs, structName, path.toArray(new String[0]), 0);
} catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
}
Node result = new StructFieldAccessExpression(varName, structName, path);
result.type = resultType;
result.line = line;
result.col = col;
return result;
var memberAccessExpression = new MemberAccessExpression(baseName, structName, path.toArray(new String[0]));
memberAccessExpression.type = resultType;
memberAccessExpression.line = line;
memberAccessExpression.col = col;
return memberAccessExpression;
}
@Override
@@ -749,7 +773,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
structDef.line = line;
structDef.col = col;
return super.visitStructDef(ctx);
return structDef;
}
@Override

View File

@@ -2,8 +2,7 @@ package de.hsrm.compiler.Klang;
import de.hsrm.compiler.Klang.helper.Helper;
import de.hsrm.compiler.Klang.nodes.*;
import de.hsrm.compiler.Klang.types.EnumType;
import de.hsrm.compiler.Klang.types.StructType;
import de.hsrm.compiler.Klang.types.NamedType;
import de.hsrm.compiler.Klang.types.Type;
import java.util.HashMap;
@@ -127,7 +126,7 @@ public class GetDefinitions extends KlangBaseVisitor<Node> {
var enumDef = new EnumDefinition(enumName, enumFields.toArray(new String[0]));
enumDef.line = ctx.start.getLine();
enumDef.col = ctx.start.getCharPositionInLine();
enumDef.type = new EnumType(enumName);
enumDef.type = new NamedType(enumName);
enumDefs.put(enumName, enumDef);
return null;
@@ -169,7 +168,7 @@ public class GetDefinitions extends KlangBaseVisitor<Node> {
var structDef = new StructDefinition(structName, structFields.values().toArray(new StructField[0]));
structDef.line = line;
structDef.col = col;
structDef.type = new StructType(structName);
structDef.type = new NamedType(structName);
structDefs.put(structName, structDef);
return null;
@@ -209,7 +208,7 @@ public class GetDefinitions extends KlangBaseVisitor<Node> {
}
var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null);
functionDef.type = Type.getByName(ctx.returnType.getText());
functionDef.type = Type.getByName(ctx.returnType.type().getText());
functionDef.line = line;
functionDef.col = col;
functionDefs.put(funcName, functionDef);

View File

@@ -0,0 +1,18 @@
package de.hsrm.compiler.Klang.nodes.expressions;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class EnumAccessExpression extends Expression {
public String enumName;
public String enumValueName;
public EnumAccessExpression(String enumName, String enumValueName) {
this.enumName = enumName;
this.enumValueName = enumValueName;
}
@Override
public <R> R welcome(Visitor<R> v) {
return v.visit(this);
}
}

View File

@@ -2,12 +2,12 @@ package de.hsrm.compiler.Klang.nodes.expressions;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class StructFieldAccessExpression extends Expression {
public class MemberAccessExpression extends Expression {
public String varName;
public String structName;
public String[] path;
public StructFieldAccessExpression(String varName, String structName, String[] path) {
public MemberAccessExpression(String varName, String structName, String[] path) {
this.varName = varName;
this.structName = structName;
this.path = path;

View File

@@ -1,10 +1,9 @@
package de.hsrm.compiler.Klang.types;
public class EnumType extends Type {
public class NamedType extends Type {
public String name;
public EnumType(String name) {
public NamedType(String name) {
this.name = name;
}
@@ -19,7 +18,7 @@ public class EnumType extends Type {
return this;
}
throw new RuntimeException("Type mismatch: cannot combine enum " + getName() + " and " + that.getName());
throw new RuntimeException("Type mismatch: cannot combine " + getName() + " and " + that.getName());
}
@Override
@@ -38,8 +37,8 @@ public class EnumType extends Type {
return true;
}
if (that instanceof EnumType) {
var thatType = (EnumType) that;
if (that instanceof NamedType) {
var thatType = (NamedType) that;
return getName().equals(thatType.getName());
}

View File

@@ -1,54 +0,0 @@
package de.hsrm.compiler.Klang.types;
public class StructType extends Type {
public String name;
public StructType(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public Type combine(Type that) {
if (that.equals(this)) {
return this;
}
// If you combine a null type with a struct type, you
// always get the struct type back.
if (that == NullType.getType()) {
return this;
}
throw new RuntimeException("Type missmatch: cannot combine " + this.getName() + " and " + that.getName());
}
@Override
public boolean isPrimitiveType() {
return false;
}
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that instanceof StructType) {
StructType thatType = (StructType) that;
return this.getName().equals(thatType.getName());
}
return false;
}
@Override
public boolean isNumericType() {
return false;
}
}

View File

@@ -1,5 +1,7 @@
package de.hsrm.compiler.Klang.types;
import java.util.Set;
public abstract class Type {
// Returns an instance of IntegerType
@@ -26,7 +28,7 @@ public abstract class Type {
case "int": return getIntegerType();
case "float": return getFloatType();
case "null": return getNullType();
default: return new StructType(name);
default: return new NamedType(name);
}
}

View File

@@ -484,7 +484,7 @@ public class EvalVisitor implements Visitor<Value> {
}
@Override
public Value visit(StructFieldAccessExpression e) {
public Value visit(MemberAccessExpression e) {
Value var = this.env.get(e.varName);
Map<String, Value> struct = var.asStruct();
@@ -496,6 +496,11 @@ public class EvalVisitor implements Visitor<Value> {
return currentValue;
}
@Override
public Value visit(EnumAccessExpression e) {
return null;
}
@Override
public Value visit(ConstructorCall e) {
StructDefinition structDef = this.structs.get(e.structName);

View File

@@ -792,7 +792,7 @@ public class GenASM implements Visitor<Void> {
}
@Override
public Void visit(StructFieldAccessExpression e) {
public Void visit(MemberAccessExpression e) {
var structDef = this.structs.get(e.structName);
int offset = this.env.get(e.varName);
@@ -817,6 +817,11 @@ public class GenASM implements Visitor<Void> {
return null;
}
@Override
public Void visit(EnumAccessExpression e) {
return null;
}
@Override
public Void visit(ConstructorCall e) {
// push arguments onto the stack

View File

@@ -250,7 +250,12 @@ class GetVars implements Visitor<Void> {
}
@Override
public Void visit(StructFieldAccessExpression e) {
public Void visit(MemberAccessExpression e) {
return null;
}
@Override
public Void visit(EnumAccessExpression e) {
return null;
}

View File

@@ -420,7 +420,7 @@ public class PrettyPrintVisitor implements Visitor<Void> {
}
@Override
public Void visit(StructFieldAccessExpression e) {
public Void visit(MemberAccessExpression e) {
ex.write(e.varName);
for (int i = 0; i < e.path.length; i++) {
ex.write(".");
@@ -429,6 +429,15 @@ public class PrettyPrintVisitor implements Visitor<Void> {
return null;
}
@Override
public Void visit(EnumAccessExpression e) {
ex.write(e.enumName);
ex.write(".");
ex.write(e.enumValueName);
return null;
}
@Override
public Void visit(ConstructorCall e) {
ex.write("create " + e.structName + "(");

View File

@@ -40,7 +40,8 @@ public interface Visitor<R> {
R visit(EnumDefinition e);
R visit(StructDefinition e);
R visit(StructField e);
R visit(StructFieldAccessExpression e);
R visit(MemberAccessExpression e);
R visit(EnumAccessExpression e);
R visit(ConstructorCall e);
R visit(NullExpression e);
R visit(DestructorCall e);

View File

@@ -10,7 +10,7 @@ public class FunctionDefinitionTest {
@Test
void shouldNotThrowIfReturnTypeIsReferringToAnEnum() {
// given
var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: int): bar { return bar.A; } foo();");
var tree = Helper.prepareParser(" enum bar {A,B,C} function foo(a: int): bar { return bar.A; } foo(1);");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),