/* * Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.ideaplugin.parsing; import com.intellij.lang.ASTNode; import com.intellij.lang.PsiBuilder; import com.intellij.lang.PsiParser; import com.intellij.psi.tree.IElementType; import org.visage.tools.antlr.v3Parser; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.Tree; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; /** * VisageParser * * @author Brian Goetz */ public class VisageParser implements PsiParser { private final VisageParsingLexer lexer; public VisageParser(VisageParsingLexer lexer) { this.lexer = lexer; } @NotNull public ASTNode parse(IElementType rootElement, PsiBuilder psiBuilder) { List<StreamAction> actions = new ArrayList<StreamAction>(); final List<ParseError> errors = new ArrayList<ParseError>(); System.out.printf("%s/%s: starting parsing %d tokens, %d chars %n", Thread.currentThread(), lexer, lexer.getSize(), lexer.getBufferEnd()); // Potentially inefficient; creating a new ANTLR lexer instead of reusing the one we have WrappedAntlrLexer antlrLexer = new WrappedAntlrLexer(new ANTLRStringStream(lexer.getBufferSequence().toString().substring(0, lexer.getBufferEnd())), false, false); v3Parser parser = new v3Parser(new CommonTokenStream(antlrLexer)) { protected String getParserName() { return "org.visage.tools.antlr.v3Parser"; } public void displayRecognitionError(String[] strings, RecognitionException e) { errors.add(new ParseError(e, getErrorMessage(e, strings))); } }; try { v3Parser.script_return antlrParseTree = parser.script(); scrubTree((CommonTree) antlrParseTree.getTree()); System.out.printf("finished parsing %d:%d%n", ((CommonTree) antlrParseTree.getTree()).getTokenStartIndex(), ((CommonTree) antlrParseTree.getTree()).getTokenStopIndex()); BeginMark beginMark = new BeginMark(0); actions.add(beginMark); if (errors.isEmpty()) { // @@@ Do the same with v3Walker, so we can get real structural information traverse((CommonTree) antlrParseTree.getTree(), actions); } else { traverse((CommonTree) antlrParseTree.getTree(), actions); for (ParseError error : errors) { int position = error.exception.token.getTokenIndex(); int index = findInsertPosition(actions, position); BeginMark beginErrorMark = new BeginMark(position); actions.add(index, beginErrorMark); actions.add(index+1, new ErrorMark(position + 1, beginErrorMark, error.errorString)); } } actions.add(new EndMark(lexer.getSize(), beginMark, rootElement)); applyActions(actions, psiBuilder); return psiBuilder.getTreeBuilt(); } catch (RecognitionException e) { throw new RuntimeException("Unexpected exception in parsing", e); } } private int findInsertPosition(List<StreamAction> actions, int tokenPosition) { for (int i=0; i<actions.size(); i++) { StreamAction a = actions.get(i); if (a.position > tokenPosition) return i; } return actions.size(); } private void scrubTree(Tree tree) { // The top-level tree often has wrong position info, so start one down from the top for (int i = tree.getChildCount() - 1; i >= 0; i--) { Tree child = tree.getChild(i); if (child.getTokenStartIndex() > child.getTokenStopIndex()) { System.out.println("Deleting node " + child); tree.deleteChild(i); continue; } if (child.getTokenStartIndex() < 0) { // System.out.println("Deleting node " + child); // tree.deleteChild(i); continue; } scrubTree(child); if (child.getTokenStartIndex() < tree.getTokenStartIndex()) { tree.setTokenStartIndex(child.getTokenStartIndex()); } if (child.getTokenStopIndex() > tree.getTokenStopIndex()) { tree.setTokenStopIndex(child.getTokenStopIndex()); } } } private void traverse(CommonTree tree, List<StreamAction> actions) { // System.out.printf("Token %s at %d:%d%n", tree.getToken(), tree.getTokenStartIndex(), tree.getTokenStopIndex()); if (tree.getTokenStartIndex() > tree.getTokenStopIndex()) return; if (tree.getTokenStartIndex() < 0) return; BeginMark beginMark = new BeginMark(tree.getTokenStartIndex()); actions.add(beginMark); for (int i = 0; i < tree.getChildCount(); i++) traverse((CommonTree) tree.getChild(i), actions); actions.add(new EndMark(tree.getTokenStopIndex() + 1, beginMark, VisageAstNodes.GENERIC_NODE.elementType)); } private void applyActions(List<StreamAction> actions, PsiBuilder builder) { for (StreamAction action : actions) { while (lexer.getIndex() < action.position && !builder.eof()) { builder.getTokenType(); builder.advanceLexer(); } action.action(builder); } while (!builder.eof()) { builder.getTokenType(); builder.advanceLexer(); } } private static class ParseError { public final RecognitionException exception; public final String errorString; private ParseError(RecognitionException exception, String errorString) { this.exception = exception; this.errorString = errorString; } } private abstract static class StreamAction implements Comparable<StreamAction> { protected final int position; protected StreamAction(int pos) { position = pos; } public abstract void action(PsiBuilder builder); public int compareTo(StreamAction other) { if (position < other.position) return -1; else if (position > other.position) return 1; else return 0; } public int hashCode() { return position; } public boolean equals(Object obj) { return (obj instanceof StreamAction) && ((StreamAction)obj).position == position; } } private static class BeginMark extends StreamAction { public PsiBuilder.Marker marker; BeginMark(int position) { super(position); } public void action(PsiBuilder builder) { marker = builder.mark(); } } private static class EndMark extends StreamAction { private final BeginMark marker; private final IElementType elementType; EndMark(int position, BeginMark marker, IElementType elementType) { super(position); this.marker = marker; this.elementType = elementType; } public void action(PsiBuilder builder) { marker.marker.done(elementType); } } private static class ErrorMark extends StreamAction { private final BeginMark marker; private final String errorString; ErrorMark(int position, BeginMark marker, String errorString) { super(position); this.marker = marker; this.errorString = errorString; } public void action(PsiBuilder builder) { marker.marker.error(errorString); } } }