de.uni.bremen.monty.moco.ast.ASTBuilder.java Source code

Java tutorial

Introduction

Here is the source code for de.uni.bremen.monty.moco.ast.ASTBuilder.java

Source

/*
 * moco, the Monty Compiler
 * Copyright (c) 2013-2014, Monty's Coconut, All rights reserved.
 *
 * This file is part of moco, the Monty Compiler.
 *
 * moco is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 *
 * moco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * Linking this program and/or its accompanying libraries statically or
 * dynamically with other modules is making a combined work based on this
 * program. Thus, the terms and conditions of the GNU General Public License
 * cover the whole combination.
 *
 * As a special exception, the copyright holders of moco give
 * you permission to link this programm and/or its accompanying libraries
 * with independent modules to produce an executable, regardless of the
 * license terms of these independent modules, and to copy and distribute the
 * resulting executable under terms of your choice, provided that you also meet,
 * for each linked independent module, the terms and conditions of the
 * license of that module.
 *
 * An independent module is a module which is not
 * derived from or based on this program and/or its accompanying libraries.
 * If you modify this library, you may extend this exception to your version of
 * the program or library, but you are not obliged to do so. If you do not wish
 * to do so, delete this exception statement from your version.
 *
 * You should have received a copy of the GNU General Public
 * License along with this library.
 */
package de.uni.bremen.monty.moco.ast;

import de.uni.bremen.monty.moco.antlr.MontyBaseVisitor;
import de.uni.bremen.monty.moco.antlr.MontyParser;
import de.uni.bremen.monty.moco.antlr.MontyParser.*;
import de.uni.bremen.monty.moco.ast.declaration.*;
import de.uni.bremen.monty.moco.ast.declaration.FunctionDeclaration.DeclarationType;
import de.uni.bremen.monty.moco.ast.expression.*;
import de.uni.bremen.monty.moco.ast.expression.literal.*;
import de.uni.bremen.monty.moco.ast.statement.*;
import de.uni.bremen.monty.moco.util.*;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.commons.io.FilenameUtils;

import java.util.*;

public class ASTBuilder extends MontyBaseVisitor<ASTNode> {
    private final String fileName;
    private Stack<Block> currentBlocks;
    // functions push null, generators push their return type
    private Stack<ResolvableIdentifier> currentGeneratorReturnType;
    private VariableDeclaration.DeclarationType currentVariableContext;
    private FunctionDeclaration.DeclarationType currentFunctionContext;
    private TupleDeclarationFactory tupleDeclarationFactory;
    private PatternMatchingContext patternMatchingContext = new PatternMatchingContext();

    private static final Map<String, String> binaryOperatorMapping = new HashMap<String, String>() {
        {
            put("+", "_add_");
            put("-", "_sub_");
            put("*", "_mul_");
            put("/", "_div_");
            put("%", "_mod_");
            put("^", "_pow_");
            put("=", "_eq_");
            put("!=", "_neq_");
            put("<", "_lt_");
            put(">", "_gt_");
            put("<=", "_leq_");
            put(">=", "_geq_");
            put("in", "_contains_");
        }
    };

    private static final Map<String, String> unaryOperatorMapping = new HashMap<String, String>() {
        {
            put("-", "_neg_");
            put("not", "_not_");
        }
    };

    public ASTBuilder(String fileName, TupleDeclarationFactory tupleDeclarationFactory) {
        this.fileName = fileName;
        currentBlocks = new Stack<>();
        currentGeneratorReturnType = new Stack<>();
        currentGeneratorReturnType.push(null); // initially, we are not inside a generator
        this.tupleDeclarationFactory = tupleDeclarationFactory;
    }

    private Position position(Token idSymbol) {
        return new Position(fileName, idSymbol.getLine(), idSymbol.getCharPositionInLine());
    }

    private String getText(TerminalNode identifier) {
        return identifier.getSymbol().getText();
    }

    @Override
    public ASTNode visitModuleDeclaration(@NotNull MontyParser.ModuleDeclarationContext ctx) {
        Block block = new Block(position(ctx.getStart()));
        ModuleDeclaration module = new ModuleDeclaration(position(ctx.getStart()),
                new Identifier(FilenameUtils.getBaseName(fileName)), block, new ArrayList<Import>());
        currentBlocks.push(block);

        for (MontyParser.ImportLineContext imp : ctx.importLine()) {

            module.getImports()
                    .add(new Import(position(imp.getStart()), new ResolvableIdentifier(getText(imp.Identifier()))));
        }

        for (ClassDeclarationContext classDeclarationContext : ctx.classDeclaration()) {
            ClassDeclaration classDecl = (ClassDeclaration) visit(classDeclarationContext);
            block.addDeclaration(classDecl);
        }
        addStatementsToBlock(block, ctx.statement());
        currentBlocks.pop();
        return module;
    }

    @Override
    public ASTNode visitUnpackAssignment(@NotNull MontyParser.UnpackAssignmentContext ctx) {
        Expression right = (Expression) visit(ctx.right);
        List<Expression> left = new ArrayList<>(ctx.left.unpackable().size());

        for (UnpackableContext target : ctx.left.unpackable()) {
            if (target.variableDeclaration() != null) {
                VariableDeclaration decl = (VariableDeclaration) visitVariableDeclaration(
                        target.variableDeclaration());
                currentBlocks.peek().addDeclaration(decl);
                left.add(new VariableAccess(position(target.getStart()),
                        ResolvableIdentifier.convert(decl.getIdentifier())));
            } else {
                left.add((Expression) visitExpression(target.expression()));
            }
        }
        UnpackAssignment assignment = new UnpackAssignment(position(ctx.getStart()), left, right);
        currentBlocks.peek().addDeclaration(assignment.getTmpDecl());
        return assignment;
    }

    @Override
    public ASTNode visitAssignment(@NotNull AssignmentContext ctx) {
        Assignment assignment = new Assignment(position(ctx.getStart()), (Expression) visit(ctx.left),
                (Expression) visit(ctx.right));
        return assignment;
    }

    @Override
    public ASTNode visitCompoundAssignment(CompoundAssignmentContext ctx) {
        Expression expr = binaryExpression(position(ctx.getStart()),
                ctx.compoundSymbol().operator.getText().substring(0, 1), ctx.left, ctx.right);

        return new Assignment(position(ctx.getStart()), (Expression) visit(ctx.left), expr);
    }

    @Override
    public ASTNode visitVariableDeclaration(@NotNull VariableDeclarationContext ctx) {
        ResolvableIdentifier type = convertResolvableIdentifier(ctx.type());
        tupleDeclarationFactory.checkTupleType(type);
        return new VariableDeclaration(position(ctx.getStart()), new Identifier(getText(ctx.Identifier())), type,
                currentVariableContext);
    }

    private ResolvableIdentifier convertResolvableIdentifier(TypeContext type) {
        String typeName;
        List<TypeContext> typeParameters = null;
        // if there is no class identifier, we have to handle syntactic sugar here
        if (type.ClassIdentifier() == null) {
            if (type.arrow() != null) {
                typeParameters = type.type();
                typeName = "Function";
            } else {
                // a tuple
                typeParameters = type.type();
                int n = typeParameters != null ? typeParameters.size() : 0;
                typeName = "Tuple" + n;
            }
        } else {
            typeName = type.ClassIdentifier().toString();
            if (type.typeList() != null) {
                typeParameters = type.typeList().type();
            }
        }

        // handle generic type parameters
        ArrayList<ResolvableIdentifier> genericTypes = new ArrayList<>();
        if (typeParameters != null) {
            for (TypeContext typeContext : typeParameters) {
                genericTypes.add(convertResolvableIdentifier(typeContext));
            }
        }
        return new ResolvableIdentifier(typeName, genericTypes);
    }

