LabelScope.java

package com.mackenziehigh.autumn.lang.compiler.compilers;

import autumn.lang.compiler.ast.nodes.BranchStatement;
import autumn.lang.compiler.ast.nodes.GotoStatement;
import autumn.lang.compiler.ast.nodes.Label;
import autumn.lang.compiler.ast.nodes.MarkerStatement;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mackenziehigh.autumn.resources.Finished;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.tree.LabelNode;

/**
 * An instance of this class controls the allocation of location labels.
 *
 * <p>
 * This class also performs the checking of marker, goto, and branch statements.
 * Since a label can be declared at a point its usage, multiple compiler passes are needed.
 * However, we can get around that need by deferring the checking until the end of one pass.
 * In particular, we simply defer the checking until the end of the type-usage-checking pass.
 * Then, the label declarations and usages can be checked all at once.
 * </p>
 *
 * @author Mackenzie High
 */
@Finished("2014/07/12")
final class LabelScope
{
    /**
     * This is essentially the program that is being compiled.
     */
    private final ProgramCompiler program;

    /**
     * This map maps the name of a label to its bytecode representation.
     */
    private final Map<String, LabelNode> labels = Maps.newTreeMap();

    /**
     * These are the goto-statements in this scope.
     */
    private final List<GotoStatement> jumps = Lists.newLinkedList();

    /**
     * These are the marker-statements in this scope.
     */
    private final List<MarkerStatement> markers = Lists.newLinkedList();

    /**
     * These are the branch-statements in this scope.
     */
    private final List<BranchStatement> branches = Lists.newLinkedList();

    /**
     * Sole Constructor.
     *
     * @param program is the program being compiled.
     */
    public LabelScope(final ProgramCompiler program)
    {
        Preconditions.checkNotNull(program);

        this.program = program;
    }

    /**
     * This method defers checking of a goto-statement.
     *
     * <p>
     * Deferring checking alleviates the need for yet another compiler pass.
     * </p>
     *
     * @param statement is the goto-statement itself.
     */
    public void defer(final GotoStatement statement)
    {
        Preconditions.checkNotNull(statement);

        jumps.add(statement);
    }

    /**
     * This method defers checking of a marker-statement.
     *
     * <p>
     * Deferring checking alleviates the need for yet another compiler pass.
     * </p>
     *
     * @param statement is the marker-statement itself.
     */
    public void defer(final MarkerStatement statement)
    {
        Preconditions.checkNotNull(statement);

        markers.add(statement);
    }

    /**
     * This method defers checking of a branch-statement.
     *
     * <p>
     * Deferring checking alleviates the need for yet another compiler pass.
     * </p>
     *
     * @param statement is the marker-statement itself.
     */
    public void defer(final BranchStatement statement)
    {
        Preconditions.checkNotNull(statement);

        branches.add(statement);
    }

    /**
     * This method performs the checking of goto-statements and marker-statements that was deferred.
     */
    public void check()
    {
        for (MarkerStatement x : markers)
        {
            checkMarker(x);
        }

        for (GotoStatement x : jumps)
        {
            checkGoto(x);
        }

        for (BranchStatement x : branches)
        {
            checkBranch(x);
        }
    }

    private void checkMarker(final MarkerStatement statement)
    {
        if (labels.containsKey(statement.getLabel().getName()))
        {
            program.checker.reportDuplicateLabel(statement.getLabel());
        }
        else
        {
            labels.put(statement.getLabel().getName(), new LabelNode());
        }
    }

    private void checkGoto(final GotoStatement statement)
    {
        if (labels.containsKey(statement.getLabel().getName()))
        {
            // The statement is OK.
        }
        else
        {
            program.checker.reportUndeclaredLabel(statement.getLabel());
        }
    }

    private void checkBranch(final BranchStatement statement)
    {
        /**
         * Each of the case labels must be declared in the enclosing function.
         */
        for (Label label : statement.getLabels())
        {
            if (labels.containsKey(label.getName()) == false)
            {
                program.checker.reportUndeclaredLabel(statement.getDefaultLabel());
            }
        }

        /**
         * The label of the default-case must be declared in the enclosing function.
         */
        if (labels.containsKey(statement.getDefaultLabel().getName()) == false)
        {
            program.checker.reportUndeclaredLabel(statement.getDefaultLabel());
        }
    }

    /**
     * This method retrieves the bytecode node that implements the label.
     *
     * @param name is the name of the label to retrieve.
     * @return the bytecode for the node.
     * @throws IllegalStateException if the label was already declared.
     */
    public LabelNode nodeOf(final String name)
    {
        return labels.get(name);
    }
}