/******************************************************************************* * Copyright (c) 2008, 2015 Wind River Systems, Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Markus Schorn - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.internal.core.parser.scanner; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeSelector; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorElifStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIfStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroExpansion; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.IMacroBinding; import org.eclipse.cdt.core.dom.rewrite.MacroExpansionExplorer; import org.eclipse.cdt.internal.core.parser.scanner.Lexer.LexerOptions; import org.eclipse.jface.text.IRegion; import org.eclipse.text.edits.ReplaceEdit; /** * Delegates the task of exploring macro expansions to simpler explorers dealing with * a single macro, only. * @since 5.0 */ public class MultiMacroExpansionExplorer extends MacroExpansionExplorer { private static final class ASTFileLocation implements IASTFileLocation { private final String fFilePath; private final int fOffset; private final int fLength; private ASTFileLocation(String filePath, int offset, int length) { fFilePath= filePath; fOffset= offset; fLength= length; } @Override public int getNodeOffset() { return fOffset; } @Override public int getNodeLength() { return fLength; } @Override public String getFileName() { return fFilePath; } @Override public int getStartingLineNumber() { return 0; } @Override public int getEndingLineNumber() { return 0; } @Override public IASTFileLocation asFileLocation() { return this; } @Override public IASTPreprocessorIncludeStatement getContextInclusionStatement() { return null; } } private final char[] fSource; private final int[] fBoundaries; private final SingleMacroExpansionExplorer[] fDelegates; private final String fFilePath; private final Map<IMacroBinding, IASTFileLocation> fMacroLocations; private MacroExpansionStep fCachedStep; private int fCachedStepID= -1; public MultiMacroExpansionExplorer(IASTTranslationUnit tu, IASTFileLocation loc) { if (tu == null || loc == null || loc.getNodeLength() == 0) { throw new IllegalArgumentException(); } final ILocationResolver resolver = getResolver(tu); final IASTNodeSelector nodeLocator= tu.getNodeSelector(null); final IASTPreprocessorMacroExpansion[] expansions= resolver.getMacroExpansions(loc); final int count= expansions.length; loc = extendLocation(loc, expansions); fMacroLocations= getMacroLocations(resolver); fFilePath= tu.getFilePath(); fSource= resolver.getUnpreprocessedSignature(loc); fBoundaries= new int[count * 2 + 1]; fDelegates= new SingleMacroExpansionExplorer[count]; final int firstOffset= loc.getNodeOffset(); int bidx= -1; int didx= -1; for (IASTPreprocessorMacroExpansion expansion : expansions) { IASTName ref= expansion.getMacroReference(); if (ref != null) { ArrayList<IASTName> refs= new ArrayList<IASTName>(); refs.add(ref); refs.addAll(Arrays.asList(expansion.getNestedMacroReferences())); IASTFileLocation refLoc= expansion.getFileLocation(); int from= refLoc.getNodeOffset()-firstOffset; int to= from+refLoc.getNodeLength(); IASTNode enclosing= nodeLocator.findEnclosingNode(from + firstOffset - 1, 2); boolean isPPCond= enclosing instanceof IASTPreprocessorIfStatement || enclosing instanceof IASTPreprocessorElifStatement; fBoundaries[++bidx]= from; fBoundaries[++bidx]= to; fDelegates[++didx]= new SingleMacroExpansionExplorer(new String(fSource, from, to - from), refs.toArray(new IASTName[refs.size()]), fMacroLocations, fFilePath, refLoc.getStartingLineNumber(), isPPCond, tu.getAdapter(LexerOptions.class)); } } fBoundaries[++bidx]= fSource.length; } private ILocationResolver getResolver(IASTTranslationUnit tu) { final ILocationResolver resolver = tu.getAdapter(ILocationResolver.class); if (resolver == null) { throw new IllegalArgumentException(); } return resolver; } private IASTFileLocation extendLocation(IASTFileLocation loc, final IASTPreprocessorMacroExpansion[] expansions) { final int count= expansions.length; if (count > 0) { int from= loc.getNodeOffset(); int to= from+loc.getNodeLength(); final int lfrom = expansions[0].getFileLocation().getNodeOffset(); final IASTFileLocation l= expansions[count-1].getFileLocation(); final int lto= l.getNodeOffset() + l.getNodeLength(); if (lfrom < from || lto > to) { from= Math.min(from, lfrom); to= Math.max(to, lto); loc= new ASTFileLocation(loc.getFileName(), from, to-from); } } return loc; } private Map<IMacroBinding, IASTFileLocation> getMacroLocations(final ILocationResolver resolver) { final Map<IMacroBinding, IASTFileLocation> result= new HashMap<IMacroBinding, IASTFileLocation>(); addLocations(resolver.getBuiltinMacroDefinitions(), result); addLocations(resolver.getMacroDefinitions(), result); return result; } private void addLocations(IASTPreprocessorMacroDefinition[] defs, final Map<IMacroBinding, IASTFileLocation> result) { for (IASTPreprocessorMacroDefinition def : defs) { IASTName name= def.getName(); if (name != null) { IASTFileLocation loc= name.getFileLocation(); if (loc != null) { final IBinding binding= name.getBinding(); if (binding instanceof IMacroBinding) { loc= new ASTFileLocation(loc.getFileName(), loc.getNodeOffset(), loc.getNodeLength()); result.put((IMacroBinding) binding, loc); } } } } } public MultiMacroExpansionExplorer(final IASTTranslationUnit tu, final IRegion loc) { this(tu, new ASTFileLocation(tu.getFilePath(), loc.getOffset(), loc.getLength())); } @Override public IMacroExpansionStep getFullExpansion() { List<ReplaceEdit> edits = combineReplaceEdits(fDelegates.length); return new MacroExpansionStep(new String(fSource), null, null, edits.toArray(new ReplaceEdit[edits.size()])); } /** * Combines the replace edits of the leading delegates. */ private List<ReplaceEdit> combineReplaceEdits(int count) { ArrayList<ReplaceEdit> edits= new ArrayList<ReplaceEdit>(); for (int i= 0; i < count; i++) { IMacroExpansionStep step= fDelegates[i].getFullExpansion(); shiftAndAddEdits(fBoundaries[2*i], step.getReplacements(), edits); } return edits; } /** * Shifts and adds the replace edits to the target list. */ private void shiftAndAddEdits(final int shift, ReplaceEdit[] stepEdits, List<ReplaceEdit> target) { for (int j = 0; j < stepEdits.length; j++) { final ReplaceEdit r = stepEdits[j]; final String rtext = r.getText(); target.add(new ReplaceEdit(shift+r.getOffset(), r.getLength(), rtext)); } } @Override public int getExpansionStepCount() { int result= 0; for (int i= 0; i < fDelegates.length; i++) { result+= fDelegates[i].getExpansionStepCount(); } return result; } @Override public IMacroExpansionStep getExpansionStep(int step) throws IndexOutOfBoundsException { if (step < 0) { throw new IndexOutOfBoundsException(); } if (fCachedStep != null && fCachedStepID == step) { return fCachedStep; } int i; MacroExpansionStep dresult= null; StringBuilder before= new StringBuilder(); before.append(fSource, 0, fBoundaries[0]); for (i= 0; i < fDelegates.length; i++) { final SingleMacroExpansionExplorer delegate = fDelegates[i]; int dsteps= delegate.getExpansionStepCount(); if (step < dsteps) { dresult= delegate.getExpansionStep(step); break; } before.append(delegate.getFullExpansion().getCodeAfterStep()); appendGap(before, i); step-= dsteps; } if (dresult == null) { throw new IndexOutOfBoundsException(); } final int shift= before.length(); final int end= fBoundaries[2 * i + 1]; before.append(dresult.getCodeBeforeStep()); before.append(fSource, end, fSource.length - end); List<ReplaceEdit> replacements= new ArrayList<ReplaceEdit>(); shiftAndAddEdits(shift, dresult.getReplacements(), replacements); fCachedStep= new MacroExpansionStep(before.toString(), dresult.getExpandedMacro(), dresult.getLocationOfExpandedMacroDefinition(), replacements.toArray(new ReplaceEdit[replacements.size()])); fCachedStepID= step; return fCachedStep; } private void appendGap(StringBuilder result, int i) { int idx= 2 * i + 1; int gapFrom= fBoundaries[idx]; int gapTo= fBoundaries[++idx]; result.append(fSource, gapFrom, gapTo - gapFrom); } }