    @Override
    public ASTNode visitFunctionCall(FunctionCallContext ctx) {
        ArrayList<Expression> arguments = new ArrayList<>();
        ResolvableIdentifier identifier;
        if (ctx.Identifier() == null) {
            identifier = convertResolvableIdentifier(ctx.type());
        } else {
            identifier = new ResolvableIdentifier(ctx.Identifier().getText());
        }
        tupleDeclarationFactory.checkTupleType(identifier); // since constructor calls are also function calls
        FunctionCall func = new FunctionCall(position(ctx.getStart()), identifier, arguments);
        if (ctx.expressionList() != null) {
            for (ExpressionContext exprC : ctx.expressionList().expression()) {

                ASTNode expr = visit(exprC);
                if (expr instanceof Expression) {

                    arguments.add((Expression) expr);
                }
            }
        }
        return new WrappedFunctionCall(position(ctx.getStart()), func);
    }

    private void buildDefaultFunctions(boolean isFunction, List<DefaultParameterContext> defaultParameter,
            List<VariableDeclaration> allVariableDeclarations, List<VariableDeclaration> params,
            List<Expression> defaultExpression, List<VariableDeclaration> defaultVariableDeclaration,
            Identifier identifier, Token token, TypeContext typeContext, DeclarationType declarationTypeCopy,
            boolean isNative) {

        for (int defaultParameterIdx = 0; defaultParameterIdx < defaultParameter.size(); defaultParameterIdx++) {
            Block block = new Block(position(token));
            List<Expression> l = new ArrayList<>();
            for (int variableDeclarationIdy = 0; variableDeclarationIdy < allVariableDeclarations
                    .size(); variableDeclarationIdy++) {
                if (variableDeclarationIdy >= params.size() + defaultParameterIdx) {
                    l.add(defaultExpression.get(variableDeclarationIdy - params.size()));
                } else if (variableDeclarationIdy < params.size()) {
                    l.add(new VariableAccess(position(token), new ResolvableIdentifier(
                            params.get(variableDeclarationIdy).getIdentifier().getSymbol())));
                } else {
                    VariableDeclaration variableDeclaration = defaultVariableDeclaration
                            .get(variableDeclarationIdy - params.size());
                    l.add(new VariableAccess(position(token),
                            new ResolvableIdentifier(variableDeclaration.getIdentifier().getSymbol())));
                }
            }

            List<VariableDeclaration> subParams = new ArrayList<>(params.size() + defaultParameterIdx);
            for (int i = 0; i < params.size() + defaultParameterIdx; i++) {
                VariableDeclaration var = allVariableDeclarations.get(i);
                subParams.add(new VariableDeclaration(var.getPosition(), var.getIdentifier(),
                        var.getTypeIdentifier(), var.getDeclarationType()));
            }

            Expression expression = new FunctionCall(position(token),
                    new ResolvableIdentifier(identifier.getSymbol()), l);

            if (declarationTypeCopy == FunctionDeclaration.DeclarationType.METHOD) {
                expression = new MemberAccess(position(token), new SelfExpression(position(token)), expression);
            }

            ResolvableIdentifier returnTypeIdent = null;
            if (isFunction) {
                block.addStatement(new ReturnStatement(new Position(), expression));
                returnTypeIdent = convertResolvableIdentifier(typeContext);
                tupleDeclarationFactory.checkTupleType(returnTypeIdent);
            } else {
                block.addStatement((Statement) expression);
                block.addStatement(new ReturnStatement(new Position(), null));
            }

            FunctionDeclaration funDecl = new FunctionDeclaration(position(token), identifier, block, subParams,
                    declarationTypeCopy, returnTypeIdent);
            funDecl.setNative(isNative);

            currentBlocks.peek().addDeclaration(funDecl);
        }
    }

    private FunctionDeclaration buildFunctions(boolean isFunction, ParameterListContext parameterListContext,
            Token token, TypeContext typeContext, StatementBlockContext statementBlockContext,
            Identifier identifier, boolean isNative) {

        FunctionDeclaration.DeclarationType declarationTypeCopy = currentFunctionContext;
        List<VariableDeclaration> params = parameterListToVarDeclList(parameterListContext);
        List<DefaultParameterContext> defaultParameter = defaultParameterListToVarDeclList(parameterListContext);

        List<VariableDeclaration> defaultVariableDeclaration = new ArrayList<>();
        List<Expression> defaultExpression = new ArrayList<>();
        for (DefaultParameterContext context : defaultParameter) {
            defaultVariableDeclaration.add((VariableDeclaration) visit(context.variableDeclaration()));
            defaultExpression.add((Expression) visit(context.expression()));
        }

        List<VariableDeclaration> allVariableDeclarations = new ArrayList<>();
        allVariableDeclarations.addAll(params);
        allVariableDeclarations.addAll(defaultVariableDeclaration);

        buildDefaultFunctions(isFunction, defaultParameter, allVariableDeclarations, params, defaultExpression,
                defaultVariableDeclaration, identifier, token, typeContext, declarationTypeCopy, isNative);

        FunctionDeclaration funDecl;

        ResolvableIdentifier returnTypeIdent = null;
        if (isFunction) {
            returnTypeIdent = convertResolvableIdentifier(typeContext);
            tupleDeclarationFactory.checkTupleType(returnTypeIdent);
        }
        funDecl = new FunctionDeclaration(position(token), identifier, (Block) visit(statementBlockContext),
                allVariableDeclarations, declarationTypeCopy, returnTypeIdent);
        funDecl.setNative(isNative);
        if (funDecl.isUnbound()) {
            FunctionWrapperFactory.generateWrapperClass(funDecl, tupleDeclarationFactory);
            currentBlocks.peek().addDeclaration(funDecl.getWrapperClass());
            currentBlocks.peek().addDeclaration(funDecl.getWrapperFunctionObjectDeclaration());
            currentBlocks.peek().addStatement(funDecl.getWrapperFunctionAssignment());
        }

        return funDecl;
    }

    private FunctionDeclaration buildAbstractMethod(boolean functionDeclaration,
            ParameterListContext parameterListContext, Token token, TypeContext typeContext,
            Identifier identifier) {

        List<VariableDeclaration> params = parameterListToVarDeclList(parameterListContext);

        ResolvableIdentifier typeIdent = null;
        if (typeContext != null) {
            typeIdent = convertResolvableIdentifier(typeContext);
            tupleDeclarationFactory.checkTupleType(typeIdent);
        }

        FunctionDeclaration procDecl = new FunctionDeclaration(position(token), identifier,
                new Block(position(token)), params, currentFunctionContext, typeIdent, true);
        return procDecl;
    }

    @Override
    public ASTNode visitFunctionDeclaration(FunctionDeclarationContext ctx) {
        currentGeneratorReturnType.push(null);
        boolean isNativeFunction = ctx.nativeAnnotation() != null;
        FunctionDeclaration proc = buildFunctions(ctx.type() != null, ctx.parameterList(), ctx.getStart(),
                ctx.type(), ctx.statementBlock(), new Identifier(getText(ctx.Identifier())), isNativeFunction);
        // if the function does not have any return type, we have to add a return statement
        if (ctx.type() == null) {
            List<Statement> list = proc.getBody().getStatements();
            if ((list.isEmpty()) || !(list.get(list.size() - 1) instanceof ReturnStatement)) {
                list.add(new ReturnStatement(new Position(), null));
            }
        }
        currentGeneratorReturnType.pop();
        return proc;
    }

