{-
    Copyright 2012-2019 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 <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell  #-}
module ShellCheck.AnalyzerLib where

import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.Data
import ShellCheck.Interface
import ShellCheck.Parser
import ShellCheck.Regex

import Control.Arrow (first)
import Control.DeepSeq
import Control.Monad.Identity
import Control.Monad.RWS
import Control.Monad.State
import Control.Monad.Writer
import Data.Char
import Data.List
import Data.Maybe
import Data.Semigroup
import qualified Data.Map as Map

import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (maxSuccess, quickCheckWithResult, stdArgs)

type Analysis = AnalyzerM ()
type AnalyzerM a = RWS Parameters [TokenComment] Cache a
nullCheck :: b -> RWST Parameters [TokenComment] Cache Identity ()
nullCheck = RWST Parameters [TokenComment] Cache Identity ()
-> b -> RWST Parameters [TokenComment] Cache Identity ()
forall a b. a -> b -> a
const (RWST Parameters [TokenComment] Cache Identity ()
 -> b -> RWST Parameters [TokenComment] Cache Identity ())
-> RWST Parameters [TokenComment] Cache Identity ()
-> b
-> RWST Parameters [TokenComment] Cache Identity ()
forall a b. (a -> b) -> a -> b
$ () -> RWST Parameters [TokenComment] Cache Identity ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


data Checker = Checker {
    Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript :: Root -> Analysis,
    Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken  :: Token -> Analysis
}

runChecker :: Parameters -> Checker -> [TokenComment]
runChecker :: Parameters -> Checker -> [TokenComment]
runChecker params :: Parameters
params checker :: Checker
checker = [TokenComment]
notes
    where
        root :: Token
root = Parameters -> Token
rootNode Parameters
params
        check :: Root -> RWST Parameters [TokenComment] Cache Identity ()
check = Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript Checker
checker (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> Root
-> RWST Parameters [TokenComment] Cache Identity ()
forall a.
(a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
`composeAnalyzers` (\(Root x :: Token
x) -> RWST Parameters [TokenComment] Cache Identity Token
-> RWST Parameters [TokenComment] Cache Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (RWST Parameters [TokenComment] Cache Identity Token
 -> RWST Parameters [TokenComment] Cache Identity ())
-> RWST Parameters [TokenComment] Cache Identity Token
-> RWST Parameters [TokenComment] Cache Identity ()
forall a b. (a -> b) -> a -> b
$ (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Token -> RWST Parameters [TokenComment] Cache Identity Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken Checker
checker) Token
x)
        notes :: [TokenComment]
notes = ((), [TokenComment]) -> [TokenComment]
forall a b. (a, b) -> b
snd (((), [TokenComment]) -> [TokenComment])
-> ((), [TokenComment]) -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ RWST Parameters [TokenComment] Cache Identity ()
-> Parameters -> Cache -> ((), [TokenComment])
forall r w s a. RWS r w s a -> r -> s -> (a, w)
evalRWS (Root -> RWST Parameters [TokenComment] Cache Identity ()
check (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> Root -> RWST Parameters [TokenComment] Cache Identity ()
forall a b. (a -> b) -> a -> b
$ Token -> Root
Root Token
root) Parameters
params Cache
Cache

instance Semigroup Checker where
    <> :: Checker -> Checker -> Checker
(<>) x :: Checker
x y :: Checker
y = Checker :: (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Checker
Checker {
        perScript :: Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript = Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript Checker
x (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> Root
-> RWST Parameters [TokenComment] Cache Identity ()
forall a.
(a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
`composeAnalyzers` Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript Checker
y,
        perToken :: Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken = Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken Checker
x (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Token
-> RWST Parameters [TokenComment] Cache Identity ()
forall a.
(a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
`composeAnalyzers` Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken Checker
y
        }

instance Monoid Checker where
    mempty :: Checker
mempty = Checker :: (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Checker
Checker {
        perScript :: Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript = Root -> RWST Parameters [TokenComment] Cache Identity ()
forall b. b -> RWST Parameters [TokenComment] Cache Identity ()
nullCheck,
        perToken :: Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken = Token -> RWST Parameters [TokenComment] Cache Identity ()
forall b. b -> RWST Parameters [TokenComment] Cache Identity ()
nullCheck
        }
    mappend :: Checker -> Checker -> Checker
mappend = Checker -> Checker -> Checker
forall a. Semigroup a => a -> a -> a
(Data.Semigroup.<>)

composeAnalyzers :: (a -> Analysis) -> (a -> Analysis) -> a -> Analysis
composeAnalyzers :: (a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
composeAnalyzers f :: a -> RWST Parameters [TokenComment] Cache Identity ()
f g :: a -> RWST Parameters [TokenComment] Cache Identity ()
g x :: a
x = a -> RWST Parameters [TokenComment] Cache Identity ()
f a
x RWST Parameters [TokenComment] Cache Identity ()
-> RWST Parameters [TokenComment] Cache Identity ()
-> RWST Parameters [TokenComment] Cache Identity ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> a -> RWST Parameters [TokenComment] Cache Identity ()
g a
x

data Parameters = Parameters {
    -- Whether this script has the 'lastpipe' option set/default.
    Parameters -> Bool
hasLastpipe        :: Bool,
    -- Whether this script has 'set -e' anywhere.
    Parameters -> Bool
hasSetE            :: Bool,
    -- A linear (bad) analysis of data flow
    Parameters -> [StackData]
variableFlow       :: [StackData],
    -- A map from Id to parent Token
    Parameters -> Map Id Token
parentMap          :: Map.Map Id Token,
    -- The shell type, such as Bash or Ksh
    Parameters -> Shell
shellType          :: Shell,
    -- True if shell type was forced via flags
    Parameters -> Bool
shellTypeSpecified :: Bool,
    -- The root node of the AST
    Parameters -> Token
rootNode           :: Token,
    -- map from token id to start and end position
    Parameters -> Map Id (Position, Position)
tokenPositions     :: Map.Map Id (Position, Position)
    } deriving (Int -> Parameters -> ShowS
[Parameters] -> ShowS
Parameters -> String
(Int -> Parameters -> ShowS)
-> (Parameters -> String)
-> ([Parameters] -> ShowS)
-> Show Parameters
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Parameters] -> ShowS
$cshowList :: [Parameters] -> ShowS
show :: Parameters -> String
$cshow :: Parameters -> String
showsPrec :: Int -> Parameters -> ShowS
$cshowsPrec :: Int -> Parameters -> ShowS
Show)

-- TODO: Cache results of common AST ops here
data Cache = Cache {}

data Scope = SubshellScope String | NoneScope deriving (Int -> Scope -> ShowS
[Scope] -> ShowS
Scope -> String
(Int -> Scope -> ShowS)
-> (Scope -> String) -> ([Scope] -> ShowS) -> Show Scope
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Scope] -> ShowS
$cshowList :: [Scope] -> ShowS
show :: Scope -> String
$cshow :: Scope -> String
showsPrec :: Int -> Scope -> ShowS
$cshowsPrec :: Int -> Scope -> ShowS
Show, Scope -> Scope -> Bool
(Scope -> Scope -> Bool) -> (Scope -> Scope -> Bool) -> Eq Scope
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Scope -> Scope -> Bool
$c/= :: Scope -> Scope -> Bool
== :: Scope -> Scope -> Bool
$c== :: Scope -> Scope -> Bool
Eq)
data StackData =
    StackScope Scope
    | StackScopeEnd
    -- (Base expression, specific position, var name, assigned values)
    | Assignment (Token, Token, String, DataType)
    | Reference (Token, Token, String)
  deriving (Int -> StackData -> ShowS
[StackData] -> ShowS
StackData -> String
(Int -> StackData -> ShowS)
-> (StackData -> String)
-> ([StackData] -> ShowS)
-> Show StackData
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [StackData] -> ShowS
$cshowList :: [StackData] -> ShowS
show :: StackData -> String
$cshow :: StackData -> String
showsPrec :: Int -> StackData -> ShowS
$cshowsPrec :: Int -> StackData -> ShowS
Show)

data DataType = DataString DataSource | DataArray DataSource
  deriving (Int -> DataType -> ShowS
[DataType] -> ShowS
DataType -> String
(Int -> DataType -> ShowS)
-> (DataType -> String) -> ([DataType] -> ShowS) -> Show DataType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DataType] -> ShowS
$cshowList :: [DataType] -> ShowS
show :: DataType -> String
$cshow :: DataType -> String
showsPrec :: Int -> DataType -> ShowS
$cshowsPrec :: Int -> DataType -> ShowS
Show)

data DataSource =
    SourceFrom [Token]
    | SourceExternal
    | SourceDeclaration
    | SourceInteger
    | SourceChecked
  deriving (Int -> DataSource -> ShowS
[DataSource] -> ShowS
DataSource -> String
(Int -> DataSource -> ShowS)
-> (DataSource -> String)
-> ([DataSource] -> ShowS)
-> Show DataSource
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DataSource] -> ShowS
$cshowList :: [DataSource] -> ShowS
show :: DataSource -> String
$cshow :: DataSource -> String
showsPrec :: Int -> DataSource -> ShowS
$cshowsPrec :: Int -> DataSource -> ShowS
Show)

data VariableState = Dead Token String | Alive deriving (Int -> VariableState -> ShowS
[VariableState] -> ShowS
VariableState -> String
(Int -> VariableState -> ShowS)
-> (VariableState -> String)
-> ([VariableState] -> ShowS)
-> Show VariableState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [VariableState] -> ShowS
$cshowList :: [VariableState] -> ShowS
show :: VariableState -> String
$cshow :: VariableState -> String
showsPrec :: Int -> VariableState -> ShowS
$cshowsPrec :: Int -> VariableState -> ShowS
Show)

defaultSpec :: ParseResult -> AnalysisSpec
defaultSpec pr :: ParseResult
pr = AnalysisSpec
spec {
    asShellType :: Maybe Shell
asShellType = Maybe Shell
forall a. Maybe a
Nothing,
    asCheckSourced :: Bool
asCheckSourced = Bool
False,
    asExecutionMode :: ExecutionMode
asExecutionMode = ExecutionMode
Executed,
    asTokenPositions :: Map Id (Position, Position)
asTokenPositions = ParseResult -> Map Id (Position, Position)
prTokenPositions ParseResult
pr
} where spec :: AnalysisSpec
spec = Token -> AnalysisSpec
newAnalysisSpec (Maybe Token -> Token
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe Token -> Token) -> Maybe Token -> Token
forall a b. (a -> b) -> a -> b
$ ParseResult -> Maybe Token
prRoot ParseResult
pr)

pScript :: String -> ParseResult
pScript s :: String
s =
  let
    pSpec :: ParseSpec
pSpec = ParseSpec
newParseSpec {
        psFilename :: String
psFilename = "script",
        psScript :: String
psScript = String
s
    }
  in Identity ParseResult -> ParseResult
forall a. Identity a -> a
runIdentity (Identity ParseResult -> ParseResult)
-> Identity ParseResult -> ParseResult
forall a b. (a -> b) -> a -> b
$ SystemInterface Identity -> ParseSpec -> Identity ParseResult
forall (m :: * -> *).
Monad m =>
SystemInterface m -> ParseSpec -> m ParseResult
parseScript ([(String, String)] -> SystemInterface Identity
mockedSystemInterface []) ParseSpec
pSpec

-- For testing. If parsed, returns whether there are any comments
producesComments :: Checker -> String -> Maybe Bool
producesComments :: Checker -> String -> Maybe Bool
producesComments c :: Checker
c s :: String
s = do
        let pr :: ParseResult
pr = String -> ParseResult
pScript String
s
        ParseResult -> Maybe Token
prRoot ParseResult
pr
        let spec :: AnalysisSpec
spec = ParseResult -> AnalysisSpec
defaultSpec ParseResult
pr
        let params :: Parameters
params = AnalysisSpec -> Parameters
makeParameters AnalysisSpec
spec
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool)
-> ([TokenComment] -> Bool) -> [TokenComment] -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool)
-> ([TokenComment] -> Bool) -> [TokenComment] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TokenComment] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([TokenComment] -> Maybe Bool) -> [TokenComment] -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Parameters -> Checker -> [TokenComment]
runChecker Parameters
params Checker
c

makeComment :: Severity -> Id -> Code -> String -> TokenComment
makeComment :: Severity -> Id -> Code -> String -> TokenComment
makeComment severity :: Severity
severity id :: Id
id code :: Code
code note :: String
note =
    TokenComment
newTokenComment {
        tcId :: Id
tcId = Id
id,
        tcComment :: Comment
tcComment = Comment
newComment {
            cSeverity :: Severity
cSeverity = Severity
severity,
            cCode :: Code
cCode = Code
code,
            cMessage :: String
cMessage = String
note
        }
    }

addComment :: a -> m ()
addComment note :: a
note = a
note a -> m () -> m ()
forall a b. NFData a => a -> b -> b
`deepseq` [a] -> m ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [a
note]

warn :: MonadWriter [TokenComment] m => Id -> Code -> String -> m ()
warn :: Id -> Code -> String -> m ()
warn  id :: Id
id code :: Code
code str :: String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC Id
id Code
code String
str
err :: Id -> Code -> String -> m ()
err   id :: Id
id code :: Code
code str :: String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
ErrorC Id
id Code
code String
str
info :: Id -> Code -> String -> m ()
info  id :: Id
id code :: Code
code str :: String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
InfoC Id
id Code
code String
str
style :: Id -> Code -> String -> m ()
style id :: Id
id code :: Code
code str :: String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
StyleC Id
id Code
code String
str

warnWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m ()
warnWithFix :: Id -> Code -> String -> Fix -> m ()
warnWithFix  = Severity -> Id -> Code -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix Severity
WarningC
styleWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m ()
styleWithFix :: Id -> Code -> String -> Fix -> m ()
styleWithFix = Severity -> Id -> Code -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix Severity
StyleC

addCommentWithFix :: MonadWriter [TokenComment] m => Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix severity :: Severity
severity id :: Id
id code :: Code
code str :: String
str fix :: Fix
fix =
    TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix Severity
severity Id
id Code
code String
str Fix
fix

makeCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix severity :: Severity
severity id :: Id
id code :: Code
code str :: String
str fix :: Fix
fix =
    let comment :: TokenComment
comment = Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
severity Id
id Code
code String
str
        withFix :: TokenComment
withFix = TokenComment
comment {
            tcFix :: Maybe Fix
tcFix = Fix -> Maybe Fix
forall a. a -> Maybe a
Just Fix
fix
        }
    in TokenComment
withFix TokenComment -> TokenComment -> TokenComment
forall a b. NFData a => a -> b -> b
`deepseq` TokenComment
withFix

makeParameters :: AnalysisSpec -> Parameters
makeParameters spec :: AnalysisSpec
spec =
    let params :: Parameters
params = Parameters :: Bool
-> Bool
-> [StackData]
-> Map Id Token
-> Shell
-> Bool
-> Token
-> Map Id (Position, Position)
-> Parameters
Parameters {
        rootNode :: Token
rootNode = Token
root,
        shellType :: Shell
shellType = Shell -> Maybe Shell -> Shell
forall a. a -> Maybe a -> a
fromMaybe (Maybe Shell -> Token -> Shell
determineShell (AnalysisSpec -> Maybe Shell
asFallbackShell AnalysisSpec
spec) Token
root) (Maybe Shell -> Shell) -> Maybe Shell -> Shell
forall a b. (a -> b) -> a -> b
$ AnalysisSpec -> Maybe Shell
asShellType AnalysisSpec
spec,
        hasSetE :: Bool
hasSetE = Token -> Bool
containsSetE Token
root,
        hasLastpipe :: Bool
hasLastpipe =
            case Parameters -> Shell
shellType Parameters
params of
                Bash -> Token -> Bool
containsLastpipe Token
root
                Dash -> Bool
False
                Sh   -> Bool
False
                Ksh  -> Bool
True,

        shellTypeSpecified :: Bool
shellTypeSpecified = Maybe Shell -> Bool
forall a. Maybe a -> Bool
isJust (AnalysisSpec -> Maybe Shell
asShellType AnalysisSpec
spec) Bool -> Bool -> Bool
|| Maybe Shell -> Bool
forall a. Maybe a -> Bool
isJust (AnalysisSpec -> Maybe Shell
asFallbackShell AnalysisSpec
spec),
        parentMap :: Map Id Token
parentMap = Token -> Map Id Token
getParentTree Token
root,
        variableFlow :: [StackData]
variableFlow = Parameters -> Token -> [StackData]
getVariableFlow Parameters
params Token
root,
        tokenPositions :: Map Id (Position, Position)
tokenPositions = AnalysisSpec -> Map Id (Position, Position)
asTokenPositions AnalysisSpec
spec
    } in Parameters
params
  where root :: Token
root = AnalysisSpec -> Token
asScript AnalysisSpec
spec


-- Does this script mention 'set -e' anywhere?
-- Used as a hack to disable certain warnings.
containsSetE :: Token -> Bool
containsSetE root :: Token
root = Maybe Token -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe Token -> Bool) -> Maybe Token -> Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe ()) -> Token -> Maybe Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Token -> Bool) -> Token -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isSetE) Token
root
  where
    isSetE :: Token -> Bool
