/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * PrettyPrintRefactor.cal * Created: July 2007 * By: Magnus Byne */ package org.openquark.cal.compiler; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.openquark.cal.compiler.SourceModel.CALDoc; import org.openquark.cal.compiler.SourceModel.FunctionDefn; import org.openquark.cal.compiler.SourceModel.FunctionTypeDeclaration; import org.openquark.cal.compiler.SourceModel.Import; import org.openquark.cal.compiler.SourceModel.InstanceDefn; import org.openquark.cal.compiler.SourceModel.LocalDefn; import org.openquark.cal.compiler.SourceModel.ModuleDefn; import org.openquark.cal.compiler.SourceModel.SourceElement; import org.openquark.cal.compiler.SourceModel.TypeClassDefn; /** * Represents the Pretty Print refactoring for parts of a module. * * The parts can be defined by a source range or a set of functions. * * It traverse the source model and reformats certain * elements that are within the source range or function set. * * Currently the elements are limited to top level elements and * local functions. * * @author Magnus Byne */ final class PrettyPrintRefactorer extends SourceModelTraverser<Void, Void> { final private String source; final private SourceRange rangeToFormat; final private Set<String> functionNames; final private List<SourceEmbellishment> embellishments = new ArrayList<SourceEmbellishment>(); //this is used to collect modifications as the source model is traversed private SourceModifier modifications = null; public PrettyPrintRefactorer(String source, SourceRange range, Set<String> functionNames) { this.rangeToFormat = range; this.source = source; this.functionNames = new HashSet<String>(functionNames); } /** returns true if the element is contained within the refactor's source range*/ private boolean withinRange(SourceElement element) { SourceRange el = element.getSourceRangeOfDefn(); if (rangeToFormat == null || el==null) { return false; } return rangeToFormat.contains(el); } /** * Constructs and returns a SoruceModification that replaces the text at sourceRange of sourceText with newText. * @param sourceText * @param sourceRange * @param newText * @return A SourceModification.ReplaceText */ private static SourceModification makeReplaceText(String sourceText, SourceRange sourceRange, String newText) { SourcePosition startPosition = sourceRange.getStartSourcePosition(); SourcePosition endPosition = sourceRange.getEndSourcePosition(); int startIndex = startPosition.getPosition(sourceText); int endIndex = endPosition.getPosition(sourceText, startPosition, startIndex); String oldText = sourceText.substring(startIndex, endIndex); return new SourceModification.ReplaceText(oldText, newText, startPosition); } /** get the source modifications or null if there is an error*/ public SourceModifier getModifier() { modifications = new SourceModifier(); CompilerMessageLogger mlogger= new MessageLogger(); embellishments.clear(); ModuleDefn moduleDefn = SourceModelUtilities.TextParsing.parseModuleDefnIntoSourceModel(source, false, mlogger, embellishments); if (moduleDefn == null) { //unable to parse module return null; } if (rangeToFormat == null && functionNames.isEmpty()) { //format the whole file String formatted = SourceModelCodeFormatter.formatCode( moduleDefn, SourceModelCodeFormatter.DEFAULT_OPTIONS, embellishments ); modifications.addSourceModification( new SourceModification.ReplaceText(source, formatted, new SourcePosition(1, 1, moduleDefn.getModuleName().toString()))); } else { //visit module elements and format those in range moduleDefn.accept(this, null); } return modifications; } /** used to find the embellishments that fall with a range being reformatted*/ private List<SourceEmbellishment> filterEmbellishments(List<SourceEmbellishment> embellishments, SourceRange range) { List<SourceEmbellishment> results = new ArrayList<SourceEmbellishment>(); for (final SourceEmbellishment em : embellishments) { if (range.contains(em.getSourceRange())) { results.add(em); } } return results; } /** * Attempt to guess the indent level that a programmer was using in * the given source range. Takes the indent from the first line * in the range that is not all white space. * * This is used to put local functions at the correct indent level */ private int guessIndent(SourceRange range) { for (int line = range.getStartLine(); line <= range.getEndLine(); line++) { int startIndex = (new SourcePosition(line, 1)).getPosition(source); int indent =0; while (startIndex < source.length() && LanguageInfo.isCALWhitespace(source.charAt(startIndex))) { indent += SourcePosition.columnWidth(indent + 1, source.charAt(startIndex)); startIndex++; } if (startIndex < source.length() && !LanguageInfo.isCALWhitespace(source.charAt(startIndex))) { return indent; } } //failed to find a clue for the correct offset - do not indent. return 0; } /** * this expands a source range to include leading white spaces - * this is used to make sure we remove any whitespace around an element that we * are replacing - the correct white space comes form the formatter. */ private SourceRange expandRange(SourceRange range) { //consume leading spaces int offset = range.getStartSourcePosition().getPosition(source); int startcol = range.getStartColumn(); while (startcol > 1 && LanguageInfo.isCALWhitespace( source.charAt(offset - 1))) { offset--; startcol--; } return new SourceRange(new SourcePosition(range.getStartLine(), startcol), range.getEndSourcePosition()); } /** format an element if it is in the source range to format * * @param elem * @return true if the element is formatted, otherwise false. */ private boolean formatElement(SourceElement elem) { if (withinRange(elem)) { //format String formatted = SourceModelCodeFormatter.formatCode( elem, SourceModelCodeFormatter.DEFAULT_OPTIONS, filterEmbellishments(embellishments, elem.getSourceRangeOfDefn()) ); modifications.addSourceModification( makeReplaceText(source, elem.getSourceRange(), formatted)); return true; } else { return false; } } /** {@inheritDoc} */ @Override public Void visit_LocalDefn_Function_Definition( LocalDefn.Function.Definition algebraic, Void arg) { if (withinRange(algebraic) || functionNames.contains(algebraic.getName())) { //format String formatted = SourceModelCodeFormatter.formatCode( algebraic, SourceModelCodeFormatter.DEFAULT_OPTIONS, filterEmbellishments(embellishments, algebraic.getSourceRangeOfDefn()), guessIndent(algebraic.getSourceRangeOfDefn()) ); modifications.addSourceModification( makeReplaceText(source, expandRange(algebraic.getSourceRange()), formatted)); return null; } else { return super.visit_LocalDefn_Function_Definition(algebraic, arg); } } /** {@inheritDoc} */ @Override public Void visit_FunctionTypeDeclaraction( FunctionTypeDeclaration declaration, Void arg) { if (!formatElement(declaration)) { return super.visit_FunctionTypeDeclaraction(declaration, arg); } else { return null; } } @Override public Void visit_LocalDefn_PatternMatch_UnpackDataCons( LocalDefn.PatternMatch.UnpackDataCons declaration, Void arg) { if (!formatElement(declaration)) { return super.visit_LocalDefn_PatternMatch_UnpackDataCons(declaration, arg); } else { return null; } } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_PatternMatch_UnpackListCons( LocalDefn.PatternMatch.UnpackListCons declaration, Void arg) { if (!formatElement(declaration)) { return super.visit_LocalDefn_PatternMatch_UnpackListCons(declaration, arg); } else { return null; } } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_PatternMatch_UnpackRecord( LocalDefn.PatternMatch.UnpackRecord declaration, Void arg) { if (!formatElement(declaration)) { return super.visit_LocalDefn_PatternMatch_UnpackRecord(declaration, arg); } else { return null; } } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_PatternMatch_UnpackTuple( LocalDefn.PatternMatch.UnpackTuple declaration, Void arg) { if (!formatElement(declaration)) { return super.visit_LocalDefn_PatternMatch_UnpackTuple(declaration, arg); } else { return null; } } /** * {@inheritDoc} */ @Override public Void visit_FunctionDefn_Primitive( FunctionDefn.Primitive declaration, Void arg) { if (!formatElement(declaration)) { return super.visit_FunctionDefn_Primitive(declaration, arg); } else { return null; } } /** * {@inheritDoc} */ @Override public Void visit_LocalDefn_Function_TypeDeclaration( LocalDefn.Function.TypeDeclaration declaration, Void arg) { if (!formatElement(declaration)) { return super.visit_LocalDefn_Function_TypeDeclaration(declaration, arg); } else { return null; } } /** {@inheritDoc} */ @Override public Void visit_Import( Import importStmt, Void arg) { if (!formatElement(importStmt)) { return super.visit_Import(importStmt, arg); } else { return null; } } /** {@inheritDoc} */ @Override public Void visit_InstanceDefn( InstanceDefn defn, Void arg) { if (!formatElement(defn)) { return super.visit_InstanceDefn(defn, arg); } else { return null; } } /** {@inheritDoc} */ @Override public Void visit_TypeClassDefn( TypeClassDefn defn, Void arg) { if (!formatElement(defn)) { return super.visit_TypeClassDefn(defn, arg); } else { return null; } } /** {@inheritDoc} */ @Override protected Void visit_CALDoc_Comment_Helper( CALDoc.Comment comment, Void arg) { if (!formatElement(comment)) { return super.visit_CALDoc_Comment_Helper(comment, arg); } else { return null; } } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Foreign( FunctionDefn.Foreign foreign, Void arg) { if (!formatElement(foreign)) { return super.visit_FunctionDefn_Foreign(foreign, arg); } else { return null; } } /** {@inheritDoc} */ @Override public Void visit_FunctionDefn_Algebraic( FunctionDefn.Algebraic algebraic, Void arg) { if (withinRange(algebraic) || functionNames.contains(algebraic.getName())) { //format String formatted = SourceModelCodeFormatter.formatCode( algebraic, SourceModelCodeFormatter.DEFAULT_OPTIONS, filterEmbellishments(embellishments, algebraic.getSourceRangeOfDefn()) ); modifications.addSourceModification( makeReplaceText(source, algebraic.getSourceRange(), formatted)); return null; } else { return super.visit_FunctionDefn_Algebraic(algebraic, arg); } } }