2011年12月19日月曜日

Pandoc で Blogger の記事を書く

Pandoc で 様々なフォーマットの文書を作成する

tanakhさんの記事で、Pandoc という Haskell 製の文書フォーマット変換ツールがあることを知りました。私は、Emacs + SKK で文書を書くのが最もストレスが低いので、Emacsで文書を書いて、他のフォーマットに変換できると非常に幸せです。とりあえず、Blogger の Blog 記事を書くときの Pandoc の活用方法についてメモしておきます。

インストール

> cabal install pandoc -fhighlighting
-fhighlighting でシンタックスハイライト機能を有効にしておきます。

使いかた

Pandoc のページ を見ると、Markdown, reStructuredText 等の様々なフォーマットから HTML, PDF, ... 等の多様なフォーマットに変換できることが書いてあります。
とりあえず、"Markdown 形式で文書を書いて、HTMLに変換し、Blogger の記事にする" という目的で使います。
この記事も勿論, Markdown で書いています。

変換のしかた

> pandoc markdown_file.txt -o output.html
で Markdown -> HTML に変換できます。

Markdown の文法

ココ(DALING FIREBALL)や, ココ(Wikipedia) が参考になるでしょう。

Haskell のソースを Syntax Highlighting する

Haskell のソースを Syntax Hilighting する方法を書きます。(そもそもコレをメモしたかったので、この記事を書きました)
通常、Markdown で program source を書くには、先頭に4文字以上の空白を入れてインデントすればいいみたいですが、これでは Syntax Highlight してくれないようです。また、私は空白でインデントする書き方が好きではありません。
ココに情報がありますが、ソースコード等を書くには
~~~~~~
source code here
~~~~~~
のように、tilde 6 個以上で囲んで、その間に書けばいいみたいです。
Haskell のコードを Syntax Highlight するには、
~~~~~~{.haskell}
hoge::[Char]->Int
hoge (x:xs) = undefined
~~~~~~
のように、上の行を ~~~~~~{.haskell} にすればいいみたいです。 行番号を入れるには、
1
2
3
4
~~~~~~{.haskell .numberLines}
hoge::[Char]->Int
hoge (x:xs) = undefined
~~~~~~
のように、上の行を ~~~~~~{.haskell .numberLines} にすればいいみたいです。
これを html に変換して Blogger の記事に貼ればよいのですが、色の部分は css で実現されているため、css を変更しないと色が付きません。
Blogger のテンプレートの カスタム css の部分に、以下の css を追加します。 (pandoc -s file.txt -o html で出力した HTML の
の部分から抽出したものです)
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode, table.sourceCode pre 
   { margin: 0; padding: 0; border: 0; vertical-align: baseline; border: none; }
