LoginSignup
4
1

More than 5 years have passed since last update.

Haskellでバイナリをパースする その1(たぶん続かない)

Posted at

概要

binaryパッケージのGetとrunGetを使っていきます。
Parsecを使ったことがある人なら感覚的にわかると思います。
今回は例としてMMDのモデルデータの形式であるpmxのバージョンを調べたいと思います

pmxの仕様

pmxの仕様はなんとpmxエディタに同梱されてます。
とある工房でpmxエディタをダウンロードしたらLib/PMX仕様/PMX仕様.txtにあります。

154行目あたりを見ると分かる通り、pmxのヘッダは"PMX "というASCII文字列(4バイト)の後にヴァージョンがfloat(4バイト)で書いてあるみたいなのでこれを読みましょう!

Haskellの方の準備

stack new pmx simpleしてpmx.cabalはこんなかんじで

pmx.cabal
name:                pmx
version:             0.1.0.0
synopsis:            Simple project template from stack
description:         Please see README.md
homepage:            https://github.com/githubuser/pmx#readme
license:             BSD3
license-file:        LICENSE
author:              Author name here
maintainer:          example@example.com
copyright:           2016 Author name here
category:            Web
build-type:          Simple
cabal-version:       >=1.10

executable pmx
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5
                     , bytestring
                     , binary >= 0.8.4.0

読み込み

コード

大体見たらわかると思います。
入力された名前のpmxファイルを開いてパースしてPmxHeader型にして表示するだけ。
GetはMonadとApplicativeのインスタンスになっているのでParsecでパーサ書くみたいな感覚で書けます。
注意点としてpmxはバイト順がリトルエンディアンなのでバージョンを取得するのにgetFloatleを使ってやる必要があります。

Main.hs
module Main where

import Control.Applicative ()
import System.IO
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as B
import Data.Binary.Get
import Data.Char (chr)

data PmxHeader = PmxHeader { getPmxVersion :: Float
                           } deriving (Show, Read)

main :: IO ()
main = do
  putStr ">> "
  hFlush stdout
  s <- getLine
  readPmx s

readPmx :: String -> IO ()
readPmx f = do
    rh <- openFile f ReadMode
    i  <- BL.hGetContents rh
    let d = runGet parsePmxHeader i
    print d

parsePmxHeader :: Get PmxHeader
parsePmxHeader = PmxHeader <$> parsePmxVersion

parsePmxVersion :: Get Float
parsePmxVersion = do
  s <- getString 4
  if s == "PMX " then getFloat else return 1.0

getString :: Int -> Get String
getString n = map (chr . fromEnum) . B.unpack <$> getByteString n

getInt :: Get Int
getInt = fromIntegral <$> getWord32le

getFloat :: Get Float
getFloat = getFloatle

実行結果

$ stack build && stack exec pmx
Setting codepage to UTF-8 (65001) to ensure correct output from GHC
>> a.pmx
PmxHeader {getPmxVersion = 2.0}

今回はここまで。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1