Make functions first class

This commit is contained in:
Astrash committed 2023-10-27 13:37:07 +11:00
1 parent 77dbe92ef7
commit ceba9512c7
10 files changed
+214 -162

No files matched your search

@@ -156,8 +156,8 @@ tasks.register("genTerrascriptAstClasses") {
ASTNode("Grouping", listOf("expression" to "Expr")), ASTNode("Grouping", listOf("expression" to "Expr")),
ASTNode("Literal", listOf("value" to "Object", "type" to "Type")), ASTNode("Literal", listOf("value" to "Object", "type" to "Type")),
ASTNode("Unary", listOf("operator" to "UnaryOperator", "operand" to "Expr")), ASTNode("Unary", listOf("operator" to "UnaryOperator", "operand" to "Expr")),
ASTNode("Call", listOf("identifier" to "String", "arguments" to "List<Expr>"), listOf("environment" to "Environment", "symbol" to "Symbol.Function")), ASTNode("Call", listOf("function" to "Expr", "arguments" to "List<Expr>")),
ASTNode("Variable", listOf("identifier" to "String"), listOf("symbol" to "Symbol.Variable")), ASTNode("Variable", listOf("identifier" to "String"), listOf("symbol" to "Symbol", "scope" to "Environment")),
ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "Expr")), ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "Expr")),
ASTNode("Void", listOf()), ASTNode("Void", listOf()),
), ),
@@ -168,6 +168,7 @@ tasks.register("genTerrascriptAstClasses") {
listOf( listOf(
"com.dfsek.terra.addons.terrascript.v2.Type", "com.dfsek.terra.addons.terrascript.v2.Type",
"com.dfsek.terra.api.util.generic.pair.Pair", "com.dfsek.terra.api.util.generic.pair.Pair",
"com.dfsek.terra.addons.terrascript.v2.Environment",
"com.dfsek.terra.addons.terrascript.v2.Environment.Symbol", "com.dfsek.terra.addons.terrascript.v2.Environment.Symbol",
"com.dfsek.terra.addons.terrascript.v2.lexer.SourcePosition", "com.dfsek.terra.addons.terrascript.v2.lexer.SourcePosition",
"java.util.List", "java.util.List",
@@ -176,8 +177,8 @@ tasks.register("genTerrascriptAstClasses") {
listOf( listOf(
ASTNode("Expression", listOf("expression" to "Expr")), ASTNode("Expression", listOf("expression" to "Expr")),
ASTNode("Block", listOf("statements" to "List<Stmt>")), ASTNode("Block", listOf("statements" to "List<Stmt>")),
ASTNode("FunctionDeclaration", listOf("identifier" to "String", "parameters" to "List<Pair<String, Type>>", "returnType" to "Type", "body" to "Block"), listOf("symbol" to "Symbol.Function")), ASTNode("FunctionDeclaration", listOf("identifier" to "String", "parameters" to "List<Pair<String, Type>>", "returnType" to "Type", "body" to "Block"), listOf("symbol" to "Symbol")),
ASTNode("VariableDeclaration", listOf("type" to "Type", "identifier" to "String", "value" to "Expr")), ASTNode("VariableDeclaration", listOf("type" to "Type", "identifier" to "String", "value" to "Expr"), listOf("scope" to "Environment")),
ASTNode("Return", listOf("value" to "Expr"), listOf("type" to "Type")), ASTNode("Return", listOf("value" to "Expr"), listOf("type" to "Type")),
ASTNode("If", listOf("condition" to "Expr", "trueBody" to "Block", "elseIfClauses" to "List<Pair<Expr, Block>>", "elseBody" to "Optional<Block>")), ASTNode("If", listOf("condition" to "Expr", "trueBody" to "Block", "elseIfClauses" to "List<Pair<Expr, Block>>", "elseBody" to "Optional<Block>")),
ASTNode("For", listOf("initializer" to "Stmt", "condition" to "Expr", "incrementer" to "Expr", "body" to "Block")), ASTNode("For", listOf("initializer" to "Stmt", "condition" to "Expr", "incrementer" to "Expr", "body" to "Block")),
@@ -201,7 +202,7 @@ tasks.register("genTerrascriptAstClasses") {
ASTNode("Grouping", listOf("expression" to "TypedExpr")), ASTNode("Grouping", listOf("expression" to "TypedExpr")),
ASTNode("Literal", listOf("value" to "Object")), ASTNode("Literal", listOf("value" to "Object")),
ASTNode("Unary", listOf("operator" to "UnaryOperator", "operand" to "TypedExpr")), ASTNode("Unary", listOf("operator" to "UnaryOperator", "operand" to "TypedExpr")),
ASTNode("Call", listOf("identifier" to "String", "arguments" to "List<TypedExpr>", "scopedIdentifier" to "String")), ASTNode("Call", listOf("function" to "TypedExpr", "arguments" to "List<TypedExpr>")),
ASTNode("Variable", listOf("identifier" to "String")), ASTNode("Variable", listOf("identifier" to "String")),
ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "TypedExpr")), ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "TypedExpr")),
ASTNode("Void", listOf()), ASTNode("Void", listOf()),
@@ -8,9 +8,6 @@ import java.util.Map;
import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.NonexistentSymbolException; import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.NonexistentSymbolException;
import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.SymbolAlreadyExistsException; import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.SymbolAlreadyExistsException;
import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.SymbolTypeMismatchException;
import com.dfsek.terra.addons.terrascript.v2.Environment.Symbol.Function;
import com.dfsek.terra.addons.terrascript.v2.Environment.Symbol.Variable;
import com.dfsek.terra.addons.terrascript.v2.codegen.NativeFunction; import com.dfsek.terra.addons.terrascript.v2.codegen.NativeFunction;
@@ -36,8 +33,10 @@ public class Environment {
this.inLoop = inLoop; this.inLoop = inLoop;
this.index = index; this.index = index;
this.name = String.join("_", getNestedIndexes().stream().map(Object::toString).toList()); this.name = String.join("_", getNestedIndexes().stream().map(Object::toString).toList());
// Populate symbol tables with built-in Java implemented methods // Populate global scope with built-in Java implemented methods
NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) -> symbolTable.put(name, new Symbol.Function(function.getReturnType(), function.getParameterTypes(), this))); // TODO - Replace with AST import nodes
if (index == 0) NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) ->
symbolTable.put(name, new Symbol.Variable(new Type.Function.Native(function.getReturnType(), function.getParameterTypes(), name, this, function))));
} }
public static Environment global() { public static Environment global() {
@@ -69,19 +68,6 @@ public class Environment {
return outer; return outer;
} }
public Function getFunction(String id) throws NonexistentSymbolException, SymbolTypeMismatchException {
Function function;
Symbol symbol = symbolTable.get(id);
if(symbol == null) {
if(outer == null) throw new NonexistentSymbolException();
function = outer.getFunction(id);
} else {
if(!(symbol instanceof Function)) throw new SymbolTypeMismatchException();
function = (Function) symbol;
}
return function;
}
/** /**
* Returns symbol table entry for a variable identifier, includes enclosing scopes in lookup. * Returns symbol table entry for a variable identifier, includes enclosing scopes in lookup.
* <br> * <br>
@@ -93,19 +79,25 @@ public class Environment {
* @return variable symbol table entry * @return variable symbol table entry
* *
* @throws NonexistentSymbolException if symbol is not declared in symbol table * @throws NonexistentSymbolException if symbol is not declared in symbol table
* @throws SymbolTypeMismatchException if symbol is not a variable
*/ */
public Variable getVariable(String id) throws NonexistentSymbolException, SymbolTypeMismatchException { public Symbol getVariable(String id) throws NonexistentSymbolException {
Variable variable;
Symbol symbol = symbolTable.get(id); Symbol symbol = symbolTable.get(id);
if(symbol == null) { if(symbol != null) return symbol;
if(!canAccessOuterVariables || outer == null) throw new NonexistentSymbolException(); if(outer == null) throw new NonexistentSymbolException();
variable = outer.getVariable(id); if(canAccessOuterVariables) return outer.getVariable(id);
} else {
if(!(symbol instanceof Variable)) throw new SymbolTypeMismatchException(); // Only functions can be accessed from restricted scopes
variable = (Variable) symbol; // TODO - Only make applicable to functions that cannot be reassigned
} Symbol potentialFunction = outer.getVariableUnrestricted(id);
return variable; if(!(potentialFunction.type instanceof Type.Function)) throw new NonexistentSymbolException();
return potentialFunction;
}
private Symbol getVariableUnrestricted(String id) throws NonexistentSymbolException {
Symbol symbol = symbolTable.get(id);
if(symbol != null) return symbol;
if(outer == null) throw new NonexistentSymbolException();
return outer.getVariableUnrestricted(id);
} }
public void put(String id, Symbol symbol) throws SymbolAlreadyExistsException { public void put(String id, Symbol symbol) throws SymbolAlreadyExistsException {
@@ -115,34 +107,22 @@ public class Environment {
public static abstract class Symbol { public static abstract class Symbol {
public static class Function extends Symbol { public final Type type;
public final Type type;
public final List<Type> parameters;
public final Environment scope;
public Function(Type type, List<Type> parameters, Environment scope) {
this.type = type;
this.parameters = parameters;
this.scope = scope;
}
}
public Symbol(Type type) {
this.type = type;
}
public static class Variable extends Symbol { public static class Variable extends Symbol {
public final Type type;
public Variable(Type type) { public Variable(Type type) {
this.type = type; super(type);
} }
} }
} }
public static class ScopeException extends RuntimeException { public static class ScopeException extends Exception {
public static class SymbolAlreadyExistsException extends ScopeException { public static class SymbolAlreadyExistsException extends ScopeException {
} }
@@ -151,8 +131,5 @@ public class Environment {
public static class NonexistentSymbolException extends ScopeException { public static class NonexistentSymbolException extends ScopeException {
} }
public static class SymbolTypeMismatchException extends ScopeException {
}
} }
} }
@@ -1,6 +1,15 @@
package com.dfsek.terra.addons.terrascript.v2; package com.dfsek.terra.addons.terrascript.v2;
import java.util.Optional; import com.dfsek.terra.addons.terrascript.v2.codegen.CodegenType;
import com.dfsek.terra.addons.terrascript.v2.codegen.NativeFunction;
import com.dfsek.terra.api.util.generic.pair.Pair;
import com.google.common.collect.Streams;
import javax.annotation.Nullable;
import java.util.List;
public interface Type { public interface Type {
@@ -21,13 +30,71 @@ public interface Type {
return this.equals(type); return this.equals(type);
} }
static Optional<Type> from(Class<?> clazz) { CodegenType getCodegenType();
return Optional.ofNullable(
clazz == double.class ? NUMBER : class Function implements Type {
clazz == String.class ? STRING :
clazz == boolean.class ? BOOLEAN : private final Type returnType;
clazz == void.class ? VOID : private final List<Type> parameters;
null); private final String id;
public Function(Type returnType, List<Type> parameters, @Nullable String identifier, Environment declarationScope) {
this.returnType = returnType;
this.parameters = parameters;
this.id = identifier == null ? "ANONYMOUS" : identifier + declarationScope.name;
}
public Type getReturnType() {
return returnType;
}
public List<Type> getParameters() {
return parameters;
}
public String getId() {
return id;
}
@Override
public boolean typeOf(Type type) {
if(!(type instanceof Function function)) return false;
return returnType.typeOf(function.returnType) && paramsAreSubtypes(parameters, function.parameters);
}
@Override
public CodegenType getCodegenType() {
return CodegenType.OBJECT;
}
private static boolean paramsAreSubtypes(List<Type> subtypes, List<Type> superTypes) {
if(subtypes.size() != superTypes.size()) return false;
return Streams.zip(subtypes.stream(), superTypes.stream(), Pair::of).allMatch(p -> p.getLeft().typeOf(p.getRight()));
}
@Override
public java.lang.reflect.Type javaType() {
return Function.class;
}
@Override
public String toString() {
return "(" + String.join(",", parameters.stream().map(Object::toString).toList()) + ") -> " + returnType;
}
public static class Native extends Function {
private final NativeFunction nativeFunction;
public Native(Type returnType, List<Type> parameters, @org.jetbrains.annotations.Nullable String identifier,
Environment declarationScope, NativeFunction nativeFunction) {
super(returnType, parameters, identifier, declarationScope);
this.nativeFunction = nativeFunction;
}
public NativeFunction getNativeFunction() {
return nativeFunction;
}
}
} }
Type NUMBER = new Type() { Type NUMBER = new Type() {
@@ -36,6 +103,11 @@ public interface Type {
return double.class; return double.class;
} }
@Override
public CodegenType getCodegenType() {
return CodegenType.DOUBLE;
}
@Override @Override
public String toString() { public String toString() {
return "num"; return "num";
@@ -49,6 +121,11 @@ public interface Type {
return int.class; return int.class;
} }
@Override
public CodegenType getCodegenType() {
return CodegenType.INTEGER;
}
@Override @Override
public String toString() { public String toString() {
return "int"; return "int";
@@ -62,6 +139,11 @@ public interface Type {
return String.class; return String.class;
} }
@Override
public CodegenType getCodegenType() {
return CodegenType.STRING;
}
@Override @Override
public String toString() { public String toString() {
return "str"; return "str";
@@ -74,6 +156,11 @@ public interface Type {
return boolean.class; return boolean.class;
} }
@Override
public CodegenType getCodegenType() {
return CodegenType.BOOLEAN;
}
@Override @Override
public String toString() { public String toString() {
return "bool"; return "bool";
@@ -86,6 +173,11 @@ public interface Type {
return void.class; return void.class;
} }
@Override
public CodegenType getCodegenType() {
return CodegenType.VOID;
}
@Override @Override
public String toString() { public String toString() {
return "()"; return "()";
@@ -1,13 +1,7 @@
package com.dfsek.terra.addons.terrascript.v2.codegen; package com.dfsek.terra.addons.terrascript.v2.codegen;
import com.dfsek.terra.addons.terrascript.v2.Type;
import com.dfsek.terra.addons.terrascript.v2.exception.CompilerBugException;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import java.util.Map;
public class CodegenType { public class CodegenType {
@@ -28,21 +22,12 @@ public class CodegenType {
return descriptor; return descriptor;
} }
public static final CodegenType BOOLEAN_PRIMITIVE = new CodegenType(InstructionType.INTEGER, "Z"); public static final CodegenType BOOLEAN = new CodegenType(InstructionType.INTEGER, "Z");
public static final CodegenType STRING = new CodegenType(InstructionType.OBJECT, "Ljava/lang/String;");
private static final Map<Type, CodegenType> TYPE_MAP = Map.of( public static final CodegenType DOUBLE = new CodegenType(InstructionType.DOUBLE, "D");
Type.BOOLEAN, BOOLEAN_PRIMITIVE, public static final CodegenType INTEGER = new CodegenType(InstructionType.INTEGER, "I");
Type.STRING, new CodegenType(InstructionType.OBJECT, "Ljava/lang/String;"), public static final CodegenType VOID = new CodegenType(InstructionType.VOID, "V");
Type.NUMBER, new CodegenType(InstructionType.DOUBLE, "D"), public static final CodegenType OBJECT = new CodegenType(InstructionType.OBJECT, "Ljava/lang/Object;");
Type.VOID, new CodegenType(InstructionType.VOID, "V")
);
public static CodegenType codegenType(Type type) {
CodegenType out = TYPE_MAP.get(type);
if(out == null)
throw new CompilerBugException();
return out;
}
public enum InstructionType { public enum InstructionType {
DOUBLE { DOUBLE {
@@ -29,8 +29,6 @@ import com.dfsek.terra.addons.terrascript.v2.codegen.NativeFunction;
import com.dfsek.terra.addons.terrascript.v2.codegen.TerraScript; import com.dfsek.terra.addons.terrascript.v2.codegen.TerraScript;
import com.dfsek.terra.addons.terrascript.v2.exception.CompilerBugException; import com.dfsek.terra.addons.terrascript.v2.exception.CompilerBugException;
import com.dfsek.terra.addons.terrascript.v2.util.ASMUtil; import com.dfsek.terra.addons.terrascript.v2.util.ASMUtil;
import com.dfsek.terra.addons.terrascript.v2.parser.BinaryOperator;
import com.dfsek.terra.addons.terrascript.v2.parser.UnaryOperator;
import com.dfsek.terra.api.util.generic.pair.Pair; import com.dfsek.terra.api.util.generic.pair.Pair;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
@@ -51,8 +49,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static com.dfsek.terra.addons.terrascript.v2.parser.BinaryOperator.*;
import static com.dfsek.terra.addons.terrascript.v2.parser.UnaryOperator.*;
import static com.dfsek.terra.addons.terrascript.v2.util.ASMUtil.dynamicName; import static com.dfsek.terra.addons.terrascript.v2.util.ASMUtil.dynamicName;
public class TerraScriptClassGenerator { public class TerraScriptClassGenerator {
@@ -163,7 +159,6 @@ public class TerraScriptClassGenerator {
public void generate(Block root) { public void generate(Block root) {
this.visitBlockTypedStmt(root); this.visitBlockTypedStmt(root);
} }
@Override @Override
@@ -172,7 +167,7 @@ public class TerraScriptClassGenerator {
case EQUALS, NOT_EQUALS, BOOLEAN_AND, BOOLEAN_OR, GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> pushComparisonBool(expr); case EQUALS, NOT_EQUALS, BOOLEAN_AND, BOOLEAN_OR, GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> pushComparisonBool(expr);
case ADD -> { case ADD -> {
pushBinaryOperands(expr); pushBinaryOperands(expr);
CodegenType codegenType = CodegenType.codegenType(expr.type); CodegenType codegenType = expr.type.getCodegenType();
if(codegenType.bytecodeType() == InstructionType.DOUBLE) if(codegenType.bytecodeType() == InstructionType.DOUBLE)
method.visitInsn(Opcodes.DADD); method.visitInsn(Opcodes.DADD);
else if (Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;")) else if (Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;"))
@@ -196,7 +191,7 @@ public class TerraScriptClassGenerator {
@Override @Override
public Void visitLiteralTypedExpr(Literal expr) { public Void visitLiteralTypedExpr(Literal expr) {
if(CodegenType.codegenType(expr.type) == CodegenType.BOOLEAN_PRIMITIVE) if(expr.type.getCodegenType() == CodegenType.BOOLEAN)
if ((boolean) expr.value) pushTrue(); else pushFalse(); if ((boolean) expr.value) pushTrue(); else pushFalse();
else method.visitLdcInsn(expr.value); else method.visitLdcInsn(expr.value);
return null; return null;
@@ -214,23 +209,25 @@ public class TerraScriptClassGenerator {
@Override @Override
public Void visitCallTypedExpr(Call expr) { public Void visitCallTypedExpr(Call expr) {
if (NativeFunction.BUILTIN_FUNCTIONS.containsKey(expr.identifier)) { // TODO - Remove specific handling of native functions
NativeFunction function = NativeFunction.BUILTIN_FUNCTIONS.get(expr.identifier); if (expr.function.type instanceof Type.Function.Native nativeFunction) {
NativeFunction function = nativeFunction.getNativeFunction();
function.pushInstance(method); function.pushInstance(method);
expr.arguments.forEach(a -> a.accept(this)); expr.arguments.forEach(a -> a.accept(this));
function.callMethod(method); function.callMethod(method);
return null; return null;
} }
// TODO - Add support for invokevirtual
expr.arguments.forEach(a -> a.accept(this)); expr.arguments.forEach(a -> a.accept(this));
List<Type> parameters = expr.arguments.stream().map(e -> e.type).toList(); List<Type> parameters = expr.arguments.stream().map(e -> e.type).toList();
method.visitMethodInsn(Opcodes.INVOKESTATIC, className, expr.scopedIdentifier, getFunctionDescriptor(parameters, expr.type), false); method.visitMethodInsn(Opcodes.INVOKESTATIC, className, ((Type.Function) expr.function.type).getId(), getFunctionDescriptor(parameters, expr.type), false);
return null; return null;
} }
@Override @Override
public Void visitVariableTypedExpr(Variable expr) { public Void visitVariableTypedExpr(Variable expr) {
Type varType = expr.type; Type varType = expr.type;
method.visitVarInsn(CodegenType.codegenType(varType).bytecodeType().loadInsn(), lvTable.get(expr.identifier)); method.visitVarInsn(varType.getCodegenType().bytecodeType().loadInsn(), lvTable.get(expr.identifier));
return null; return null;
} }
@@ -238,7 +235,7 @@ public class TerraScriptClassGenerator {
public Void visitAssignmentTypedExpr(Assignment expr) { public Void visitAssignmentTypedExpr(Assignment expr) {
expr.rValue.accept(this); expr.rValue.accept(this);
Type type = expr.lValue.type; Type type = expr.lValue.type;
method.visitVarInsn(CodegenType.codegenType(type).bytecodeType().storeInsn(), lvTable.get(expr.lValue.identifier)); method.visitVarInsn(type.getCodegenType().bytecodeType().storeInsn(), lvTable.get(expr.lValue.identifier));
return null; return null;
} }
@@ -280,7 +277,7 @@ public class TerraScriptClassGenerator {
int lvidx = 0; int lvidx = 0;
for (Pair<String, Type> parameter : stmt.parameters) { for (Pair<String, Type> parameter : stmt.parameters) {
funcGenerator.lvTable.put(parameter.getLeft(), lvidx); funcGenerator.lvTable.put(parameter.getLeft(), lvidx);
lvidx += CodegenType.codegenType(parameter.getRight()).bytecodeType().slotSize(); // Increment by how many slots data type takes lvidx += parameter.getRight().getCodegenType().bytecodeType().slotSize(); // Increment by how many slots data type takes
} }
// Generate method bytecode // Generate method bytecode
@@ -298,14 +295,14 @@ public class TerraScriptClassGenerator {
public Void visitVariableDeclarationTypedStmt(VariableDeclaration stmt) { public Void visitVariableDeclarationTypedStmt(VariableDeclaration stmt) {
stmt.value.accept(this); stmt.value.accept(this);
lvTable.put(stmt.identifier, lvs.newLocal(ASMUtil.tsTypeToAsmType(stmt.type))); lvTable.put(stmt.identifier, lvs.newLocal(ASMUtil.tsTypeToAsmType(stmt.type)));
method.visitVarInsn(CodegenType.codegenType(stmt.type).bytecodeType().storeInsn(), lvTable.get(stmt.identifier)); method.visitVarInsn(stmt.type.getCodegenType().bytecodeType().storeInsn(), lvTable.get(stmt.identifier));
return null; return null;
} }
@Override @Override
public Void visitReturnTypedStmt(Return stmt) { public Void visitReturnTypedStmt(Return stmt) {
stmt.value.accept(this); stmt.value.accept(this);
method.visitInsn(CodegenType.codegenType(stmt.value.type).bytecodeType().returnInsn()); method.visitInsn(stmt.value.type.getCodegenType().bytecodeType().returnInsn());
return null; return null;
} }
@@ -530,9 +527,9 @@ public class TerraScriptClassGenerator {
private String getFunctionDescriptor(List<Type> parameters, Type returnType) { private String getFunctionDescriptor(List<Type> parameters, Type returnType) {
StringBuilder sb = new StringBuilder().append("("); StringBuilder sb = new StringBuilder().append("(");
parameters.stream().map(parameter -> CodegenType.codegenType(parameter).getDescriptor()).forEach(sb::append); parameters.stream().map(parameter -> parameter.getCodegenType().getDescriptor()).forEach(sb::append);
sb.append(")"); sb.append(")");
sb.append(CodegenType.codegenType(returnType).getDescriptor()); sb.append(returnType.getCodegenType().getDescriptor());
return sb.toString(); return sb.toString();
} }
} }
@@ -326,7 +326,15 @@ public class Parser {
return new Expr.Unary(operator, unary(), position); return new Expr.Unary(operator, unary(), position);
} }
} }
return primary(); return postfix();
}
private Expr postfix() {
Expr expr = primary();
while (current().isType(TokenType.OPEN_PAREN)) {
expr = call(expr);
}
return expr;
} }
private Expr primary() { private Expr primary() {
@@ -336,10 +344,7 @@ public class Parser {
case NUMBER -> new Expr.Literal(Double.parseDouble(token.lexeme()), Type.NUMBER, position); case NUMBER -> new Expr.Literal(Double.parseDouble(token.lexeme()), Type.NUMBER, position);
case STRING -> new Expr.Literal(token.lexeme(), Type.STRING, position); case STRING -> new Expr.Literal(token.lexeme(), Type.STRING, position);
case BOOLEAN -> new Expr.Literal(Boolean.parseBoolean(token.lexeme()), Type.BOOLEAN, position); case BOOLEAN -> new Expr.Literal(Boolean.parseBoolean(token.lexeme()), Type.BOOLEAN, position);
case IDENTIFIER -> { case IDENTIFIER -> variable(token);
if(current().isType(TokenType.OPEN_PAREN)) yield call(token);
else yield variable(token);
}
case OPEN_PAREN -> { case OPEN_PAREN -> {
if(current().isType(TokenType.CLOSE_PAREN)) { if(current().isType(TokenType.CLOSE_PAREN)) {
consumeUnchecked(); // Consume ')' consumeUnchecked(); // Consume ')'
@@ -353,22 +358,20 @@ public class Parser {
}; };
} }
private Expr call(Token identifier) { private Expr call(Expr function) {
String id = identifier.lexeme(); SourcePosition position = consume("Expected '(' to initiate function call on function", TokenType.OPEN_PAREN).position();
SourcePosition position = consume("Expected '(' to initiate function call on function '" + id + "'",
TokenType.OPEN_PAREN).position();
List<Expr> args = new ArrayList<>(); List<Expr> args = new ArrayList<>();
while(!current().isType(TokenType.CLOSE_PAREN)) { while(!current().isType(TokenType.CLOSE_PAREN)) {
args.add(expression()); args.add(expression());
if(current().isType(TokenType.CLOSE_PAREN)) break; if(current().isType(TokenType.CLOSE_PAREN)) break;
consume("Expected ',' or ')' after passed argument in function call of '" + id + "'", TokenType.SEPARATOR); consume("Expected ',' or ')' after passed argument in function call", TokenType.SEPARATOR);
} }
consume("Expected ')' after " + (args.size() == 0 ? "')'" : "arguments") + " in function call of '" + id + "'", consume("Expected ')' after " + (args.size() == 0 ? "')'" : "arguments") + " in function call",
TokenType.CLOSE_PAREN); TokenType.CLOSE_PAREN);
return new Expr.Call(id, args, position); return new Expr.Call(function, args, position);
} }
private Expr variable(Token identifier) { private Expr variable(Token identifier) {
@@ -2,10 +2,10 @@ package com.dfsek.terra.addons.terrascript.v2.semanticanalysis;
import com.dfsek.terra.addons.terrascript.v2.Environment; import com.dfsek.terra.addons.terrascript.v2.Environment;
import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.NonexistentSymbolException; import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.NonexistentSymbolException;
import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.SymbolTypeMismatchException;
import com.dfsek.terra.addons.terrascript.v2.Environment.Symbol; import com.dfsek.terra.addons.terrascript.v2.Environment.Symbol;
import com.dfsek.terra.addons.terrascript.v2.ErrorHandler; import com.dfsek.terra.addons.terrascript.v2.ErrorHandler;
import com.dfsek.terra.addons.terrascript.v2.Type; import com.dfsek.terra.addons.terrascript.v2.Type;
import com.dfsek.terra.addons.terrascript.v2.Type.Function;
import com.dfsek.terra.addons.terrascript.v2.ast.Expr; import com.dfsek.terra.addons.terrascript.v2.ast.Expr;
import com.dfsek.terra.addons.terrascript.v2.ast.Expr.Assignment; import com.dfsek.terra.addons.terrascript.v2.ast.Expr.Assignment;
import com.dfsek.terra.addons.terrascript.v2.ast.Expr.Binary; import com.dfsek.terra.addons.terrascript.v2.ast.Expr.Binary;
@@ -19,9 +19,12 @@ import com.dfsek.terra.addons.terrascript.v2.ast.Expr.Void;
import com.dfsek.terra.addons.terrascript.v2.ast.Stmt; import com.dfsek.terra.addons.terrascript.v2.ast.Stmt;
import com.dfsek.terra.addons.terrascript.v2.exception.semanticanalysis.IdentifierAlreadyDeclaredException; import com.dfsek.terra.addons.terrascript.v2.exception.semanticanalysis.IdentifierAlreadyDeclaredException;
import com.dfsek.terra.addons.terrascript.v2.exception.semanticanalysis.UndefinedReferenceException; import com.dfsek.terra.addons.terrascript.v2.exception.semanticanalysis.UndefinedReferenceException;
import com.dfsek.terra.addons.terrascript.v2.parser.ParseException;
import com.dfsek.terra.api.util.generic.pair.Pair; import com.dfsek.terra.api.util.generic.pair.Pair;
import java.util.List;
import static com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.*;
public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> { public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
@@ -61,22 +64,14 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
@Override @Override
public Void visitCallExpr(Call expr) { public Void visitCallExpr(Call expr) {
expr.setEnvironment(currentScope); expr.function.accept(this);
expr.arguments.forEach(e -> e.accept(this)); expr.arguments.forEach(e -> e.accept(this));
return null; return null;
} }
@Override @Override
public Void visitVariableExpr(Variable expr) { public Void visitVariableExpr(Variable expr) {
String id = expr.identifier; expr.setScope(currentScope);
try {
expr.setSymbol(currentScope.getVariable(id));
} catch(NonexistentSymbolException e) {
errorHandler.add(
new UndefinedReferenceException("No variable by the name '" + id + "' is defined in this scope", expr.position));
} catch(SymbolTypeMismatchException e) {
errorHandler.add(new ParseException("Identifier '" + id + "' is not defined as a variable", expr.position));
}
return null; return null;
} }
@@ -111,8 +106,8 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
currentScope = currentScope.functionalInner(); currentScope = currentScope.functionalInner();
for(Pair<String, Type> param : stmt.parameters) { for(Pair<String, Type> param : stmt.parameters) {
try { try {
currentScope.put(param.getLeft(), new Environment.Symbol.Variable(param.getRight())); currentScope.put(param.getLeft(), new Symbol.Variable(param.getRight()));
} catch(Environment.ScopeException.SymbolAlreadyExistsException e) { } catch(SymbolAlreadyExistsException e) {
throw new IllegalStateException("Formal parameter '" + param.getLeft() + "' defined in '" + stmt.identifier + throw new IllegalStateException("Formal parameter '" + param.getLeft() + "' defined in '" + stmt.identifier +
"' already exists in the function scope"); "' already exists in the function scope");
} }
@@ -120,10 +115,12 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
stmt.body.accept(this); stmt.body.accept(this);
currentScope = currentScope.outer(); currentScope = currentScope.outer();
try { try {
Symbol.Function symbol = new Symbol.Function(stmt.returnType, stmt.parameters.stream().map(Pair::getRight).toList(), currentScope); List<Type> parameters = stmt.parameters.stream().map(Pair::getRight).toList();
Function function = new Function(stmt.returnType, parameters, stmt.identifier, currentScope);
Symbol.Variable symbol = new Symbol.Variable(function);
stmt.setSymbol(symbol); stmt.setSymbol(symbol);
currentScope.put(stmt.identifier, symbol); currentScope.put(stmt.identifier, symbol);
} catch(Environment.ScopeException.SymbolAlreadyExistsException e) { } catch(SymbolAlreadyExistsException e) {
errorHandler.add(new IdentifierAlreadyDeclaredException("Name '" + stmt.identifier + "' is already defined in this scope", errorHandler.add(new IdentifierAlreadyDeclaredException("Name '" + stmt.identifier + "' is already defined in this scope",
stmt.position)); stmt.position));
} }
@@ -132,13 +129,8 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
@Override @Override
public Void visitVariableDeclarationStmt(Stmt.VariableDeclaration stmt) { public Void visitVariableDeclarationStmt(Stmt.VariableDeclaration stmt) {
stmt.setScope(currentScope);
stmt.value.accept(this); stmt.value.accept(this);
try {
currentScope.put(stmt.identifier, new Environment.Symbol.Variable(stmt.type));
} catch(Environment.ScopeException.SymbolAlreadyExistsException e) {
errorHandler.add(new IdentifierAlreadyDeclaredException("Name '" + stmt.identifier + "' is already defined in this scope",
stmt.position));
}
return null; return null;
} }
@@ -12,7 +12,7 @@ public class SemanticAnalyzer {
new ScopeAnalyzer(Environment.global(), errorHandler).visitBlockStmt(root); new ScopeAnalyzer(Environment.global(), errorHandler).visitBlockStmt(root);
errorHandler.throwAny(); errorHandler.throwAny();
new FunctionReferenceAnalyzer(errorHandler).visitBlockStmt(root); new VariableAnalyzer(errorHandler).visitBlockStmt(root);
errorHandler.throwAny(); errorHandler.throwAny();
TypedStmt.Block checkedRoot = (TypedStmt.Block) new TypeChecker(errorHandler).visitBlockStmt(root); TypedStmt.Block checkedRoot = (TypedStmt.Block) new TypeChecker(errorHandler).visitBlockStmt(root);
@@ -33,10 +33,6 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
TypeChecker(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } TypeChecker(ErrorHandler errorHandler) { this.errorHandler = errorHandler; }
private static String scopedIdentifier(String identifier, Symbol.Function symbol) {
return identifier + "$" + symbol.scope.name;
}
@Override @Override
public TypedExpr visitBinaryExpr(Binary expr) { public TypedExpr visitBinaryExpr(Binary expr) {
TypedExpr left = expr.left.accept(this); TypedExpr left = expr.left.accept(this);
@@ -107,28 +103,31 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
@Override @Override
public TypedExpr visitCallExpr(Call expr) { public TypedExpr visitCallExpr(Call expr) {
String id = expr.identifier;
Environment.Symbol.Function signature = expr.getSymbol(); TypedExpr function = expr.function.accept(this);
if(!(function.type instanceof Type.Function functionType)) {
errorHandler.add(new InvalidTypeException("Cannot call type '" + function.type + "', only functions can be called", expr.position));
return new TypedExpr.Void(Type.VOID);
}
List<TypedExpr> arguments = expr.arguments.stream().map(a -> a.accept(this)).toList(); List<TypedExpr> arguments = expr.arguments.stream().map(a -> a.accept(this)).toList();
List<Type> parameters = signature.parameters; List<Type> parameters = functionType.getParameters();
if(arguments.size() != parameters.size()) if(arguments.size() != parameters.size())
errorHandler.add(new ParseException( errorHandler.add(new ParseException(
"Provided " + arguments.size() + " arguments to function call of '" + id + "', expected " + parameters.size() + "Provided " + arguments.size() + " arguments to function call, expected " + parameters.size() + " arguments", expr.position));
" arguments", expr.position));
for(int i = 0; i < parameters.size(); i++) { for(int i = 0; i < parameters.size(); i++) {
Type expectedType = parameters.get(i); Type expectedType = parameters.get(i);
Type providedType = arguments.get(i).type; Type providedType = arguments.get(i).type;
if(!expectedType.typeOf(providedType)) if(!expectedType.typeOf(providedType))
errorHandler.add(new InvalidTypeException( errorHandler.add(new InvalidTypeException(
ordinalOf(i + 1) + " argument provided for function '" + id + "' expects type " + expectedType + ", found " + ordinalOf(i + 1) + " argument provided for function. Function expects type " + expectedType + ", found " +
providedType + " instead", expr.position)); providedType + " instead", expr.position));
} }
return new TypedExpr.Call(expr.identifier, arguments, scopedIdentifier(expr.identifier, expr.getSymbol()), signature.type); return new TypedExpr.Call(function, arguments, functionType.getReturnType());
} }
@Override @Override
@@ -144,7 +143,7 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
String id = expr.lValue.identifier; String id = expr.lValue.identifier;
if(!right.type.typeOf(expected)) if(!right.type.typeOf(expected))
errorHandler.add(new InvalidTypeException( errorHandler.add(new InvalidTypeException(
"Cannot assign variable '" + id + "' to value of type " + right.type + ", '" + id + "' is declared with type " + expected, "Cannot assign variable '" + id + "' to value of type '" + right.type + "', '" + id + "' is declared with type '" + expected + "'",
expr.position)); expr.position));
return new TypedExpr.Assignment(left, right, right.type); return new TypedExpr.Assignment(left, right, right.type);
} }
@@ -173,7 +172,7 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
new InvalidFunctionDeclarationException("Function body for '" + stmt.identifier + "' does not contain return statement", new InvalidFunctionDeclarationException("Function body for '" + stmt.identifier + "' does not contain return statement",
stmt.position)); stmt.position));
} }
return new TypedStmt.FunctionDeclaration(stmt.identifier, stmt.parameters, stmt.returnType, body, scopedIdentifier(stmt.identifier, stmt.getSymbol())); return new TypedStmt.FunctionDeclaration(stmt.identifier, stmt.parameters, stmt.returnType, body, ((Type.Function) stmt.getSymbol().type).getId());
} }
private boolean alwaysReturns(TypedStmt stmt, Stmt.FunctionDeclaration function) { private boolean alwaysReturns(TypedStmt stmt, Stmt.FunctionDeclaration function) {
@@ -1,7 +1,8 @@
package com.dfsek.terra.addons.terrascript.v2.semanticanalysis; package com.dfsek.terra.addons.terrascript.v2.semanticanalysis;
import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.NonexistentSymbolException; import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.NonexistentSymbolException;
import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.SymbolTypeMismatchException; import com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.SymbolAlreadyExistsException;
import com.dfsek.terra.addons.terrascript.v2.Environment.Symbol;
import com.dfsek.terra.addons.terrascript.v2.ErrorHandler; import com.dfsek.terra.addons.terrascript.v2.ErrorHandler;
import com.dfsek.terra.addons.terrascript.v2.ast.Expr; import com.dfsek.terra.addons.terrascript.v2.ast.Expr;
import com.dfsek.terra.addons.terrascript.v2.ast.Expr.Assignment; import com.dfsek.terra.addons.terrascript.v2.ast.Expr.Assignment;
@@ -24,16 +25,16 @@ import com.dfsek.terra.addons.terrascript.v2.ast.Stmt.NoOp;
import com.dfsek.terra.addons.terrascript.v2.ast.Stmt.Return; import com.dfsek.terra.addons.terrascript.v2.ast.Stmt.Return;
import com.dfsek.terra.addons.terrascript.v2.ast.Stmt.VariableDeclaration; import com.dfsek.terra.addons.terrascript.v2.ast.Stmt.VariableDeclaration;
import com.dfsek.terra.addons.terrascript.v2.ast.Stmt.While; import com.dfsek.terra.addons.terrascript.v2.ast.Stmt.While;
import com.dfsek.terra.addons.terrascript.v2.exception.semanticanalysis.IdentifierAlreadyDeclaredException;
import com.dfsek.terra.addons.terrascript.v2.exception.semanticanalysis.UndefinedReferenceException; import com.dfsek.terra.addons.terrascript.v2.exception.semanticanalysis.UndefinedReferenceException;
import com.dfsek.terra.addons.terrascript.v2.parser.ParseException;
import com.dfsek.terra.api.util.generic.pair.Pair; import com.dfsek.terra.api.util.generic.pair.Pair;
public class FunctionReferenceAnalyzer implements Expr.Visitor<Void>, Stmt.Visitor<Void> { public class VariableAnalyzer implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
private final ErrorHandler errorHandler; private final ErrorHandler errorHandler;
public FunctionReferenceAnalyzer(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } public VariableAnalyzer(ErrorHandler errorHandler) { this.errorHandler = errorHandler; }
@Override @Override
public Void visitBinaryExpr(Binary expr) { public Void visitBinaryExpr(Binary expr) {
@@ -61,21 +62,20 @@ public class FunctionReferenceAnalyzer implements Expr.Visitor<Void>, Stmt.Visit
@Override @Override
public Void visitCallExpr(Call expr) { public Void visitCallExpr(Call expr) {
String id = expr.identifier; expr.function.accept(this);
try {
expr.setSymbol(expr.getEnvironment().getFunction(expr.identifier));
} catch(NonexistentSymbolException e) {
errorHandler.add(
new UndefinedReferenceException("No function by the name '" + id + "' is defined in this scope", expr.position));
} catch(SymbolTypeMismatchException e) {
errorHandler.add(new ParseException("Identifier '" + id + "' is not defined as a function", expr.position));
}
expr.arguments.forEach(e -> e.accept(this)); expr.arguments.forEach(e -> e.accept(this));
return null; return null;
} }
@Override @Override
public Void visitVariableExpr(Variable expr) { public Void visitVariableExpr(Variable expr) {
String id = expr.identifier;
try {
expr.setSymbol(expr.getScope().getVariable(id));
} catch(NonexistentSymbolException e) {
errorHandler.add(
new UndefinedReferenceException("'" + id + "' not is defined in this scope", expr.position));
}
return null; return null;
} }
@@ -112,6 +112,12 @@ public class FunctionReferenceAnalyzer implements Expr.Visitor<Void>, Stmt.Visit
@Override @Override
public Void visitVariableDeclarationStmt(VariableDeclaration stmt) { public Void visitVariableDeclarationStmt(VariableDeclaration stmt) {
stmt.value.accept(this); stmt.value.accept(this);
try {
stmt.getScope().put(stmt.identifier, new Symbol.Variable(stmt.type));
} catch(SymbolAlreadyExistsException e) {
errorHandler.add(new IdentifierAlreadyDeclaredException("Name '" + stmt.identifier + "' is already defined in this scope",
stmt.position));
}
return null; return null;
} }