/******************************************************************************* * MontiCore Language Workbench * Copyright (c) 2015, 2016, MontiCore, All rights reserved. * * This project 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 3.0 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 project. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package de.se_rwth.langeditor.util.antlr; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.getLast; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.Trees; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import de.se_rwth.langeditor.util.Misc; public final class ParseTrees { private ParseTrees() { // noninstantiable } public static Interval tokenInterval(ParseTree parseTree) { Optional<TerminalNode> firstTerminal = getFirstTerminal(parseTree); Optional<TerminalNode> lastTerminal = getLastTerminal(parseTree); if (firstTerminal.isPresent() && lastTerminal.isPresent()) { int startIndex = firstTerminal.get().getSymbol().getStartIndex(); int stopIndex = lastTerminal.get().getSymbol().getStopIndex() + 1; return new Interval(startIndex, stopIndex); } else { return Interval.INVALID; } } public static Stream<ParseTree> successors(ParseTree parseTree) { return Misc.preorder(parseTree, ParseTrees::getChildren); } public static List<ParseTree> getChildren(ParseTree parseTree) { List<ParseTree> children = new ArrayList<ParseTree>(); for (int i = 0; i < parseTree.getChildCount(); i++) { children.add(parseTree.getChild(i)); } return children; } public static ParseTree root(ParseTree parseTree) { ParseTree root = parseTree; while (root.getParent() != null) { root = root.getParent(); } return root; } public static Optional<TerminalNode> getTerminalBySourceCharIndex(ParseTree parseTree, int documentOffset) { List<TerminalNode> terminals = Trees.getDescendants(parseTree).stream() .filter(TerminalNode.class::isInstance) .map(TerminalNode.class::cast) .collect(Collectors.toList()); for (TerminalNode terminal : terminals) { int startIndex = terminal.getSymbol().getStartIndex(); int stopIndex = terminal.getSymbol().getStopIndex() + 1; if (startIndex <= documentOffset && documentOffset < stopIndex) { return Optional.of(terminal); } } return Optional.empty(); } /** Find smallest subtree of t enclosing range start..stop * inclusively using postorder traversal. Recursive depth-first-search. * * @since 4.5.1 */ public static ParserRuleContext getRootOfSubtreeEnclosingRegion(ParseTree t, int start, // inclusive int stop) // inclusive { int n = t.getChildCount(); for (int i = 0; i<n; i++) { ParseTree child = t.getChild(i); ParserRuleContext r = getRootOfSubtreeEnclosingRegion(child, start, stop); if ( r!=null ) return r; } if ( t instanceof ParserRuleContext ) { ParserRuleContext r = (ParserRuleContext) t; if ( start>=r.getStart().getStartIndex() && // is range fully contained in t? (r.getStop()==null || stop<=r.getStop().getStopIndex()) ) { // note: r.getStop()==null likely implies that we bailed out of parser and there's nothing to the right return r; } } return null; } public static Optional<TerminalNode> getTerminalByLineAndColumn(ParseTree parseTree, int line, int column) { List<TerminalNode> terminals = Trees.getDescendants(parseTree).stream() .filter(TerminalNode.class::isInstance) .map(TerminalNode.class::cast) .collect(Collectors.toList()); for (TerminalNode terminal : terminals) { boolean sameLine = terminal.getSymbol().getLine() == line; boolean sameColumn = terminal.getSymbol().getCharPositionInLine() == column; if (sameLine && sameColumn) { return Optional.of(terminal); } } return Optional.empty(); } public static Optional<TerminalNode> getFirstTerminal(ParseTree parseTree) { return Trees.getDescendants(parseTree).stream() .filter(TerminalNode.class::isInstance) .map(TerminalNode.class::cast) .findFirst(); } public static Optional<TerminalNode> getLastTerminal(ParseTree parseTree) { TerminalNode lastTerminal = getLast(filter(Trees.getDescendants(parseTree), TerminalNode.class), null); return Optional.ofNullable(lastTerminal); } public static Optional<Token> getFirstToken(ParseTree parseTree) { return getFirstTerminal(parseTree).map(TerminalNode::getSymbol); } public static Optional<Token> getLastToken(ParseTree parseTree) { return getLastTerminal(parseTree).map(TerminalNode::getSymbol); } public static int getTokenLength(Token token) { return (token.getStopIndex() + 1) - token.getStartIndex(); } public static ImmutableSet<ParseTree> filterContexts(ParseTree parseTree, ImmutableSet<Class<? extends ParseTree>> types) { Set<ParserRuleContext> matchingRules = Trees.getDescendants(parseTree).stream() .filter(descendant -> types.contains(descendant.getClass())) .map(ParserRuleContext.class::cast) .collect(Collectors.toSet()); return ImmutableSet.copyOf(matchingRules); } public static ImmutableList<ParseTree> bottomUpAncestors(ParseTree parseTree) { return ImmutableList.copyOf( Iterables.filter(Lists.reverse(Trees.getAncestors(parseTree)), ParseTree.class)); } }