ruke.vrj.phase.TypeCheck.java Source code

Java tutorial

Introduction

Here is the source code for ruke.vrj.phase.TypeCheck.java

Source

package ruke.vrj.phase;

import java.util.ArrayList;
import org.antlr.v4.runtime.ParserRuleContext;
import ruke.vrj.Symbol;
import ruke.vrj.SymbolFlag;
import ruke.vrj.SymbolTable;
import ruke.vrj.TypeChecker;
import ruke.vrj.antlr.vrjBaseVisitor;
import ruke.vrj.antlr.vrjParser;
import ruke.vrj.antlr.vrjParser.ExpressionContext;
import ruke.vrj.antlr.vrjParser.NameContext;
import ruke.vrj.compiler.Result;

/**
 * MIT License
 *
 * Copyright (c) 2017 Franco Montenegro
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
public class TypeCheck extends vrjBaseVisitor<Symbol> {

    private final ArrayList<Result> results = new ArrayList<>();
    private SymbolTable symbols;
    private Symbol integer;
    private Symbol real;
    private Symbol string;
    private Symbol code;
    private Symbol booleanType;
    private Symbol nullType;

    /**
     * Create a type check visitor.
     *
     * @param symbols Symbols
     */
    public TypeCheck(final SymbolTable symbols) {
        this.symbols = symbols;
        this.integer = symbols.resolve("integer");
        this.real = symbols.resolve("real");
        this.string = symbols.resolve("string");
        this.code = symbols.resolve("code");
        this.booleanType = symbols.resolve("boolean");
        this.nullType = symbols.resolve("null");
    }

    public ArrayList<Result> getResults() {
        return this.results;
    }

    private final void checkForNumeric(final ParserRuleContext ctx, final Symbol expression) {
        if (!TypeChecker.isValidNumber(expression)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.getStart().getCharPositionInLine(),
                    ctx.getStart().getCharPositionInLine() + ctx.getText().length(), "Expected number"));
        }
    }

    private final void checkForBoolean(final ParserRuleContext ctx, final Symbol expression) {
        if (!TypeChecker.compatible(this.booleanType, expression)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.getStart().getCharPositionInLine(),
                    ctx.getStart().getCharPositionInLine() + ctx.getText().length(),
                    "Expected boolean expression"));
        }
    }

    @Override
    public Symbol visitLibraryDeclaration(vrjParser.LibraryDeclarationContext ctx) {
        final String name = ctx.name(0).getText();
        final Symbol library = this.symbols.resolve(name);

        if (!library.initializer.isEmpty()) {
            final Symbol initializer = library.children.resolve(library.initializer);

            if (!TypeChecker.isValidInitializer(initializer)) {
                this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(),
                        ctx.getStart().getLine(), ctx.initializer.getStart().getCharPositionInLine(),
                        ctx.initializer.getStart().getCharPositionInLine() + library.initializer.length(),
                        "Invalid initializer. It must be a function with no parameters"));
            }
        }

        if (!library.extendsFrom.isEmpty()) {
            Symbol requirement;

            for (final NameContext requirementName : ctx.libraryRequirementsExpression().name()) {
                requirement = library.children.resolve(requirementName.getText());

                if (!TypeChecker.isLibrary(requirement)) {
                    this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(),
                            ctx.getStart().getLine(), requirementName.getStart().getCharPositionInLine(),
                            requirementName.getStart().getCharPositionInLine() + requirementName.getText().length(),
                            "Invalid library requirement. Only libraries are valid"));
                }
            }
        }

        final SymbolTable prevSymbols = this.symbols;
        this.symbols = library.children;

        this.visit(ctx.libraryBody());

        this.symbols = prevSymbols;

        return library;
    }

    @Override
    public Symbol visitStructDeclaration(vrjParser.StructDeclarationContext ctx) {
        final String name = ctx.name().getText();
        final Symbol struct = this.symbols.resolve(name);

        if (!struct.extendsFrom.isEmpty() && !struct.extendsFrom.get(0).equals("array")) {
            final Symbol parent = struct.children.resolve(struct.extendsFrom.get(0));

            if (!TypeChecker.isStruct(parent)) {
                this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(),
                        ctx.getStart().getLine(), ctx.extendsFromExpression().getStart().getCharPositionInLine(),
                        ctx.extendsFromExpression().getStart().getCharPositionInLine()
                                + struct.extendsFrom.get(0).length(),
                        "Structs can only extend from struct or array"));
            }
        }

        final SymbolTable prevSymbols = this.symbols;
        this.symbols = struct.children;

        this.visit(ctx.structBody());

        this.symbols = prevSymbols;

        return struct;
    }

    @Override
    public Symbol visitFunctionDeclaration(vrjParser.FunctionDeclarationContext ctx) {
        final String name = ctx.functionSignature().name().getText();
        final Symbol function = this.symbols.resolve(name);

        final SymbolTable prevSymbols = this.symbols;
        this.symbols = function.children;

        this.visit(ctx.functionSignature());
        this.visit(ctx.statements());

        this.symbols = prevSymbols;

        return function;
    }

    @Override
    public Symbol visitMethodDeclaration(vrjParser.MethodDeclarationContext ctx) {
        final String name = ctx.functionSignature().name().getText();
        final Symbol method = this.symbols.resolve(name);

        method.registerImplementationIfNecessary();

        if ("onInit".equals(name) && !TypeChecker.isValidInitializer(method)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.functionSignature().name().getStart().getCharPositionInLine(),
                    ctx.functionSignature().name().getStart().getCharPositionInLine() + name.length(),
                    "Struct initializer must be static and must take no parameters"));
        }

        final SymbolTable prevSymbols = this.symbols;
        this.symbols = method.children;

        this.visit(ctx.functionSignature());
        this.visit(ctx.statements());

        this.symbols = prevSymbols;

        return method;
    }

    @Override
    public Symbol visitParenthesisExpression(vrjParser.ParenthesisExpressionContext ctx) {
        return this.visit(ctx.expression());
    }

    @Override
    public Symbol visitNegativeExpression(vrjParser.NegativeExpressionContext ctx) {
        final Symbol a = this.visit(ctx.expression());
        this.checkForNumeric(ctx.expression(), a);
        return this.integer;
    }

    @Override
    public Symbol visitNotExpression(vrjParser.NotExpressionContext ctx) {
        this.visit(ctx.expression());
        return this.booleanType;
    }

    @Override
    public Symbol visitModuloExpression(vrjParser.ModuloExpressionContext ctx) {
        final Symbol a = this.visit(ctx.left);
        final Symbol b = this.visit(ctx.right);

        this.checkForNumeric(ctx.left, a);
        this.checkForNumeric(ctx.right, b);

        return this.integer;
    }

    @Override
    public Symbol visitDivMultExpression(vrjParser.DivMultExpressionContext ctx) {
        final Symbol a = this.visit(ctx.left);
        final Symbol b = this.visit(ctx.right);

        this.checkForNumeric(ctx.left, a);
        this.checkForNumeric(ctx.right, b);

        return this.integer;
    }

    @Override
    public Symbol visitSumSubExpression(vrjParser.SumSubExpressionContext ctx) {
        final Symbol a = this.visit(ctx.left);
        final Symbol b = this.visit(ctx.right);

        this.checkForNumeric(ctx.left, a);
        this.checkForNumeric(ctx.right, b);

        return this.integer;
    }

    @Override
    public Symbol visitChainExpression(vrjParser.ChainExpressionContext ctx) {
        final SymbolTable prevSymbols = this.symbols;
        Symbol symbol = null;

        for (final ExpressionContext member : ctx.expression()) {
            symbol = this.visit(member);

            if (symbol.equals(Symbol.NOTHING)) {
                break;
            }

            // Make a copy to avoid searching in parents (we are only interested in children)
            this.symbols = SymbolTable.copyOf(symbol.children.resolve(symbol.type).children);
        }

        this.symbols = prevSymbols;

        return symbol;
    }

    @Override
    public Symbol visitVariableExpression(vrjParser.VariableExpressionContext ctx) {
        final String name = ctx.name().getText();
        final Symbol variable = this.symbols.resolve(name);

        if (variable.equals(Symbol.NOTHING)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.name().getStart().getCharPositionInLine(),
                    ctx.name().getStart().getCharPositionInLine() + name.length(),
                    "Variable " + name + " is not defined"));
            return variable;
        }

        if (ctx.index != null) {
            if (!variable.flags.contains(SymbolFlag.ARRAY)) {
                this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(),
                        ctx.getStart().getLine(), ctx.getStart().getCharPositionInLine(),
                        ctx.getStart().getCharPositionInLine() + ctx.getText().length(),
                        "Variable " + name + " is not an array"));

                return variable;
            }

            final Symbol index = this.visit(ctx.index);

            if (!TypeChecker.isValidArrayIndex(index)) {
                this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(),
                        ctx.getStart().getLine(), ctx.index.getStart().getCharPositionInLine(),
                        ctx.index.getStart().getCharPositionInLine() + ctx.index.getText().length(),
                        "Array index must be integer"));
            } else {
                return this.symbols.resolve(index.type);
            }
        }

        return variable;
    }

    @Override
    public Symbol visitFunctionExpression(vrjParser.FunctionExpressionContext ctx) {
        final String name = ctx.name().getText();
        final Symbol function = this.symbols.resolve(name);

        if (function.equals(Symbol.NOTHING)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.name().getStart().getCharPositionInLine(),
                    ctx.name().getStart().getCharPositionInLine() + name.length(),
                    "Function " + name + " is not defined"));
        } else {
            this.visit(ctx.arguments());
        }

        return function;
    }

    @Override
    public Symbol visitComparisonExpression(vrjParser.ComparisonExpressionContext ctx) {
        final Symbol a = this.visit(ctx.left);
        final Symbol b = this.visit(ctx.right);

        switch (ctx.operator.getText()) {
        case "==":
        case "!=":
            if (!TypeChecker.compatible(a, b)) {
                this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(),
                        ctx.getStart().getLine(), ctx.getStart().getCharPositionInLine(),
                        ctx.getStart().getCharPositionInLine() + ctx.getText().length(),
                        "Comparing incompatible types"));
            }
            break;
        case "<=":
        case ">=":
        case "<":
        case ">":
            this.checkForNumeric(ctx.left, a);
            this.checkForNumeric(ctx.right, b);
            break;
        default:
            break;
        }

        return this.booleanType;
    }

    @Override
    public Symbol visitLogicalExpression(vrjParser.LogicalExpressionContext ctx) {
        final Symbol a = this.visit(ctx.left);
        final Symbol b = this.visit(ctx.right);

        this.checkForBoolean(ctx.left, a);
        this.checkForBoolean(ctx.right, b);

        return this.booleanType;
    }

    @Override
    public Symbol visitCodeExpression(vrjParser.CodeExpressionContext ctx) {
        final Symbol function = this.visit(ctx.code);

        if (!function.equals(Symbol.NOTHING) && !function.flags.contains(SymbolFlag.FUNCTION)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.code.getStart().getCharPositionInLine(),
                    ctx.code.getStart().getCharPositionInLine() + ctx.code.getText().length(),
                    "Expected function"));
        }

        return this.code;
    }

    @Override
    public Symbol visitBooleanExpression(vrjParser.BooleanExpressionContext ctx) {
        return this.booleanType;
    }

    @Override
    public Symbol visitNullExpression(vrjParser.NullExpressionContext ctx) {
        return this.nullType;
    }

    @Override
    public Symbol visitStringExpression(vrjParser.StringExpressionContext ctx) {
        return this.string;
    }

    @Override
    public Symbol visitRealExpression(vrjParser.RealExpressionContext ctx) {
        return this.real;
    }

    @Override
    public Symbol visitIntegerExpression(vrjParser.IntegerExpressionContext ctx) {
        return this.integer;
    }

    @Override
    public Symbol visitNonArrayVariableDeclaration(vrjParser.NonArrayVariableDeclarationContext ctx) {
        final String name = ctx.name().getText();
        final Symbol variable = this.symbols.resolve(name);

        if (ctx.value != null) {
            final Symbol value = this.visit(ctx.value);

            if (!TypeChecker.compatible(variable, value)) {
                this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(),
                        ctx.getStart().getLine(), ctx.value.getStart().getCharPositionInLine(),
                        ctx.value.getStart().getCharPositionInLine() + ctx.value.getText().length(),
                        "Incompatible type"));
            }
        }

        return variable;
    }

    @Override
    public Symbol visitSetVariableStatement(vrjParser.SetVariableStatementContext ctx) {
        final Symbol variable = this.visit(ctx.variable);
        final Symbol value = this.visit(ctx.value);
        final Symbol valueType = this.symbols.resolve(value.type);

        if (!value.equals(Symbol.NOTHING) && !TypeChecker.compatible(variable, valueType)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.value.getStart().getCharPositionInLine(),
                    ctx.value.getStart().getCharPositionInLine() + ctx.value.getText().length(),
                    "Incompatible type"));
        }

        return variable;
    }

    @Override
    public Symbol visitFunctionCallStatement(vrjParser.FunctionCallStatementContext ctx) {
        final Symbol function = this.visit(ctx.function);

        if (!function.equals(Symbol.NOTHING) && !function.flags.contains(SymbolFlag.FUNCTION)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.function.getStart().getCharPositionInLine(),
                    ctx.function.getStart().getCharPositionInLine() + ctx.function.getText().length(),
                    "Not a function"));
        }

        return function;
    }

    @Override
    public Symbol visitExitWhenStatement(vrjParser.ExitWhenStatementContext ctx) {
        final Symbol condition = visit(ctx.condition);

        if (!TypeChecker.compatible(this.booleanType, condition)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.condition.getStart().getCharPositionInLine(),
                    ctx.condition.getStart().getCharPositionInLine() + ctx.condition.getText().length(),
                    "Exitwhen condition must be a boolean expression"));
        }

        return Symbol.NOTHING;
    }

    @Override
    public Symbol visitReturnStatement(vrjParser.ReturnStatementContext ctx) {
        final Symbol expectedValue = this.symbols.resolve(this.symbols.owner.type);
        final Symbol value = ctx.expression() == null ? Symbol.NOTHING : this.visit(ctx.expression());

        if (!TypeChecker.compatible(expectedValue, value)) {
            this.results.add(new Result(ctx.getStart().getInputStream().getSourceName(), ctx.getStart().getLine(),
                    ctx.getStart().getCharPositionInLine(),
                    ctx.getStart().getCharPositionInLine() + ctx.getText().length(), "Incompatible type"));
        }

        return expectedValue;
    }

    @Override
    public Symbol visitIfStatement(vrjParser.IfStatementContext ctx) {
        final Symbol condition = this.visit(ctx.condition);
        this.checkForBoolean(ctx.condition, condition);

        this.visit(ctx.statements());

        if (ctx.elseIfStatement() != null) {
            for (final vrjParser.ElseIfStatementContext elseif : ctx.elseIfStatement()) {
                this.visit(elseif);
            }
        }

        if (ctx.elseStatement() != null) {
            this.visit(ctx.elseStatement());
        }

        return Symbol.NOTHING;
    }

    @Override
    public Symbol visitElseIfStatement(vrjParser.ElseIfStatementContext ctx) {
        final Symbol condition = this.visit(ctx.condition);
        this.checkForBoolean(ctx.condition, condition);

        this.visit(ctx.statements());

        return Symbol.NOTHING;
    }
}