SourceNode.java

package org.sterling.source.syntax;

import static org.sterling.source.LocationRange.extent;
import static org.sterling.source.syntax.NodeKind.UNDEFINED;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.sterling.SterlingException;
import org.sterling.source.LocationAware;
import org.sterling.source.LocationRange;

public class SourceNode implements LocationAware {

    public static final SourceNode VOID = new SourceNode();

    private final State state;
    private SourceNode parent;

    public SourceNode(NodeKind kind) {
        if (kind.isTerminal()) {
            state = new TerminalState(kind);
        } else {
            state = new NonTerminalState(kind, this);
        }
    }

    private SourceNode() {
        state = new VoidState();
    }

    public <R, S> R accept(SourceVisitor<R, S> visitor, S state) throws SterlingException {
        return null;
    }

    public void addChild(SourceNode child) {
        state.addChild(child);
    }

    public boolean childOf(SourceNode parent) {
        return this.parent != null && (this.parent.equals(parent) || this.parent.childOf(parent));
    }

    public int childrenSize() {
        return state.childrenSize();
    }

    public void clearChildren() {
        state.clearChildren();
    }

    @Override
    public boolean equals(Object o) {
        return o == this || o instanceof SourceNode && state.equals(((SourceNode) o).state);
    }

    public SourceNode getChildAt(int index) {
        return state.getChildAt(index);
    }

    public List<SourceNode> getChildren() {
        return state.getChildren();
    }

    public NodeKind getKind() {
        return state.getKind();
    }

    @Override
    public LocationRange getRange() {
        return state.getRange();
    }

    public Token getToken() {
        return state.getToken();
    }

    public void setToken(Token token) {
        state.setToken(token);
    }

    @Override
    public int hashCode() {
        return state.hashCode();
    }

    public boolean isEmpty() {
        return state.isEmpty();
    }

    public boolean isTerminal() {
        return state.getKind().isTerminal();
    }

    public void prune() {
        state.prune();
    }

    public String toString() {
        return state.toString();
    }

    private interface State extends LocationAware {

        void addChild(SourceNode child);

        int childrenSize();

        void clearChildren();

        SourceNode getChildAt(int index);

        List<SourceNode> getChildren();

        NodeKind getKind();

        Token getToken();

        void setToken(Token token);

        boolean isEmpty();

        void prune();
    }

    private static final class NonTerminalState implements State {

        private final NodeKind kind;
        private final SourceNode node;
        private final List<SourceNode> children;

        public NonTerminalState(NodeKind kind, SourceNode node) {
            this.kind = kind;
            this.node = node;
            this.children = new ArrayList<>();
        }

        @Override
        public void addChild(SourceNode child) {
            children.add(child);
            child.parent = node;
        }

        @Override
        public int childrenSize() {
            return children.size();
        }

        @Override
        public void clearChildren() {
            for (SourceNode child : children) {
                child.parent = null;
            }
            children.clear();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof NonTerminalState) {
                NonTerminalState other = (NonTerminalState) o;
                return Objects.equals(kind, other.kind)
                    && Objects.equals(children, other.children);
            } else {
                return false;
            }
        }

        @Override
        public SourceNode getChildAt(int index) {
            if (index >= children.size()) {
                return VOID;
            } else {
                return children.get(index);
            }
        }

        @Override
        public List<SourceNode> getChildren() {
            return new ArrayList<>(children);
        }

        @Override
        public NodeKind getKind() {
            return kind;
        }

        @Override
        public LocationRange getRange() {
            return extent(children);
        }

        @Override
        public Token getToken() {
            throw new IllegalStateException();
        }

        @Override
        public void setToken(Token token) {
            throw new IllegalStateException();
        }

        @Override
        public int hashCode() {
            return Objects.hash(kind, children);
        }

        @Override
        public boolean isEmpty() {
            return children.isEmpty();
        }

        @Override
        public void prune() {
            Iterator<SourceNode> iterator = children.iterator();
            while (iterator.hasNext()) {
                SourceNode node = iterator.next();
                node.prune();
                if (node.isEmpty()) {
                    iterator.remove();
                }
            }
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append('(');
            builder.append(kind);
            Iterator<SourceNode> iterator = children.iterator();
            if (iterator.hasNext()) {
                builder.append(" children=[");
                builder.append(iterator.next());
                while (iterator.hasNext()) {
                    builder.append(", ");
                    builder.append(iterator.next());
                }
                builder.append(']');
            }
            builder.append(')');
            return builder.toString();
        }
    }

    private static final class TerminalState implements State {

        private final NodeKind kind;
        private Token token;

        public TerminalState(NodeKind kind) {
            this.kind = kind;
        }

        @Override
        public void addChild(SourceNode child) {
            throw new IllegalStateException();
        }

        @Override
        public int childrenSize() {
            throw new IllegalStateException();
        }

        @Override
        public void clearChildren() {
            throw new IllegalStateException();
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof TerminalState) {
                TerminalState other = (TerminalState) o;
                return Objects.equals(kind, other.kind)
                    && Objects.equals(token, other.token);
            } else {
                return false;
            }
        }

        @Override
        public SourceNode getChildAt(int index) {
            throw new IllegalStateException();
        }

        @Override
        public List<SourceNode> getChildren() {
            throw new IllegalStateException();
        }

        @Override
        public NodeKind getKind() {
            return kind;
        }

        @Override
        public LocationRange getRange() {
            return token.getRange();
        }

        @Override
        public Token getToken() {
            return token;
        }

        @Override
        public void setToken(Token token) {
            if (!token.is(kind)) {
                throw new IllegalArgumentException("Expecting token kind " + kind + " but got " + token.getKind());
            }
            this.token = token;
        }

        @Override
        public int hashCode() {
            return Objects.hash(kind, token);
        }

        @Override
        public boolean isEmpty() {
            return token == null;
        }

        @Override
        public void prune() {
            // intentionally empty
        }

        @Override
        public String toString() {
            if (token == null) {
                return "(" + kind + ")";
            } else {
                return "(" + kind + " token=" + token + ")";
            }
        }
    }

    private static final class VoidState implements State {

        @Override
        public void addChild(SourceNode child) {
            throw new IllegalStateException();
        }

        @Override
        public int childrenSize() {
            throw new IllegalStateException();
        }

        @Override
        public void clearChildren() {
            throw new IllegalStateException();
        }

        @Override
        public SourceNode getChildAt(int index) {
            throw new IllegalStateException();
        }

        @Override
        public List<SourceNode> getChildren() {
            throw new IllegalStateException();
        }

        @Override
        public NodeKind getKind() {
            return UNDEFINED;
        }

        @Override
        public LocationRange getRange() {
            return LocationRange.NULL;
        }

        @Override
        public Token getToken() {
            throw new IllegalStateException();
        }

        @Override
        public void setToken(Token token) {
            throw new IllegalStateException();
        }

        @Override
        public boolean isEmpty() {
            return true;
        }

        @Override
        public void prune() {
            // intentionally empty
        }
    }
}