{-| Description : Brainfuck parsing Copyright : (c) Sebastian Galkin, 2018 License : GPL-3 Parsing 'Text' into 'Program' 'Unparsed' -} module HBF.Parser ( module HBF.Parser -- * Reexport from "Text.Parsec" , Text.Parsec.ParseError ) where import Control.Applicative ((<|>)) import Data.Text.Lazy (Text) import Text.Parsec (ParseError, between, eof, many, many1, runP) import Text.Parsec.Char (char, noneOf, oneOf) import Text.Parsec.Text.Lazy (Parser) import HBF.Types -- $setup -- >>> :set -XOverloadedStrings -- >>> import Data.Either -- >>> let parse :: Parser a -> Text -> Either ParseError a; parse p text = runP p () "" text -- | Parser for a full 'Program'. -- -- >>> isRight $ parse program " +[->>+ +[<] ##garbage## ],.[-] can ignore garbage" -- True program :: Parser (Program Unoptimized) program = Program <$> many1 operation -- | Parser for an 'Op', ignoring unknown characters. -- -- >>> parse operation " +///" -- Right (Inc 1 0) -- -- >>> parse operation "fooo [+>] baaar " -- Right (Loop [Inc 1 0,Move 1]) operation :: Parser Op operation = many garbage *> (simpleOp <|> loopOp) <* many garbage -- | The characters allowed in a Brainfuck program except for the loop characters @[@ and @]@. bfSimpleTokens :: String bfSimpleTokens = "><+-.," -- | The characters allowed in a Brainfuck program. bfTokens :: String bfTokens = "[]" ++ bfSimpleTokens -- | Parser for unknown characters -- -- >>> parse garbage "this is @#! garbage" -- Right 't' -- -- >>> isLeft $ parse garbage "+" -- True garbage :: Parser Char garbage = noneOf bfTokens -- | Parser for simple operations (not loops). -- -- >>> parse simpleOp ">" -- Right (Move 1) -- -- >>> parse simpleOp "." -- Right (Out 1 0) simpleOp :: Parser Op simpleOp = build <$> oneOf bfSimpleTokens where build '>' = Move 1 build '<' = Move (-1) build '+' = Inc 1 0 build '-' = Inc (-1) 0 build '.' = Out 1 0 build ',' = In 1 0 build _ = error "Unknown character" -- | Parser for loops. -- -- >>> parse loopOp "[+-]" -- Right (Loop [Inc 1 0,Inc (-1) 0]) loopOp :: Parser Op loopOp = Loop . instructions <$> between (char '[') (char ']') program -- | Parse program stream. Returns an error or the parsed 'Program' parseProgram :: Text -> Either ParseError (Program Unoptimized) parseProgram = runP (program <* eof) () ""