Package org.jnode.shell.syntax

Source Code of org.jnode.shell.syntax.MuParser$ChoicePoint

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.shell.syntax;

import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;
import org.jnode.driver.console.CompletionInfo;
import org.jnode.shell.CommandLine;
import org.jnode.shell.SymbolSource;
import org.jnode.shell.CommandLine.Token;
import org.jnode.shell.syntax.CommandSyntaxException.Context;

/**
* This class implements parsing of a token stream against a MuSyntax graph.  The parser
* binds token values against Argument instances as it goes, and does full backtracking
* when it reaches a point where it cannot make forward progress.  The backtracking mechanism
* manages the parser state and also records the Arguments that have been 'set', and
* therefore need to be 'unset' when we backtrack.
* <p>
* When we are doing a normal parse, the various alternatives in the MuSyntax graph are
* explored until either there is a successful parse (consuming all tokens), or we run
* out of alternatives.  The latter case results in an exception and a failed parse.
* <p>
* When we are doing a completion parse, all alternatives are explored irrespective of
* parse success.
* <p>
* A MuSyntax may contain "infinite" loops, or other pathologies that trigger
* excessive backtracking.  To avoid problems, the 'parse' method takes a
* 'stepLimit' parameter that causes the parse to fail if it has not terminated
* soon enough.
* <p>
* The MuParser uses the SharedStack class to record syntax stacks for backtracking.
* This is a special purpose Deque that avoids unnecessary copying of the stack
* state.  If you suspect that this is causing problems, replace {@code new SharedStack(...)}
* with {@code new LinkedList(...)}.
*
* @author crawley@jnode.org
*/
public class MuParser {
   
    /**
     * This is the default value for the stepLimit parameter.
     */
    public static final int DEFAULT_STEP_LIMIT = 10000;
   
    private static final boolean DEBUG = false;
    private static final Logger log = Logger.getLogger(MuParser.class);
   
    private static class ChoicePoint {
        public final int sourcePos;
        public final Deque<MuSyntax> syntaxStack;
        public final MuSyntax[] choices;
        public int choiceNo;
        public final List<Argument<?>> argsModified;
       