isSetE t :: Token
t =
        case Token
t of
            T_Script _ (T_Literal _ str :: String
str) _ -> String
str String -> Regex -> Bool
`matches` Regex
re
            T_SimpleCommand {}  ->
                Token
t Token -> String -> Bool
`isUnqualifiedCommand` "set" Bool -> Bool -> Bool
&&
                    ("errexit" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Token -> [String]
oversimplify Token
t Bool -> Bool -> Bool
||
                        "e" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t))
            _ -> Bool
False
    re :: Regex
re = String -> Regex
mkRegex "[[:space:]]-[^-]*e"

-- Does this script mention 'shopt -s lastpipe' anywhere?
-- Also used as a hack.
containsLastpipe :: Token -> Bool
containsLastpipe root :: Token
root =
        Maybe Token -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe Token -> Bool) -> Maybe Token -> Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe ()) -> Token -> Maybe Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Token -> Bool) -> Token -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isShoptLastPipe) Token
root
    where
        isShoptLastPipe :: Token -> Bool
isShoptLastPipe t :: Token
t =
            case Token
t of
                T_SimpleCommand {}  ->
                    Token
t Token -> String -> Bool
`isUnqualifiedCommand` "shopt" Bool -> Bool -> Bool
&&
                        ("lastpipe" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Token -> [String]
oversimplify Token
t)
                _ -> Bool
False


prop_determineShell0 :: Bool
prop_determineShell0 = String -> Shell
determineShellTest "#!/bin/sh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell1 :: Bool
prop_determineShell1 = String -> Shell
determineShellTest "#!/usr/bin/env ksh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Ksh
prop_determineShell2 :: Bool
prop_determineShell2 = String -> Shell
determineShellTest "" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Bash
prop_determineShell3 :: Bool
prop_determineShell3 = String -> Shell
determineShellTest "#!/bin/sh -e" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell4 :: Bool
prop_determineShell4 = String -> Shell
determineShellTest "#!/bin/ksh\n#shellcheck shell=sh\nfoo" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell5 :: Bool
prop_determineShell5 = String -> Shell
determineShellTest "#shellcheck shell=sh\nfoo" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell6 :: Bool
prop_determineShell6 = String -> Shell
determineShellTest "#! /bin/sh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell7 :: Bool
prop_determineShell7 = String -> Shell
determineShellTest "#! /bin/ash" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Dash
prop_determineShell8 :: Bool
prop_determineShell8 = Maybe Shell -> String -> Shell
determineShellTest' (Shell -> Maybe Shell
forall a. a -> Maybe a
Just Shell
Ksh) "#!/bin/sh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh

determineShellTest :: String -> Shell
determineShellTest = Maybe Shell -> String -> Shell
determineShellTest' Maybe Shell
forall a. Maybe a
Nothing
determineShellTest' :: Maybe Shell -> String -> Shell
determineShellTest' fallbackShell :: Maybe Shell
fallbackShell = Maybe Shell -> Token -> Shell
determineShell Maybe Shell
fallbackShell (Token -> Shell) -> (String -> Token) -> String -> Shell
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Token -> Token
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe Token -> Token)
-> (String -> Maybe Token) -> String -> Token
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ParseResult -> Maybe Token
prRoot (ParseResult -> Maybe Token)
-> (String -> ParseResult) -> String -> Maybe Token
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ParseResult
pScript
determineShell :: Maybe Shell -> Token -> Shell
determineShell fallbackShell :: Maybe Shell
fallbackShell t :: Token
t = Shell -> Maybe Shell -> Shell
forall a. a -> Maybe a -> a
fromMaybe Shell
Bash (Maybe Shell -> Shell) -> Maybe Shell -> Shell
forall a b. (a -> b) -> a -> b
$
    String -> Maybe Shell