    @Override
    public ASTNode visitGeneratorDeclaration(GeneratorDeclarationContext ctx) {
        Position pos = position(ctx.getStart());
        ResolvableIdentifier returnType = convertResolvableIdentifier(ctx.type());
        ResolvableIdentifier className = new ResolvableIdentifier(getText(ctx.ClassIdentifier()));
        tupleDeclarationFactory.checkTupleType(returnType);
        currentGeneratorReturnType.push(returnType);

        List<VariableDeclaration> params = new ArrayList<>();
        List<VariableDeclaration> defaultParams = new ArrayList<>();
        List<Expression> defaultValues = new ArrayList<>();
        parameterListToVarDeclListIncludingDefaults(ctx.parameterList(), params, defaultParams, defaultValues);

        Block funBody = (Block) visit(ctx.statementBlock());
        currentGeneratorReturnType.pop();

        return createGenerator(pos, className, returnType, params, defaultParams, defaultValues, funBody);
    }

    private ASTNode createGenerator(Position pos, ResolvableIdentifier className, ResolvableIdentifier returnType,
            List<VariableDeclaration> params, List<VariableDeclaration> defaultParams,
            List<Expression> defaultValues, Block body) {
        tupleDeclarationFactory.checkTupleType(returnType);
        currentGeneratorReturnType.push(returnType);

        List<VariableDeclaration> allParams = new ArrayList<>();
        allParams.addAll(params);
        allParams.addAll(defaultParams);

        List<VariableDeclaration> allParamsCopy = new ArrayList<>(allParams.size());
        for (VariableDeclaration param : allParams) {
            allParamsCopy.add(new VariableDeclaration(param.getPosition(), param.getIdentifier(),
                    param.getTypeIdentifier(), param.getDeclarationType()));
        }

        ClassDeclaration iterator = GeneratorClassFactory.generateGeneratorIteratorClass(pos, allParamsCopy, body,
                returnType);
        currentBlocks.peek().addDeclaration(iterator);

        ClassDeclaration generator = GeneratorClassFactory.generateGeneratorClass(pos, className,
                ResolvableIdentifier.convert(iterator.getIdentifier()), params, defaultParams, defaultValues,
                returnType);
        currentGeneratorReturnType.pop();
        return generator;
    }

    protected List<VariableDeclaration> createParameterListWithoutDefaults(
            ParameterListWithoutDefaultsContext ctx) {
        ArrayList<VariableDeclaration> parameterList = new ArrayList<>();
        ParameterListWithoutDefaultsContext plist = ctx;
        if (plist != null) {
            currentVariableContext = VariableDeclaration.DeclarationType.PARAMETER;
            for (VariableDeclarationContext var : plist.variableDeclaration()) {
                parameterList.add((VariableDeclaration) visit(var));
            }
        }
        return parameterList;
    }

    @Override
    public ASTNode visitFunctionExpression(FunctionExpressionContext ctx) {
        List<VariableDeclaration> parameterList = createParameterListWithoutDefaults(
                ctx.parameterListWithoutDefaults());

        Block body = new Block(position(ctx.expression().getStart()));
        body.addStatement(new ReturnStatement(body.getPosition(), (Expression) visit(ctx.expression())));
        FunctionDeclaration funDecl = new FunctionDeclaration(position(ctx.getStart()),
                TmpIdentifierFactory.getUniqueIdentifier(), body, parameterList);

        FunctionWrapperFactory.generateWrapperClass(funDecl, tupleDeclarationFactory);
        currentBlocks.peek().addDeclaration(funDecl);
        currentBlocks.peek().addDeclaration(funDecl.getWrapperClass());
        Declaration wrapperObject = funDecl.getWrapperFunctionObjectDeclaration();
        currentBlocks.peek().addDeclaration(wrapperObject);
        currentBlocks.peek().addStatement(funDecl.getWrapperFunctionAssignment());

        return new VariableAccess(funDecl.getPosition(),
                ResolvableIdentifier.convert(wrapperObject.getIdentifier()));
    }

    @Override
    public ASTNode visitClassDeclaration(ClassDeclarationContext ctx) {
        // if there is an 'abstract' keyword, the class is abstract
        return createClass(position(ctx.getStart()), ctx.type(), ctx.typeList(),
                ctx.getTokens(MontyParser.AbstractKeyword).size() > 0, ctx.memberDeclaration());
    }

    protected ClassDeclaration createClass(Position pos, TypeContext className, TypeListContext inheritsFrom,
            boolean isAbstract, List<MemberDeclarationContext> members) {
        List<ResolvableIdentifier> superClasses = new ArrayList<>();
        if (inheritsFrom != null) {
            for (TypeContext typeContext : inheritsFrom.type()) {
                ResolvableIdentifier type = convertResolvableIdentifier(typeContext);
                superClasses.add(type);
                tupleDeclarationFactory.checkTupleType(type);
            }
        }

        ArrayList<AbstractGenericType> genericTypes = new ArrayList<>();
        ClassDeclaration cl = new ClassDeclaration(pos, convertResolvableIdentifier(className), superClasses,
                new Block(pos), isAbstract, genericTypes);

        TypeContext type = className;
        if (type.typeList() != null) {
            for (TypeContext typeContext1 : type.typeList().type()) {
                genericTypes.add(new AbstractGenericType(cl, position(typeContext1.getStart()),
                        convertResolvableIdentifier(typeContext1)));
            }
        }

        if (members == null) {
            members = new ArrayList<>();
        }
        currentBlocks.push(cl.getBlock());
        for (MemberDeclarationContext member : members) {
            currentVariableContext = VariableDeclaration.DeclarationType.ATTRIBUTE;
            currentFunctionContext = FunctionDeclaration.DeclarationType.METHOD;
            ASTNode astNode = visit(member);
            if (astNode instanceof Declaration) {

                Declaration decl = (Declaration) astNode;
                AccessModifierContext modifierCtx = member.accessModifier();

                // access modifiers are optional
                if (modifierCtx != null) {
                    decl.setAccessModifier(AccessModifier.stringToAccess(modifierCtx.modifier.getText()));
                }
                // if none is given, the default accessibility is "package"
                else {
                    decl.setAccessModifier(AccessModifier.PACKAGE);
                }

                cl.getBlock().addDeclaration(decl);
            } else if (astNode instanceof Assignment) {

                Assignment asgnmnt = new Assignment(
                        astNode.getPosition(), new MemberAccess(astNode.getPosition(),
                                new SelfExpression(new Position()), ((Assignment) astNode).getLeft()),
                        ((Assignment) astNode).getRight());
                cl.getBlock().addStatement(asgnmnt);
            }
        }
        currentBlocks.pop();
        return cl;
    }

    @Override
    public ASTNode visitAbstractMethodDeclaration(AbstractMethodDeclarationContext ctx) {
        return buildAbstractMethod(true, ctx.parameterList(), ctx.getStart(), ctx.type(),
                new Identifier(getText(ctx.Identifier())));
    }

    private List<VariableDeclaration> parameterListToVarDeclList(ParameterListContext parameter) {
        if (parameter == null) {
            return new ArrayList<>();
        }
        ArrayList<VariableDeclaration> parameterList = new ArrayList<>();
        currentVariableContext = VariableDeclaration.DeclarationType.PARAMETER;
        for (VariableDeclarationContext var : parameter.variableDeclaration()) {
            parameterList.add((VariableDeclaration) visit(var));
        }
        return parameterList;
    }