        public ChoicePoint(int sourcePos, Deque<MuSyntax> syntaxStack, MuSyntax[] choices) {
            super();
            this.sourcePos = sourcePos;
            this.syntaxStack = syntaxStack;
            this.choices = choices;
            this.argsModified = new LinkedList<Argument<?>>();
            this.choiceNo = 0;
        }
       
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("CP{");
            sb.append("sourcePos=").append(sourcePos);
            sb.append(",syntaxStack=").append(showStack(syntaxStack, true));
            sb.append(",choiceNo=").append(choiceNo).append("...").append("}");
            return sb.toString();
        }
    }
   
    public MuParser() {
        super();
    }
   
    /**
     * Parse Tokens against a MuSyntax using a default stepLimit.  On success, tokens will
     * have been used to populate Argument values in the ArgumentBundle.
     *
     * @param rootSyntax the root of the MuSyntax graph.
     * @param completions if this is not <code>null</null>, do a completion parse, and record
     *     the completions here.
     * @param source the source of Tokens to be parsed
     * @param bundle the container for Argument objects; e.g. provided by the command.
     * @throws CommandSyntaxException
     */
    public void parse(MuSyntax rootSyntax, CompletionInfo completions,
            SymbolSource<Token> source, ArgumentBundle bundle)
        throws CommandSyntaxException, SyntaxFailureException {
        parse(rootSyntax, completions, source, bundle, DEFAULT_STEP_LIMIT);
    }
   
    /**
     * Parse Tokens against a MuSyntax using a default stepLimit.  On success, tokens will
     * have been used to populate Argument values in the ArgumentBundle.
     *
     * @param rootSyntax the root of the MuSyntax graph.
     * @param completions if this is not <code>null</null>, do a completion parse, and record
     *     the completions here.
     * @param source the source of Tokens to be parsed
     * @param bundle the container for Argument objects; e.g. provided by the command.
     * @param stepLimit the maximum allowed parse steps allowed.  A 'stepLimit' of zero or less
     * means that there is no limit.
     * @throws CommandSyntaxException
     */
    public synchronized void parse(MuSyntax rootSyntax, CompletionInfo completions,
            SymbolSource<Token> source, ArgumentBundle bundle, int stepLimit)
        throws CommandSyntaxException, SyntaxFailureException {
        // FIXME - deal with syntax error messages and completion
        // FIXME - deal with grammars that cause stack explosion
        if (bundle != null) {
            // FIXME - why am I doing this here?  Is it just for the unit tests?
            bundle.clear();
        }
        Deque<MuSyntax> syntaxStack = new LinkedList<MuSyntax>();
        Deque<ChoicePoint> backtrackStack = new LinkedList<ChoicePoint>();
        if (DEBUG) {
            log.debug("Parsing with rootSyntax = " + (rootSyntax == null ? "null" : rootSyntax.format()));
        }
        if (rootSyntax == null) {
            if (source.hasNext()) {
                throw new CommandSyntaxException("No arguments expected for this command");
            }
            return;
        }
        List<Context> argFailures = new LinkedList<Context>();
        syntaxStack.addFirst(rootSyntax);
        int stepCount = 0;
        while (true) {
            if (stepLimit > 0 && ++stepCount > stepLimit) {
                throw new SyntaxFailureException(
                        "Parse exceeded the step limit (" + stepLimit + "). " +
                        "Either the command line is too large, " +
                        "or the syntax is too complex (or pathological)");
            }
            boolean backtrack = false;
            if (DEBUG) {
                log.debug("syntaxStack % " + showStack(syntaxStack, true));
            }
            if (syntaxStack.isEmpty()) {
                if (source.hasNext()) {
                    if (DEBUG) {
                        log.debug("exhausted syntax stack too soon");
                    }
                } else if (completions != null && !backtrackStack.isEmpty()) {
                    if (DEBUG) {
                        log.debug("try alternatives for completion");
                    }
                } else {
                    if (DEBUG) {
                        log.debug("parse succeeded");
                    }
                    return;
                }
                backtrack = true;
            } else {
                MuSyntax syntax = syntaxStack.removeFirst();
                if (DEBUG) {
                    log.debug("Trying kind = " + syntax.getKind() + ", syntax = " + syntax.format());
                    if (source.hasNext()) {
                        log.debug("source -> " + source.peek().text);
                    } else {
                        log.debug("source at end");
                    }
                }
                CommandLine.Token token = null;
                switch (syntax.getKind()) {
                    case SYMBOL:
                        String symbol = ((MuSymbol) syntax).getSymbol();
                        token = source.hasNext() ? source.next() : null;

                        if (completions == null || source.hasNext()) {
                            backtrack = token == null || !token.text.equals(symbol);
                        } else {
                            if (token == null) {
                                completions.addCompletion(symbol);
                                backtrack = true;
                            } else if (source.whitespaceAfterLast()) {
                                if (!token.text.equals(symbol)) {
                                    backtrack = true;
                                }
                            } else {
                                if (symbol.startsWith(token.text)) {
                                    completions.addCompletion(symbol);
                                    completions.setCompletionStart(token.start);
                                }
                                backtrack = true;
                            }
                        }
                        break;
                    case ARGUMENT:
                        MuArgument muArg = (MuArgument) syntax;
                        String argName = muArg.getArgName();
                        int flags = muArg.getFlags();
                        Argument<?> arg = bundle.getArgument(argName);
                        try {
                            if (source.hasNext()) {
                                token = source.next();
                                if (completions == null || source.hasNext() || source.whitespaceAfterLast()) {
                                    arg.accept(token, flags);
                                    if (!backtrackStack.isEmpty()) {
                                        backtrackStack.getFirst().argsModified.add(arg);
                                        if (DEBUG) {
                                            log.debug("recording undo for arg " + argName);
                                        }
                                    }
                                } else {
                                    arg.complete(completions, token.text, flags);
                                    completions.setCompletionStart(token.start);
                                    backtrack = true;
                                }
                            } else {
                                if (completions != null) {
                                    arg.complete(completions, "", flags);
                                }
                                backtrack = true;
                            }
                        } catch (CommandSyntaxException ex) {
                            argFailures.add(new Context(token, syntax, source.tell(), ex));
                            if (DEBUG) {
                                log.debug("accept for arg " + argName + " threw SyntaxErrorException('" +
                                        ex.getMessage() + "'");
                            }
                            backtrack = true;
                        }
                        break;
                    case PRESET:
                        MuPreset muPreset = (MuPreset) syntax;
                        arg = bundle.getArgument(muPreset.getArgName());
                        flags = muPreset.getFlags();
                        try {
                            arg.accept(new CommandLine.Token(muPreset.getPreset()), flags);
                            if (!backtrackStack.isEmpty()) {
                                backtrackStack.getFirst().argsModified.add(arg);
                                if (DEBUG) {
                                    log.debug("recording undo for preset arg " + arg.getLabel());
                                }
                            }
                        } catch (CommandSyntaxException ex) {
                            argFailures.add(new Context(null, syntax, source.tell(), ex));
                            backtrack = true;
                        }
                        break;
                    case SEQUENCE:
                        MuSyntax[] elements = ((MuSequence) syntax).getElements();
                        for (int i = elements.length - 1; i >= 0; i--) {
                            syntaxStack.addFirst(elements[i]);
                        }
                        break;
                    case ALTERNATION:
                        MuSyntax[] choices = ((MuAlternation) syntax).getAlternatives();

                        // The test below optimizes the case where there is only one
                        // alternative. This avoids the non-trivial cost of creating
                        // a choicepoint, backtracking, etc.
                        if (choices.length > 1) {
                            ChoicePoint choicePoint =
                                new ChoicePoint(source.tell(), syntaxStack, choices);
                            backtrackStack.addFirst(choicePoint);
                            syntaxStack = new SharedStack<MuSyntax>(syntaxStack);
                            if (DEBUG) {
                                log.debug("pushed choicePoint #" + backtrackStack.size() +
                                        " - " + choicePoint);
                            }
                        }
                        if (choices[0] != null) {
                            syntaxStack.addFirst(choices[0]);
                        }
                        if (DEBUG) {
                            log.debug("syntaxStack " + showStack(syntaxStack, true));
                        }
                        break;
                    case BACK_REFERENCE:
                        throw new SyntaxFailureException(
                                "Found an unresolved MuBackReference");
                    default:
                        throw new SyntaxFailureException(
                                "Unknown MuSyntax kind (" + syntax.getKind() + ")");
                }
            }
            if (backtrack) {
                if (DEBUG) {
                    log.debug("backtracking ...");
                }
                while (!backtrackStack.isEmpty()) {
                    ChoicePoint choicePoint = backtrackStack.getFirst();
                    if (DEBUG) {
                        log.debug("top choicePoint - " + choicePoint);
                        log.debug("syntaxStack " + showStack(syntaxStack, true));
                    }
                    // Issue undo's for any argument values added.
                    for (Argument<?> arg : choicePoint.argsModified) {
                        if (DEBUG) {
                            log.debug("undo for arg " + arg.getLabel());
                        }
                        arg.undoLastValue();
                    }
                    // If possible, take the next choice in the current choice
                    // point and stop backtracking
                    int lastChoice = choicePoint.choices.length - 1;
                    int choiceNo = ++choicePoint.choiceNo;
                    if (choiceNo <= lastChoice) {
                        MuSyntax choice = choicePoint.choices[choiceNo];
                        choicePoint.argsModified.clear();
                        source.seek(choicePoint.sourcePos);
                        // (If this is the last choice in the choice point, we
                        // won't need to use this choice point's saved syntax
                        // stack again ...)
                        if (choiceNo == lastChoice) {
                            syntaxStack = choicePoint.syntaxStack;
                        } else {
                            syntaxStack = new SharedStack<MuSyntax>(choicePoint.syntaxStack);
                        }
                        if (choice != null) {
                            syntaxStack.addFirst(choice);
                        }
                        backtrack = false;
                        if (DEBUG) {
                            log.debug("taking choice #" + choiceNo);
                            log.debug("syntaxStack : " + showStack(syntaxStack, true));
                        }
                        break;
                    }
                    // Otherwise, pop the choice point and keep going.
                    if (DEBUG) {
                        log.debug("popped choice point #" + backtrackStack.size());
                    }
                    backtrackStack.removeFirst();
                }
                // If we are still backtracking and we are out of choices ...
                if (backtrack) {
                    if (completions == null) {
                        throw new CommandSyntaxException("ran out of alternatives", argFailures);
                    } else {
                        if (DEBUG) {
                            log.debug("end completion");
                        }
                        return;
                    }
                }
                if (DEBUG) {
                    log.debug("end backtracking");
                }
            }
        }
    }

   
    private static String showStack(Deque<MuSyntax> stack, boolean oneLine) {
        StringBuilder sb = new StringBuilder();
        for (MuSyntax syntax : stack) {
            if (sb.length() > 0) {
                sb.append(", ");
                if (!oneLine) {
                    sb.append("\n    ");
                }
            }
            sb.append(syntax.format());
        }
        return sb.toString();
    }
}
TOP

Related Classes of org.jnode.shell.syntax.MuParser$ChoicePoint

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.