shellForExecutable String
shellString Maybe Shell -> Maybe Shell -> Maybe Shell
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` Maybe Shell
fallbackShell
  where
    shellString :: String
shellString = Token -> String
getCandidate Token
t
    getCandidate :: Token -> String
    getCandidate :: Token -> String
getCandidate t :: Token
t@T_Script {} = Token -> String
fromShebang Token
t
    getCandidate (T_Annotation _ annotations :: [Annotation]
annotations s :: Token
s) =
        String -> [String] -> String
forall p. p -> [p] -> p
headOrDefault (Token -> String
fromShebang Token
s) [String
s | ShellOverride s :: String
s <- [Annotation]
annotations]
    fromShebang :: Token -> String
fromShebang (T_Script _ (T_Literal _ s :: String
s) _) = ShowS
executableFromShebang String
s

-- Given a string like "/bin/bash" or "/usr/bin/env dash",
-- return the shell basename like "bash" or "dash"
executableFromShebang :: String -> String
executableFromShebang :: ShowS
executableFromShebang = ShowS
shellFor
  where
    shellFor :: ShowS
shellFor s :: String
s | "/env " String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s = String -> [String] -> String
forall p. p -> [p] -> p
headOrDefault "" (Int -> [String] -> [String]
forall a. Int -> [a] -> [a]
drop 1 ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$ String -> [String]
words String
s)
    shellFor s :: String
s | ' ' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
s = ShowS
shellFor ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= ' ') String
s
    shellFor s :: String
s = ShowS
forall a. [a] -> [a]
reverse ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '/') ShowS -> ShowS -> ShowS
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ShowS
forall a. [a] -> [a]
reverse ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ String
s



-- Given a root node, make a map from Id to parent Token.
-- This is used to populate parentMap in Parameters
getParentTree :: Token -> Map.Map Id Token
getParentTree :: Token -> Map Id Token
getParentTree t :: Token
t =
    ([Token], Map Id Token) -> Map Id Token
forall a b. (a, b) -> b
snd (([Token], Map Id Token) -> Map Id Token)
-> ((Token, ([Token], Map Id Token)) -> ([Token], Map Id Token))
-> (Token, ([Token], Map Id Token))
-> Map Id Token
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, ([Token], Map Id Token)) -> ([Token], Map Id Token)
forall a b. (a, b) -> b
snd ((Token, ([Token], Map Id Token)) -> Map Id Token)
-> (Token, ([Token], Map Id Token)) -> Map Id Token
forall a b. (a -> b) -> a -> b
$ State ([Token], Map Id Token) Token
-> ([Token], Map Id Token) -> (Token, ([Token], Map Id Token))
forall s a. State s a -> s -> (a, s)
runState ((Token -> StateT ([Token], Map Id Token) Identity ())
-> (Token -> StateT ([Token], Map Id Token) Identity ())
-> Token
-> State ([Token], Map Id Token) Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> (Token -> m ()) -> Token -> m Token
doStackAnalysis Token -> StateT ([Token], Map Id Token) Identity ()
forall a d (m :: * -> *). MonadState ([a], d) m => a -> m ()
pre Token -> StateT ([Token], Map Id Token) Identity ()
forall (m :: * -> *) a.
MonadState ([a], Map Id a) m =>
Token -> m ()
post Token
t) ([], Map Id Token
forall k a. Map k a
Map.empty)
  where
    pre :: a -> m ()
pre t :: a
t = (([a], d) -> ([a], d)) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (([a] -> [a]) -> ([a], d) -> ([a], d)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first ((:) a
t))
    post :: Token -> m ()
post t :: Token
t = do
        (x :: [a]
x, map :: Map Id a
map) <- m ([a], Map Id a)
forall s (m :: * -> *). MonadState s m => m s
get
        case [a]
x of
          _:rest :: [a]
rest -> case [a]
rest of []    -> ([a], Map Id a) -> m ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put ([a]
rest, Map Id a
map)
                                 (x :: a
x:_) -> ([a], Map Id a) -> m ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put ([a]
rest, Id -> a -> Map Id a -> Map Id a
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (Token -> Id
getId Token
t) a
x Map Id a
map)

-- Given a root node, make a map from Id to Token
getTokenMap :: Token -> Map.Map Id Token
getTokenMap :: Token -> Map Id Token
getTokenMap t :: Token
t =
    State (Map Id Token) Token -> Map Id Token -> Map Id Token
forall s a. State s a -> s -> s
execState ((Token -> StateT (Map Id Token) Identity ())
-> Token -> State (Map Id Token) Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> StateT (Map Id Token) Identity ()
forall (m :: * -> *). MonadState (Map Id Token) m => Token -> m ()
f Token
t) Map Id Token
forall k a. Map k a
Map.empty
  where
    f :: Token -> m ()
f t :: Token
t = (Map Id Token -> Map Id Token) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (Id -> Token -> Map Id Token -> Map Id Token
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (Token -> Id
getId Token
t) Token
t)


-- Is this token in a quoting free context? (i.e. would variable expansion split)
-- True:  Assignments, [[ .. ]], here docs, already in double quotes
-- False: Regular words
isStrictlyQuoteFree :: Map Id Token -> Token -> Bool
isStrictlyQuoteFree = Bool -> Map Id Token -> Token -> Bool
isQuoteFreeNode Bool
True

-- Like above, but also allow some cases where splitting may be desired.
-- True:  Like above + for loops
-- False: Like above
isQuoteFree :: Map Id Token -> Token -> Bool
isQuoteFree = Bool -> Map Id Token -> Token -> Bool
isQuoteFreeNode Bool
False


isQuoteFreeNode :: Bool -> Map Id Token -> Token -> Bool
isQuoteFreeNode strict :: Bool
strict tree :: Map Id Token
tree t :: Token
t =
    (Token -> Maybe Bool
isQuoteFreeElement Token
t Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True) Bool -> Bool -> Bool
||
        Bool -> [Bool] -> Bool
forall p. p -> [p] -> p
headOrDefault Bool
False ((Token -> Maybe Bool) -> [Token] -> [Bool]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Bool
isQuoteFreeContext (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop 1 ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
t))
  where
    -- Is this node self-quoting in itself?
    isQuoteFreeElement :: Token -> Maybe Bool
isQuoteFreeElement t :: Token
t =
        case Token
t of
            T_Assignment {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_FdRedirect {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            _               -> Maybe Bool
forall a. Maybe a
Nothing

    -- Are any subnodes inherently self-quoting?
    isQuoteFreeContext :: Token -> Maybe Bool
isQuoteFreeContext t :: Token
t =
        case Token
t of
            TC_Nullary _ DoubleBracket _    -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            TC_Unary _ DoubleBracket _ _    -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            TC_Binary _ DoubleBracket _ _ _ -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            TA_Sequence {}                  -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Arithmetic {}                 -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Assignment {}                 -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Redirecting {}                -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            T_DoubleQuoted _ _              -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_DollarDoubleQuoted _ _        -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_CaseExpression {}             -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_HereDoc {}                    -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_DollarBraced {}               -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            -- When non-strict, pragmatically assume it's desirable to split here
            T_ForIn {}                      -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Bool
not Bool
strict)
            T_SelectIn {}                   -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Bool
not Bool
strict)
            _                               -> Maybe Bool
forall a. Maybe a
Nothing

-- Check if a token is a parameter to a certain command by name:
-- Example: isParamTo (parentMap params) "sed" t
isParamTo :: Map.Map Id Token -> String -> Token -> Bool
isParamTo :: Map Id Token -> String -> Token -> Bool
isParamTo tree :: Map Id Token
tree cmd :: String
cmd =
    Token -> Bool
go
  where
    go :: Token -> Bool
go x :: Token
x = case Id -> Map Id Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
x) Map Id Token
tree of
                Nothing     -> Bool
False
                Just parent :: Token
parent -> Token -> Bool
check Token
parent
    check :: Token -> Bool
check t :: Token
t =
        case Token
t of
            T_SingleQuoted _ _ -> Token -> Bool
go Token
t
            T_DoubleQuoted _ _ -> Token -> Bool
go Token
t
            T_NormalWord _ _   -> Token -> Bool
go Token
t
            T_SimpleCommand {} -> Token -> String -> Bool
isCommand Token
t String
cmd
            T_Redirecting {}   -> Token -> String -> Bool
isCommand Token
t String
cmd
            _                  -> Bool
False

-- Get the parent command (T_Redirecting) of a Token, if any.
getClosestCommand :: Map.Map Id Token -> Token -> Maybe Token
getClosestCommand :: Map Id Token -> Token -> Maybe Token
getClosestCommand tree :: Map Id Token
tree t :: Token
t =
    (Token -> Maybe Bool) -> [Token] -> Maybe Token
forall a. (a -> Maybe Bool) -> [a] -> Maybe a
findFirst Token -> Maybe Bool
findCommand ([Token] -> Maybe Token) -> [Token] -> Maybe Token
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
t
  where
    findCommand :: Token -> Maybe Bool
findCommand t :: Token
t =
        case Token
t of
            T_Redirecting {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Script {}      -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            _                -> Maybe Bool
forall a. Maybe a
Nothing

-- Like above, if koala_man knew Haskell when starting this project.
getClosestCommandM :: Token -> m (Maybe Token)
getClosestCommandM t :: Token
t = do
    Map Id Token
tree <- (Parameters -> Map Id Token) -> m (Map Id Token)
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Map Id Token
parentMap
    Maybe Token -> m (Maybe Token)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe Token -> m (Maybe Token)) -> Maybe Token -> m (Maybe Token)
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> Maybe Token
getClosestCommand Map Id Token
tree Token
t

-- Is the token used as a command name (the first word in a T_SimpleCommand)?
usedAsCommandName :: Map Id Token -> Token -> Bool
usedAsCommandName tree :: Map Id Token
tree token :: Token
token = Id -> [Token] -> Bool
go (Token -> Id
getId Token
token) ([Token] -> [Token]
forall a. [a] -> [a]
tail ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
token)
  where
    go :: Id -> [Token] -> Bool
go currentId :: Id
currentId (T_NormalWord id :: Id
id [word :: Token
word]:rest :: [Token]
rest)
        | Id
currentId Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
word = Id -> [Token] -> Bool
go Id
id [Token]
rest
    go currentId :: Id
currentId (T_DoubleQuoted id :: Id
id [word :: Token
word]:rest :: [Token]
rest)
        | Id
currentId Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
word = Id -> [Token] -> Bool
go Id
id [Token]
rest
    go currentId :: Id
currentId (T_SimpleCommand _ _ (word :: Token
word:_):_)
        | Id
currentId Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
word = Bool
True
    go _ _ = Bool
False

-- A list of the element and all its parents up to the root node.
getPath :: Map Id Token -> Token -> [Token]
getPath tree :: Map Id Token
tree t :: Token
t = Token
t Token -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:
    case Id -> Map Id Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
t) Map Id Token
tree of
        Nothing     -> []
        Just parent :: Token
parent -> Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
parent

-- Version of the above taking the map from the current context
-- Todo: give this the name "getPath"
getPathM :: Token -> m [Token]
getPathM t :: Token
t = do
    Map Id Token
map <- (Parameters -> Map Id Token) -> m (Map Id Token)
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Map Id Token
parentMap
    [Token] -> m [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return ([Token] -> m [Token]) -> [Token] -> m [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
map Token
t

isParentOf :: Map Id Token -> Token -> Token -> Bool
isParentOf tree :: Map Id Token
tree parent :: Token
parent child :: Token
child =
    Id -> [Id] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem (Token -> Id
getId Token
parent) ([Id] -> Bool) -> ([Token] -> [Id]) -> [Token] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
child

parents :: Parameters -> Token -> [Token]
parents params :: Parameters
params = Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params)

-- Find the first match in a list where the predicate is Just True.
-- Stops if it's Just False and ignores Nothing.
findFirst :: (a -> Maybe Bool) -> [a] -> Maybe a
findFirst :: (a -> Maybe Bool) -> [a] -> Maybe a
findFirst p :: a -> Maybe Bool
p l :: [a]
l =
    case [a]
l of
        [] -> Maybe a
forall a. Maybe a
Nothing
        (x :: a
x:xs :: [a]
xs) ->
            case a -> Maybe Bool
p a
x of
                Just True  -> a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return a
x
                Just False -> Maybe a
forall a. Maybe a
Nothing
                Nothing    -> (a -> Maybe Bool) -> [a] -> Maybe a
forall a. (a -> Maybe Bool) -> [a] -> Maybe a
findFirst a -> Maybe Bool
p [a]
xs

-- Check whether a word is entirely output from a single command
tokenIsJustCommandOutput :: Token -> Bool
tokenIsJustCommandOutput t :: Token
t = case Token
t of
    T_NormalWord id :: Id
id [T_DollarExpansion _ cmds :: [Token]
cmds] -> [Token] -> Bool
check [Token]
cmds
    T_NormalWord id :: Id
id [T_DoubleQuoted _ [T_DollarExpansion _ cmds :: [Token]
cmds]] -> [Token] -> Bool
check [Token]
cmds
    T_NormalWord id :: Id
id [T_Backticked _ cmds :: [Token]
cmds] -> [Token] -> Bool
check [Token]
cmds
    T_NormalWord id :: Id
id [T_DoubleQuoted _ [T_Backticked _ cmds :: [Token]
cmds]] -> [Token] -> Bool
check [Token]
cmds
    _ -> Bool
False
  where
    check :: [Token] -> Bool
check [x :: Token
x] = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isOnlyRedirection Token
x
    check _   = Bool
False

-- TODO: Replace this with a proper Control Flow Graph
getVariableFlow :: Parameters -> Token -> [StackData]
getVariableFlow params :: Parameters
params t :: Token
t =
    let (_, stack :: [StackData]
stack) = State [StackData] Token -> [StackData] -> (Token, [StackData])
forall s a. State s a -> s -> (a, s)
runState ((Token -> StateT [StackData] Identity ())
-> (Token -> StateT [StackData] Identity ())
-> Token
-> State [StackData] Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> (Token -> m ()) -> Token -> m Token
doStackAnalysis Token -> StateT [StackData] Identity ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
startScope Token -> StateT [StackData] Identity ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
endScope Token
t) []
    in [StackData] -> [StackData]
forall a. [a] -> [a]
reverse [StackData]
stack
  where
    startScope :: Token -> m ()
startScope t :: Token
t =
        let scopeType :: Scope
scopeType = Parameters -> Token -> Scope
leadType Parameters
params Token
t
        in do
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Scope
scopeType Scope -> Scope -> Bool
forall a. Eq a => a -> a -> Bool
/= Scope
NoneScope) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (Scope -> StackData
StackScope Scope
scopeTypeStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
assignFirst Token
t) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> m ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
setWritten Token
t

    endScope :: Token -> m ()
endScope t :: Token
t =
        let scopeType :: Scope
scopeType = Parameters -> Token -> Scope
leadType Parameters
params Token
t
        in do
            Token -> m ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
setRead Token
t
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> Bool
assignFirst Token
t) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> m ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
setWritten Token
t
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Scope
scopeType Scope -> Scope -> Bool
forall a. Eq a => a -> a -> Bool
/= Scope
NoneScope) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (StackData
StackScopeEndStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)

    assignFirst :: Token -> Bool
assignFirst T_ForIn {}    = Bool
True
    assignFirst T_SelectIn {} = Bool
True
    assignFirst (T_BatsTest {}) = Bool
True
    assignFirst _             = Bool
False

    setRead :: Token -> m ()
setRead t :: Token
t =
        let read :: [(Token, Token, String)]
read    = Map Id Token -> Token -> [(Token, Token, String)]
getReferencedVariables (Parameters -> Map Id Token
parentMap Parameters
params) Token
t
        in ((Token, Token, String) -> m ())
-> [(Token, Token, String)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\v :: (Token, Token, String)
v -> ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Token, Token, String) -> StackData
Reference (Token, Token, String)
vStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)) [(Token, Token, String)]
read

    setWritten :: Token -> m ()
setWritten t :: Token
t =
        let written :: [(Token, Token, String, DataType)]
written = Token -> [(Token, Token, String, DataType)]
getModifiedVariables Token
t
        in ((Token, Token, String, DataType) -> m ())
-> [(Token, Token, String, DataType)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\v :: (Token, Token, String, DataType)
v -> ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Token, Token, String, DataType) -> StackData
Assignment (Token, Token, String, DataType)
vStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)) [(Token, Token, String, DataType)]
written


leadType :: Parameters -> Token -> Scope
leadType params :: Parameters
params t :: Token
t =
    case Token
t of
        T_DollarExpansion _ _  -> String -> Scope
SubshellScope "$(..) expansion"
        T_Backticked _ _  -> String -> Scope
SubshellScope "`..` expansion"
        T_Backgrounded _ _  -> String -> Scope
SubshellScope "backgrounding &"
        T_Subshell _ _  -> String -> Scope
SubshellScope "(..) group"
        T_BatsTest {} -> String -> Scope
SubshellScope "@bats test"
        T_CoProcBody _ _  -> String -> Scope
SubshellScope "coproc"
        T_Redirecting {}  ->
            if Maybe Bool
causesSubshell Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
            then String -> Scope
SubshellScope "pipeline"
            else Scope
NoneScope
        _ -> Scope
NoneScope
  where
    parentPipeline :: Maybe Token
parentPipeline = do
        Token
parent <- Id -> Map Id Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
t) (Parameters -> Map Id Token
parentMap Parameters
params)
        case Token
parent of
            T_Pipeline {} -> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
parent
            _             -> Maybe Token
forall a. Maybe a
Nothing

    causesSubshell :: Maybe Bool
causesSubshell = do
        (T_Pipeline _ _ list :: [Token]
list) <- Maybe Token
parentPipeline
        if [Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
list Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 1
            then Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            else if Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Parameters -> Bool
hasLastpipe Parameters
params
                then Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
                else Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> (Bool -> Bool) -> Bool -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Id
getId (Token -> Id) -> ([Token] -> Token) -> [Token] -> Id
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> Token
forall a. [a] -> a
head ([Token] -> Id) -> [Token] -> Id
forall a b. (a -> b) -> a -> b
$ [Token] -> [Token]
forall a. [a] -> [a]
reverse [Token]
list) Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
t

getModifiedVariables :: Token -> [(Token, Token, String, DataType)]
getModifiedVariables t :: Token
t =
    case Token
t of
        T_SimpleCommand _ vars :: [Token]
vars [] ->
            (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\x :: Token
x -> case Token
x of
                                T_Assignment id :: Id
id _ name :: String
name _ w :: Token
w  ->
                                    [(Token
x, Token
x, String
name, (DataSource -> DataType) -> Token -> DataType
dataTypeFrom DataSource -> DataType
DataString Token
w)]
                                _ -> []
                      ) [Token]
vars
        c :: Token
c@T_SimpleCommand {} ->
            Token -> [(Token, Token, String, DataType)]
getModifiedVariableCommand Token
c

        TA_Unary _ "++|" v :: Token
v@(TA_Variable _ name :: String
name _)  ->
            [(Token
t, Token
v, String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
v])]
        TA_Unary _ "|++" v :: Token
v@(TA_Variable _ name :: String
name _)  ->
            [(Token
t, Token
v, String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
v])]
        TA_Assignment _ op :: String
op (TA_Variable _ name :: String
name _) rhs :: Token
rhs -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|="]
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
t, Token
t, String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
rhs])

        T_BatsTest {} -> [
            (Token
t, Token
t, "lines", DataSource -> DataType
DataArray DataSource
SourceExternal),
            (Token
t, Token
t, "status", DataSource -> DataType
DataString DataSource
SourceInteger),
            (Token
t, Token
t, "output", DataSource -> DataType
DataString DataSource
SourceExternal)
            ]

        -- Count [[ -v foo ]] as an "assignment".
        -- This is to prevent [ -v foo ] being unassigned or unused.
        TC_Unary id :: Id
id _ "-v" token :: Token
token -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
            String
str <- ShowS -> Maybe String -> Maybe String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '[')) (Maybe String -> Maybe String) -> Maybe String -> Maybe String
forall a b. (a -> b) -> a -> b
$ -- Quoted index
                    ((Token -> Maybe String) -> Token -> Maybe String)
-> Token -> (Token -> Maybe String) -> Maybe String
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Token -> Maybe String) -> Token -> Maybe String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt Token
token ((Token -> Maybe String) -> Maybe String)
-> (Token -> Maybe String) -> Maybe String
forall a b. (a -> b) -> a -> b
$ \x :: Token
x ->
                case Token
x of
                    T_Glob _ s :: String
s -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s -- Unquoted index
                    _          -> Maybe String
forall a. Maybe a
Nothing

            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (String -> Bool) -> String -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (String -> Maybe ()) -> String -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
str
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
t, Token
token, String
str, DataSource -> DataType
DataString DataSource
SourceChecked)

        T_DollarBraced _ _ l :: Token
l -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
            let string :: String
string = Token -> String
bracedString Token
t
            let modifier :: String
modifier = ShowS
getBracedModifier String
string
            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier) ["=", ":="]
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
t, Token
t, ShowS
getBracedReference String
string, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
l])

        t :: Token
t@(T_FdRedirect _ ('{':var :: String
var) op :: Token
op) -> -- {foo}>&2 modifies foo
            [(Token
t, Token
t, (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '}') String
var, DataSource -> DataType
DataString DataSource
SourceInteger) | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isClosingFileOp Token
op]

        t :: Token
t@(T_CoProc _ name :: Maybe String
name _) ->
            [(Token
t, Token
t, String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe "COPROC" Maybe String
name, DataSource -> DataType
DataArray DataSource
SourceInteger)]

        --Points to 'for' rather than variable
        T_ForIn id :: Id
id str :: String
str [] _ -> [(Token
t, Token
t, String
str, DataSource -> DataType
DataString DataSource
SourceExternal)]
        T_ForIn id :: Id
id str :: String
str words :: [Token]
words _ -> [(Token
t, Token
t, String
str, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token]
words)]
        T_SelectIn id :: Id
id str :: String
str words :: [Token]
words _ -> [(Token
t, Token
t, String
str, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token]
words)]
        _ -> []

isClosingFileOp :: Token -> Bool
isClosingFileOp op :: Token
op =
    case Token
op of
        T_IoDuplicate _ (T_GREATAND _) "-" -> Bool
True
        T_IoDuplicate _ (T_LESSAND  _) "-" -> Bool
True
        _                                  -> Bool
False


-- Consider 'export/declare -x' a reference, since it makes the var available
getReferencedVariableCommand :: Token -> [(Token, Token, String)]
getReferencedVariableCommand base :: Token
base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Literal _ x :: String
x:_):rest :: [Token]
rest)) =
    case String
x of
        "export" -> if "f" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags
            then []
            else (Token -> [(Token, Token, String)])
-> [Token] -> [(Token, Token, String)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String)]
getReference [Token]
rest
        "declare" -> if
                (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) ["x", "p"] Bool -> Bool -> Bool
&&
                    (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) ["f", "F"])
            then (Token -> [(Token, Token, String)])
-> [Token] -> [(Token, Token, String)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String)]
getReference [Token]
rest
            else []
        "trap" ->
            case [Token]
rest of
                head :: Token
head:_ -> (String -> (Token, Token, String))
-> [String] -> [(Token, Token, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: String
x -> (Token
base, Token
head, String
x)) ([String] -> [(Token, Token, String)])
-> [String] -> [(Token, Token, String)]
forall a b. (a -> b) -> a -> b
$ Token -> [String]
getVariablesFromLiteralToken Token
head
                _ -> []
        "alias" -> [(Token
base, Token
token, String
name) | Token
token <- [Token]
rest, String
name <- Token -> [String]
getVariablesFromLiteralToken Token
token]
        _ -> []
  where
    getReference :: Token -> [(Token, Token, String)]
getReference t :: Token
t@(T_Assignment _ _ name :: String
name _ value :: Token
value) = [(Token
t, Token
t, String
name)]
    getReference t :: Token
t@(T_NormalWord _ [T_Literal _ name :: String
name]) | Bool -> Bool
not ("-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
name) = [(Token
t, Token
t, String
name)]
    getReference _ = []
    flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String]) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
base

getReferencedVariableCommand _ = []

-- The function returns a tuple consisting of four items describing an assignment.
-- Given e.g. declare foo=bar
-- (
--   BaseCommand :: Token,     -- The command/structure assigning the variable, i.e. declare foo=bar
--   AssignmentToken :: Token, -- The specific part that assigns this variable, i.e. foo=bar
--   VariableName :: String,   -- The variable name, i.e. foo
--   VariableValue :: DataType -- A description of the value being assigned, i.e. "Literal string with value foo"
-- )
getModifiedVariableCommand :: Token -> [(Token, Token, String, DataType)]
getModifiedVariableCommand base :: Token
base@(T_SimpleCommand id :: Id
id cmdPrefix :: [Token]
cmdPrefix (T_NormalWord _ (T_Literal _ x :: String
x:_):rest :: [Token]
rest)) =
   ((Token, Token, String, DataType) -> Bool)
-> [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(_,_,s :: String
s,_) -> Bool -> Bool
not ("-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s)) ([(Token, Token, String, DataType)]
 -> [(Token, Token, String, DataType)])
-> [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$
    case String
x of
        "builtin" ->
            Token -> [(Token, Token, String, DataType)]
getModifiedVariableCommand (Token -> [(Token, Token, String, DataType)])
-> Token -> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Id -> [Token] -> [Token] -> Token
T_SimpleCommand Id
id [Token]
cmdPrefix [Token]
rest
        "read" ->
            let params :: [Maybe (Token, Token, String, DataType)]
params = (Token -> Maybe (Token, Token, String, DataType))
-> [Token] -> [Maybe (Token, Token, String, DataType)]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe (Token, Token, String, DataType)
getLiteral [Token]
rest
                readArrayVars :: [Maybe (Token, Token, String, DataType)]
readArrayVars = [Token] -> [Maybe (Token, Token, String, DataType)]
getReadArrayVariables [Token]
rest
            in
                [Maybe (Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe (Token, Token, String, DataType)]
 -> [(Token, Token, String, DataType)])
-> ([Maybe (Token, Token, String, DataType)]
    -> [Maybe (Token, Token, String, DataType)])
-> [Maybe (Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall a. [a] -> [a] -> [a]
++ [Maybe (Token, Token, String, DataType)]
readArrayVars) ([Maybe (Token, Token, String, DataType)]
 -> [Maybe (Token, Token, String, DataType)])
-> ([Maybe (Token, Token, String, DataType)]
    -> [Maybe (Token, Token, String, DataType)])
-> [Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Maybe (Token, Token, String, DataType) -> Bool)
-> [Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Maybe (Token, Token, String, DataType) -> Bool
forall a. Maybe a -> Bool
isJust ([Maybe (Token, Token, String, DataType)]
 -> [Maybe (Token, Token, String, DataType)])
-> ([Maybe (Token, Token, String, DataType)]
    -> [Maybe (Token, Token, String, DataType)])
-> [Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall a. [a] -> [a]
reverse ([Maybe (Token, Token, String, DataType)]
 -> [(Token, Token, String, DataType)])
-> [Maybe (Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Maybe (Token, Token, String, DataType)]
params
        "getopts" ->
            case [Token]
rest of
                opts :: Token
opts:var :: Token
var:_ -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Token -> Maybe (Token, Token, String, DataType)
getLiteral Token
var
                _          -> []

        "let" -> (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
letParamToLiteral [Token]
rest

        "export" ->
            if "f" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags then [] else (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
getModifierParamString [Token]
rest

        "declare" -> if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) ["F", "f", "p"] then [] else [(Token, Token, String, DataType)]
declaredVars
        "typeset" -> [(Token, Token, String, DataType)]
declaredVars

        "local" -> (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
getModifierParamString [Token]
rest
        "readonly" ->
            if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) ["f", "p"]
            then []
            else (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
getModifierParamString [Token]
rest
        "set" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
            [Token]
params <- [Token] -> Maybe [Token]
getSetParams [Token]
rest
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
base, "@", DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token]
params)

        "printf" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
forall (m :: * -> *).
MonadFail m =>
[Token] -> m (Token, Token, String, DataType)
getPrintfVariable [Token]
rest

        "mapfile" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Token -> [Token] -> Maybe (Token, Token, String, DataType)
forall a. a -> [Token] -> Maybe (a, Token, String, DataType)
getMapfileArray Token
base [Token]
rest
        "readarray" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Token -> [Token] -> Maybe (Token, Token, String, DataType)
forall a. a -> [Token] -> Maybe (a, Token, String, DataType)
getMapfileArray Token
base [Token]
rest

        "DEFINE_boolean" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest
        "DEFINE_float" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest
        "DEFINE_integer" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest
        "DEFINE_string" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest

        _ -> []
  where
    flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String]) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
base
    stripEquals :: ShowS
stripEquals s :: String
s = Int -> ShowS
forall a. Int -> [a] -> [a]
drop 1 ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '=') String
s
    stripEqualsFrom :: Token -> Token
stripEqualsFrom (T_NormalWord id1 :: Id
id1 (T_Literal id2 :: Id
id2 s :: String
s:rs :: [Token]
rs)) =
        Id -> [Token] -> Token
T_NormalWord Id
id1 (Id -> String -> Token
T_Literal Id
id2 (ShowS
stripEquals String
s)Token -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token]
rs)
    stripEqualsFrom (T_NormalWord id1 :: Id
id1 [T_DoubleQuoted id2 :: Id
id2 [T_Literal id3 :: Id
id3 s :: String
s]]) =
        Id -> [Token] -> Token
T_NormalWord Id
id1 [Id -> [Token] -> Token
T_DoubleQuoted Id
id2 [Id -> String -> Token
T_Literal Id
id3 (ShowS
stripEquals String
s)]]
    stripEqualsFrom t :: Token
t = Token
t

    declaredVars :: [(Token, Token, String, DataType)]
declaredVars = (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ((DataSource -> DataType)
-> Token -> [(Token, Token, String, DataType)]
getModifierParam DataSource -> DataType
defaultType) [Token]
rest
      where
        defaultType :: DataSource -> DataType
defaultType = if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) ["a", "A"] then DataSource -> DataType
DataArray else DataSource -> DataType
DataString

    getLiteralOfDataType :: Token -> d -> Maybe (Token, Token, String, d)
getLiteralOfDataType t :: Token
t d :: d
d = do
        String
s <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe () -> Maybe ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ("-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s) (Maybe () -> Maybe ()) -> Maybe () -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Maybe ()
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "argument"
        (Token, Token, String, d) -> Maybe (Token, Token, String, d)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
t, String
s, d
d)

    getLiteral :: Token -> Maybe (Token, Token, String, DataType)
getLiteral t :: Token
t = Token -> DataType -> Maybe (Token, Token, String, DataType)
forall d. Token -> d -> Maybe (Token, Token, String, d)
getLiteralOfDataType Token
t (DataSource -> DataType
DataString DataSource
SourceExternal)

    getLiteralArray :: Token -> Maybe (Token, Token, String, DataType)
getLiteralArray t :: Token
t = Token -> DataType -> Maybe (Token, Token, String, DataType)
forall d. Token -> d -> Maybe (Token, Token, String, d)
getLiteralOfDataType Token
t (DataSource -> DataType
DataArray DataSource
SourceExternal)

    getModifierParamString :: Token -> [(Token, Token, String, DataType)]
getModifierParamString = (DataSource -> DataType)
-> Token -> [(Token, Token, String, DataType)]
getModifierParam DataSource -> DataType
DataString

    getModifierParam :: (DataSource -> DataType)
-> Token -> [(Token, Token, String, DataType)]
getModifierParam def :: DataSource -> DataType
def t :: Token
t@(T_Assignment _ _ name :: String
name _ value :: Token
value) =
        [(Token
base, Token
t, String
name, (DataSource -> DataType) -> Token -> DataType
dataTypeFrom DataSource -> DataType
def Token
value)]
    getModifierParam def :: DataSource -> DataType
def t :: Token
t@T_NormalWord {} = Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
        String
name <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
name
        (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
t, String
name, DataSource -> DataType
def DataSource
SourceDeclaration)
    getModifierParam _ _ = []

    letParamToLiteral :: Token -> [(Token, Token, String, DataType)]
letParamToLiteral token :: Token
token =
          if String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
var
            then []
            else [(Token
base, Token
token, String
var, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token -> Token
stripEqualsFrom Token
token])]
        where var :: String
var = (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Char -> Bool
isVariableChar ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` "+-") ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
token

    getSetParams :: [Token] -> Maybe [Token]