    private void parameterListToVarDeclListIncludingDefaults(ParameterListContext context,
            List<VariableDeclaration> params, List<VariableDeclaration> defaultParams,
            List<Expression> defaultValues) {

        if (context == null) {
            return;
        }
        currentVariableContext = VariableDeclaration.DeclarationType.PARAMETER;
        if (context.variableDeclaration() != null) {
            for (VariableDeclarationContext var : context.variableDeclaration()) {
                params.add((VariableDeclaration) visit(var));
            }
        }

        if (context.defaultParameter() != null) {
            for (DefaultParameterContext var : context.defaultParameter()) {
                defaultParams.add((VariableDeclaration) visit(var.variableDeclaration()));
                defaultValues.add((Expression) visit(var.expression()));
            }
        }

    }

    private List<DefaultParameterContext> defaultParameterListToVarDeclList(ParameterListContext parameter) {
        if (parameter == null) {
            return new ArrayList<>();
        }
        currentVariableContext = VariableDeclaration.DeclarationType.PARAMETER;
        return parameter.defaultParameter();
    }

    @Override
    public ASTNode visitForStatement(ForStatementContext ctx) {
        ResolvableIdentifier ident = new ResolvableIdentifier(getText(ctx.Identifier()));
        Position pos = position(ctx.getStart());
        Expression iterableExpression = new MemberAccess(pos, (Expression) visit(ctx.expression()),
                new FunctionCall(pos, new ResolvableIdentifier("getIterator"), new ArrayList<Expression>()));
        return createForLoop(pos, ident, iterableExpression, (Block) visit(ctx.statementBlock()));
    }

    private ASTNode createForLoop(Position pos, ResolvableIdentifier indexVar, Expression iterableExpression,
            Block body) {
        // Iterator<Int> r := MyRange(10).getIterator()
        // while true:
        // ....Maybe<Int> _i := r.getNext()
        // ....if _i.hasValue():
        // ........Int i := (_i as Just<Int>).getValue()
        // ........// body
        // ....else:
        // ........break

        // ## MONTY: ## Iterator<Int> r := MyRange(10).getIterator()
        // get the iterator object
        ResolvableIdentifier iterableIdentifier = TmpIdentifierFactory.getUniqueIdentifier();
        VariableDeclaration iterableDeclaration = new VariableDeclaration(pos, iterableIdentifier,
                iterableExpression);
        Assignment iterableAssignment = new Assignment(pos, new VariableAccess(pos, iterableIdentifier),
                iterableExpression);
        // add the declaration and the assignment to the current block
        currentBlocks.peek().addDeclaration(iterableDeclaration);
        currentBlocks.peek().addStatement(iterableAssignment);

        // ## MONTY: ## while true:
        Block whileBlock = new Block(pos);
        WhileLoop loop = new WhileLoop(pos, new BooleanLiteral(pos, true), whileBlock);

        // ## MONTY: ## Maybe<Int> _i := r.getNext()
        // add the maybe value containing the indexing variable to the loop's body
        ResolvableIdentifier maybeIdent = TmpIdentifierFactory.getUniqueIdentifier();
        Expression callGetNext = new MemberAccess(pos, new VariableAccess(pos, iterableIdentifier),
                new FunctionCall(pos, new ResolvableIdentifier("getNext"), new ArrayList<Expression>()));
        whileBlock.addDeclaration(new VariableDeclaration(pos, maybeIdent, callGetNext));
        whileBlock.getStatements().add(0, new Assignment(pos,
                new VariableAccess(pos, ResolvableIdentifier.convert(maybeIdent)), callGetNext));

        // ## MONTY: ## if _i.hasValue():
        // check whether we have a Just<T> or a Nothing<T> here...
        Block thenBlock = body;
        Block elseBlock = new Block(pos);
        Statement ifstm = new ConditionalStatement(pos,
                new MemberAccess(pos, new VariableAccess(pos, maybeIdent),
                        new FunctionCall(pos, new ResolvableIdentifier("hasValue"), new ArrayList<Expression>())),
                thenBlock, elseBlock);

        // ## MONTY: ## Int i := (_i as Just<Int>).getValue()
        ResolvableIdentifier iteratorType = new ResolvableIdentifier("Just",
                Arrays.asList((ResolvableIdentifier) null));
        Expression getValueExpr = new MemberAccess(pos,
                new CastExpression(pos, new VariableAccess(pos, maybeIdent), iteratorType, callGetNext),
                new FunctionCall(pos, new ResolvableIdentifier("getValue"), new ArrayList<Expression>()));
        VariableDeclaration itDecl = new VariableDeclaration(pos, indexVar, getValueExpr);
        Assignment itAss = new Assignment(pos, new VariableAccess(pos, indexVar), getValueExpr);
        thenBlock.addDeclaration(itDecl);
        thenBlock.getStatements().add(0, itAss);

        whileBlock.addStatement(ifstm);

        // ## MONTY: ## else: break
        elseBlock.addStatement(new BreakStatement(pos));
        return loop;
    }

    @Override
    public ASTNode visitWhileStatement(WhileStatementContext ctx) {
        ASTNode expr = visit(ctx.expression());
        WhileLoop loop = new WhileLoop(position(ctx.getStart()), (Expression) expr,
                (Block) visit(ctx.statementBlock()));

        return loop;
    }

    @Override
    public ASTNode visitIfStatement(IfStatementContext ctx) {

        Block leastElseBlock = new Block(new Position());
        if (ctx.elseBlock != null) {
            leastElseBlock = (Block) visit(ctx.elseBlock);
        }
        Block firstElseBlock;

        if (ctx.elif().isEmpty()) {
            firstElseBlock = leastElseBlock;
        } else {

            Block lastElseBlock = new Block(position(ctx.getStart()));
            firstElseBlock = lastElseBlock;
            Block currentElseBlock;

            for (int i = 0; i < ctx.elif().size(); i++) {
                ElifContext currentCtx = ctx.elif(i);

                if (i == ctx.elif().size() - 1) {
                    currentElseBlock = leastElseBlock;
                } else {
                    currentElseBlock = new Block(position(currentCtx.getStart()));
                }

                lastElseBlock.addStatement(new ConditionalStatement(position(ctx.elif().get(i).getStart()),
                        (Expression) visit(currentCtx.elifCondition), (Block) visit(currentCtx.elifBlock),
                        currentElseBlock));
                lastElseBlock = currentElseBlock;

            }
        }
        return new ConditionalStatement(position(ctx.getStart()), (Expression) visit(ctx.ifCondition),
                (Block) visit(ctx.thenBlock), firstElseBlock);

    }

    @Override
    public ASTNode visitTryStatement(TryStatementContext ctx) {
        ASTNode decl = visit(ctx.variableDeclaration().get(0));
        TryStatement tryStm = new TryStatement(position(ctx.getStart()), (VariableDeclaration) decl,
                new Block(position(ctx.getStart())), new Block(position(ctx.getStart())));
        addStatementsToBlock(tryStm.getTryBlock(), ctx.tryBlock.statement());
        addStatementsToBlock(tryStm.getHandleBlock(), ctx.handleBlock.statement());
        return tryStm;
    }

    public void addStatementsToBlock(Block block, List<StatementContext> statements) {
        for (StatementContext stm : statements) {
            currentVariableContext = VariableDeclaration.DeclarationType.VARIABLE;
            currentFunctionContext = FunctionDeclaration.DeclarationType.UNBOUND;
            ASTNode node = visit(stm);
            if (node instanceof Statement) {
                block.addStatement((Statement) node);
            } else {
                block.addDeclaration((Declaration) node);
            }
        }
    }

