ParsecでByteStringを使う場合、Text.Parsec.ByteStringやText.Parsec.ByteString.Lazyを使いますが、これらのパッケージは、Data.ByteString.Char8やData.ByteString.Lazy.Char8を中で使っているので、非ASCIIな文字列を正しくパースできません。
UTF-8のバイト列がByteStringに格納されている場合には、utf8-stringパッケージのData.ByteString.UTF8やData.ByteString.Lazy.UTF8を使うとパースできるようになります。まずは、Streamとして扱えるように、以下のようにインスタンスを宣言します。
import qualified Data.ByteString.Lazy.UTF8 as U instance Monad m => Stream U.ByteString m Char where uncons = return . U.uncons
これで、UTF-8の一文字ずつを読み込むストリームができたので、あとはByteStringを普通にパースします。
import qualified Data.ByteString.Lazy as B import Text.Parsec.Prim parseFile path = do input <- B.readFile path result <- runPT parser () path input case result of Right m -> return m Left e -> ioError $ userError $ show e
ただし、Text.Parsecをインポートすると、Text.Parsec.ByteString.Lazyもインポートされてしまうため、ここで定義されているChar8版のインスタンス宣言とぶつかってしまいます。回避するためには、Text.Parsec.PrimやText.Parsec.Combinatorなどなどを個別にインポートする必要があります。
もしくは、newtypeで別の型にして、そちらを使うか。
newtype UTF8ByteString = UTF8ByteString B.ByteString instance Monad m => Stream UTF8ByteString m Char where uncons (UTF8ByteString s) = return $ second UTF8ByteString <$> U.uncons s