getSetParams (t :: Token
t:_:rest :: [Token]
rest) | Token -> Maybe String
getLiteralString Token
t Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just "-o" = [Token] -> Maybe [Token]
getSetParams [Token]
rest
    getSetParams (t :: Token
t:rest :: [Token]
rest) =
        let s :: Maybe String
s = Token -> Maybe String
getLiteralString Token
t in
            case Maybe String
s of
                Just "--"    -> [Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return [Token]
rest
                Just ('-':_) -> [Token] -> Maybe [Token]
getSetParams [Token]
rest
                _            -> [Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
tToken -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token] -> Maybe [Token] -> [Token]
forall a. a -> Maybe a -> a
fromMaybe [] ([Token] -> Maybe [Token]
getSetParams [Token]
rest))
    getSetParams [] = Maybe [Token]
forall a. Maybe a
Nothing

    getPrintfVariable :: [Token] -> m (Token, Token, String, DataType)
getPrintfVariable list :: [Token]
list = [(Token, Maybe String)] -> m (Token, Token, String, DataType)
forall (m :: * -> *) b.
MonadFail m =>
[(b, Maybe String)] -> m (Token, b, String, DataType)
f ([(Token, Maybe String)] -> m (Token, Token, String, DataType))
-> [(Token, Maybe String)] -> m (Token, Token, String, DataType)
forall a b. (a -> b) -> a -> b
$ (Token -> (Token, Maybe String))
-> [Token] -> [(Token, Maybe String)]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: Token
x -> (Token
x, Token -> Maybe String
getLiteralString Token
x)) [Token]
list
      where
        f :: [(b, Maybe String)] -> m (Token, b, String, DataType)