    @Override
    public ASTNode visitIndependentDeclaration(IndependentDeclarationContext ctx) {
        ASTNode node;
        if (ctx.functionDeclaration() != null) {
            node = visit(ctx.functionDeclaration());
        } else {
            node = visit(ctx.variableDeclaration());
            if (ctx.expression() != null) {
                currentBlocks.peek().addDeclaration((Declaration) node);
                return new Assignment(position(ctx.getStart()),
                        new VariableAccess(position(ctx.getStart()),
                                ResolvableIdentifier.convert(((VariableDeclaration) node).getIdentifier())),
                        (Expression) visit(ctx.expression()));
            }
        }
        return node;
    }

    @Override
    public ASTNode visitStatementBlock(StatementBlockContext ctx) {

        Block block = new Block(position(ctx.getStart()));
        currentBlocks.push(block);
        addStatementsToBlock(block, ctx.statement());
        currentBlocks.pop();
        return block;
    }

    @Override
    public ASTNode visitReturnStm(ReturnStmContext ctx) {
        if (currentGeneratorReturnType.peek() != null) {
            // if we are inside a generator, automatically return "Nothing<>"
            return new ReturnStatement(position(ctx.getStart()),
                    new FunctionCall(position(ctx.getStop()),
                            new ResolvableIdentifier("Nothing", Arrays.asList(currentGeneratorReturnType.peek())),
                            new ArrayList<Expression>()));
        } else {
            ASTNode expr = null;
            if (ctx.expression() != null) {
                expr = visit(ctx.expression());
            }

            return new ReturnStatement(position(ctx.getStart()), (Expression) expr);
        }
    }

    @Override
    public ASTNode visitYieldStm(YieldStmContext ctx) {
        ASTNode expr = visit(ctx.expression());
        return createYieldStatement(position(ctx.getStart()), (Expression) expr);
    }

    protected Statement createYieldStatement(Position pos, Expression expr) {
        return new YieldStatement(pos,
                new FunctionCall(expr.getPosition(),
                        new ResolvableIdentifier("Just", Arrays.asList(currentGeneratorReturnType.peek())),
                        Arrays.asList(expr)));
    }

    @Override
    public ASTNode visitRaiseStm(RaiseStmContext ctx) {
        ASTNode expr = null;
        if (ctx.expression() != null) {

            expr = visit(ctx.expression());
        }
        return new RaiseStatement(position(ctx.getStart()), (Expression) expr);
    }

    @Override
    public ASTNode visitBreakStm(BreakStmContext ctx) {

        return new BreakStatement(position(ctx.getStart()));
    }

    @Override
    public ASTNode visitSkipStm(SkipStmContext ctx) {

        return new SkipStatement(position(ctx.getStart()));
    }

    @Override
    public ASTNode visitExpression(ExpressionContext ctx) {

        if (ctx.primary() != null) {
            return visit(ctx.primary());
        } else if (ctx.ifExpCondition != null && ctx.ifExprElse != null && ctx.ifExprThen != null) {
            return visitTernary(ctx);
        } else if (ctx.functionCall() != null) {
            return visit(ctx.functionCall());
        } else if (ctx.functionExpression() != null) {
            return visit(ctx.functionExpression());
        } else if (ctx.accessOperator() != null) {
            return visitMemberAccessExpr(ctx);
        } else if (ctx.plusMinusOperator() != null && ctx.singleExpression != null) {

            return unaryExpression(position(ctx.getStart()), ctx.plusMinusOperator().operator.getText(),
                    ctx.singleExpression);
        } else if (ctx.notOperator() != null) {

            return unaryExpression(position(ctx.getStart()), ctx.notOperator().operator.getText(),
                    ctx.singleExpression);
        } else if (ctx.powerOperator() != null) {

            return binaryExpression(position(ctx.getStart()), ctx.powerOperator().getText(), ctx.left, ctx.right);
        } else if (ctx.dotOperator() != null) {

            return binaryExpression(position(ctx.getStart()), ctx.dotOperator().getText(), ctx.left, ctx.right);
        } else if (ctx.plusMinusOperator() != null) {

            return binaryExpression(position(ctx.getStart()), ctx.plusMinusOperator().getText(), ctx.left,
                    ctx.right);
        } else if (ctx.compareOperator() != null) {

            return binaryExpression(position(ctx.getStart()), ctx.compareOperator().getText(), ctx.left, ctx.right);
        } else if (ctx.eqOperator() != null) {

            return binaryExpression(position(ctx.getStart()), ctx.eqOperator().getText(), ctx.left, ctx.right);
        } else if (ctx.inOperator() != null) {

            return binaryExpression(position(ctx.getStart()), ctx.inOperator().getText(), ctx.left, ctx.right);
        } else if (ctx.andOperator() != null) {

            return booleanExpression(position(ctx.getStart()), ctx.andOperator().getText(), ctx.left, ctx.right);
        } else if (ctx.orOperator() != null) {

            return booleanExpression(position(ctx.getStart()), ctx.orOperator().getText(), ctx.left, ctx.right);
        } else if (ctx.asOperator() != null) {
            return visitCastExpression(ctx);
        } else if (ctx.isOperator() != null) {
            return visitIsExpression(ctx);
        } else if (ctx.listComprehension() != null) {
            return visitListComprehension(ctx.listComprehension());
        }
        return null;
    }

    @Override
    public ASTNode visitPrimary(PrimaryContext ctx) {
        if (ctx.singleExpression != null) {

            return visit(ctx.singleExpression);
        } else if (ctx.literal() != null) {

            return visit(ctx.literal());
        } else if (ctx.parent != null) {

            return visitParent(ctx);
        } else if (ctx.Identifier() != null) {

            return visitIdentifier(ctx);
        } else {

            return visitSelf(ctx);
        }
    }

    @Override
    public ASTNode visitLiteral(LiteralContext ctx) {

        if (ctx.IntegerLiteral() != null) {

            return new IntegerLiteral(position(ctx.getStart()),
                    Integer.parseInt(ctx.IntegerLiteral().getSymbol().getText()));
        } else if (ctx.RealLiteral() != null) {

            return new FloatLiteral(position(ctx.getStart()),
                    Float.parseFloat(ctx.RealLiteral().getSymbol().getText()));
        } else if (ctx.CharacterLiteral() != null) {

            return new CharacterLiteral(position(ctx.getStart()), ctx.CharacterLiteral().getSymbol().getText());
        } else if (ctx.StringLiteral() != null) {

            return new StringLiteral(position(ctx.getStart()), ctx.StringLiteral().getSymbol().getText());
        } else if (ctx.arrayLiteral() != null) {
            ArrayList<Expression> elements = new ArrayList<>();
            for (ExpressionContext eContext : ctx.arrayLiteral().expression()) {
                elements.add((Expression) visit(eContext));
            }
            return new ArrayLiteral(position(ctx.getStart()), elements);
        } else if (ctx.rangeLiteral() != null) {
            ArrayList<Expression> parameters = new ArrayList<>();
            for (ExpressionContext eContext : ctx.rangeLiteral().expression()) {
                parameters.add((Expression) visit(eContext));
            }
            return new FunctionCall(position(ctx.getStart()), new ResolvableIdentifier("Range"), parameters);
        } else if (ctx.tupleLiteral() != null) {
            ArrayList<Expression> elements = new ArrayList<>();
            for (ExpressionContext eContext : ctx.tupleLiteral().expression()) {
                elements.add((Expression) visit(eContext));
            }
            // generate a new tuple type if necessary
            tupleDeclarationFactory.checkTupleType(elements.size());
            TupleLiteral tuple = new TupleLiteral(position(ctx.getStart()), elements);
            return tuple;
        } else {
            return new BooleanLiteral(position(ctx.getStart()),
                    Boolean.parseBoolean(ctx.BooleanLiteral().toString()));
        }
    }

