-- | This module provides the token type used in the lexer and
-- parser and provides the extra pass to insert layout tokens.
module Config.Tokens
  ( Token(..)
  , Located(..)
  , Position(..)
  , startPos
  , Error(..)
  , layoutPass
  ) where

import Data.Text (Text)
import Config.Number

-- | A position in a text file
data Position = Position
  { posIndex, posLine, posColumn :: {-# UNPACK #-} !Int }
  deriving (Read, Show, Ord, Eq)

-- | The initial 'Position' for the start of a file
startPos :: Position
startPos = Position { posIndex = 0, posLine = 1, posColumn = 1 }

-- | A value annotated with its text file position
data Located a = Located
  { locPosition :: {-# UNPACK #-} !Position
  , locThing    :: !a
  }
  deriving (Read, Show)

instance Functor Located where
  fmap f (Located p x) = Located p (f x)

-- | The token type used by "Config.Lexer" and "Config.Parser"
data Token
  = Section Text
  | String Text
  | Atom Text
  | Bullet
  | Comma
  | Number Number
  | OpenList
  | CloseList
  | OpenMap
  | CloseMap

  | Error Error

  -- "Virtual" tokens used by the subsequent layout processor
  | LayoutSep
  | LayoutEnd
  | EOF
  deriving (Show)

-- | Types of lexical errors
data Error
  = UntermComment
  | UntermString
  | UntermList
  | UntermSections
  | BadEscape Text
  | NoMatch Char
  deriving (Show)

-- | Process a list of position-annotated tokens inserting
-- layout end tokens as appropriate.
layoutPass ::
  [Located Token] {- ^ tokens without layout markers -} ->
  [Located Token] {- ^ tokens with    layout markers -}
layoutPass toks = foldr step (\_ -> []) toks [Layout (-1)]

data Layout = NoLayout | Layout Int

-- | Single step of the layout pass
step ::
  Located Token                 {- ^ current token            -} ->
  ([Layout] -> [Located Token]) {- ^ continuation             -} ->
  [Layout]                      {- ^ stack of layout scopes   -} ->
  [Located Token]               {- ^ token stream with layout -}

-- start blocks must be indented
-- tokens before the current layout end the current layout
-- note that EOF occurs on column 1 for properly formatted text files
step t next cols =
  case cols of
    NoLayout:cols' | CloseMap <- locThing t -> t : next cols'
    _              | OpenMap  <- locThing t -> t : next (NoLayout : cols)
    Layout col:_     | toCol t == col -> t{locThing=LayoutSep} : t : next cols
    Layout col:cols' | toCol t <  col -> t{locThing=LayoutEnd} : step t next cols'
    Layout{}:_       | usesLayout t   -> t : next (Layout (toCol t) : cols)
    _                                 -> t : next cols

-- | Extract the column number from a located thing.
toCol :: Located a -> Int
toCol = posColumn . locPosition


-- | Return True when a token starts a layout scope.
usesLayout :: Located Token -> Bool
usesLayout t
  | Section{} <- locThing t = True
  | Bullet    <- locThing t = True
  | otherwise               = False