td.lineNumbers { border-right: 1px solid #AAAAAA; text-align: right; color: #AAAAAA; padding-right: 5px; padding-left: 5px; }
td.sourceCode { padding-left: 5px; }
code.sourceCode span.kw { color: #007020; font-weight: bold; } 
code.sourceCode span.dt { color: #902000; }
code.sourceCode span.dv { color: #40a070; }
code.sourceCode span.bn { color: #40a070; }
code.sourceCode span.fl { color: #40a070; }
code.sourceCode span.ch { color: #4070a0; }
code.sourceCode span.st { color: #4070a0; }
code.sourceCode span.co { color: #60a0b0; font-style: italic; }
code.sourceCode span.ot { color: #007020; }
code.sourceCode span.al { color: red; font-weight: bold; }
code.sourceCode span.fu { color: #06287e; }
code.sourceCode span.re { }
code.sourceCode span.er { color: red; font-weight: bold; }
これで、上記の Markdown を変換した HTML を記事に貼れば、シンタックスハイライトされます。

2011年12月18日日曜日

Haskell で parser を書くには (初心者編)

Haskell で parser を書くには (初心者編)

勝手に Haskell Advent Calendar 2011

Haskell Advent Calendar 2011 にはエントリーできませんでしたけど、一人寂しく「勝手に Haskell Advent Calendar 2011」を開催して、わくわくクリスマスを待ちたいと思います。

目的:

Parsec3 と attoparsec の基本的使用法, Applicative スタイルを習得する

前置き:

Haskell で parser を書くにはどうすればいいのでしょうか? 私は、これを勉強すべく情報源を探しましたが、一体どこからどう始めればいいのか分からず、非常に混乱しました。「この記事を読めば大体概要が全部分かる」という情報源が日本語でも英語でも見つけられなかったからです。なので自分でまとめてみることにしました。 (私は、まだ Haskell 初心者+αのレベルで、parser の情報も現時点での情報ということをご理解ください)
私が混乱したのは、以下のような情報に出会ったからです。
  • Programming In Haskell に書いてある parser は、本の中のコードのみでは動かない
  • Haskell で parser を書くには、Parsec を使うとよいらしい
  • Parsec2 は使うな。Parsec3 使え。
  • Real World Haskell は Parsec2 で記述されているので古い
  • Parsec は遅い。attoparsec は速い。
  • attoparsec は、エラーメッセージが不親切
???。一体どうすればいいのでしょうか? Parsec はいいの?ダメなの? attoparsec はいいの?ダメなの? 最良の選択肢は、どれなの??
勉強会で質問してみた結果、以下のように理解しました。
  • デファクトスタンダードが どれか一つ というわけではなく、皆さんは "Parsec3" と "attoparsec" を使い分けているようだ
  • Parsec3 は attoparsec より速度は遅いが、エラーメッセージが親切
  • attoparsec は Parsec3 より速度は速いが、エラーメッセージが不親切
  • ニーズに応じて Parsec3 と attoparsec を使い分ける(もしくは両方使う)のがオススメ。でも、Parsec2 は古いので使わないことをオススメ
やっと状況が分かりました。Parsec3 と attoparsec を理解すればいいようです。しかし、それでも まだ困惑は残りました。なぜなら、Parsec3 と attoparsec の初心者向けの、よい Tutorial 記事を見つけられなかったからです。
この記事では、「Parsec3 と attoparsec の基本的な書きかたを理解する」ことを目的とします。副産物として、Applicative スタイルというのに触れます。
順番としては、以下の順番で書きます。
  • (1): 簡単な BNF を定義する
  • (2): Parsec3 で、(1) を解析してみる
  • (3): (2) を Applicative スタイルにする
  • (4): (3) を attoparsec に移植する
  • (5): さらに勉強するための情報源
尚, 内容のほとんどは、kazu-yamamoto さんの記事 を参考にさせていただいてます。(kazu さん ありがとうございます)

(1): 簡単な BNF を定義する

プログラミング Haskell の 8章の内容を多少簡単化して、 "1ケタの数字の加算と乗算ができるパーサー" を題材にします。BNF は以下です。
1
2
3
4
expr ::= term '+' expr | term
term ::= factor '*' term | factor
factor ::= '(' expr ')' | nat
nat ::= '0' | '1' | '2' | ...
1ケタの数値のみしか扱えませんが、()が使え、乗算が加算より優先順位が高く、'+','*'は右結合になっています。

(2): Parsec3 で、(1) を解析してみる

上記 (1) のBNF を解析し、data Exp 型の結果を返す parser を Parsec3 で実装します。Parsec3 を使うには
1
2
import Text.Parsec
import Text.Parsec.String
します。 次に、BNF の定義を Haskell のコードに対応させて実装していきます。BNF で、選択を意味する '|' は、Haskellでは、Text.Parsec.(<|>) という二項演算子です。 (本:プログラミングHaskell では、(+++) と記述されています) 一文字の記号 '+', '*' を parse するには
1
char c
を使います。文字が、複数の文字リスト(ex:数字列)のどれかにマッチすればよい場合は、
1
oneOf [Char]
を使います。 ParsecSample1.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import Data.Char (ord)
import Text.Parsec (char, oneOf, (<|>), parseTest)
import Text.Parsec.String (Parser)

data Exp = Add Exp Exp | Mul Exp Exp | Nat Int deriving Show
{- BNF of one digit addtion & multiplication
expr ::= term ('+' expr | ε)
term ::= factor ('*' term | ε)
factor ::= '(' expr ')' | nat
nat ::= '0' | '1' | '2' | ...
-}

-- expr ::= term ('+' expr | ε)
expr::Parser Exp
expr = 
  do 
    t <- term
    do
      char '+'
      e <- expr
      return (Add t e)
     <|> return t

-- term ::= factor ('*' term | ε)
term::Parser Exp
term = 
  do
    f <- factor
    do
      char '*'
      t <- term
      return (Mul f t)
     <|> return f

-- factor ::= '(' expr ')' | nat
factor::Parser Exp    
factor = 
  do
    char '('
    e <- expr
    char ')'
    return e
   <|> nat

-- nat ::= '0' | '1' | '2' | ...
nat::Parser Exp
nat = 
  do
    c <- oneOf ['0'..'9']
    return (Nat (charToInt c))
  where
    charToInt c = ord c - ord '0'

{- How to test this
parseTest expr "1+2*3"
parseTest expr "(1+2)*3"
-}
do の後に、素直に BNF の個々の定義を並べて処理して行けばよいのですが、ちょっと読みにくいですね(このままだと、「BNFをそのまま実装できる」は、ちょっと言い過ぎな感じ)。 動作をテストするには、ghci にロードして
1
2
parseTest expr "1+2*3"
parseTest expr "(1+2)*3"
等で解析結果を確認できます。

(3): (2) を Applicative スタイルにする

Applicative スタイル にすると、もっと簡潔に記述できます。やってみましょう。
ParsecSample2.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import Control.Applicative ((<$>),(*>),(<*),pure)
import Data.Char (ord)
import Text.Parsec (char, oneOf, (<|>), parseTest)
import Text.Parsec.String (Parser)

data Exp = Add Exp Exp | Mul Exp Exp | Nat Int deriving Show
{- BNF of one digit addtion & multiplication
expr ::= term ('+' expr | ε)
term ::= factor ('*' term | ε)
factor ::= '(' expr ')' | nat
nat ::= '0' | '1' | '2' | ...
-}

-- expr ::= term ('+' expr | ε)
expr::Parser Exp
expr = 
  do 
    t <- term
    (Add t <$> (char '+' *> expr)) <|> pure t

-- term ::= factor ('*' term | ε)
term::Parser Exp
term = 
  do
    f <- factor
    (Mul f <$> (char '*' *> term)) <|> pure f    

-- factor ::= '(' expr ')' | nat
factor::Parser Exp    
factor = (char '(' *> expr <* char ')') <|> nat

-- nat ::= '0' | '1' | '2' | ...
nat::Parser Exp
nat = Nat . charToInt <$> oneOf ['0'..'9']
  where
    charToInt c = ord c - ord '0'

{- How to test this
parseTest expr "1+2*3"
parseTest expr "(1+2)*3"
-}
ぐっと短くなりましたね。これなら BNF を、そのまま Haskell コードに実装できると言っても過言ではなさそうです。 Applicative スタイルの詳細は、kazu さんの記事を参照していただくとよいですが、
  • pure は return と同じ (値を返り値の型に wrap する)
  • (<$>) は fmap (文脈に持ち上げる)
  • (*>) は左の処理結果を捨てて連結
  • (<*) は右の処理結果を捨てて連結
という意味のようです(私も勉強中...。)

(4): (3) を attoparsec に移植する

なんとか Parsec3 を使って簡単な BNF を Haskell で解析できるようになりました。でも、attoparsec も使ってみたいです。やってみましょう。
1
cabal install attoparsec
等で適当にインストールしてください(省略)。 attoparsec を使うには、先程の Parsec3 の import の変わりに、以下を記述します。
1
2
import Data.Attoparsec.Text
import Data.Text
attoparsec は String ではなくて、ByteString と Text を parse するようです。ここでは、Data.Text を使います。
もしかして、import を変えれば、そのまま動いたりして、、と思ったのですが、Parsec3 にあった oneOf という関数が無いようです。attoparsec の satisfy 関数を使ってテキトーに作ります。
1
2
oneOf::[Char]->Parser Char -- attoparsec には oneOf がないので自作
oneOf xs = satisfy $ flip elem $ xs
ここだけ変更すれば、そのまま行けそうです。
AttoparsecSample1.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import Control.Applicative ((<$>),(*>),(<*),pure,(<|>))
import Data.Char (ord)
-- import Text.Parsec (char, oneOf, (<|>), parseTest)
-- import Text.Parsec.String (Parser)
import Data.Attoparsec.Text (char, Parser, parseTest, satisfy)
import Data.Text (pack)

oneOf::[Char]->Parser Char -- attoparsec には oneOf がないので自作
oneOf xs = satisfy $ flip elem $ xs

data Exp = Add Exp Exp | Mul Exp Exp | Nat Int deriving Show
{- BNF of one digit addtion & multiplication
expr ::= term ('+' expr | ε)
term ::= factor ('*' term | ε)
factor ::= '(' expr ')' | nat
nat ::= '0' | '1' | '2' | ...
-}

-- expr ::= term ('+' expr | ε)
expr::Parser Exp
expr = 
  do 
    t <- term
    (Add t <$> (char '+' *> expr)) <|> pure t

-- term ::= factor ('*' term | ε)
term::Parser Exp
term = 
  do
    f <- factor
    (Mul f <$> (char '*' *> term)) <|> pure f    

-- factor ::= '(' expr ')' | nat
factor::Parser Exp    
factor = (char '(' *> expr <* char ')') <|> nat

-- nat ::= '0' | '1' | '2' | ...
nat::Parser Exp
nat = Nat . charToInt <$> oneOf ['0'..'9']
  where
    charToInt c = ord c - ord '0'

{- How to test this
parseTest expr "1+2*3"
parseTest expr "(1+2)*3"

parseTest expr $ pack "1+2*3 "
parseTest expr $ pack "(1+2)*3 "
※ 最後にスペースがないと、成功していても "Partial _" という出力になる
-}
ghci に load してテストしてみましょう。
1
parseTest expr "1+2*3"
うまく行きませんね。あ、String じゃなくて、Text にしないとダメだったかな? pack で String->Text にしてみます。
1
2
> parseTest expr $ pack "1+2*3"
Partial _
あれ、"Partial _" とか言われますね。うまく行ってないのだろうか??? ちょっと末尾に余分なスペースを入れてみよう。
1
2
> parseTest expr $ pack "1+2*3 "
Done " " Add (Nat 1) (Mul (Nat 2) (Nat 3))
どうやら、直前までは、うまく解析できてるみたい。。よかったー(?)。 今日は、この辺で勘弁してください。attoparsec を深く理解するには、ByteString,Text あたりの知識が必要かも(ByteString, Text については、私もまだ勉強していません)

(5): さらに勉強するための情報源

なんとか、Parsec3 と attoparsec を使って、動作するコードを実装し、雰囲気を掴みました。あとは、情報があれば、自分で勉強していけそうです。勉強会, Twitter で、参考になる情報源を紹介していただいたので、ここにまとめてみます。
Parsec3 や、attoparsec の使い方について、もう少し詳しい解説記事があると、喜ぶ人が多そうです(私も含めて)。私も上記の文書は、まだ勉強できていませんが、理解が進んだらまた紹介したいと思います。