    public ASTNode visitIdentifier(PrimaryContext ctx) {

        return new VariableAccess(position(ctx.getStart()), new ResolvableIdentifier(getText(ctx.Identifier())));
    }

    public ASTNode visitSelf(PrimaryContext ctx) {

        return new SelfExpression(position(ctx.getStart()));
    }

    public ParentExpression visitParent(PrimaryContext ctx) {
        return new ParentExpression(position(ctx.getStart()), convertResolvableIdentifier(ctx.type()));
    }

    public ASTNode visitTernary(ExpressionContext ctx) {
        ASTNode condition = visit(ctx.ifExpCondition);
        ASTNode thenExpr = visit(ctx.ifExprThen);
        ASTNode elseExpr = visit(ctx.ifExprElse);
        return new ConditionalExpression(position(ctx.getStart()), (Expression) condition, (Expression) thenExpr,
                (Expression) elseExpr);
    }

    @Override
    public ASTNode visitMemberAccessStmt(@NotNull MemberAccessStmtContext ctx) {
        ASTNode left = visit(ctx.left);
        ASTNode right = visit(ctx.right);
        return new MemberAccess(position(ctx.getStart()), (Expression) left, (Expression) right);
    }

    public ASTNode visitMemberAccessExpr(ExpressionContext ctx) {
        ASTNode left = visit(ctx.left);
        ASTNode right = visit(ctx.right);
        return new MemberAccess(position(ctx.getStart()), (Expression) left, (Expression) right);
    }

    private MemberAccess unaryExpression(Position position, String operator, ExpressionContext expr) {
        Expression self = (Expression) visit(expr);
        FunctionCall operatorCall = new FunctionCall(position,
                new ResolvableIdentifier(unaryOperatorMapping.get(operator)), new ArrayList<Expression>());
        return new MemberAccess(position, self, operatorCall);
    }

    private MemberAccess binaryExpression(Position position, String operator, ExpressionContext left,
            ExpressionContext right) {

        Expression self = (Expression) visit(left);
        String methodName = binaryOperatorMapping.get(operator);
        FunctionCall operatorCall;
        // we have a special case for "a in x", which translates to "x._contains_(a)"
        if (methodName.equals("_contains_")) {
            Expression rightExpr = (Expression) visit(right);
            operatorCall = new FunctionCall(position, new ResolvableIdentifier(methodName), Arrays.asList(self));

            return new MemberAccess(position, rightExpr, operatorCall);
        } else {
            operatorCall = new FunctionCall(position, new ResolvableIdentifier(methodName),
                    Arrays.asList((Expression) visit(right)));
            return new MemberAccess(position, self, operatorCall);
        }

    }

    private Expression booleanExpression(Position position, String operator, ExpressionContext left,
            ExpressionContext right) {
        Expression leftExp = (Expression) visit(left);
        Expression rightExp = (Expression) visit(right);
        if (operator.equals("and")) {
            return createAndExpression(position, leftExp, rightExp);
        } else {
            return createOrExpression(position, leftExp, rightExp);
        }
    }

    private ConditionalExpression createAndExpression(Position position, Expression left, Expression right) {
        // (x and y) ==> y if x else false
        return new ConditionalExpression(position, left, right, new BooleanLiteral(position, false));
    }

    private ConditionalExpression createOrExpression(Position position, Expression left, Expression right) {
        // (x or y) ==> true if x else y
        return new ConditionalExpression(position, left, new BooleanLiteral(position, true), right);
    }

    private CastExpression visitCastExpression(ExpressionContext ctx) {
        ResolvableIdentifier type = convertResolvableIdentifier(ctx.type());
        tupleDeclarationFactory.checkTupleType(type);
        return new CastExpression(position(ctx.getStart()), (Expression) visit(ctx.expr), type);
    }

    private IsExpression visitIsExpression(ExpressionContext ctx) {
        ResolvableIdentifier type = new ResolvableIdentifier(getText(ctx.ClassIdentifier()));
        tupleDeclarationFactory.checkTupleType(type);
        return new IsExpression(position(ctx.getStart()), (Expression) visit(ctx.expr), type);
    }

    protected ASTNode aggregateResult(ASTNode aggregate, ASTNode nextResult) {
        return nextResult == null ? aggregate : nextResult;
    }

    @Override
    public ASTNode visitListComprehension(ListComprehensionContext ctx) {
        Expression target = (Expression) visit(ctx.expression());
        Position pos = position(ctx.getStart());
        Stack<ResolvableIdentifier> identifiers = new Stack<>();
        Stack<Expression> sources = new Stack<>();
        Stack<Expression> filters = new Stack<>();
        ResolvableIdentifier type = convertResolvableIdentifier(ctx.type());
        tupleDeclarationFactory.checkTupleType(type);

        for (ListGeneratorContext genCtx : ctx.listGenerator()) {
            identifiers.push(new ResolvableIdentifier(getText(genCtx.Identifier())));
            sources.push((Expression) visit(genCtx.expression()));
            if (genCtx.listFilter() != null) {
                filters.push((Expression) visit(genCtx.listFilter().expression()));
            } else {
                filters.push(null);
            }
        }

        // we start with the innermost block
        Block currentBlock = new Block(position(ctx.expression().getStart()));
        currentBlocks.push(currentBlock);
        currentGeneratorReturnType.push(type);
        currentBlock.addStatement(createYieldStatement(position(ctx.expression().getStart()), target));
        currentGeneratorReturnType.pop();
        for (int i = identifiers.size(); i > 0; i--) {
            Expression filter = filters.pop();
            Expression source = sources.pop();
            source = new MemberAccess(source.getPosition(), source, new FunctionCall(source.getPosition(),
                    new ResolvableIdentifier("getIterator"), new ArrayList<Expression>()));
            ResolvableIdentifier ident = identifiers.pop();
            if (filter != null) {
                Position filterPos = filter.getPosition();
                Statement ifStm = new ConditionalStatement(filterPos, filter, currentBlock, new Block(filterPos));
                currentBlocks.pop();
                currentBlock = new Block(filterPos);
                currentBlocks.push(currentBlock);
                currentBlock.addStatement(ifStm);
            }
            currentBlocks.pop();
            Block forBlock = new Block(source.getPosition()); // the block containing the for stm
            currentBlocks.push(forBlock);
            Statement forStm = (Statement) createForLoop(source.getPosition(), ident, source, currentBlock);
            currentBlock = forBlock;
            currentBlock.addStatement(forStm);
        }
        currentBlocks.pop();
        // the current block is now the outermost for loop
        ClassDeclaration generator = (ClassDeclaration) createGenerator(pos,
                TmpIdentifierFactory.getUniqueIdentifier(), type,
                // new ResolvableIdentifier("Object"),
                new ArrayList<VariableDeclaration>(), new ArrayList<VariableDeclaration>(),
                new ArrayList<Expression>(), currentBlock);
        currentBlocks.peek().addDeclaration(generator);
        // return new instance
        return new WrappedFunctionCall(pos, new FunctionCall(pos,
                ResolvableIdentifier.convert(generator.getIdentifier()), new ArrayList<Expression>()));
    }

    /* ---- Pattern matching ---- */

