{-# OPTIONS_HADDOCK show-extensions #-}

{- |
Module      : HaskBF.Parser
Description : Parse brainfuck program into a Program abstract datatype
Copyright   : (c) Sebastian Galkin, 2014
License     : MIT
Maintainer  : paraseba@gmail.com
Stability   : experimental

-}

module HaskBF.Parser (
       Program
     , Op (..)
     , parseProgram
     , module Text.Parsec.Error
     ) where

import Text.Parsec.Prim
  ( (<|>), runP, skipMany )

import Text.Parsec.Combinator
  ( between, sepEndBy, eof )

import Text.Parsec.Error
  ( ParseError )

import Text.Parsec.ByteString.Lazy
  ( Parser )

import Text.Parsec.Char
  ( space, spaces, char )

import Control.Applicative
  ( (<*), (<$>) )

import qualified Data.ByteString.Lazy as BS

-- | Brainfuck operations
data Op
  = IncP      -- ^ Increment the byte pointer: @ ">" @
  | DecP      -- ^ Decrement the byte pointer: @ "<" @
  | Inc       -- ^ Increment the byte pointed by the pointer: @ "+" @
  | Dec       -- ^ Decrement the byte pointed by the pointer: @ "-" @
  | PutByte   -- ^ Write the byte pointed by the pointer (side-effect): @ "." @
  | GetByte   -- ^ Read a byte, write at the current location
              -- (side-effect): @ "," @
  | Loop [Op] -- ^ Loop over 'Op's untill current @ == 0 @: @ "[...]" @
  deriving (Show, Eq)

-- | A program is a list of 'Op's. One possible 'Op' is a 'Loop' of other 'Op's
type Program = [Op]

program :: Parser Program
program = do
  skipMany space
  sepEndBy operation spaces

fullProgram :: Parser Program
fullProgram = program <* eof

operation :: Parser Op
operation = simpleOp <|> loop

simpleChar :: Parser Char
simpleChar = char '>' <|>
             char '<' <|>
             char '+' <|>
             char '-' <|>
             char '.' <|>
             char ','

simpleOp :: Parser Op
simpleOp = fmap build simpleChar
  where build '>' = IncP
        build '<' = DecP
        build '+' = Inc
        build '-' = Dec
        build '.' = PutByte
        build ',' = GetByte
        build _   = error "Unknown character" -- this should never happen

loop :: Parser Op
loop = Loop <$> between (char '[') (char ']') program

-- | Parse program stream. Returns an error or the parsed 'Program'
parseProgram :: BS.ByteString -> Either ParseError Program
parseProgram = runP fullProgram () ""