f ((_, Just "-v") : (t :: b
t, Just var :: String
var) : _) = (Token, b, String, DataType) -> m (Token, b, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, b
t, String
varName, DataSource -> DataType
varType (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token]
list)
            where
                (varName :: String
varName, varType :: DataSource -> DataType
varType) = case Char -> String -> Maybe Int
forall a. Eq a => a -> [a] -> Maybe Int
elemIndex '[' String
var of
                    Just i :: Int
i -> (Int -> ShowS
forall a. Int -> [a] -> [a]
take Int
i String
var, DataSource -> DataType
DataArray)
                    Nothing -> (String
var, DataSource -> DataType
DataString)
        f (_:rest :: [(b, Maybe String)]
rest) = [(b, Maybe String)] -> m (Token, b, String, DataType)
f [(b, Maybe String)]
rest
        f [] = String -> m (Token, b, String, DataType)
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "not found"

    -- mapfile has some curious syntax allowing flags plus 0..n variable names
    -- where only the first non-option one is used if any. Here we cheat and
    -- just get the last one, if it's a variable name.
    getMapfileArray :: a -> [Token] -> Maybe (a, Token, String, DataType)
getMapfileArray base :: a
base arguments :: [Token]
arguments = do
        Token
lastArg <- [Token] -> Maybe Token
forall a. [a] -> Maybe a
listToMaybe ([Token] -> [Token]
forall a. [a] -> [a]
reverse [Token]
arguments)
        String