    @Override
    public ASTNode visitCaseStatement(CaseStatementContext ctx) {
        Position pos = position(ctx.getStart());
        // extract the expression and make a new temporary variable for it...
        Expression subject = (Expression) visit(ctx.expression());
        ResolvableIdentifier subjectIdent = TmpIdentifierFactory.getUniqueIdentifier();
        VariableDeclaration subjectVar = new VariableDeclaration(subject.getPosition(), subjectIdent, subject);
        Assignment subjectAss = new Assignment(subject.getPosition(),
                new VariableAccess(subject.getPosition(), subjectIdent), subject);
        currentBlocks.peek().addDeclaration(subjectVar);
        currentBlocks.peek().addStatement(subjectAss);

        // add a boolean variable in order to indicate, whether a match was successful (more flexible than nesting else)
        ResolvableIdentifier alreadyMatchedIdent = TmpIdentifierFactory.getUniqueIdentifier();
        VariableDeclaration alreadyMatchedDecl = new VariableDeclaration(pos, alreadyMatchedIdent,
                new ResolvableIdentifier("Bool"));
        currentBlocks.peek().addDeclaration(alreadyMatchedDecl);
        currentBlocks.peek().addStatement(setAlreadyMatched(pos, alreadyMatchedIdent, false));

        // convert the different cases into an if-else-orgy
        List<PatternContext> patterns = ctx.caseBlock().pattern();
        List<StatementBlockContext> blocks = ctx.caseBlock().statementBlock();
        return createPatternIfElifs(patterns, blocks, new VariableAccess(pos, subjectIdent), alreadyMatchedIdent);
    }

    public Statement createPatternIfElifs(List<PatternContext> patterns, List<StatementBlockContext> blocks,
            Expression subject, ResolvableIdentifier alreadyMatchedIdent) {
        Block outermostBlock = new Block(new Position());
        Block currentBlock = outermostBlock;
        int i = 0;
        for (PatternContext pattern : patterns) {
            patternMatchingContext.clearBindings();
            Position patternPos = position(pattern.getStart());
            Block patternBlock = (Block) visit(blocks.get(i));
            // at the end of every case block, we have to set the alreadyMatched variable to true
            patternBlock.addStatement(setAlreadyMatched(patternPos, alreadyMatchedIdent, true));

            currentBlock.addStatement(handlePattern(pattern, subject, patternBlock));
            Block insteadOfElseBlock = new Block(patternPos);
            currentBlock.addStatement(ifNotAlreadyMatched(patternPos, alreadyMatchedIdent, insteadOfElseBlock,
                    new Block(patternPos)));
            currentBlock = insteadOfElseBlock;
            i++;
        }
        currentBlocks.peek().addStatement(outermostBlock.getStatements().get(0));
        return outermostBlock.getStatements().get(1);
    }

    public ConditionalStatement handlePattern(PatternContext ctx, Expression subject, Block patternBlock) {
        Expression guardExpression = null;
        if (ctx.patternGuard() != null) {
            guardExpression = (Expression) visit(ctx.patternGuard().expression());
        }
        if (ctx.compoundPattern() != null) {
            return handleCompoundPattern(ctx.compoundPattern(), subject, patternBlock, guardExpression);
        } else if (ctx.expression() != null) {
            return handleExpressionPattern(ctx, subject, patternBlock, guardExpression);
        } else if (ctx.typedPattern() != null) {
            return handleTypedPattern(ctx, subject, patternBlock, guardExpression);
        } else {
            return handleWildcardPattern(ctx, subject, patternBlock, guardExpression);
        }
    }

    protected ConditionalStatement handleCompoundPattern(CompoundPatternContext ctx, Expression subject,
            Block patternBlock, Expression guardExpression) {
        Position pos = position(ctx.getStart());
        int patternCount = ctx.patternList() != null ? ctx.patternList().pattern().size() : 0;

        if (guardExpression != null) {
            ConditionalStatement guardStm = new ConditionalStatement(guardExpression.getPosition(), guardExpression,
                    patternBlock, new Block(guardExpression.getPosition()));
            patternBlock = new Block(pos);
            patternBlock.addStatement(guardStm);
        }

        // if there was no type specified, the pattern matches tuples, thus we assume that the value is already
        // a tuple and there is no need for type checks or type casts. Further, tuples don't have to be decomposed.
        if (ctx.type() != null) {

            ResolvableIdentifier type = convertResolvableIdentifier(ctx.type());
            tupleDeclarationFactory.checkTupleType(type);
            Expression castedArg = new CastExpression(pos, subject, type, true);
            ResolvableIdentifier decomposedIdent = TmpIdentifierFactory.getUniqueIdentifier();
            Block thenBlock = ctx.patternList() != null ? new Block(pos) : patternBlock;

            ConditionalStatement stm = new ConditionalStatement(pos, new IsExpression(pos, subject, type),
                    thenBlock, new Block(pos));

            if (ctx.patternList() != null) {
                Expression decomposeExpr = new FunctionCall(pos, new ResolvableIdentifier("decompose"),
                        Arrays.asList(castedArg));
                thenBlock.addDeclaration(new VariableDeclaration(pos, decomposedIdent, decomposeExpr));
                thenBlock
                        .addStatement(new Assignment(pos, new VariableAccess(pos, decomposedIdent), decomposeExpr));
                subject = new VariableAccess(pos, decomposedIdent);
                if (patternCount == 1) {
                    thenBlock
                            .addStatement(handlePattern(ctx.patternList().pattern().get(0), subject, patternBlock));
                } else {
                    thenBlock.addStatement(
                            processNestedCompoundPattern(ctx.patternList().pattern(), subject, 1, patternBlock));
                }
            }
            return stm;
        }
        if (patternCount == 1) {
            return handlePattern(ctx.patternList().pattern().get(0), subject, patternBlock);
        }
        return processNestedCompoundPattern(ctx.patternList().pattern(), subject, 1, patternBlock);
    }

    protected ConditionalStatement processNestedCompoundPattern(List<PatternContext> patterns, Expression subject,
            int i, Block patternBlock) {
        PatternContext pattern = patterns.get(0);
        patterns.remove(0);
        Position subPatternPos = position(pattern.getStart());

        Expression subjectPart = new MemberAccess(subPatternPos, subject,
                new VariableAccess(subPatternPos, new ResolvableIdentifier("_" + i)));

        Block thenBlock;
        if (patterns.size() > 0) {
            thenBlock = new Block(subPatternPos);
            thenBlock.addStatement(processNestedCompoundPattern(patterns, subject, i + 1, patternBlock));
        } else {
            thenBlock = patternBlock;
        }
        return handlePattern(pattern, subjectPart, thenBlock);
    }

    protected ConditionalStatement handleWildcardPattern(PatternContext ctx, Expression subject, Block patternBlock,
            Expression guardExpression) {
        // _ matches everything, but binds nothing
        Position pos = position(ctx.getStart());
        Expression condition = guardExpression != null ? guardExpression : new BooleanLiteral(pos, true);
        return new ConditionalStatement(pos, condition, patternBlock, new Block(pos));
    }

    protected ConditionalStatement handleTypedPattern(PatternContext ctx, Expression subject, Block patternBlock,
            Expression guardExpression) {
        Position pos = position(ctx.getStart());
        Expression condition;
        // a typed pattern matches everything that is an instance of "type"
        ResolvableIdentifier type = convertResolvableIdentifier(ctx.typedPattern().type());
        tupleDeclarationFactory.checkTupleType(type);
        condition = new IsExpression(position(ctx.getStart()), subject, type);

        if (guardExpression != null) {
            ConditionalStatement guardStm = new ConditionalStatement(guardExpression.getPosition(), guardExpression,
                    patternBlock, new Block(guardExpression.getPosition()));
            patternBlock = new Block(pos);
            patternBlock.addStatement(guardStm);
        }

        // if the identifier is not the wildcard '_', the value is bound to that identifier
        if (ctx.typedPattern().Identifier() != null) {
            ResolvableIdentifier localDecl = new ResolvableIdentifier(getText(ctx.typedPattern().Identifier()));
            VariableDeclaration localVarDecl = new VariableDeclaration(pos, localDecl, type);
            patternBlock.addDeclaration(localVarDecl);
            patternMatchingContext.addBinding(localVarDecl);
            patternBlock.getStatements().add(0, new Assignment(pos, new VariableAccess(pos, localDecl),
                    new CastExpression(pos, subject, type)));
        }
        return new ConditionalStatement(pos, condition, patternBlock, new Block(pos));
    }

