/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.antlr.works.editor.antlr4.completion; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import org.antlr.v4.runtime.DefaultErrorStrategy; import org.antlr.v4.runtime.Parser; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.atn.ATNConfig; import org.antlr.v4.runtime.atn.ATNConfigSet; import org.antlr.v4.runtime.atn.ATNState; import org.antlr.v4.runtime.atn.PredictionContext; import org.antlr.v4.runtime.atn.PredictionContextCache; import org.antlr.v4.runtime.atn.Transition; import org.antlr.v4.runtime.misc.IntervalSet; /** * * @author Sam Harwell */ public class CodeCompletionErrorStrategy extends DefaultErrorStrategy { @Override public void reportError(Parser recognizer, RecognitionException e) throws RecognitionException { if (e != null && e.getOffendingToken() != null && e.getOffendingToken().getType() == CaretToken.CARET_TOKEN_TYPE) { return; } super.reportError(recognizer, e); } @Override public void sync(Parser recognizer) { if (recognizer.getInputStream().LA(1) == CaretToken.CARET_TOKEN_TYPE) { return; } // TODO: incorporate error recovery as a fallback option if no trees match correctly if (true) { return; } super.sync(recognizer); } /** Consume tokens until one matches the given token set * @param recognizer * @param set */ @Override public void consumeUntil(Parser recognizer, IntervalSet set) { //System.out.println("consumeUntil("+set.toString(getTokenNames())+")"); int ttype = recognizer.getInputStream().LA(1); while (ttype != Token.EOF && ttype != CaretToken.CARET_TOKEN_TYPE && !set.contains(ttype) ) { //System.out.println("consume during recover LA(1)="+getTokenNames()[input.LA(1)]); recognizer.getInputStream().consume(); //recognizer.consume(); ttype = recognizer.getInputStream().LA(1); } } @Override public void recover(Parser recognizer, RecognitionException e) { if (recognizer instanceof CodeCompletionParser && ((CodeCompletionParser)recognizer).getInterpreter().getCaretTransitions() != null) { // int stateNumber = recognizer.getContext().s; // ATNState state = recognizer.getATN().states.get(stateNumber); // if (state instanceof DecisionState && recognizer.getInputStream() instanceof ObjectStream) { // int decision = ((DecisionState)state).decision; // ParserATNSimulator simulator = recognizer.getInterpreter(); // int prediction = simulator.adaptivePredict((ObjectStream)recognizer.getInputStream(), decision, recognizer.getContext()); // } CodeCompletionParser parser = (CodeCompletionParser)recognizer; CaretToken token = parser.getInterpreter().getCaretToken(); AbstractCompletionParserATNSimulator interpreter = parser.getInterpreter(); throw new CaretReachedException(parser.getContext(), token, interpreter.getCaretTransitions(), e); } // TODO: incorporate error recovery as a fallback option if no trees match correctly throw e; //super.recover(recognizer, e); } @Override public Token recoverInline(Parser recognizer) throws RecognitionException { if (recognizer instanceof CodeCompletionParser && recognizer.getInputStream().LT(1) instanceof CaretToken) { CodeCompletionParser parser = (CodeCompletionParser)recognizer; CaretToken token = (CaretToken)recognizer.getInputStream().LT(1); AbstractCompletionParserATNSimulator interp = parser.getInterpreter(); int stateNumber = recognizer.getState(); ATNState state = interp.atn.states.get(stateNumber); PredictionContext context = PredictionContext.fromRuleContext(interp.atn, recognizer.getContext(), false); ATNConfigSet intermediate = new ATNConfigSet(); ATNConfigSet closure = new ATNConfigSet(); for (int i = 0; i < state.getNumberOfTransitions(); i++) { Transition transition = state.transition(i); if (transition.isEpsilon()) { ATNState target = transition.target; ATNConfig config = ATNConfig.create(target, i + 1, context); intermediate.add(config); } } final boolean collectPredicates = false; final boolean hasMoreContext = true; interp.closure(intermediate, closure, collectPredicates, hasMoreContext, PredictionContextCache.UNCACHED, false); if (!state.onlyHasEpsilonTransitions()) { for (int i = 0; i < state.getNumberOfTransitions(); i++) { closure.add(ATNConfig.create(state, i + 1, PredictionContext.fromRuleContext(interp.atn, recognizer.getContext()))); } } LinkedHashMap<ATNConfig, List<Transition>> transitions = null; int ncl = closure.size(); for (int ci = 0; ci < ncl; ci++) { // TODO: foreach ATNConfig c = closure.get(ci); List<Transition> configTransitions = null; int n = c.getState().getNumberOfTransitions(); for (int ti = 0; ti < n; ti++) { // for each transition Transition trans = c.getState().transition(ti); ATNState target = interp.getReachableTarget(c, trans, CaretToken.CARET_TOKEN_TYPE); if (target != null) { if (transitions == null) { transitions = new LinkedHashMap<>(); } if (configTransitions == null) { configTransitions = new ArrayList<>(); transitions.put(c, configTransitions); } configTransitions.add(trans); } } } /* * This should be null if the intended token is not "wordlike", and * should be a single transition from the current state. */ if (transitions != null) { assert transitions.size() == 1; assert transitions.values().iterator().next().size() == 1; assert state.getNumberOfTransitions() == 1; assert transitions.values().iterator().next().get(0) == state.transition(0); } throw new CaretReachedException(parser.getContext(), token, transitions, null); } return super.recoverInline(recognizer); } @Override public boolean singleTokenInsertion(Parser recognizer) { if (recognizer.getInputStream().LA(1) == CaretToken.CARET_TOKEN_TYPE) { return false; } return false; // return super.singleTokenInsertion(recognizer); } @Override public Token singleTokenDeletion(Parser recognizer) { if (recognizer.getInputStream().LA(1) == CaretToken.CARET_TOKEN_TYPE) { return null; } return null; // return super.singleTokenDeletion(recognizer); } }