Make functions first class

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

View File

@@ -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()),

View File

@@ -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 {
}
}
}

View File

@@ -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 "()";

View File

@@ -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 {

View File

@@ -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();
}
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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;
}