    protected ConditionalStatement handleExpressionPattern(PatternContext ctx, Expression subject,
            Block patternBlock, Expression guardExpression) {
        Position pos = position(ctx.getStart());
        // if the pattern is an expression, we simply check the subject and the pattern expression for equality
        Expression expression = (Expression) visitExpression(ctx.expression());
        Expression condition = createAndExpression(pos, new IsExpression(pos, subject, expression),
                new MemberAccess(pos, new CastExpression(pos, subject, expression, true),
                        new FunctionCall(pos, new ResolvableIdentifier("_eq_"), Arrays.asList(expression))));

        if (guardExpression != null) {
            condition = createAndExpression(pos, condition, guardExpression);
        }
        return new ConditionalStatement(pos, condition, patternBlock, new Block(pos));
    }

    protected Statement setAlreadyMatched(Position pos, ResolvableIdentifier alreadyMatchedIdent, boolean value) {
        return new Assignment(pos, new VariableAccess(pos, alreadyMatchedIdent), new BooleanLiteral(pos, value));
    }

    protected ConditionalStatement ifNotAlreadyMatched(Position pos, ResolvableIdentifier alreadyMatchedIdent,
            Block thenBlock, Block elseBlock) {
        return new ConditionalStatement(pos,
                new MemberAccess(pos, new VariableAccess(pos, alreadyMatchedIdent),
                        new FunctionCall(pos, new ResolvableIdentifier("_not_"), new ArrayList<Expression>())),
                thenBlock, elseBlock);
    }

    /** {@inheritDoc} */
    @Override
    public ASTNode visitCaseClassDeclaration(CaseClassDeclarationContext ctx) {
        Position pos = position(ctx.getStart());
        ClassDeclaration classDecl = createClass(pos, ctx.type(), ctx.typeList(), false, ctx.memberDeclaration());

        List<VariableDeclaration> parameterList = createParameterListWithoutDefaults(
                ctx.parameterListWithoutDefaults());
        Block classBlock = classDecl.getBlock();
        Block initBody = new Block(pos);

        for (VariableDeclaration var : parameterList) {
            Position vp = var.getPosition();
            ResolvableIdentifier resId = ResolvableIdentifier.convert(var.getIdentifier());
            VariableDeclaration attr = new VariableDeclaration(vp, resId, var.getTypeIdentifier(),
                    VariableDeclaration.DeclarationType.ATTRIBUTE);
            attr.setAccessModifier(AccessModifier.PACKAGE);
            classBlock.addDeclaration(attr);

            initBody.addStatement(
                    new Assignment(pos, new MemberAccess(vp, new SelfExpression(vp), new VariableAccess(vp, resId)),
                            new VariableAccess(vp, resId)));
        }

        FunctionDeclaration caseInit = new FunctionDeclaration(pos, new Identifier("initializer"), initBody,
                parameterList, DeclarationType.INITIALIZER, null);
        classDecl.getBlock().addDeclaration(caseInit);
        classDecl.getBlock().addDeclaration(createCaseEqMethod(pos, parameterList, classDecl));
        currentBlocks.peek().addDeclaration(classDecl);

        // create the decompose function
        return createDecomposerFunction(pos, parameterList, classDecl);
    }

    protected FunctionDeclaration createDecomposerFunction(Position pos, List<VariableDeclaration> parameterList,
            ClassDeclaration classDecl) {
        int n = parameterList.size();
        Block decomposeBody = new Block(pos);

        ResolvableIdentifier decompReturnType;
        ResolvableIdentifier decompParam = new ResolvableIdentifier("obj");
        if (n != 1) {
            List<ResolvableIdentifier> decomposeTypes = new ArrayList<>(n);
            List<Expression> decompExp = new ArrayList<>(n);

            for (VariableDeclaration var : parameterList) {
                ResolvableIdentifier varType = var.getTypeIdentifier();
                tupleDeclarationFactory.checkTupleType(varType);
                decomposeTypes.add(varType);
                decompExp.add(new MemberAccess(pos, new VariableAccess(pos, decompParam),
                        new VariableAccess(pos, ResolvableIdentifier.convert(var.getIdentifier()))));
            }
            decompReturnType = new ResolvableIdentifier("Tuple" + n, decomposeTypes);
            tupleDeclarationFactory.checkTupleType(decompReturnType);
            decomposeBody
                    .addStatement(new ReturnStatement(pos, new FunctionCall(pos, decompReturnType, decompExp)));
        } else {
            VariableDeclaration value = parameterList.get(0);
            decompReturnType = value.getTypeIdentifier();
            decomposeBody.addStatement(
                    new ReturnStatement(pos, new MemberAccess(pos, new VariableAccess(pos, decompParam),
                            new VariableAccess(pos, ResolvableIdentifier.convert(value.getIdentifier())))));
        }

        return new FunctionDeclaration(pos, new Identifier("decompose"), decomposeBody,
                Arrays.asList(new VariableDeclaration(pos, decompParam,
                        ResolvableIdentifier.convert(classDecl.getIdentifier()),
                        VariableDeclaration.DeclarationType.PARAMETER)),
                DeclarationType.UNBOUND, decompReturnType);
    }

    protected FunctionDeclaration createCaseEqMethod(Position pos, List<VariableDeclaration> attributes,
            ClassDeclaration classDecl) {
        Block body = new Block(pos);
        ResolvableIdentifier paramIdent = new ResolvableIdentifier("other");
        ResolvableIdentifier classIdentifier = ResolvableIdentifier.convert(classDecl.getIdentifier());

        Expression returnExpression;

        if (attributes.size() > 0) {
            List<Expression> returnExpressions = new ArrayList<>(attributes.size());

            for (VariableDeclaration attr : attributes) {
                ResolvableIdentifier attrId = ResolvableIdentifier.convert(attr.getIdentifier());
                Expression selfAttr = new MemberAccess(pos, new SelfExpression(pos),
                        new VariableAccess(pos, attrId));
                Expression otherAttr = new MemberAccess(pos, new VariableAccess(pos, paramIdent),
                        new VariableAccess(pos, attrId));
                returnExpressions.add(new MemberAccess(pos, selfAttr,
                        new FunctionCall(pos, new ResolvableIdentifier("_eq_"), Arrays.asList(otherAttr))));
            }
            returnExpression = returnExpressions.get(0);
            returnExpressions.remove(0);
            while (returnExpressions.size() > 0) {
                returnExpression = createAndExpression(pos, returnExpression, returnExpressions.get(0));
                returnExpressions.remove(0);
            }
        } else {
            returnExpression = new BooleanLiteral(pos, true);
        }

        body.addStatement(new ReturnStatement(pos, returnExpression));

        return new FunctionDeclaration(pos, new ResolvableIdentifier("_eq_"), body,
                Arrays.asList(new VariableDeclaration(pos, paramIdent, classIdentifier,
                        VariableDeclaration.DeclarationType.PARAMETER)),
                DeclarationType.METHOD, ResolvableIdentifier.convert(CoreClasses.boolType().getIdentifier()));
    }
}