mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-04-04 06:46:21 +00:00
Make functions first class
This commit is contained in:
@@ -156,8 +156,8 @@ tasks.register("genTerrascriptAstClasses") {
|
||||
ASTNode("Grouping", listOf("expression" to "Expr")),
|
||||
ASTNode("Literal", listOf("value" to "Object", "type" to "Type")),
|
||||
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("Variable", listOf("identifier" to "String"), listOf("symbol" to "Symbol.Variable")),
|
||||
ASTNode("Call", listOf("function" to "Expr", "arguments" to "List<Expr>")),
|
||||
ASTNode("Variable", listOf("identifier" to "String"), listOf("symbol" to "Symbol", "scope" to "Environment")),
|
||||
ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "Expr")),
|
||||
ASTNode("Void", listOf()),
|
||||
),
|
||||
@@ -168,6 +168,7 @@ tasks.register("genTerrascriptAstClasses") {
|
||||
listOf(
|
||||
"com.dfsek.terra.addons.terrascript.v2.Type",
|
||||
"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.lexer.SourcePosition",
|
||||
"java.util.List",
|
||||
@@ -176,8 +177,8 @@ tasks.register("genTerrascriptAstClasses") {
|
||||
listOf(
|
||||
ASTNode("Expression", listOf("expression" to "Expr")),
|
||||
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("VariableDeclaration", listOf("type" to "Type", "identifier" to "String", "value" to "Expr")),
|
||||
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"), listOf("scope" to "Environment")),
|
||||
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("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("Literal", listOf("value" to "Object")),
|
||||
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("Assignment", listOf("lValue" to "Variable", "rValue" to "TypedExpr")),
|
||||
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.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;
|
||||
|
||||
|
||||
@@ -36,8 +33,10 @@ public class Environment {
|
||||
this.inLoop = inLoop;
|
||||
this.index = index;
|
||||
this.name = String.join("_", getNestedIndexes().stream().map(Object::toString).toList());
|
||||
// Populate symbol tables with built-in Java implemented methods
|
||||
NativeFunction.BUILTIN_FUNCTIONS.forEach((name, function) -> symbolTable.put(name, new Symbol.Function(function.getReturnType(), function.getParameterTypes(), this)));
|
||||
// Populate global scope with built-in Java implemented methods
|
||||
// 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() {
|
||||
@@ -69,19 +68,6 @@ public class Environment {
|
||||
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.
|
||||
* <br>
|
||||
@@ -93,19 +79,25 @@ public class Environment {
|
||||
* @return variable symbol table entry
|
||||
*
|
||||
* @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 {
|
||||
Variable variable;
|
||||
public Symbol getVariable(String id) throws NonexistentSymbolException {
|
||||
Symbol symbol = symbolTable.get(id);
|
||||
if(symbol == null) {
|
||||
if(!canAccessOuterVariables || outer == null) throw new NonexistentSymbolException();
|
||||
variable = outer.getVariable(id);
|
||||
} else {
|
||||
if(!(symbol instanceof Variable)) throw new SymbolTypeMismatchException();
|
||||
variable = (Variable) symbol;
|
||||
}
|
||||
return variable;
|
||||
if(symbol != null) return symbol;
|
||||
if(outer == null) throw new NonexistentSymbolException();
|
||||
if(canAccessOuterVariables) return outer.getVariable(id);
|
||||
|
||||
// Only functions can be accessed from restricted scopes
|
||||
// TODO - Only make applicable to functions that cannot be reassigned
|
||||
Symbol potentialFunction = outer.getVariableUnrestricted(id);
|
||||
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 {
|
||||
@@ -115,34 +107,22 @@ public class Environment {
|
||||
|
||||
public static abstract class Symbol {
|
||||
|
||||
public static class Function extends Symbol {
|
||||
|
||||
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 final Type type;
|
||||
|
||||
public Symbol(Type type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static class Variable extends Symbol {
|
||||
|
||||
public final 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 {
|
||||
}
|
||||
@@ -151,8 +131,5 @@ public class Environment {
|
||||
public static class NonexistentSymbolException extends ScopeException {
|
||||
}
|
||||
|
||||
|
||||
public static class SymbolTypeMismatchException extends ScopeException {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
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 {
|
||||
|
||||
@@ -21,13 +30,71 @@ public interface Type {
|
||||
return this.equals(type);
|
||||
}
|
||||
|
||||
static Optional<Type> from(Class<?> clazz) {
|
||||
return Optional.ofNullable(
|
||||
clazz == double.class ? NUMBER :
|
||||
clazz == String.class ? STRING :
|
||||
clazz == boolean.class ? BOOLEAN :
|
||||
clazz == void.class ? VOID :
|
||||
null);
|
||||
CodegenType getCodegenType();
|
||||
|
||||
class Function implements Type {
|
||||
|
||||
private final Type returnType;
|
||||
private final List<Type> parameters;
|
||||
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() {
|
||||
@@ -36,6 +103,11 @@ public interface Type {
|
||||
return double.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.DOUBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "num";
|
||||
@@ -49,6 +121,11 @@ public interface Type {
|
||||
return int.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.INTEGER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "int";
|
||||
@@ -62,6 +139,11 @@ public interface Type {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "str";
|
||||
@@ -74,6 +156,11 @@ public interface Type {
|
||||
return boolean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.BOOLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "bool";
|
||||
@@ -86,6 +173,11 @@ public interface Type {
|
||||
return void.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodegenType getCodegenType() {
|
||||
return CodegenType.VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "()";
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
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 java.util.Map;
|
||||
|
||||
|
||||
public class CodegenType {
|
||||
|
||||
@@ -28,21 +22,12 @@ public class CodegenType {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public static final CodegenType BOOLEAN_PRIMITIVE = new CodegenType(InstructionType.INTEGER, "Z");
|
||||
|
||||
private static final Map<Type, CodegenType> TYPE_MAP = Map.of(
|
||||
Type.BOOLEAN, BOOLEAN_PRIMITIVE,
|
||||
Type.STRING, new CodegenType(InstructionType.OBJECT, "Ljava/lang/String;"),
|
||||
Type.NUMBER, new CodegenType(InstructionType.DOUBLE, "D"),
|
||||
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 static final CodegenType BOOLEAN = new CodegenType(InstructionType.INTEGER, "Z");
|
||||
public static final CodegenType STRING = new CodegenType(InstructionType.OBJECT, "Ljava/lang/String;");
|
||||
public static final CodegenType DOUBLE = new CodegenType(InstructionType.DOUBLE, "D");
|
||||
public static final CodegenType INTEGER = new CodegenType(InstructionType.INTEGER, "I");
|
||||
public static final CodegenType VOID = new CodegenType(InstructionType.VOID, "V");
|
||||
public static final CodegenType OBJECT = new CodegenType(InstructionType.OBJECT, "Ljava/lang/Object;");
|
||||
|
||||
public enum InstructionType {
|
||||
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.exception.CompilerBugException;
|
||||
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 org.objectweb.asm.ClassReader;
|
||||
@@ -51,8 +49,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
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;
|
||||
|
||||
public class TerraScriptClassGenerator {
|
||||
@@ -163,7 +159,6 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
public void generate(Block root) {
|
||||
this.visitBlockTypedStmt(root);
|
||||
|
||||
}
|
||||
|
||||
@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 ADD -> {
|
||||
pushBinaryOperands(expr);
|
||||
CodegenType codegenType = CodegenType.codegenType(expr.type);
|
||||
CodegenType codegenType = expr.type.getCodegenType();
|
||||
if(codegenType.bytecodeType() == InstructionType.DOUBLE)
|
||||
method.visitInsn(Opcodes.DADD);
|
||||
else if (Objects.equals(codegenType.getDescriptor(), "Ljava/lang/String;"))
|
||||
@@ -196,7 +191,7 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
@Override
|
||||
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();
|
||||
else method.visitLdcInsn(expr.value);
|
||||
return null;
|
||||
@@ -214,23 +209,25 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
@Override
|
||||
public Void visitCallTypedExpr(Call expr) {
|
||||
if (NativeFunction.BUILTIN_FUNCTIONS.containsKey(expr.identifier)) {
|
||||
NativeFunction function = NativeFunction.BUILTIN_FUNCTIONS.get(expr.identifier);
|
||||
// TODO - Remove specific handling of native functions
|
||||
if (expr.function.type instanceof Type.Function.Native nativeFunction) {
|
||||
NativeFunction function = nativeFunction.getNativeFunction();
|
||||
function.pushInstance(method);
|
||||
expr.arguments.forEach(a -> a.accept(this));
|
||||
function.callMethod(method);
|
||||
return null;
|
||||
}
|
||||
// TODO - Add support for invokevirtual
|
||||
expr.arguments.forEach(a -> a.accept(this));
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariableTypedExpr(Variable expr) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -238,7 +235,7 @@ public class TerraScriptClassGenerator {
|
||||
public Void visitAssignmentTypedExpr(Assignment expr) {
|
||||
expr.rValue.accept(this);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -280,7 +277,7 @@ public class TerraScriptClassGenerator {
|
||||
int lvidx = 0;
|
||||
for (Pair<String, Type> parameter : stmt.parameters) {
|
||||
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
|
||||
@@ -298,14 +295,14 @@ public class TerraScriptClassGenerator {
|
||||
public Void visitVariableDeclarationTypedStmt(VariableDeclaration stmt) {
|
||||
stmt.value.accept(this);
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitReturnTypedStmt(Return stmt) {
|
||||
stmt.value.accept(this);
|
||||
method.visitInsn(CodegenType.codegenType(stmt.value.type).bytecodeType().returnInsn());
|
||||
method.visitInsn(stmt.value.type.getCodegenType().bytecodeType().returnInsn());
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -530,9 +527,9 @@ public class TerraScriptClassGenerator {
|
||||
|
||||
private String getFunctionDescriptor(List<Type> parameters, Type returnType) {
|
||||
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(CodegenType.codegenType(returnType).getDescriptor());
|
||||
sb.append(returnType.getCodegenType().getDescriptor());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,15 @@ public class Parser {
|
||||
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() {
|
||||
@@ -336,10 +344,7 @@ public class Parser {
|
||||
case NUMBER -> new Expr.Literal(Double.parseDouble(token.lexeme()), Type.NUMBER, position);
|
||||
case STRING -> new Expr.Literal(token.lexeme(), Type.STRING, position);
|
||||
case BOOLEAN -> new Expr.Literal(Boolean.parseBoolean(token.lexeme()), Type.BOOLEAN, position);
|
||||
case IDENTIFIER -> {
|
||||
if(current().isType(TokenType.OPEN_PAREN)) yield call(token);
|
||||
else yield variable(token);
|
||||
}
|
||||
case IDENTIFIER -> variable(token);
|
||||
case OPEN_PAREN -> {
|
||||
if(current().isType(TokenType.CLOSE_PAREN)) {
|
||||
consumeUnchecked(); // Consume ')'
|
||||
@@ -353,22 +358,20 @@ public class Parser {
|
||||
};
|
||||
}
|
||||
|
||||
private Expr call(Token identifier) {
|
||||
String id = identifier.lexeme();
|
||||
SourcePosition position = consume("Expected '(' to initiate function call on function '" + id + "'",
|
||||
TokenType.OPEN_PAREN).position();
|
||||
private Expr call(Expr function) {
|
||||
SourcePosition position = consume("Expected '(' to initiate function call on function", TokenType.OPEN_PAREN).position();
|
||||
|
||||
List<Expr> args = new ArrayList<>();
|
||||
while(!current().isType(TokenType.CLOSE_PAREN)) {
|
||||
args.add(expression());
|
||||
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);
|
||||
|
||||
return new Expr.Call(id, args, position);
|
||||
return new Expr.Call(function, args, position);
|
||||
}
|
||||
|
||||
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.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.ErrorHandler;
|
||||
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.Assignment;
|
||||
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.exception.semanticanalysis.IdentifierAlreadyDeclaredException;
|
||||
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 java.util.List;
|
||||
|
||||
import static com.dfsek.terra.addons.terrascript.v2.Environment.ScopeException.*;
|
||||
|
||||
|
||||
public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
|
||||
@@ -61,22 +64,14 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
|
||||
@Override
|
||||
public Void visitCallExpr(Call expr) {
|
||||
expr.setEnvironment(currentScope);
|
||||
expr.function.accept(this);
|
||||
expr.arguments.forEach(e -> e.accept(this));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visitVariableExpr(Variable expr) {
|
||||
String id = expr.identifier;
|
||||
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));
|
||||
}
|
||||
expr.setScope(currentScope);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -111,8 +106,8 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
currentScope = currentScope.functionalInner();
|
||||
for(Pair<String, Type> param : stmt.parameters) {
|
||||
try {
|
||||
currentScope.put(param.getLeft(), new Environment.Symbol.Variable(param.getRight()));
|
||||
} catch(Environment.ScopeException.SymbolAlreadyExistsException e) {
|
||||
currentScope.put(param.getLeft(), new Symbol.Variable(param.getRight()));
|
||||
} catch(SymbolAlreadyExistsException e) {
|
||||
throw new IllegalStateException("Formal parameter '" + param.getLeft() + "' defined in '" + stmt.identifier +
|
||||
"' already exists in the function scope");
|
||||
}
|
||||
@@ -120,10 +115,12 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
stmt.body.accept(this);
|
||||
currentScope = currentScope.outer();
|
||||
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);
|
||||
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",
|
||||
stmt.position));
|
||||
}
|
||||
@@ -132,13 +129,8 @@ public class ScopeAnalyzer implements Visitor<Void>, Stmt.Visitor<Void> {
|
||||
|
||||
@Override
|
||||
public Void visitVariableDeclarationStmt(Stmt.VariableDeclaration stmt) {
|
||||
stmt.setScope(currentScope);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public class SemanticAnalyzer {
|
||||
new ScopeAnalyzer(Environment.global(), errorHandler).visitBlockStmt(root);
|
||||
errorHandler.throwAny();
|
||||
|
||||
new FunctionReferenceAnalyzer(errorHandler).visitBlockStmt(root);
|
||||
new VariableAnalyzer(errorHandler).visitBlockStmt(root);
|
||||
errorHandler.throwAny();
|
||||
|
||||
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; }
|
||||
|
||||
private static String scopedIdentifier(String identifier, Symbol.Function symbol) {
|
||||
return identifier + "$" + symbol.scope.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedExpr visitBinaryExpr(Binary expr) {
|
||||
TypedExpr left = expr.left.accept(this);
|
||||
@@ -107,28 +103,31 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
|
||||
@Override
|
||||
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<Type> parameters = signature.parameters;
|
||||
List<Type> parameters = functionType.getParameters();
|
||||
|
||||
if(arguments.size() != parameters.size())
|
||||
errorHandler.add(new ParseException(
|
||||
"Provided " + arguments.size() + " arguments to function call of '" + id + "', expected " + parameters.size() +
|
||||
" arguments", expr.position));
|
||||
"Provided " + arguments.size() + " arguments to function call, expected " + parameters.size() + " arguments", expr.position));
|
||||
|
||||
for(int i = 0; i < parameters.size(); i++) {
|
||||
Type expectedType = parameters.get(i);
|
||||
Type providedType = arguments.get(i).type;
|
||||
if(!expectedType.typeOf(providedType))
|
||||
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));
|
||||
}
|
||||
|
||||
return new TypedExpr.Call(expr.identifier, arguments, scopedIdentifier(expr.identifier, expr.getSymbol()), signature.type);
|
||||
return new TypedExpr.Call(function, arguments, functionType.getReturnType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,7 +143,7 @@ public class TypeChecker implements Visitor<TypedExpr>, Stmt.Visitor<TypedStmt>
|
||||
String id = expr.lValue.identifier;
|
||||
if(!right.type.typeOf(expected))
|
||||
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));
|
||||
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",
|
||||
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) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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.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.ast.Expr;
|
||||
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.VariableDeclaration;
|
||||
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.parser.ParseException;
|
||||
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;
|
||||
|
||||
public FunctionReferenceAnalyzer(ErrorHandler errorHandler) { this.errorHandler = errorHandler; }
|
||||
public VariableAnalyzer(ErrorHandler errorHandler) { this.errorHandler = errorHandler; }
|
||||
|
||||
@Override
|
||||
public Void visitBinaryExpr(Binary expr) {
|
||||
@@ -61,21 +62,20 @@ public class FunctionReferenceAnalyzer implements Expr.Visitor<Void>, Stmt.Visit
|
||||
|
||||
@Override
|
||||
public Void visitCallExpr(Call expr) {
|
||||
String id = expr.identifier;
|
||||
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.function.accept(this);
|
||||
expr.arguments.forEach(e -> e.accept(this));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -112,6 +112,12 @@ public class FunctionReferenceAnalyzer implements Expr.Visitor<Void>, Stmt.Visit
|
||||
@Override
|
||||
public Void visitVariableDeclarationStmt(VariableDeclaration stmt) {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user