TableDrivenParser.java

package org.sterling.source.parser;

import static java.util.Arrays.asList;
import static org.sterling.source.parser.ParserState.state;

import java.util.*;
import org.sterling.SterlingException;
import org.sterling.source.exception.ParserException;
import org.sterling.source.exception.ScannerException;
import org.sterling.source.scanner.Scanner;
import org.sterling.source.syntax.NodeKind;
import org.sterling.source.syntax.SourceNode;

public class TableDrivenParser {

    private static final ParserAction MATCH = new ParserAction() {
        @Override
        public void invoke(TableDrivenParser parser) throws SterlingException {
            parser.match();
        }
    };

    private final Scanner scanner;
    private final ParserTable table;
    private final Deque<SourceNode> stack;
    private final TryManager tries;

    public TableDrivenParser(NodeKind firstKind, ParserTable table, Scanner scanner) {
        this.table = table;
        this.scanner = scanner;
        this.stack = new ArrayDeque<>();
        this.tries = new TryManager();
        this.stack.push(firstKind.createNode());
    }

    public void error() throws SterlingException {
        if (!tries.handleError()) {
            try {
                scanner.requireOne(getPossibleLookAhead());
            } catch (ScannerException exception) {
                throw new ParserException("Parse error in " + peek().getKind(), exception);
            }
        }
    }

    public ParserAction getAction() {
        ParserAction action = table.getErrorAction();
        if (peek().isTerminal()) {
            if (scanner.expect(peek().getKind())) {
                action = MATCH;
            }
        } else {
            action = table.getAction(getState());
        }
        return action;
    }

    public NodeKind getLookAhead() {
        return scanner.expected(getPossibleLookAhead());
    }

    public ParserState getState() {
        return state(peek().getKind(), getLookAhead());
    }

    public void match() throws SterlingException {
        peek().setToken(scanner.require(getState().getStack()));
        output();
    }

    public SourceNode output() {
        return stack.pop();
    }

    public SourceNode parse() throws SterlingException {
        SourceNode root = stack.peek();
        while (stack.size() > 0) {
            getAction().invoke(this);
        }
        root.prune();
        return root;
    }

    public SourceNode peek() {
        return stack.peek();
    }

    public void push(SourceNode node) {
        stack.push(node);
        tries.handlePush(node);
    }

    private void backtrack(SourceNode parent) {
        while (peek().childOf(parent)) {
            output();
        }
        parent.clearChildren();
        scanner.restore();
    }

    private void begin() {
        scanner.begin();
    }

    private void beginTry(List<Derive> derivations) {
        tries.begin(this, output(), derivations);
    }

    private void end() {
        scanner.end();
    }

    private List<NodeKind> getPossibleLookAhead() {
        if (peek().isTerminal()) {
            return asList(peek().getKind());
        } else {
            return table.getLookAhead(peek());
        }
    }

    public static class Derive implements ParserAction {

        private final List<NodeKind> kinds;

        public Derive(NodeKind... kinds) {
            this.kinds = asList(kinds);
        }

        public void derive(TableDrivenParser parser, SourceNode parent) {
            Deque<SourceNode> children = new ArrayDeque<>();
            for (NodeKind kind : kinds) {
                SourceNode child = kind.createNode();
                parent.addChild(child);
                children.push(child);
            }
            while (children.size() > 0) {
                parser.push(children.pop());
            }
        }

        @Override
        public void invoke(TableDrivenParser parser) throws ParserException {
            derive(parser, parser.output());
        }
    }

    public static class TryDerive implements ParserAction {

        private final List<Derive> derivations;

        public TryDerive(NodeKind... stacks) {
            derivations = new ArrayList<>(stacks.length);
            for (NodeKind stack : stacks) {
                derivations.add(new Derive(stack));
            }
        }

        @Override
        public void invoke(TableDrivenParser parser) throws ParserException {
            parser.beginTry(derivations);
        }
    }

    private static final class TryManager {

        private final Deque<TryState> tries;

        public TryManager() {
            tries = new ArrayDeque<>();
        }

        public void begin(TableDrivenParser parser, SourceNode parent, List<Derive> derivations) {
            push(new TryState(parser, parent, derivations));
            currentTry().begin();
        }

        public boolean handleError() {
            return hasTries() && currentTry().handleError();
        }

        public void handlePush(SourceNode node) {
            if (hasTries() && currentTry().handlePush(node)) {
                tries.pop();
            }
        }

        public void push(TryState state) {
            tries.push(state);
        }

        private TryState currentTry() {
            return tries.peek();
        }

        private boolean hasTries() {
            return tries.size() > 0;
        }
    }

    private static final class TryState {

        private final TableDrivenParser parser;
        private final SourceNode parent;
        private final Iterator<Derive> derivations;

        public TryState(TableDrivenParser parser, SourceNode parent, List<Derive> derivations) {
            this.parser = parser;
            this.parent = parent;
            this.derivations = derivations.iterator();
        }

        public void begin() {
            parser.begin();
            derivations.next().derive(parser, parent);
        }

        public boolean handleError() {
            if (derivations.hasNext()) {
                parser.backtrack(parent);
                begin();
                return true;
            } else {
                return false;
            }
        }

        public boolean handlePush(SourceNode node) {
            if (node.childOf(parent)) {
                return false;
            } else {
                parser.end();
                return true;
            }
        }
    }
}