{- Copyright 2012-2015 Vidar Holen This file is part of ShellCheck. https://www.shellcheck.net ShellCheck is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ShellCheck 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -} module ShellCheck.AST where import Control.Monad.Identity import Text.Parsec import qualified ShellCheck.Regex as Re import Prelude hiding (id) newtype Id = Id Int deriving (Show, Eq, Ord) data Quoted = Quoted | Unquoted deriving (Show, Eq) data Dashed = Dashed | Undashed deriving (Show, Eq) data AssignmentMode = Assign | Append deriving (Show, Eq) newtype FunctionKeyword = FunctionKeyword Bool deriving (Show, Eq) newtype FunctionParentheses = FunctionParentheses Bool deriving (Show, Eq) data CaseType = CaseBreak | CaseFallThrough | CaseContinue deriving (Show, Eq) newtype Root = Root Token data Token = TA_Binary Id String Token Token | TA_Assignment Id String Token Token | TA_Variable Id String [Token] | TA_Expansion Id [Token] | TA_Sequence Id [Token] | TA_Trinary Id Token Token Token | TA_Unary Id String Token | TC_And Id ConditionType String Token Token | TC_Binary Id ConditionType String Token Token | TC_Group Id ConditionType Token | TC_Nullary Id ConditionType Token | TC_Or Id ConditionType String Token Token | TC_Unary Id ConditionType String Token | TC_Empty Id ConditionType | T_AND_IF Id | T_AndIf Id Token Token | T_Arithmetic Id Token | T_Array Id [Token] | T_IndexedElement Id [Token] Token -- Store the index as string, and parse as arithmetic or string later | T_UnparsedIndex Id SourcePos String | T_Assignment Id AssignmentMode String [Token] Token | T_Backgrounded Id Token | T_Backticked Id [Token] | T_Bang Id | T_Banged Id Token | T_BraceExpansion Id [Token] | T_BraceGroup Id [Token] | T_CLOBBER Id | T_Case Id | T_CaseExpression Id Token [(CaseType, [Token], [Token])] | T_Condition Id ConditionType Token | T_DGREAT Id | T_DLESS Id | T_DLESSDASH Id | T_DSEMI Id | T_Do Id | T_DollarArithmetic Id Token | T_DollarBraced Id Token | T_DollarBracket Id Token | T_DollarDoubleQuoted Id [Token] | T_DollarExpansion Id [Token] | T_DollarSingleQuoted Id String | T_DollarBraceCommandExpansion Id [Token] | T_Done Id | T_DoubleQuoted Id [Token] | T_EOF Id | T_Elif Id | T_Else Id | T_Esac Id | T_Extglob Id String [Token] | T_FdRedirect Id String Token | T_Fi Id | T_For Id | T_ForArithmetic Id Token Token Token [Token] | T_ForIn Id String [Token] [Token] | T_Function Id FunctionKeyword FunctionParentheses String Token | T_GREATAND Id | T_Glob Id String | T_Greater Id | T_HereDoc Id Dashed Quoted String [Token] | T_HereString Id Token | T_If Id | T_IfExpression Id [([Token],[Token])] [Token] | T_In Id | T_IoFile Id Token Token | T_IoDuplicate Id Token String | T_LESSAND Id | T_LESSGREAT Id | T_Lbrace Id | T_Less Id | T_Literal Id String | T_Lparen Id | T_NEWLINE Id | T_NormalWord Id [Token] | T_OR_IF Id | T_OrIf Id Token Token | T_ParamSubSpecialChar Id String -- e.g. '%' in ${foo%bar} or '/' in ${foo/bar/baz} | T_Pipeline Id [Token] [Token] -- [Pipe separators] [Commands] | T_ProcSub Id String [Token] | T_Rbrace Id | T_Redirecting Id [Token] Token | T_Rparen Id | T_Script Id String [Token] | T_Select Id | T_SelectIn Id String [Token] [Token] | T_Semi Id | T_SimpleCommand Id [Token] [Token] | T_SingleQuoted Id String | T_Subshell Id [Token] | T_Then Id | T_Until Id | T_UntilExpression Id [Token] [Token] | T_While Id | T_WhileExpression Id [Token] [Token] | T_Annotation Id [Annotation] Token | T_Pipe Id String | T_CoProc Id (Maybe String) Token | T_CoProcBody Id Token | T_Include Id Token | T_SourceCommand Id Token Token deriving (Show) data Annotation = DisableComment Integer | SourceOverride String | ShellOverride String deriving (Show, Eq) data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq) -- This is an abomination. tokenEquals :: Token -> Token -> Bool tokenEquals a b = kludge a == kludge b where kludge s = Re.subRegex (Re.mkRegex "\\(Id [0-9]+\\)") (show s) "(Id 0)" instance Eq Token where (==) = tokenEquals analyze :: Monad m => (Token -> m ()) -> (Token -> m ()) -> (Token -> m Token) -> Token -> m Token analyze f g i = round where round t = do f t newT <- delve t g t i newT roundAll = mapM round dl l v = do x <- roundAll l return $ v x dll l m v = do x <- roundAll l y <- roundAll m return $ v x y d1 t v = do x <- round t return $ v x d2 t1 t2 v = do x <- round t1 y <- round t2 return $ v x y delve (T_NormalWord id list) = dl list $ T_NormalWord id delve (T_DoubleQuoted id list) = dl list $ T_DoubleQuoted id delve (T_DollarDoubleQuoted id list) = dl list $ T_DollarDoubleQuoted id delve (T_DollarExpansion id list) = dl list $ T_DollarExpansion id delve (T_DollarBraceCommandExpansion id list) = dl list $ T_DollarBraceCommandExpansion id delve (T_BraceExpansion id list) = dl list $ T_BraceExpansion id delve (T_Backticked id list) = dl list $ T_Backticked id delve (T_DollarArithmetic id c) = d1 c $ T_DollarArithmetic id delve (T_DollarBracket id c) = d1 c $ T_DollarBracket id delve (T_IoFile id op file) = d2 op file $ T_IoFile id delve (T_IoDuplicate id op num) = d1 op $ \x -> T_IoDuplicate id x num delve (T_HereString id word) = d1 word $ T_HereString id delve (T_FdRedirect id v t) = d1 t $ T_FdRedirect id v delve (T_Assignment id mode var indices value) = do a <- roundAll indices b <- round value return $ T_Assignment id mode var a b delve (T_Array id t) = dl t $ T_Array id delve (T_IndexedElement id indices t) = do a <- roundAll indices b <- round t return $ T_IndexedElement id a b delve (T_Redirecting id redirs cmd) = do a <- roundAll redirs b <- round cmd return $ T_Redirecting id a b delve (T_SimpleCommand id vars cmds) = dll vars cmds $ T_SimpleCommand id delve (T_Pipeline id l1 l2) = dll l1 l2 $ T_Pipeline id delve (T_Banged id l) = d1 l $ T_Banged id delve (T_AndIf id t u) = d2 t u $ T_AndIf id delve (T_OrIf id t u) = d2 t u $ T_OrIf id delve (T_Backgrounded id l) = d1 l $ T_Backgrounded id delve (T_Subshell id l) = dl l $ T_Subshell id delve (T_ProcSub id typ l) = dl l $ T_ProcSub id typ delve (T_Arithmetic id c) = d1 c $ T_Arithmetic id delve (T_IfExpression id conditions elses) = do newConds <- mapM (\(c, t) -> do x <- mapM round c y <- mapM round t return (x,y) ) conditions newElses <- roundAll elses return $ T_IfExpression id newConds newElses delve (T_BraceGroup id l) = dl l $ T_BraceGroup id delve (T_WhileExpression id c l) = dll c l $ T_WhileExpression id delve (T_UntilExpression id c l) = dll c l $ T_UntilExpression id delve (T_ForIn id v w l) = dll w l $ T_ForIn id v delve (T_SelectIn id v w l) = dll w l $ T_SelectIn id v delve (T_CaseExpression id word cases) = do newWord <- round word newCases <- mapM (\(o, c, t) -> do x <- mapM round c y <- mapM round t return (o, x,y) ) cases return $ T_CaseExpression id newWord newCases delve (T_ForArithmetic id a b c group) = do x <- round a y <- round b z <- round c list <- mapM round group return $ T_ForArithmetic id x y z list delve (T_Script id s l) = dl l $ T_Script id s delve (T_Function id a b name body) = d1 body $ T_Function id a b name delve (T_Condition id typ token) = d1 token $ T_Condition id typ delve (T_Extglob id str l) = dl l $ T_Extglob id str delve (T_DollarBraced id op) = d1 op $ T_DollarBraced id delve (T_HereDoc id d q str l) = dl l $ T_HereDoc id d q str delve (TC_And id typ str t1 t2) = d2 t1 t2 $ TC_And id typ str delve (TC_Or id typ str t1 t2) = d2 t1 t2 $ TC_Or id typ str delve (TC_Group id typ token) = d1 token $ TC_Group id typ delve (TC_Binary id typ op lhs rhs) = d2 lhs rhs $ TC_Binary id typ op delve (TC_Unary id typ op token) = d1 token $ TC_Unary id typ op delve (TC_Nullary id typ token) = d1 token $ TC_Nullary id typ delve (TA_Binary id op t1 t2) = d2 t1 t2 $ TA_Binary id op delve (TA_Assignment id op t1 t2) = d2 t1 t2 $ TA_Assignment id op delve (TA_Unary id op t1) = d1 t1 $ TA_Unary id op delve (TA_Sequence id l) = dl l $ TA_Sequence id delve (TA_Trinary id t1 t2 t3) = do a <- round t1 b <- round t2 c <- round t3 return $ TA_Trinary id a b c delve (TA_Expansion id t) = dl t $ TA_Expansion id delve (TA_Variable id str t) = dl t $ TA_Variable id str delve (T_Annotation id anns t) = d1 t $ T_Annotation id anns delve (T_CoProc id var body) = d1 body $ T_CoProc id var delve (T_CoProcBody id t) = d1 t $ T_CoProcBody id delve (T_Include id script) = d1 script $ T_Include id delve (T_SourceCommand id includer t_include) = d2 includer t_include $ T_SourceCommand id delve t = return t getId :: Token -> Id getId t = case t of T_AND_IF id -> id T_OR_IF id -> id T_DSEMI id -> id T_Semi id -> id T_DLESS id -> id T_DGREAT id -> id T_LESSAND id -> id T_GREATAND id -> id T_LESSGREAT id -> id T_DLESSDASH id -> id T_CLOBBER id -> id T_If id -> id T_Then id -> id T_Else id -> id T_Elif id -> id T_Fi id -> id T_Do id -> id T_Done id -> id T_Case id -> id T_Esac id -> id T_While id -> id T_Until id -> id T_For id -> id T_Select id -> id T_Lbrace id -> id T_Rbrace id -> id T_Lparen id -> id T_Rparen id -> id T_Bang id -> id T_In id -> id T_NEWLINE id -> id T_EOF id -> id T_Less id -> id T_Greater id -> id T_SingleQuoted id _ -> id T_Literal id _ -> id T_NormalWord id _ -> id T_DoubleQuoted id _ -> id T_DollarExpansion id _ -> id T_DollarBraced id _ -> id T_DollarArithmetic id _ -> id T_BraceExpansion id _ -> id T_ParamSubSpecialChar id _ -> id T_DollarBraceCommandExpansion id _ -> id T_IoFile id _ _ -> id T_IoDuplicate id _ _ -> id T_HereDoc id _ _ _ _ -> id T_HereString id _ -> id T_FdRedirect id _ _ -> id T_Assignment id _ _ _ _ -> id T_Array id _ -> id T_IndexedElement id _ _ -> id T_Redirecting id _ _ -> id T_SimpleCommand id _ _ -> id T_Pipeline id _ _ -> id T_Banged id _ -> id T_AndIf id _ _ -> id T_OrIf id _ _ -> id T_Backgrounded id _ -> id T_IfExpression id _ _ -> id T_Subshell id _ -> id T_BraceGroup id _ -> id T_WhileExpression id _ _ -> id T_UntilExpression id _ _ -> id T_ForIn id _ _ _ -> id T_SelectIn id _ _ _ -> id T_CaseExpression id _ _ -> id T_Function id _ _ _ _ -> id T_Arithmetic id _ -> id T_Script id _ _ -> id T_Condition id _ _ -> id T_Extglob id _ _ -> id T_Backticked id _ -> id TC_And id _ _ _ _ -> id TC_Or id _ _ _ _ -> id TC_Group id _ _ -> id TC_Binary id _ _ _ _ -> id TC_Unary id _ _ _ -> id TC_Nullary id _ _ -> id TA_Binary id _ _ _ -> id TA_Assignment id _ _ _ -> id TA_Unary id _ _ -> id TA_Sequence id _ -> id TA_Trinary id _ _ _ -> id TA_Expansion id _ -> id T_ProcSub id _ _ -> id T_Glob id _ -> id T_ForArithmetic id _ _ _ _ -> id T_DollarSingleQuoted id _ -> id T_DollarDoubleQuoted id _ -> id T_DollarBracket id _ -> id T_Annotation id _ _ -> id T_Pipe id _ -> id T_CoProc id _ _ -> id T_CoProcBody id _ -> id T_Include id _ -> id T_SourceCommand id _ _ -> id T_UnparsedIndex id _ _ -> id TC_Empty id _ -> id TA_Variable id _ _ -> id blank :: Monad m => Token -> m () blank = const $ return () doAnalysis :: Monad m => (Token -> m ()) -> Token -> m Token doAnalysis f = analyze f blank return doStackAnalysis :: Monad m => (Token -> m ()) -> (Token -> m ()) -> Token -> m Token doStackAnalysis startToken endToken = analyze startToken endToken return doTransform :: (Token -> Token) -> Token -> Token doTransform i = runIdentity . analyze blank blank (return . i)