From ceba9512c7fc22242b3dc77caf9458c8bac9eedd Mon Sep 17 00:00:00 2001 From: Astrash Date: Fri, 27 Oct 2023 13:37:07 +1100 Subject: [PATCH] Make functions first class --- .../structure-terrascript-v2/build.gradle.kts | 11 +- .../addons/terrascript/v2/Environment.java | 77 +++++-------- .../terra/addons/terrascript/v2/Type.java | 108 ++++++++++++++++-- .../terrascript/v2/codegen/CodegenType.java | 27 +---- .../asm/TerraScriptClassGenerator.java | 31 +++-- .../addons/terrascript/v2/parser/Parser.java | 27 +++-- .../v2/semanticanalysis/ScopeAnalyzer.java | 36 +++--- .../v2/semanticanalysis/SemanticAnalyzer.java | 2 +- .../v2/semanticanalysis/TypeChecker.java | 25 ++-- ...nceAnalyzer.java => VariableAnalyzer.java} | 32 +++--- 10 files changed, 214 insertions(+), 162 deletions(-) rename common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/{FunctionReferenceAnalyzer.java => VariableAnalyzer.java} (82%) diff --git a/common/addons/structure-terrascript-v2/build.gradle.kts b/common/addons/structure-terrascript-v2/build.gradle.kts index f52a9b7c8..e964a5312 100644 --- a/common/addons/structure-terrascript-v2/build.gradle.kts +++ b/common/addons/structure-terrascript-v2/build.gradle.kts @@ -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"), 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")), + 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")), - ASTNode("FunctionDeclaration", listOf("identifier" to "String", "parameters" to "List>", "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>", "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>", "elseBody" to "Optional")), 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", "scopedIdentifier" to "String")), + ASTNode("Call", listOf("function" to "TypedExpr", "arguments" to "List")), ASTNode("Variable", listOf("identifier" to "String")), ASTNode("Assignment", listOf("lValue" to "Variable", "rValue" to "TypedExpr")), ASTNode("Void", listOf()), diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java index 8664f47ce..e35ac2b02 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Environment.java @@ -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. *
@@ -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 parameters; - - public final Environment scope; - - public Function(Type type, List 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 { - } } } diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java index db7ec8417..3623b0377 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/Type.java @@ -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 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 parameters; + private final String id; + + public Function(Type returnType, List 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 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 subtypes, List 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 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 "()"; diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java index 3891bf213..4a4e93f7a 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/CodegenType.java @@ -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_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 { diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java index 92ec9808e..ec49cbedb 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/codegen/asm/TerraScriptClassGenerator.java @@ -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 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 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 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(); } } diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java index 122775261..683edf867 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/parser/Parser.java @@ -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 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) { diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java index 2c09aed8e..2b00c4dba 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/ScopeAnalyzer.java @@ -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, Stmt.Visitor { @@ -61,22 +64,14 @@ public class ScopeAnalyzer implements Visitor, Stmt.Visitor { @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, Stmt.Visitor { currentScope = currentScope.functionalInner(); for(Pair 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, Stmt.Visitor { 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 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, Stmt.Visitor { @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; } diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/SemanticAnalyzer.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/SemanticAnalyzer.java index d0456d6b3..455ab295a 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/SemanticAnalyzer.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/SemanticAnalyzer.java @@ -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); diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java index 6dd2299ae..7c6188658 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/TypeChecker.java @@ -33,10 +33,6 @@ public class TypeChecker implements Visitor, Stmt.Visitor 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, Stmt.Visitor @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 arguments = expr.arguments.stream().map(a -> a.accept(this)).toList(); - List parameters = signature.parameters; + List 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, Stmt.Visitor 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, Stmt.Visitor 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) { diff --git a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/FunctionReferenceAnalyzer.java b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/VariableAnalyzer.java similarity index 82% rename from common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/FunctionReferenceAnalyzer.java rename to common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/VariableAnalyzer.java index 43828cf3a..8d0b0115a 100644 --- a/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/FunctionReferenceAnalyzer.java +++ b/common/addons/structure-terrascript-v2/src/main/java/com/dfsek/terra/addons/terrascript/v2/semanticanalysis/VariableAnalyzer.java @@ -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, Stmt.Visitor { +public class VariableAnalyzer implements Expr.Visitor, Stmt.Visitor { 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, 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, 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; }