インストール

まず始めに、Peggyをあなたのシステムにインストールしましょう。 既にHaskell Platformがインストールされているなら、 次のコマンドをタイプするだけでインストールできます。 Haskell Platformがインストールされていない場合は、 各プラットフォームでの手順に従い、 まずそちらをインストールしてください。

$ cabal update
$ cabal install Peggy

さあ、Peggyが利用できるようになりました。

最初の例

まず、簡単な例を用いて文法を見ていきます。

{-# Language TemplateHaskell, QuasiQuotes, FlexibleContexts #-}

import Text.Peggy

[peggy|
nums :: [Int]
  = num*
num ::: Int
  = [0-9]+ { read $1 }
|]

main :: IO ()
main = print . parseString nums "<stdin>" =<< getContents

これは、数の列をパーズするパーザです。 実際に動かしてみましょう。

$ cat input 
1 2 3 4 5
$ runhaskell Test.hs < input
Right [1,2,3,4,5]

正しく動いているようですね(´・_・`)!

Haskell言語拡張

Peggyを使用するにあたって、最初の行に次のプラグマを書く必要があります。

{-# Language TemplateHaskell, QuasiQuotes, FlexibleContexts #-}

これらの拡張は、peggyをEDSLとして使うためと、 peggyが内部的に生成するコードをコンパイルさせるために必要になります。

Peggyモジュールのimport

Peggyを使うためには、Peggyのモジュールをimportする必要があります。 次の一つの行を単純に追加するだけです。

import Text.Peggy

パーザの定義

さて、ようやくパーザを書く準備が整いました。 Peggyは embeded DSL (EDSL) スタイルのパーザジェネレータですので、 パーザはHaskellのソースコード中に直に書き込みます。

Peggyはquasi-quoterとして提供されます。 パーザを定義するためには、quasi-quoter [peggy| … |] にて、パーザの定義を囲います。

[peggy|
nums :: [Int]
  = num*
... ここに文法を記述
|]

パーザは定義の集合です。 定義にはPEGのフル機能と、幾つかの独自拡張が利用できます。 Peggyの構文の詳細はこちらを参照してください。

典型的な定義の形

典型的な構文定義は次のような形になっています。

<name> :: <Haskell-Type>
  = <expr> ... { <semantic-by-haskell-code> }
  / <expr> ... { ... }
  ...

各定義は幾つかの代替部を持っています。 そしてそのそれぞれは、セマンティクスの付いた式の列になっています。 セマンティクスは、プレースホルダを含む、単一のHaskellの式として表現されます。 これは各非終端記号のパーズ結果の値として用いられます。

トークン(字句)

Peggyは暗黙的にトークンを扱います。 トークンを定義するには、型名の前のdouble-colon (::) を triple-colon (:::) に変えます。

num ::: Int
  = [0-9]+ { read $1 }

これで、前後の空白を読み飛ばし、デミリタ区切りになりました。 トークンの振る舞いの詳細は構文マニュアルを参照してください。

生成されたパーザ

Peggyのquasi-quoterは パーザ の集合を生成します。 各非終端記号の定義一つに対し、一つのパーザが定義されます。

nums :: Parser [Int] -- これは単純化した型であり、実際の型とは異なります
nums = ... (Haskell code generated by Peggy)

パーザを起動する

ついに、パーザを動作させる準備が整いました。 次の関数を用いて、任意の文字列をパーズすることができます。

parseString :: ListLike str Char
               => Parser a
               -> String
               -> str
               -> Either ParseError a
parseString parser inputName input =
  ...

quasi-quoter を生成する

Peggyはユーザ定義のquasi-quoterを簡単に生成することができます。

module Nums (numsqq) where

genParser [("numsqq", "nums")] [peggy|
nums :: [Int]
  = num*
num ::: Int
  = [0-9]+ { read $1 }
|]

genParserという関数を用いてパーザの生成を行います。 このとき、第一引数には定義したいquasi-quoterの情報を渡します。 これは、定義するquasi-quoterの名前と、それが利用する非終端記号の名前のペアのリストです。 この場合だと、numsqqというquasi-quoterが生成されます。

Template Haskellの制限により、この定義は利用するモジュールから分離しなければなりません。

{-# Language TemplateHaskell, QuasiQuotes, FlexibleContexts #-}
import Nums

main :: IO ()
main = print [numsqq| 1 2 3 4 5 |]

さて、使ってみましょう。

$ runhaskell Test.hs
[1, 2, 3, 4, 5]

期待した動作です。

さらなる学習のために

  • もう少し複雑な幾つかの例がここにあります
  • Peggy構文の完全な記述がここにあります
  • PeggyによるPeggy文法の自己記述ファイルがここにあります。Peggyのパーザは実際にこのファイルからブートストラップされています。
  • github のレポジトリがここにあります。パッチ・プルリクエスト大歓迎です。