name <- Token -> Maybe String
getLiteralString Token
lastArg
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
name
        (a, Token, String, DataType) -> Maybe (a, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (a
base, Token
lastArg, String
name, DataSource -> DataType
DataArray DataSource
SourceExternal)

    -- get all the array variables used in read, e.g. read -a arr
    getReadArrayVariables :: [Token] -> [Maybe (Token, Token, String, DataType)]
getReadArrayVariables args :: [Token]
args =
        ((Token, Token) -> Maybe (Token, Token, String, DataType))
-> [(Token, Token)] -> [Maybe (Token, Token, String, DataType)]
forall a b. (a -> b) -> [a] -> [b]
map (Token -> Maybe (Token, Token, String, DataType)
getLiteralArray (Token -> Maybe (Token, Token, String, DataType))
-> ((Token, Token) -> Token)
-> (Token, Token)
-> Maybe (Token, Token, String, DataType)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, Token) -> Token
forall a b. (a, b) -> b
snd)
            (((Token, Token) -> Bool) -> [(Token, Token)] -> [(Token, Token)]
forall a. (a -> Bool) -> [a] -> [a]
filter (Token -> Bool
isArrayFlag (Token -> Bool)
-> ((Token, Token) -> Token) -> (Token, Token) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, Token) -> Token
forall a b. (a, b) -> a
fst) ([Token] -> [Token] -> [(Token, Token)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Token]
args ([Token] -> [Token]
forall a. [a] -> [a]
tail [Token]
args)))

    isArrayFlag :: Token -> Bool
isArrayFlag x :: Token
x = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        String
str <- Token -> Maybe String
getLiteralString Token
x
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ case String
str of
                    '-':'-':_ -> Bool
False
                    '-':str :: String
str -> 'a' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
str
                    _ -> Bool
False

    -- get the FLAGS_ variable created by a shflags DEFINE_ call
    getFlagVariable :: [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable (n :: Token
n:v :: Token
v:_) = do
        String
name <- Token -> Maybe String
getLiteralString Token
n
        (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
n, "FLAGS_" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ DataSource
SourceExternal)
    getFlagVariable _ = Maybe (Token, Token, String, DataType)
forall a. Maybe a
Nothing

getModifiedVariableCommand _ = []

getIndexReferences :: String -> [String]
getIndexReferences s :: String
s = [String] -> Maybe [String] -> [String]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [String] -> [String]) -> Maybe [String] -> [String]
forall a b. (a -> b) -> a -> b
$ do
    [String]
match <- Regex -> String -> Maybe [String]
matchRegex Regex
re String
s
    String
index <- [String]
match [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! 0
    [String] -> Maybe [String]
forall (m :: * -> *) a. Monad m => a -> m a
return ([String] -> Maybe [String]) -> [String] -> Maybe [String]
forall a b. (a -> b) -> a -> b
$ Regex -> String -> [String]
matchAllStrings Regex
variableNameRegex String
index
  where
    re :: Regex
re = String -> Regex
mkRegex "(\\[.*\\])"

prop_getOffsetReferences1 :: Bool
prop_getOffsetReferences1 = String -> [String]
getOffsetReferences ":bar" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== ["bar"]
prop_getOffsetReferences2 :: Bool
prop_getOffsetReferences2 = String -> [String]
getOffsetReferences ":bar:baz" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== ["bar", "baz"]
prop_getOffsetReferences3 :: Bool
prop_getOffsetReferences3 = String -> [String]
getOffsetReferences "[foo]:bar" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== ["bar"]
prop_getOffsetReferences4 :: Bool
prop_getOffsetReferences4 = String -> [String]
getOffsetReferences "[foo]:bar:baz" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== ["bar", "baz"]
getOffsetReferences :: String -> [String]
getOffsetReferences mods :: String
mods = [String] -> Maybe [String] -> [String]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [String] -> [String]) -> Maybe [String] -> [String]
forall a b. (a -> b) -> a -> b
$ do
-- if mods start with [, then drop until ]
    [String]
match <- Regex -> String -> Maybe [String]
matchRegex Regex
re String
mods
    String
