-- This example refactoring transforms an user-selected if-then-else expression to a case expression. -- If the user did not select an if-then-else expression, then an error message is given. -- To apply this refactoring, the user need to first highlight the if-then-else expression in the program -- source, then select the 'From if to case' from the 'Definitions' sub-menu in the 'Refactor' menu. -- The refactoring name is 'ifToCase' and is the only name exported by this module. module RefacCase(ifToCase) where -- The 'RefacUtils' modules defines the API, and also re-exported those modules from Programatica -- that defines the abstract syntax for Haskell 98, and the StrategyLib module from Strafunski -- which contains the AST traversal combinators. import RefacUtils ifToCase args = do let fileName = args!!0 -- The first argument is always the filename of the current module. beginRow = read (args!!1)::Int -- The begin line number of the highlighted area. beginCol = read (args!!2)::Int -- The begin column number of the highlighted area. endRow = read (args!!3)::Int -- The end line number of the highlighted area. endCol = read (args!!4)::Int -- The end column number of the highlighted area. -- Parse the source file. In the returned result, 'inscps' in the in-scope relation which has the -- information about which top-level identifiers are visible to this module; 'exps' is the export -- relation which contains the information about which identifiers are exported by this module; -- 'mod' is the AST for the module and toks is the Token stream for the parsed module. -- As this refactoring only works on the current module, and does not care about the -- imports and exports, so 'inscps' and 'exps' won't be used in the following code. (inscps, exps, mod, toks) <- parseSourceFile fileName -- Get the largest-leftmost expression contained by the user-highlighted area. 'locToExp' returns -- a default expression if the highlighted area does not contain an expression. let exp =locToExp (beginRow,beginCol) (endRow, endCol) toks mod -- Check whether the user has selected an expression and whether the expression is an if-then-else expression. if exp/= defaultExp && isIfExp exp then -- The user has selected an if-then-else expression, so do the transformation. -- 'doIfToCase' is the function that really does the transformation. We run this function in a state monad -- in order to manipulate the token stream. In the state, we have the token stream, ie. toks, a flag -- indicating whether the token stream has been modified, and an integer which can be used when creating -- new names. do (mod',((toks',m),_))<-runStateT (doIfToCase exp mod) ((toks,False),0) -- once the transformation has been done, overwrite the original file by the new result. -- Here, 'False' means that is refactoring is not a sub-refactoring of another composite refactoring. writeRefactoredFiles False [((fileName,m), (toks',mod'))] -- The user did not selected an if-then-else expression, so given an error message. else error "You haven't selected an if-then-else expression!" where -- Return True if an expression is an If-then-else expression. isIfExp (Exp (HsIf e e1 e2))= True isIfExp _ = False -- The function that performs the transformation. This function traverses the AST of the module in -- an top-down manner. Whenever it comes across an expression in the AST, it checks whether this -- expression is the same as the user-selected expression, if the answer is True, then replace this -- expression by an newly-composed case expression, and stop the traversal, otherwise returns mzero -- and continues the traversal. -- 'sameOccurrence' checks not only whether two syntax phrases are syntactically the same, but also -- whether they have the same source locations. doIfToCase exp mod = applyTP (once_tdTP (failTP `adhocTP` inExp)) mod where inExp exp1@((Exp (HsIf e e1 e2))::HsExpP) | sameOccurrence exp exp1 = do let newExp =Exp (HsCase e [HsAlt loc0 (nameToPat "True") (HsBody e1) [], HsAlt loc0 (nameToPat "False")(HsBody e2) []]) -- replace 'exp1' by 'newExp' in both the AST and the token stream. the -- third argument specifies the context where the first argument occurs. update exp1 newExp exp1 inExp _ = mzero