module RefacAddRmParam(addOneParameter,rmOneParameter) where import PosSyntax import TypedIds import UniqueNames hiding (srcLoc) import PNT import TiPNT import Maybe import List hiding (delete) import RefacUtils import Char ----------------------------------------------------------------------------------------------------- {- An argument can be added to the definition of a function or constant. Adding an argument to a constant definition will change the constant definition to a function definition. The new parameter is always added as the first parameter of the function. A default parameter will be added as the first argument to each of the function's call site. Suppose a new parameter named 'p' is added to function 'f', then default parameter will be defined automatically as p_f_i=undefined, where 'i' is an integer. To ensure that the default parameter name does not cause name clash in the client modules, we take the visble names both in the current module and in the client modules into account when creating the name. -} ----------------------------------------------------------------------------------------------------- addOneParameter args = let fileName = args!!0 paramName= args!!1 row = read (args!!2)::Int col = read (args!!3)::Int in if isVarId paramName then do modName <-fileNameToModName fileName (inscps, exps, mod, tokList)<-parseSourceFile fileName let pnt@(PNT pn _ _)=locToPNT fileName (row, col) mod --make sure this name is defined in this module if pn /= defaultPN && isFunOrPatName pn mod then if isExported pnt exps then do clients <- clientModsAndFiles modName info <- mapM parseSourceFile $ map snd clients defaultArg <-mkTopLevelDefaultArgName pn paramName fileName modName ( map (\(x, _,_,_)->x) (concatMap inScopeInfo (map myfst info))) (hsDecls mod) (mod',((ts',m), _))<-doAddingParam fileName modName pn paramName (Just defaultArg) True mod tokList refactoredClients<-mapM (addArgInClientMod pnt defaultArg modName) $ zip info (map snd clients) writeRefactoredFiles False $ ((fileName,m),(ts',mod')):refactoredClients else do (mod',((ts',m),_))<-doAddingParam fileName modName pn paramName Nothing False mod tokList writeRefactoredFiles False [((fileName,m),(ts',mod'))] else error "Invalid cursor position or identifier is not a function/pattern name defined in this module!\n" else error "Invalid parameter name!\n" doAddingParam fileName modName pn newParam defaultArg isExported mod tokList = runStateT (applyTP (once_tdTP (failTP `adhocTP` inMod `adhocTP` inMatch `adhocTP` inPat `adhocTP` inLet `adhocTP` inAlt `adhocTP` inLetStmt) `choiceTP` failure) mod) ((tokList,unmodified),(-1000,0)) where --1.pn is declared in top level inMod (mod@(HsModule loc name exps imps ds):: HsModuleP) | definingDecls [pn] ds False False/=[] = do mod'<-doAdding mod ds if isExported && isExplicitlyExported pn mod then addItemsToExport mod' (Just pn) False (Left [pNtoName (fromJust defaultArg)]) else return mod' inMod _ = mzero --2. pn is declared locally in the where clause of a match. inMatch (match@(HsMatch loc1 name pats rhs ds)::HsMatchP) | definingDecls [pn] ds False False/=[] = doAdding match ds inMatch _ = mzero --3. pn is declared locally in the where clause of a pattern binding. inPat (pat@(Dec (HsPatBind loc p rhs ds))::HsDeclP) | definingDecls [pn] ds False False/=[] = doAdding pat ds inPat _ = mzero --4: pn is declared locally in a Let expression inLet (letExp@(Exp (HsLet ds e))::HsExpP) | definingDecls [pn] ds False False /=[] = doAdding letExp ds inLet _ = mzero --5. pn is declared locally in a case alternative. inAlt (alt@(HsAlt loc p rhs ds)::HsAltP) | definingDecls [pn] ds False False/=[] = doAdding alt ds inAlt _ = mzero --6.pn is declared locally in a let statement. inLetStmt (letStmt@(HsLetStmt ds stmts):: HsStmtP) | definingDecls [pn] ds False False/=[] = doAdding letStmt ds inLetStmt _ = mzero failure = idTP `adhocTP` mod where mod (m::HsModuleP) = error "Refactoring failed" doAdding parent ds = if paramNameOk pn newParam ds then do ds' <- addParamsToDecls ds pn [nameToPN newParam] True --addFormalParam pn newParam ds defaultParamPName <-if isNothing defaultArg then mkLocalDefaultArgName pn newParam modName parent else return (fromJust defaultArg) parent' <- addDefaultActualArg False pn defaultParamPName (replaceDecls parent ds') parent''<- addDefaultActualArgDecl defaultParamPName parent' pn isExported ds'' <- addArgToSig pn (hsDecls parent'') return (replaceDecls parent'' ds'') else error " Refactoring failed." -- check whether the new parameter is a legal. paramNameOk pn newParam t = (fromMaybe True) (applyTU (once_tdTU (failTU `adhocTU` decl)) t) where decl ((Dec (HsFunBind _ matches@((HsMatch _ fun pats rhs ds):ms)))::HsDeclP) | pNTtoPN fun == pn = do results'<-mapM checkInMatch matches Just (all (==True) results') decl pat@(Dec (HsPatBind loc p rhs ds)) | patToPN p == pn = do (f,d) <- hsFDNamesFromInside pat if elem newParam (f `union` d) then error "The new parameter name will cause name clash or semantics change, please select another name!" else Just True decl (Dec (HsPatBind _ p _ _)) | patToPN p /= pn && elem pn (hsPNs p) = error "Parameter can not be added to complex pattern binding" decl _=mzero checkInMatch match = do (f,d) <- hsFDNamesFromInside match if elem newParam (f `union` d) then error "The new parameter name will cause name clash or semantics change, please choose another name!" else return True --add the default argument declaration right after the declaration defining pn addDefaultActualArgDecl defaultParamPName parent pn isExported =if not (findEntity pn parent) && not isExported then return parent else addDecl parent (Just pn) (defaultArgDecl,Nothing) True where defaultArgDecl= [Dec (HsPatBind loc0 (nameToPat (pNtoName defaultParamPName))(HsBody (nameToExp "undefined"))[])] --suppose function name is f, parameter name is p, then the default argument name is like f_p. mkLocalDefaultArgName fun paramName modName t =do (f,d)<-hsFDNamesFromInside t vs<-hsVisibleNames fun t let name=mkNewName ((pNtoName fun)++"_"++paramName) (nub (f `union` d `union` vs)) 0 return (PN (UnQual name) (S loc0)) mkTopLevelDefaultArgName fun paramName fileName modName inscopeNames t =do (f,d)<-hsFDNamesFromInside t let name=mkNewName ((pNtoName fun)++"_"++paramName) (nub (f `union` d `union` inscopeNames)) 0 let loc = SrcLoc fileName 0 (-1000) (-1000) return (PN (UnQual name) (G modName name (N (Just loc)))) addDefaultActualArg recursion pn argPName = if recursion then applyTP (stop_tdTP (failTP `adhocTP` funApp)) else applyTP (stop_tdTP (failTP `adhocTP` inDecl `adhocTP` funApp)) where inDecl (fun@(Dec (HsFunBind _ ((HsMatch _ (PNT pname _ _) _ _ _):ms)))::HsDeclP) | pn == pname = return fun inDecl (pat@(Dec (HsPatBind loc1 ps rhs ds))::HsDeclP) | pn == patToPN ps = return pat inDecl _ = mzero funApp (exp@(Exp (HsId (HsVar (PNT pname _ _))))::HsExpP) |pname==pn = update exp (Exp (HsParen (Exp (HsApp exp (pNtoExp argPName))))) exp funApp _ = mzero --Add type arg to type siginature addArgToSig pn decls = let (before,after)=break (\d ->definesTypeSig pn d) decls in if after==[] then return decls else do newSig<-addArgToSig' [(head after)] --no problem with head. return (before++newSig++(tail after)) where addArgToSig' sig@[(Dec (HsTypeSig loc is c tp))] =do let tVar=mkNewTypeVarName sig typeVar=newTypeVar tVar tp let newSig=if length is==1 then --the type sig only defines the type for pn [Dec (HsTypeSig loc is c typeVar)] else --otherwise, seperate it into two type signatures. [Dec (HsTypeSig loc (filter (\x->pNTtoPN x/=pn) is) c tp), Dec (HsTypeSig loc (filter (\x->pNTtoPN x==pn) is) c typeVar)] update sig newSig sig --compose a type application using type expressions tv and tp newTypeVar tVar tp =(Typ (HsTyFun (Typ (HsTyVar (PNT (PN (UnQual tVar) (S loc0)) (Type (TypeInfo {defType=Nothing, constructors=[], fields=[]})) (N (Just loc0))))) tp)) {- make a fresh type variable name. the new name is the first letter in the alphabet which is not used in the type signature. -} mkNewTypeVarName sig =mkNewName "a" $ map pNtoName $ (snd.hsTypeVbls) sig where mkNewName initName v =if elem initName v then mkNewName ((intToDigit (digitToInt(head initName)+1)):tail initName) v else initName addArgInClientMod pnt defaultArg serverModName ((inscps, exps, mod,ts), fileName) = let qual = hsQualifier pnt inscps pn = pNTtoPN pnt in if qual == [] then return ((fileName,unmodified), (ts,mod)) else do (mod',((ts',m), _))<- runStateT (do mod'<-addItemsToImport serverModName (Just pn) (Left [pNtoName defaultArg]) mod mod''<-addItemsToExport mod (Just pn) False (Left [pNtoName defaultArg]) if isInScopeAndUnqualified (pNtoName pn) inscps then addDefaultActualArgInClientMod pn (head qual) defaultArg False mod'' else addDefaultActualArgInClientMod pn (head qual) defaultArg True mod'') ((ts,unmodified), (-1000,0)) return ((fileName,m),(ts',mod')) --add default actual argument to pn in all the calling places. addDefaultActualArgInClientMod pn qual argPName toBeQualified t = applyTP (stop_tdTP (failTP `adhocTP` funApp)) t where funApp (exp@(Exp (HsId (HsVar pnt@(PNT pname _ _))))::HsExpP) | pname == pn = do vs <- hsVisibleNames pnt t let argExp=if toBeQualified || elem (pNtoName argPName) vs then pNtoExp (qualifyPName qual argPName) else pNtoExp argPName newExp =(Exp (HsParen (Exp (HsApp exp argExp)))) update exp newExp exp funApp _=mzero myfst (a,_,_,_) = a -------------------------------End of adding a parameter----------------------------------- ----------------------------------------------------------------------------------------------------- {-Refactoring: Remove a parameter Description: The refactoring removes a user specified formal parameter in a function binding,and the corresponding actual parameters in all calling places of this function. The condition acompanying this refactoring is that the parameter to be removed is not being used. --To select a parameter, just stop the cursor at any position between the start and end position of this parameter. -} ----------------------------------------------------------------------------------------------------- rmOneParameter args =do let fileName=args!!0 row=read (args!!1)::Int col=read (args!!2)::Int (inscps, exps, mod, tokList)<-parseSourceFile fileName --pn is the function names. --nth is the nth paramter of pn is to be removed,index starts form 0. let (pnt,nth)=getParam tokList (row,col) mod pn=pNTtoPN pnt if pn/=defaultPN then do (mod',((ts',m), _))<-doRmParam pn nth mod fileName tokList if isExported pnt exps then do modName <- RefacUtils.fileNameToModName fileName clients<-clientModsAndFiles modName refactoredClients<-mapM (rmParamInClientMod pnt nth) clients writeRefactoredFiles False $ ((fileName,m),(ts',mod')):refactoredClients else writeRefactoredFiles False [((fileName,m), (ts',mod'))] else error "Invalid cursor position!" --pn: function name; nth: the index of the parameter to be removed. doRmParam pn nth mod fileName tokList =runStateT (applyTP ((once_tdTP (failTP `adhocTP` inMod `adhocTP` inMatch `adhocTP` inPat `adhocTP` inLet `adhocTP` inAlt `adhocTP` inLetStmt)) `choiceTP` failure) mod) ((tokList,unmodified),(-1000,0)) where --1. pn is declared in top level. inMod (mod@(HsModule loc name exps imps ds):: HsModuleP) |definingDecls [pn] ds False False/=[] = doRemoving mod ds inMod _ =mzero --2. pn is declared locally in the where clause of a match. inMatch (match@(HsMatch loc1 name pats rhs ds)::HsMatchP) |definingDecls [pn] ds False False /=[] = doRemoving match ds inMatch _ =mzero --3. pn is declared locally in the where clause of a pattern binding. inPat (pat@(Dec (HsPatBind loc p rhs ds))::HsDeclP) | definingDecls [pn] ds False False/=[] = doRemoving pat ds inPat _=mzero --4: pn is declared locally in a Let expression inLet (letExp@(Exp (HsLet ds e))::HsExpP) | definingDecls [pn] ds False False/=[] = doRemoving letExp ds inLet _=mzero --5. pn is declared locally in a case alternative. inAlt (alt@(HsAlt loc p rhs ds)::HsAltP) | definingDecls [pn] ds False False/=[] = doRemoving alt ds inAlt _=mzero --6.pn is declared locally in a let statement. inLetStmt (letStmt@(HsLetStmt ds stmts):: HsStmtP) | definingDecls [pn] ds False False/=[] = doRemoving letStmt ds inLetStmt _=mzero failure=idTP `adhocTP` mod where mod (m::HsModuleP)=error "Refactoring failed" doRemoving parent ds --PROBLEM: How about doRemoving fails? =do rmFormalArg pn nth False True =< fun " else return e1 funApp (exp@(Exp (HsApp e1 e2))::HsExpP) |pn==(expToPN.(ghead "rmNthArgInFunCall").unfoldHsApp) exp --test if this application is a calling of fun pn. =do let (before,after)=splitAt (nth+1) (unfoldHsApp exp) --remove the nth argument if updateToks then delete (ghead "rmNthArgInFunCall" after) exp else return (foldHsApp (before++tail after)) --reconstruct the function application. funApp _=mzero --deconstruct a function application into a list of expressions. unfoldHsApp exp= case exp of (Exp (HsApp e1 e2))->(unfoldHsApp e1)++[e2] _ ->[exp] --reconstruct a function application by a list of expressions. foldHsApp exps=foldl1 (\e1 e2->(Exp (HsApp e1 e2))) exps rmNthArgInSig pn nth decls = let (before,after)=break (\d ->definesTypeSig pn d) decls in if after==[] then return decls else do newSig<-rmNthArgInSig' nth [(head after)] --no problem with 'head' return (before++newSig++(tail after)) where rmNthArgInSig' nth sig@[(Dec (HsTypeSig loc is c tp))] =do let newSig=if length is ==1 then --this type signature only defines the type of pn [Dec (HsTypeSig loc is c (rmNth tp))] else --this type signature also defines the type of other ids. [Dec (HsTypeSig loc (filter (\x->pNTtoPN x/=pn) is) c tp), Dec (HsTypeSig loc (filter (\x->pNTtoPN x==pn) is) c (rmNth tp))] update sig newSig sig rmNth tp=let (before,after)=splitAt nth (unfoldHsTypApp tp) in (foldHsTypApp (before ++ tail after)) --deconstruct a type application into a list of types unfoldHsTypApp typ = case typ of (Typ (HsTyFun t1 t2)) ->t1:unfoldHsTypApp t2 _ ->[typ] --reconstruct a type application by a list of type expression. foldHsTypApp ts=foldr1 (\t1 t2->(Typ (HsTyFun t1 t2))) ts --get the function name and the index of the parameter to be removed from the cursor position. getParam toks pos =(fromMaybe (defaultPNT, 0)).(applyTU (once_tdTU (failTU `adhocTU` inMatch))) where inMatch (match@(HsMatch loc fun pats rhs decls)::HsMatchP) |inRange pos (getStartEndLoc toks pats) =let paramPosRanges=map (getStartEndLoc toks) pats elem=find (inRange pos) paramPosRanges in if isNothing elem then error "Invalid cursor position!" -- cursor doesn't stop at a parameter position. else Just (fun, fromJust (elemIndex (fromJust elem) paramPosRanges)) inMatch _=Nothing inRange pos (startPos,endPos)=pos>=startPos && pos<=endPos rmParamInClientMod pnt nth (m, fileName) = do (inscps, exps, mod, ts) <-parseSourceFile fileName let qualifier = hsQualifier pnt inscps pn = pNTtoPN pnt if qualifier == [] then return ((fileName,unmodified), (ts,mod)) else do (mod',((ts',m), _)) <- runStateT (rmNthArgInFunCall pn nth True mod) ((ts,unmodified),(-1000,0)) return ((fileName,m), (ts',mod'))