offsets <- [String]
match [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! 1
    [String] -> Maybe [String]
forall (m :: * -> *) a. Monad m => a -> m a
return ([String] -> Maybe [String]) -> [String] -> Maybe [String]
forall a b. (a -> b) -> a -> b
$ Regex -> String -> [String]
matchAllStrings Regex
variableNameRegex String
offsets
  where
    re :: Regex
re = String -> Regex
mkRegex "^(\\[.+\\])? *:([^-=?+].*)"

getReferencedVariables :: Map Id Token -> Token -> [(Token, Token, String)]
getReferencedVariables parents :: Map Id Token
parents t :: Token
t =
    case Token
t of
        T_DollarBraced id :: Id
id _ l :: Token
l -> let str :: String
str = Token -> String
bracedString Token
t in
            (Token
t, Token
t, ShowS
getBracedReference String
str) (Token, Token, String)
-> [(Token, Token, String)] -> [(Token, Token, String)]
forall a. a -> [a] -> [a]
:
                (String -> (Token, Token, String))
-> [String] -> [(Token, Token, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: String
x -> (Token
l, Token
l, String
x)) (
                    String -> [String]
getIndexReferences String
str
                    [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ String -> [String]
getOffsetReferences (ShowS
getBracedModifier String
str))
        TA_Variable id :: Id
id name :: String
name _ ->
            if Token -> Bool
isArithmeticAssignment Token
t
            then []
            else [(Token
t, Token
t, String
name)]
        T_Assignment id :: Id
id mode :: AssignmentMode
mode str :: String
str _ word :: Token
word ->
            [(Token
t, Token
t, String
str) | AssignmentMode
mode AssignmentMode -> AssignmentMode -> Bool
forall a. Eq a => a -> a -> Bool
== AssignmentMode
Append] [(Token, Token, String)]
-> [(Token, Token, String)] -> [(Token, Token, String)]
forall a. [a] -> [a] -> [a]
++ String -> Token -> Token -> [(Token, Token, String)]
forall b. String -> b -> Token -> [(b, b, String)]
specialReferences String
str Token
t Token
word

        TC_Unary id :: Id
id _ "-v" token :: Token
token -> Token -> Token -> [(Token, Token, String)]
forall a. a -> Token -> [(a, Token, String)]
getIfReference Token
t Token
token
        TC_Unary id :: Id
id _ "-R" token :: Token
token -> Token -> Token -> [(Token, Token, String)]
forall a. a -> Token -> [(a, Token, String)]
getIfReference Token
t Token
token
        TC_Binary id :: Id
id DoubleBracket op :: String
op lhs :: Token
lhs rhs :: Token
rhs ->
            if String -> Bool
isDereferencing String
op
            then (Token -> [(Token, Token, String)])
-> [Token] -> [(Token, Token, String)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Token -> Token -> [(Token, Token, String)]
forall a. a -> Token -> [(a, Token, String)]
getIfReference Token
t) [Token
lhs, Token
rhs]
            else []

        T_BatsTest {} -> [ -- pretend @test references vars to avoid warnings
            (Token
t, Token
t, "lines"),
            (Token
t, Token
t, "status"),
            (Token
t, Token
t, "output")
            ]

        t :: Token
t@(T_FdRedirect _ ('{':var :: String
var) op :: Token
op) -> -- {foo}>&- references and closes foo
            [(Token
t, Token
t, (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '}') String
var) | Token -> Bool
isClosingFileOp Token
op]
        x :: Token
x -> Token -> [(Token, Token, String)]
getReferencedVariableCommand Token
x
  where
    -- Try to reduce false positives for unused vars only referenced from evaluated vars
    specialReferences :: String -> b -> Token -> [(b, b, String)]
specialReferences name :: String
name base :: b
base word :: Token
word =
        if String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [
            "PS1", "PS2", "PS3", "PS4",
            "PROMPT_COMMAND"
          ]
        then
            (String -> (b, b, String)) -> [String] -> [(b, b, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: String
x -> (b
base, b
base, String
x)) ([String] -> [(b, b, String)]) -> [String] -> [(b, b, String)]
forall a b. (a -> b) -> a -> b
$
                Token -> [String]
getVariablesFromLiteralToken Token
word
        else []

    literalizer :: Token -> Maybe String
literalizer t :: Token
t = case Token
t of
        T_Glob _ s :: String
s -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s    -- Also when parsed as globs
        _          -> Maybe String
forall a. Maybe a
Nothing

    getIfReference :: a -> Token -> [(a, Token, String)]
getIfReference context :: a
context token :: Token
token = Maybe (a, Token, String) -> [(a, Token, String)]
forall a. Maybe a -> [a]
maybeToList (Maybe (a, Token, String) -> [(a, Token, String)])
-> Maybe (a, Token, String) -> [(a, Token, String)]
forall a b. (a -> b) -> a -> b
$ do
            str :: String
str@(h :: Char
h:_) <- (Token -> Maybe String) -> Token -> Maybe String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt Token -> Maybe String
literalizer Token
token
            Bool -> Maybe () -> Maybe ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char -> Bool
isDigit Char
h) (Maybe () -> Maybe ()) -> Maybe () -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Maybe ()
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "is a number"
            (a, Token, String) -> Maybe (a, Token, String)
forall (m :: * -> *) a. Monad m => a -> m a
return (a
context, Token
token, ShowS
getBracedReference String
str)

    isDereferencing :: String -> Bool
isDereferencing = (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["-eq", "-ne", "-lt", "-le", "-gt", "-ge"])

    isArithmeticAssignment :: Token -> Bool
isArithmeticAssignment t :: Token
t = case Map Id Token -> Token -> [Token]
getPath Map Id Token
parents Token
t of
        this :: Token
this: TA_Assignment _ "=" lhs :: Token
lhs _ :_ -> Token
lhs Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
== Token
t
        _                                  -> Bool
False

dataTypeFrom :: (DataSource -> DataType) -> Token -> DataType
dataTypeFrom defaultType :: DataSource -> DataType
defaultType v :: Token
v = (case Token
v of T_Array {} -> DataSource -> DataType
DataArray; _ -> DataSource -> DataType
defaultType) (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
v]


--- Command specific checks

-- Compare a command to a string: t `isCommand` "sed" (also matches /usr/bin/sed)
isCommand :: Token -> String -> Bool
isCommand token :: Token
token str :: String
str = Token -> (String -> Bool) -> Bool
isCommandMatch Token
token (\cmd :: String
cmd -> String
cmd  String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
str Bool -> Bool -> Bool
|| ('/' Char -> ShowS
forall a. a -> [a] -> [a]
: String
str) String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
cmd)

-- Compare a command to a literal. Like above, but checks full path.
isUnqualifiedCommand :: Token -> String -> Bool
isUnqualifiedCommand token :: Token
token str :: String
str = Token -> (String -> Bool) -> Bool
isCommandMatch Token
token (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
str)

isCommandMatch :: Token -> (String -> Bool) -> Bool
isCommandMatch token :: Token
token matcher :: String -> Bool
matcher = Bool -> (String -> Bool) -> Maybe String -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False
    String -> Bool
matcher (Token -> Maybe String
getCommandName Token
token)

-- Does this regex look like it was intended as a glob?
-- True:  *foo*
-- False: .*foo.*
isConfusedGlobRegex :: String -> Bool
isConfusedGlobRegex :: String -> Bool
isConfusedGlobRegex ('*':_) = Bool
True
isConfusedGlobRegex [x :: Char
x,'*'] | Char
x Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` "\\." = Bool
True
isConfusedGlobRegex _       = Bool
False

isVariableStartChar :: Char -> Bool
isVariableStartChar x :: Char
x = Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '_' Bool -> Bool -> Bool
|| Char -> Bool
isAsciiLower Char
x Bool -> Bool -> Bool
|| Char -> Bool
isAsciiUpper Char
x
isVariableChar :: Char -> Bool
isVariableChar x :: Char
x = Char -> Bool
isVariableStartChar Char
x Bool -> Bool -> Bool
|| Char -> Bool
isDigit Char
x
variableNameRegex :: Regex
variableNameRegex = String -> Regex
mkRegex "[_a-zA-Z][_a-zA-Z0-9]*"

prop_isVariableName1 :: Bool
prop_isVariableName1 = String -> Bool
isVariableName "_fo123"
prop_isVariableName2 :: Bool
prop_isVariableName2 = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName "4"
prop_isVariableName3 :: Bool
prop_isVariableName3 = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName "test: "
isVariableName :: String -> Bool
isVariableName (x :: Char
x:r :: String
r) = Char -> Bool
isVariableStartChar Char
x Bool -> Bool -> Bool
&& (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
r
isVariableName _     = Bool
False

getVariablesFromLiteralToken :: Token -> [String]
getVariablesFromLiteralToken token :: Token
token =
    String -> [String]
getVariablesFromLiteral (String -> Token -> String
getLiteralStringDef " " Token
token)

-- Try to get referenced variables from a literal string like "$foo"
-- Ignores tons of cases like arithmetic evaluation and array indices.
prop_getVariablesFromLiteral1 :: Bool
prop_getVariablesFromLiteral1 =
    String -> [String]
getVariablesFromLiteral "$foo${bar//a/b}$BAZ" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== ["foo", "bar", "BAZ"]
getVariablesFromLiteral :: String -> [String]
getVariablesFromLiteral string :: String
string =
    ([String] -> String) -> [[String]] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map ([String] -> Int -> String
forall a. [a] -> Int -> a
!! 0) ([[String]] -> [String]) -> [[String]] -> [String]
forall a b. (a -> b) -> a -> b
$ Regex -> String -> [[String]]
matchAllSubgroups Regex
variableRegex String
string
  where
    variableRegex :: Regex
variableRegex = String -> Regex
mkRegex "\\$\\{?([A-Za-z0-9_]+)"

-- Get the variable name from an expansion like ${var:-foo}
prop_getBracedReference1 :: Bool
prop_getBracedReference1 = ShowS
getBracedReference "foo" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "foo"
prop_getBracedReference2 :: Bool
prop_getBracedReference2 = ShowS
getBracedReference "#foo" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "foo"
prop_getBracedReference3 :: Bool
prop_getBracedReference3 = ShowS
getBracedReference "#" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "#"
prop_getBracedReference4 :: Bool
prop_getBracedReference4 = ShowS
getBracedReference "##" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "#"
prop_getBracedReference5 :: Bool
prop_getBracedReference5 = ShowS
getBracedReference "#!" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "!"
prop_getBracedReference6 :: Bool
prop_getBracedReference6 = ShowS
getBracedReference "!#" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "#"
prop_getBracedReference7 :: Bool
prop_getBracedReference7 = ShowS
getBracedReference "!foo#?" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "foo"
prop_getBracedReference8 :: Bool
prop_getBracedReference8 = ShowS
getBracedReference "foo-bar" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "foo"
prop_getBracedReference9 :: Bool
prop_getBracedReference9 = ShowS
getBracedReference "foo:-bar" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "foo"
prop_getBracedReference10 :: Bool
prop_getBracedReference10= ShowS
getBracedReference "foo: -1" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "foo"
prop_getBracedReference11 :: Bool
prop_getBracedReference11= ShowS
getBracedReference "!os*" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== ""
prop_getBracedReference12 :: Bool
prop_getBracedReference12= ShowS
getBracedReference "!os?bar**" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== ""
prop_getBracedReference13 :: Bool
prop_getBracedReference13= ShowS
getBracedReference "foo[bar]" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "foo"
getBracedReference :: ShowS
getBracedReference s :: String
s = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
s (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$
    String -> Maybe String
nameExpansion String
s Maybe String -> Maybe String -> Maybe String
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` String -> Maybe String
forall (m :: * -> *).
(Monad m, Alternative m) =>
String -> m String
takeName String
noPrefix Maybe String -> Maybe String -> Maybe String
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` String -> Maybe String
forall (m :: * -> *). MonadFail m => String -> m String
getSpecial String
noPrefix Maybe String -> Maybe String -> Maybe String
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` String -> Maybe String
forall (m :: * -> *). MonadFail m => String -> m String
getSpecial String
s
  where
    noPrefix :: String
noPrefix = ShowS
dropPrefix String
s
    dropPrefix :: ShowS
dropPrefix (c :: Char
c:rest :: String
rest) = if Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` "!#" then String
rest else Char
cChar -> ShowS
forall a. a -> [a] -> [a]
:String
rest
    dropPrefix ""       = ""
    takeName :: String -> m String
takeName s :: String
s = do
        let name :: String
name = (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Char -> Bool
isVariableChar String
s
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> (Bool -> Bool) -> Bool -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
name
        String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
name
    getSpecial :: String -> m String
getSpecial (c :: Char
c:_) =
        if Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` "*@#?-$!" then String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return [Char
c] else String -> m String
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "not special"
    getSpecial _ = String -> m String
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "empty"

    nameExpansion :: String -> Maybe String
nameExpansion ('!':rest :: String
rest) = do -- e.g. ${!foo*bar*}
        let suffix :: String
suffix = (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isVariableChar String
rest
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
suffix String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
rest -- e.g. ${!@}
        Char
first <- String
suffix String -> Int -> Maybe Char
forall a. [a] -> Int -> Maybe a
!!! 0
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Char
first Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` "*?"
        String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return ""
    nameExpansion _ = Maybe String
forall a. Maybe a
Nothing

prop_getBracedModifier1 :: Bool
prop_getBracedModifier1 = ShowS
getBracedModifier "foo:bar:baz" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== ":bar:baz"
prop_getBracedModifier2 :: Bool
prop_getBracedModifier2 = ShowS
getBracedModifier "!var:-foo" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== ":-foo"
prop_getBracedModifier3 :: Bool
prop_getBracedModifier3 = ShowS
getBracedModifier "foo[bar]" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "[bar]"
getBracedModifier :: ShowS
getBracedModifier s :: String
s = String -> [String] -> String
forall p. p -> [p] -> p
headOrDefault "" ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ do
    let var :: String
var = ShowS
getBracedReference String
s
    String
a <- String -> [String]
dropModifier String
s
    String -> String -> [String]
forall a. Eq a => [a] -> [a] -> [[a]]
dropPrefix String
var String
a
  where
    dropPrefix :: [a] -> [a] -> [[a]]
dropPrefix [] t :: [a]
t        = [a] -> [[a]]
forall (m :: * -> *) a. Monad m => a -> m a
return [a]
t
    dropPrefix (a :: a
a:b :: [a]
b) (c :: a
c:d :: [a]
d) | a
a a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c = [a] -> [a] -> [[a]]
dropPrefix [a]
b [a]
d
    dropPrefix _ _         = []

    dropModifier :: String -> [String]
dropModifier (c :: Char
c:rest :: String
rest) | Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` "#!" = [String
rest, Char
cChar -> ShowS
forall a. a -> [a] -> [a]
:String
rest]
    dropModifier x :: String
x        = [String
x]

-- Useful generic functions.

-- Get element 0 or a default. Like `head` but safe.
headOrDefault :: p -> [p] -> p
headOrDefault _ (a :: p
a:_) = p
a
headOrDefault def :: p
def _   = p
def

--- Get element n of a list, or Nothing. Like `!!` but safe.
!!! :: [a] -> Int -> Maybe a
(!!!) list :: [a]
list i :: Int
i =
    case Int -> [a] -> [a]
forall a. Int -> [a] -> [a]
drop Int
i [a]
list of
        []    -> Maybe a
forall a. Maybe a
Nothing
        (r :: a
r:_) -> a -> Maybe a
forall a. a -> Maybe a
Just a
r

-- Run a command if the shell is in the given list
whenShell :: t Shell -> m () -> m ()
whenShell l :: t Shell
l c :: m ()
c = do
    Shell
shell <- (Parameters -> Shell) -> m Shell
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Parameters -> Shell
shellType
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Shell
shell Shell -> t Shell -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t Shell
l ) m ()
c


filterByAnnotation :: AnalysisSpec -> Parameters -> [TokenComment] -> [TokenComment]
filterByAnnotation asSpec :: AnalysisSpec
asSpec params :: Parameters
params =
    (TokenComment -> Bool) -> [TokenComment] -> [TokenComment]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (TokenComment -> Bool) -> TokenComment -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TokenComment -> Bool
shouldIgnore)
  where
    token :: Token
token = AnalysisSpec -> Token
asScript AnalysisSpec
asSpec
    shouldIgnore :: TokenComment -> Bool
shouldIgnore note :: TokenComment
note =
        (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Code -> Token -> Bool
shouldIgnoreFor (TokenComment -> Code
getCode TokenComment
note)) ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$
            Map Id Token -> Token -> [Token]
getPath Map Id Token
parents (Id -> Token
T_Bang (Id -> Token) -> Id -> Token
forall a b. (a -> b) -> a -> b
$ TokenComment -> Id
tcId TokenComment
note)
    shouldIgnoreFor :: Code -> Token -> Bool
shouldIgnoreFor _ T_Include {} = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ AnalysisSpec -> Bool
asCheckSourced AnalysisSpec
asSpec
    shouldIgnoreFor code :: Code
code t :: Token
t = Code -> Token -> Bool
isAnnotationIgnoringCode Code
code Token
t
    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params
    getCode :: TokenComment -> Code
getCode = Comment -> Code
cCode (Comment -> Code)
-> (TokenComment -> Comment) -> TokenComment -> Code
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TokenComment -> Comment
tcComment

shouldIgnoreCode :: Parameters -> Code -> Token -> Bool
shouldIgnoreCode params :: Parameters
params code :: Code
code t :: Token
t =
    (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Code -> Token -> Bool
isAnnotationIgnoringCode Code
code) ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$
        Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t

-- Is this a ${#anything}, to get string length or array count?
isCountingReference :: Token -> Bool
isCountingReference (T_DollarBraced id :: Id
id _ token :: Token
token) =
    case [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
token of
        '#':_ -> Bool
True
        _     -> Bool
False
isCountingReference _ = Bool
False

-- FIXME: doesn't handle ${a:+$var} vs ${a:+"$var"}
isQuotedAlternativeReference :: Token -> Bool
isQuotedAlternativeReference t :: Token
t =
    case Token
t of
        T_DollarBraced _ _ _ ->
            ShowS
getBracedModifier (Token -> String
bracedString Token
t) String -> Regex -> Bool
`matches` Regex
re
        _ -> Bool
False
  where
    re :: Regex
re = String -> Regex
mkRegex "(^|\\]):?\\+"

-- getGnuOpts "erd:u:" will parse a SimpleCommand like
--     read -re -d : -u 3 bar
-- into
--     Just [("r", -re), ("e", -re), ("d", :), ("u", 3), ("", bar)]
-- where flags with arguments map to arguments, while others map to themselves.
-- Any unrecognized flag will result in Nothing.
getGnuOpts :: String -> Token -> Maybe [(String, Token)]
getGnuOpts str :: String
str t :: Token
t = String -> [(Token, String)] -> Maybe [(String, Token)]
getOpts String
str ([(Token, String)] -> Maybe [(String, Token)])
-> [(Token, String)] -> Maybe [(String, Token)]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
t
getBsdOpts :: String -> Token -> Maybe [(String, Token)]
getBsdOpts str :: String
str t :: Token
t = String -> [(Token, String)] -> Maybe [(String, Token)]
getOpts String
str ([(Token, String)] -> Maybe [(String, Token)])
-> [(Token, String)] -> Maybe [(String, Token)]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getLeadingFlags Token
t
getOpts :: String -> [(Token, String)] -> Maybe [(String, Token)]
getOpts :: String -> [(Token, String)] -> Maybe [(String, Token)]
getOpts string :: String
string flags :: [(Token, String)]
flags = [(Token, String)] -> Maybe [(String, Token)]
forall b. [(b, String)] -> Maybe [(String, b)]
process [(Token, String)]
flags
  where
    flagList :: String -> [(String, Bool)]
flagList (c :: Char
c:':':rest :: String
rest) = ([Char
c], Bool
True) (String, Bool) -> [(String, Bool)] -> [(String, Bool)]
forall a. a -> [a] -> [a]
: String -> [(String, Bool)]
flagList String
rest
    flagList (c :: Char
c:rest :: String
rest)     = ([Char
c], Bool
False) (String, Bool) -> [(String, Bool)] -> [(String, Bool)]
forall a. a -> [a] -> [a]
: String -> [(String, Bool)]
flagList String
rest
    flagList []           = []
    flagMap :: Map String Bool
flagMap = [(String, Bool)] -> Map String Bool
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(String, Bool)] -> Map String Bool)
-> [(String, Bool)] -> Map String Bool
forall a b. (a -> b) -> a -> b
$ ("", Bool
False) (String, Bool) -> [(String, Bool)] -> [(String, Bool)]
forall a. a -> [a] -> [a]
: String -> [(String, Bool)]
flagList String
string

    process :: [(b, String)] -> Maybe [(String, b)]
process [] = [(String, b)] -> Maybe [(String, b)]
forall (m :: * -> *) a. Monad m => a -> m a
return []
    process [(token :: b
token, flag :: String
flag)] = do
        Bool
takesArg <- String -> Map String Bool -> Maybe Bool
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
flag Map String Bool
flagMap
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not Bool
takesArg
        [(String, b)] -> Maybe [(String, b)]
forall (m :: * -> *) a. Monad m => a -> m a
return [(String
flag, b
token)]
    process ((token1 :: b
token1, flag1 :: String
flag1):rest2 :: [(b, String)]
rest2@((token2 :: b
token2, flag2 :: String
flag2):rest :: [(b, String)]
rest)) = do
        Bool
takesArg <- String -> Map String Bool -> Maybe Bool
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
flag1 Map String Bool
flagMap
        if Bool
takesArg
            then do
                Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
flag2
                [(String, b)]
more <- [(b, String)] -> Maybe [(String, b)]
process [(b, String)]
rest
                [(String, b)] -> Maybe [(String, b)]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, b)] -> Maybe [(String, b)])
