package com.haskforce.psi; import com.haskforce.HaskellParserDefinition; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.lang.parser.GeneratedParserUtilBase; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.LinkedList; /** * Implementation holder for external rules in Haskell.bnf. */ public class HaskellParserUtilBase extends GeneratedParserUtilBase { /** * Called when the parser gets confused from layout rules. * * Increases the debt of rbraces in the token remapper in * HaskellParserWrapper. When the remapper is in debt it will swallow * (=remap them to TokenType.WHITE_SPACE) synthetic rbraces until no debt * remains. */ public static boolean stateHackMess(@NotNull PsiBuilder builder, int level) { if (!(builder instanceof Builder)) return false; PsiParser wrapper = ((Builder) builder).parser; if (!(wrapper instanceof HaskellParserWrapper)) return false; // IElementType tok = builder.getTokenType(); int offs = builder.getCurrentOffset(); int line = StringUtil.offsetToLineNumber(builder.getOriginalText(), offs); int lineStart = StringUtil.lineColToOffset(builder.getOriginalText(), line, 1); Pair<Integer, Integer> p = ((HaskellParserWrapper) wrapper).debtPoints.get(offs); if (p != null && p.getSecond() == 0 && !((HaskellParserWrapper) wrapper).regressed) { ((HaskellParserWrapper) wrapper).maxRbraceDebt = ((HaskellParserWrapper) wrapper).debtPoints.get(offs).getFirst(); ((HaskellParserWrapper) wrapper).rbraceDebt = ((HaskellParserWrapper) wrapper).rbraceDebt - ((HaskellParserWrapper) wrapper).maxRbraceDebt; ((HaskellParserWrapper) wrapper).debtPoints.put(offs, Pair.create(((HaskellParserWrapper) wrapper).maxRbraceDebt, ((HaskellParserWrapper) wrapper).maxRbraceDebt)); } else if (((HaskellParserWrapper) wrapper).maxRbraceDebt == -1) { int numOpen = findBraces(((HaskellParserWrapper) wrapper).lexer.openBraces, offs, line, lineStart); ((HaskellParserWrapper) wrapper).maxRbraceDebt = numOpen; } // System.out.println("Confused at: " + offs + " line " + line + " on token " + tok // + " regressed: " + ((HaskellParserWrapper) wrapper).regressed + " max: " // + ((HaskellParserWrapper) wrapper).maxRbraceDebt); boolean ret = ((HaskellParserWrapper) wrapper).increaseRbraceDebt(builder.getCurrentOffset()); if (((HaskellParserWrapper) wrapper).maxRbraceDebt == 0) ((HaskellParserWrapper) wrapper).maxRbraceDebt = -1; return ret; } /** * Returns the number of stealable braces (=open braces - 1) in the parsing lexer at a * given line/column. */ private static int findBraces(LinkedList<Pair<Pair<Integer, Integer>, Integer>> l, int offs, int line, int lineStart) { int i = l.size() - 1; Pair<Pair<Integer, Integer>, Integer> last; if (i == 0) return 0; Pair<Pair<Integer, Integer>, Integer> e = last = l.get(i--); while (e != null) { if (e.getFirst().getFirst() > line || e.getFirst().getFirst() == line && e.getFirst().getSecond() > offs - lineStart) { e = last; break; } last = e; e = l.get(i--); } // Comepnsate if we are looking for the last element. if (e == null) { e = last; } return e != null ? e.getSecond() - 1 : -1; } public static boolean indented(@NotNull PsiBuilder builder, int level, Parser geq) { if (!(builder instanceof Builder)) return false; PsiParser wrapper = ((Builder) builder).parser; if (!(wrapper instanceof HaskellParserWrapper)) return false; if (builder.eof()) return false; // IElementType currtok = builder.getTokenType(); Pair<Integer, IElementType> prevtok = previousElem(builder); if (prevtok == null) return true; int offs = builder.getCurrentOffset(); int line = StringUtil.offsetToLineNumber(builder.getOriginalText(), offs); int prevline = StringUtil.offsetToLineNumber(builder.getOriginalText(), offs + prevtok.getFirst()); if (prevline == line) return true; int thisLineStart = StringUtil.lineColToOffset(builder.getOriginalText(), line, 0); int prevLineStart = StringUtil.lineColToOffset(builder.getOriginalText(), prevline, 0); // CharSequence lineStuff = builder.getOriginalText().subSequence(thisLineStart, offs); CharSequence prevLineStuff = builder.getOriginalText().subSequence(prevLineStart, thisLineStart - 1); int indentation = indentationLevel(prevLineStuff); int myindentation = offs - thisLineStart; // String tokName = builder.getTokenText(); boolean geqVal = geq.parse(builder, level); if (geqVal && myindentation >= indentation || !geqVal && myindentation > indentation) return true; return false; } @Nullable public static Pair<Integer, IElementType> previousElem(@NotNull PsiBuilder builder) { int i = -1; IElementType t = builder.rawLookup(i); while (t != null && (HaskellParserDefinition.COMMENTS.contains(t) || HaskellParserDefinition.WHITE_SPACES.contains(t))) { t = builder.rawLookup(--i); } return t == null ? null : Pair.create(i - 1, t); } public static int indentationLevel(CharSequence c) { int i = 0; while(i < c.length() && c.charAt(i) == ' ') { i++; } return i; } }