-- | Shell script types.
module Language.Bash.Syntax
    ( -- * Syntax
      -- ** Words
      Word
    , Subscript
      -- ** Commands
    , Command(..)
    , Redir(..)
    , IODesc(..)
    , ShellCommand(..)
    , CaseClause(..)
    , CaseTerm(..)
      -- * Lists
    , List(..)
    , Statement(..)
    , ListTerm(..)
    , AndOr(..)
    , Pipeline(..)
      -- * Assignments
    , Assign(..)
    , LValue(..)
    , AssignOp(..)
    , RValue(..)
      -- * Syntax elements
    , reservedWords
    , assignBuiltins
    , redirOps
    , heredocOps
    , controlOps
    , normalOps
    ) where

-- | A Bash word.
type Word = String

-- | A variable subscript @[...]@.
type Subscript = Word

-- | A Bash command with redirections.
data Command = Command ShellCommand [Redir]
    deriving (Eq, Read, Show)

-- | A redirection.
data Redir
    -- | A redirection.
    = Redir
        { -- | An optional file descriptor.
          redirDesc   :: Maybe IODesc
          -- | The redirection operator.
        , redirOp     :: String
          -- | The redirection target.
        , redirTarget :: Word
        }
    -- | A here document.
    | Heredoc
        { redirOp            :: String
          -- | The here document delimiter.
        , heredocDelim       :: String
          -- | 'True' if the delimiter was quoted.
        , heredocDelimQuoted :: Bool
          -- | The document itself.
        , document           :: String
        }
    deriving (Eq, Read, Show)

-- | A redirection file descriptor.
data IODesc
    -- | A file descriptor number.
    = IONumber Int
    -- | A variable @{/varname/}@ to allocate a file descriptor for.
    | IOVar String
    deriving (Eq, Read, Show)

-- | A Bash command.
data ShellCommand
    -- | A simple command consisting of assignments followed by words.
    = SimpleCommand [Assign] [Word]
    -- | The shell builtins @declare@, @eval@, @export@, @local@, @readonly@,
    -- and @typeset@ can accept both assignments and words as arguments.
    | AssignBuiltin Word [Either Assign Word]
    -- | A function name and definition.
    | FunctionDef String List
    -- | A named coprocess.
    | Coproc String Command
    -- | A @(...)@ list, denoting a subshell.
    | Subshell List
    -- | A @{...}@ list.
    | Group List
    -- | An arithmetic expression.
    | Arith String
    -- | A Bash @[[...]]@ conditional expression.
    | Cond [Word]
    -- | A @for /word/ in /words/@ command. If @in /words/@ is absent,
    -- the word list defaults to @\"$\@\"@.
    | For Word [Word] List
    -- | An arithmetic @for ((...))@ command.
    | ArithFor String List
    -- | A @select /word/ in /words/@ command. If @in /words/@ is absent,
    -- the word list defaults to @\"$\@\"@.
    | Select Word [Word] List
    -- | A @case@ command.
    | Case Word [CaseClause]
    -- | An @if@ command, with a predicate, consequent, and alternative.
    -- @elif@ clauses are parsed as nested @if@ statements.
    | If List List (Maybe List)
    -- | An @until@ command.
    | Until List List
    -- | A @while@ command.
    | While List List
    deriving (Eq, Read, Show)

-- | A single case clause.
data CaseClause = CaseClause [Word] List CaseTerm
    deriving (Eq, Read, Show)

-- | A case clause terminator.
data CaseTerm
    -- | The @;;@ operator.
    = Break
    -- | The @;&@ operator.
    | FallThrough
    -- | The @;;&@ operator.
    | Continue
    deriving (Eq, Ord, Read, Show, Bounded, Enum)

-- | A compound list of statements.
newtype List = List [Statement]
    deriving (Eq, Read, Show)

-- | A single statement in a list.
data Statement = Statement AndOr ListTerm
    deriving (Eq, Read, Show)

-- | A statement terminator.
data ListTerm
    -- | The @;@ operator.
    = Sequential
    -- | The @&@ operator.
    | Asynchronous
    deriving (Eq, Ord, Read, Show, Bounded, Enum)

-- | A right-associative list of pipelines.
data AndOr
    -- | The last pipeline of a list.
    = Last Pipeline
    -- | An @&&@ construct.
    | And Pipeline AndOr
    -- | An @||@ construct.
    | Or Pipeline AndOr
    deriving (Eq, Read, Show)

-- | A (possibly timed or inverted) pipeline, linked with @|@ or @|&@.
data Pipeline = Pipeline
    { -- | 'True' if the pipeline is timed with @time@.
      timed      :: Bool
      -- | 'True' if the pipeline is timed with the @-p@ flag.
    , timedPosix :: Bool
      -- | 'True' if the pipeline is inverted with @!@.
    , inverted   :: Bool
      -- | A list of commands, separated by @|@, or @|&@.
      -- @command1 |& command2@ is treated as a shorthand for
      -- @command1 2>&1 | command2@.
    , commands   :: [Command]
    } deriving (Eq, Read, Show)

-- | An assignment.
data Assign = Assign LValue AssignOp RValue
    deriving (Eq, Read, Show)

-- | The left side of an assignment.
data LValue = LValue String (Maybe Subscript)
    deriving (Eq, Read, Show)

-- | An assignment operator.
data AssignOp
    -- | The @=@ operator.
    = Equals
    -- | The @+=@ operator.
    | PlusEquals
    deriving (Eq, Ord, Read, Show, Bounded, Enum)

-- | The right side of an assignment.
data RValue
    -- | A simple word.
    = RValue Word
    -- | An array assignment.
    | RArray [(Maybe Subscript, Word)]
    deriving (Eq, Read, Show)

-- | Shell reserved words.
reservedWords :: [Word]
reservedWords =
    [ "!", "[[", "]]", "{", "}"
    , "if", "then", "else", "elif", "fi"
    , "case", "esac", "for", "select", "while", "until"
    , "in", "do", "done", "time", "function"
    ]

-- | Shell assignment builtins. These builtins can take assignments as
-- arguments.
assignBuiltins :: [Word]
assignBuiltins =
    [ "alias", "declare", "export", "eval"
    , "let", "local", "readonly", "typeset"
    ]

-- | Redirection operators, not including here document operators.
redirOps :: [String]
redirOps = [">", "<", ">>", ">|", "<>", "<<<", "<&", ">&", "&>", "&>>"]

-- | Here document operators.
heredocOps :: [String]
heredocOps = ["<<", "<<-"]

-- | Shell control operators.
controlOps :: [String]
controlOps =
    [ "(", ")", ";;", ";&", ";;&"
    , "|", "|&", "||", "&&", ";", "&", "\n"
    ]

-- | All normal operators.
normalOps :: [String]
normalOps = redirOps ++ heredocOps ++ controlOps