-> [(String, b)] -> Maybe [(String, b)]
forall a b. (a -> b) -> a -> b
$ (String
flag1, b
token2) (String, b) -> [(String, b)] -> [(String, b)]
forall a. a -> [a] -> [a]
: [(String, b)]
more
            else do
                [(String, b)]
more <- [(b, String)] -> Maybe [(String, b)]
process [(b, String)]
rest2
                [(String, b)] -> Maybe [(String, b)]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, b)] -> Maybe [(String, b)])
-> [(String, b)] -> Maybe [(String, b)]
forall a b. (a -> b) -> a -> b
$ (String
flag1, b
token1) (String, b) -> [(String, b)] -> [(String, b)]
forall a. a -> [a] -> [a]
: [(String, b)]
more

supportsArrays :: Shell -> Bool
supportsArrays shell :: Shell
shell = Shell
shell Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Bash Bool -> Bool -> Bool
|| Shell
shell Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Ksh

-- Returns true if the shell is Bash or Ksh (sorry for the name, Ksh)
isBashLike :: Parameters -> Bool
isBashLike :: Parameters -> Bool
isBashLike params :: Parameters
params =
    case Parameters -> Shell
shellType Parameters
params of
        Bash -> Bool
True
        Ksh -> Bool
True
        Dash -> Bool
False
        Sh -> Bool
False

return []
runTests :: IO Bool
runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])