diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Changelog for `sequitur`
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to the
+[Haskell Package Versioning Policy](https://pvp.haskell.org/).
+
+## Unreleased
+
+## 0.1.0.0 - 2024-07-13
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2024, Masahiro Sakai
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,59 @@
+# Haskell implementation of _SEQUITUR_ algorithm
+
+[![build](https://github.com/msakai/haskell-sequitur/actions/workflows/build.yaml/badge.svg)](https://github.com/msakai/haskell-sequitur/actions/workflows/build.yaml)
+
+## About _SEQUITUR_
+
+_SEQUITUR_ is a linear-time, online algorithm for producing a context-free
+grammar from an input sequence. The resulting grammar is a compact representation
+of original sequence and can be used for data compression.
+
+Example:
+
+- Input string: `abcabcabcabcabc`
+
+- Resulting grammar
+  - `S` → `AAB`
+  - `A` → `BB`
+  - `B` → `abc`
+
+_SEQUITUR_ consumes input symbols one-by-one and append each symbol at the end of the
+grammar's start production (`S` in the above example), then substitutes repeating
+patterns in the given sequence with new rules. _SEQUITUR_ maintains two invariants:
+
+* **Digram Uniqueness**: _SEQUITUR_ ensures that no digram
+  (a.k.a. bigram) occurs more than once in the grammar. If a digram
+  (e.g. `ab`) occurs twice, SEQUITUR introduce a fresh non-terminal
+  symbol (e.g. `M`) and a rule (e.g. `M` → `ab`) and replace
+  original occurences with the newly introduced non-terminals.  One
+  exception is the cases where two occurrence overlap.
+
+* **Rule Utility**: If a non-terminal symbol occurs only once,
+  _SEQUITUR_ removes the associated rule and substitute the occurence
+  with the right-hand side of the rule.
+
+## Usage
+
+```console
+ghci> import Language.Grammar.Sequitur
+ghci> encode "baaabacaa"
+fromList [(0,[NonTerminal 1,NonTerminal 2,NonTerminal 1,Terminal 'c',NonTerminal 2]),(1,[Terminal 'b',Terminal 'a']),(2,[Terminal 'a',Terminal 'a'])]
+```
+
+The output represents the following grammar:
+
+```
+0 → 1 2 1 c 2
+1 → b a
+2 → a a
+```
+
+
+## References
+
+- [Sequitur algorithm - Wikipedia](https://en.m.wikipedia.org/wiki/Sequitur_algorithm)
+- [sequitur.info](http://www.sequitur.info/)
+- Nevill-Manning, C.G. and Witten, I.H. (1997) "[Identifying
+  Hierarchical Structure in Sequences: A linear-time
+  algorithm](https://doi.org/10.1613/jair.374)," Journal of
+  Artificial Intelligence Research, 7, 67-82.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/sequitur.cabal b/sequitur.cabal
new file mode 100644
--- /dev/null
+++ b/sequitur.cabal
@@ -0,0 +1,62 @@
+cabal-version: 2.2
+
+-- This file has been generated from package.yaml by hpack version 0.36.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           sequitur
+version:        0.1.0.0
+synopsis:       Grammar-based compression algorithms SEQUITUR
+description:    Please see the README on GitHub at <https://github.com/msakai/haskell-sequitur#readme>
+category:       Formal Languages, Language, Natural Language Processing, NLP, Text, Compression
+homepage:       https://github.com/msakai/haskell-sequitur#readme
+bug-reports:    https://github.com/msakai/haskell-sequitur/issues
+author:         Masahiro Sakai
+maintainer:     masahiro.sakai@gmail.com
+copyright:      Copyright (c) 2024 Masahiro Sakai
+license:        BSD-3-Clause
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/msakai/haskell-sequitur
+
+library
+  exposed-modules:
+      Language.Grammar.Sequitur
+  other-modules:
+      Paths_sequitur
+  autogen-modules:
+      Paths_sequitur
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
+  build-depends:
+      base >=4.7 && <5
+    , containers >=0.6.4.1 && <0.7
+    , hashable >=1.3.0.0 && <1.5
+    , hashtables >=1.2.4.2 && <1.4
+    , primitive >=0.7.3.0 && <0.10
+  default-language: Haskell2010
+
+test-suite sequitur-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Paths_sequitur
+  autogen-modules:
+      Paths_sequitur
+  hs-source-dirs:
+      test
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      QuickCheck >=2.14.2 && <2.15
+    , base >=4.7 && <5
+    , containers >=0.6.4.1 && <0.7
+    , hspec >=2.7.10 && <2.12
+    , sequitur
+  default-language: Haskell2010
diff --git a/src/Language/Grammar/Sequitur.hs b/src/Language/Grammar/Sequitur.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/Grammar/Sequitur.hs
@@ -0,0 +1,514 @@
+{-# OPTIONS_GHC -Wall #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Language.Grammar.Sequitur
+-- Copyright   :  (c) Masahiro Sakai 2024
+-- License     :  BSD-style
+--
+-- Maintainer  :  masahiro.sakai@gmail.com
+-- Stability   :  provisional
+-- Portability :  non-portable
+--
+-- /SEQUITUR/ is a linear-time, online algorithm for producing a context-free
+-- grammar from an input sequence. The resulting grammar is a compact representation
+-- of original sequence and can be used for data compression.
+--
+-- Example:
+--
+--   - Input string: @abcabcabcabcabc@
+--
+--   - Resulting grammar
+--
+--       - @S@ → @AAB@
+--
+--       - @A@ → @BB@
+--
+--       - @B@ → @abc@
+--
+-- /SEQUITUR/ consumes input symbols one-by-one and append each symbol at the end of the
+-- grammar's start production (@S@ in the above example), then substitutes repeating
+-- patterns in the given sequence with new rules. /SEQUITUR/ maintains two invariants:
+--
+--   [/Digram Uniqueness/]: /SEQUITUR/ ensures that no digram
+--   (a.k.a. bigram) occurs more than once in the grammar. If a digram
+--   (e.g. @ab@) occurs twice, SEQUITUR introduce a fresh non-terminal
+--   symbol (e.g. @M@) and a rule (e.g. @M@ → @ab@) and replace
+--   original occurences with the newly introduced non-terminals.  One
+--   exception is the cases where two occurrence overlap.
+--
+--   [/Rule Utility/]: If a non-terminal symbol occurs only once,
+--   /SEQUITUR/ removes the associated rule and substitute the occurence
+--   with the right-hand side of the rule.
+--
+-- References:
+--
+--   - [Sequitur algorithm - Wikipedia](https://en.m.wikipedia.org/wiki/Sequitur_algorithm)
+--
+--   - [sequitur.info](http://www.sequitur.info/)
+--
+--   - Nevill-Manning, C.G. and Witten, I.H. (1997) "[Identifying
+--     Hierarchical Structure in Sequences: A linear-time
+--     algorithm](https://doi.org/10.1613/jair.374)," Journal of
+--     Artificial Intelligence Research, 7, 67-82.
+--
+-----------------------------------------------------------------------------
+module Language.Grammar.Sequitur
+  (
+  -- * Basic type definition
+    Grammar
+  , RuleId
+  , Symbol (..)
+
+  -- * High-level API
+  --
+  -- Use these APIs if the entire sequence is given at once and you
+  -- only need to create a single grammar from it.
+  , encode
+  , decode
+  , decodeLazy
+  , decodeToSeq
+  , decodeToMonoid
+
+  -- * Low-level monadic API
+  --
+  -- Use these low-level monadic API if the input sequence is given
+  -- incrementally, or you want to re-construct grammar after you
+  -- receive additinal inputs.
+  , Builder
+  , newBuilder
+  , add
+  , build
+  ) where
+
+import Control.Exception
+import Control.Monad
+import Control.Monad.Primitive
+import Control.Monad.ST
+import Data.Either
+import qualified Data.Foldable as F
+import Data.Function (on)
+import Data.Hashable
+import Data.IntMap.Strict (IntMap)
+import qualified Data.IntMap.Strict as IntMap
+import Data.Primitive.MutVar
+import qualified Data.HashTable.Class as H (toList)
+import qualified Data.HashTable.ST.Cuckoo as H
+import Data.Maybe
+import Data.Semigroup (Endo (..))
+import Data.Sequence (Seq)
+import qualified Data.Sequence as Seq
+import GHC.Generics (Generic)
+import GHC.Stack
+
+-- TODO:
+--
+-- * Use PrimVar after dropping support for primitive <0.8.0.0
+--
+-- * Remove Eq requirements after dropping support for hashable <1.4.0.0
+
+-- -------------------------------------------------------------------
+
+sanityCheck :: Bool
+sanityCheck = False
+
+-- -------------------------------------------------------------------
+
+-- | A non-terminal symbol is represented by an 'Int'.
+--
+-- The number @0@ is reserved for the start symbol of the grammar.
+type RuleId = Int
+
+-- | A symbol is either a terminal symbol (from user-specified type)
+-- or a non-terminal symbol which we represent using 'RuleId' type.
+data Symbol a
+  = NonTerminal !RuleId
+  | Terminal !a
+  deriving (Eq, Ord, Show, Generic)
+
+instance (Hashable a) => Hashable (Symbol a)
+
+type Digram a = (Symbol a, Symbol a)
+
+-- | A grammar is a mappping from non-terminal (left-hand side of the
+-- rule) to sequnce of symbols (right hand side of the rule).
+--
+-- Non-terminal is represented as a 'RuleId'.
+type Grammar a = IntMap [Symbol a]
+
+-- -------------------------------------------------------------------
+
+data Node s a
+  = Node
+  { nodePrev :: {-# UNPACK #-} !(MutVar s (Node s a))
+  , nodeNext :: {-# UNPACK #-} !(MutVar s (Node s a))
+  , nodeData :: Either RuleId (Symbol a)
+  } deriving (Generic)
+
+instance Eq (Node s a) where
+  (==) = (==) `on` nodePrev
+
+isGuardNode :: Node s a -> Bool
+isGuardNode s = isLeft $ nodeData s
+
+nodeSymbolMaybe :: Node s a -> Maybe (Symbol a)
+nodeSymbolMaybe node =
+  case nodeData node of
+    Left _ -> Nothing
+    Right sym -> Just sym
+
+nodeSymbol :: HasCallStack => Node s a -> Symbol a
+nodeSymbol node =
+  case nodeSymbolMaybe node of
+    Nothing -> error "nodeSymbol is called for guard node"
+    Just sym -> sym
+
+ruleOfGuardNode :: Node s a -> Maybe RuleId
+ruleOfGuardNode node =
+  case nodeData node of
+    Left rule -> Just rule
+    Right _ -> Nothing
+
+getPrev :: PrimMonad m => Node (PrimState m) a -> m (Node (PrimState m) a)
+getPrev node = readMutVar (nodePrev node)
+
+getNext :: PrimMonad m => Node (PrimState m) a -> m (Node (PrimState m) a)
+getNext node = readMutVar (nodeNext node)
+
+setPrev :: PrimMonad m => Node (PrimState m) a -> Node (PrimState m) a -> m ()
+setPrev node prev = writeMutVar (nodePrev node) prev
+
+setNext :: PrimMonad m => Node (PrimState m) a -> Node (PrimState m) a -> m ()
+setNext node next = writeMutVar (nodeNext node) next
+
+mkGuardNode :: PrimMonad m => RuleId -> m (Node (PrimState m) a)
+mkGuardNode rid = do
+  prevRef <- newMutVar undefined
+  nextRef <- newMutVar undefined
+  let node = Node prevRef nextRef (Left rid)
+  writeMutVar prevRef node
+  writeMutVar nextRef node
+  return node
+
+-- -------------------------------------------------------------------
+
+data Rule s a
+  = Rule
+  { ruleId :: {-# UNPACK #-} !RuleId
+  , ruleGuardNode :: !(Node s a)
+  , ruleRefCounter :: {-# UNPACK #-} !(MutVar s Int)
+  }
+
+instance Eq (Rule s a) where
+  (==) = (==) `on` ruleId
+
+instance Hashable (Rule s a) where
+  hashWithSalt salt rule = hashWithSalt salt (ruleId rule)
+
+getFirstNodeOfRule :: PrimMonad m => Rule (PrimState m) a -> m (Node (PrimState m) a)
+getFirstNodeOfRule rule = getNext (ruleGuardNode rule)
+
+getLastNodeOfRule :: PrimMonad m => Rule (PrimState m) a -> m (Node (PrimState m) a)
+getLastNodeOfRule rule = getPrev (ruleGuardNode rule)
+
+mkRule :: PrimMonad m => RuleId -> m (Rule (PrimState m) a)
+mkRule rid = do
+  g <- mkGuardNode rid
+  refCounter <- newMutVar 0
+  return $ Rule rid g refCounter
+
+newRule :: PrimMonad m => Builder (PrimState m) a -> m (Rule (PrimState m) a)
+newRule s = do
+  rid <- readMutVar (sRuleIdCounter s)
+  modifyMutVar' (sRuleIdCounter s) (+ 1)
+  rule <- mkRule rid
+  stToPrim $ H.insert (sRules s) rid rule
+  return rule
+
+-- -------------------------------------------------------------------
+
+-- | 'Builder' denotes a internal state of the /SEQUITUR/ algorithm.
+data Builder s a
+  = Builder
+  { sRoot :: !(Rule s a)
+  , sDigrams :: !(H.HashTable s (Digram a) (Node s a))
+  , sRules :: !(H.HashTable s RuleId (Rule s a))
+  , sRuleIdCounter :: {-# UNPACK #-} !(MutVar s Int)
+  , sDummyNode :: !(Node s a)
+  }
+
+-- | Create a new 'Builder'.
+newBuilder :: PrimMonad m => m (Builder (PrimState m) a)
+newBuilder = do
+  root <- mkRule 0
+  digrams <- stToPrim $ H.new
+  rules <- stToPrim $ H.new
+  counter <- newMutVar 1
+
+  prevRef <- newMutVar undefined
+  nextRef <- newMutVar undefined
+  let dummyNode = Node prevRef nextRef (Left 0)
+  writeMutVar prevRef dummyNode
+  writeMutVar nextRef dummyNode
+
+  return $ Builder root digrams rules counter dummyNode
+
+getRule :: (PrimMonad m, HasCallStack) => Builder (PrimState m) a -> RuleId -> m (Rule (PrimState m) a)
+getRule s rid = stToPrim $ do
+  ret <- H.lookup (sRules s) rid
+  case ret of
+    Nothing -> error "getRule: invalid rule id"
+    Just rule -> return rule
+
+-- | Add a new symbol to the end of grammar's start production,
+-- and perform normalization to keep the invariants of /SEQUITUR/ algorithm.
+add :: (PrimMonad m, Eq a, Hashable a) => Builder (PrimState m) a -> a -> m ()
+add s a = do
+  lastNode <- getLastNodeOfRule (sRoot s)
+  _ <- insertAfter s lastNode (Terminal a)
+  _ <- check s lastNode
+  return ()
+
+-- | Retrieve a grammar (as a persistent data structure) from 'Builder'\'s internal state.
+build :: (PrimMonad m) => Builder (PrimState m) a -> m (Grammar a)
+build s = do
+  root <- freezeGuardNode $ ruleGuardNode (sRoot s)
+  xs <- stToPrim $ H.toList (sRules s)
+  m <- forM xs $ \(rid, rule) -> do
+    ys <- freezeGuardNode (ruleGuardNode rule)
+    return $ (rid, ys)
+  return $ IntMap.insert 0 root $ IntMap.fromList m
+
+freezeGuardNode :: forall a m. (PrimMonad m) => Node (PrimState m) a -> m [Symbol a]
+freezeGuardNode g = f [] =<< getPrev g
+  where
+    f :: [Symbol a] -> Node (PrimState m) a -> m [Symbol a]
+    f ret node = do
+      if isGuardNode node then
+        return ret
+      else do
+        node' <- getPrev node
+        f (nodeSymbol node : ret) node'
+
+-- -------------------------------------------------------------------
+
+link :: (PrimMonad m, Eq a, Hashable a) => Builder (PrimState m) a -> Node (PrimState m) a -> Node (PrimState m) a -> m ()
+link s left right = do
+  leftPrev <- getPrev left
+  leftNext <- getNext left
+  rightPrev <- getPrev right
+  rightNext <- getNext right
+
+  unless (isGuardNode leftNext) $ do
+    deleteDigram s left
+
+    -- これが不要なのは何故?
+    -- unless (isGuardNode rightPrev) $ deleteDigram s rightPrev
+
+    case (nodeSymbolMaybe rightPrev, nodeSymbolMaybe right, nodeSymbolMaybe rightNext) of
+      (Just sym1, Just sym2, Just sym3) | sym1 == sym2 && sym2 == sym3 ->
+        stToPrim $ H.insert (sDigrams s) (sym2, sym3) right
+      _ -> return ()
+
+    case (nodeSymbolMaybe leftPrev, nodeSymbolMaybe left, nodeSymbolMaybe leftNext) of
+      (Just sym1, Just sym2, Just sym3) | sym1 == sym2 && sym2 == sym3 ->
+        stToPrim $ H.insert (sDigrams s) (sym1, sym2) leftPrev
+      _ -> return ()
+
+  setNext left right
+  setPrev right left
+
+insertAfter :: (PrimMonad m, Eq a, Hashable a, HasCallStack) => Builder (PrimState m) a -> Node (PrimState m) a -> Symbol a -> m (Node (PrimState m) a)
+insertAfter s node sym = do
+  prevRef <- newMutVar (sDummyNode s)
+  nextRef <- newMutVar (sDummyNode s)
+  let newNode = Node prevRef nextRef (Right sym)
+
+  next <- getNext node
+  link s newNode next
+  link s node newNode
+
+  case sym of
+    Terminal _ -> return ()
+    NonTerminal rid -> do
+      rule <- getRule s rid
+      modifyMutVar' (ruleRefCounter rule) (+ 1)
+
+  return newNode
+
+deleteDigram :: (PrimMonad m, Eq a, Hashable a) => Builder (PrimState m) a -> Node (PrimState m) a -> m ()
+deleteDigram s n
+  | isGuardNode n = return ()
+  | otherwise = do
+      next <- getNext n
+      unless (isGuardNode next) $ do
+        _ <- stToPrim $ H.mutate (sDigrams s) (nodeSymbol n, nodeSymbol next) $ \case
+          Just n' | n /= n' -> (Just n', ())
+          _ -> (Nothing, ())
+        return ()
+
+check :: (PrimMonad m, Eq a, Hashable a) => Builder (PrimState m) a -> Node (PrimState m) a -> m Bool
+check s node
+  | isGuardNode node = return False
+  | otherwise = do
+      next <- getNext node
+      if isGuardNode next then
+        return False
+      else do
+        ret <- stToPrim $ H.mutate (sDigrams s) (nodeSymbol node, nodeSymbol next) $ \case
+          Nothing -> (Just node, Nothing)
+          Just node' -> (Just node', Just node')
+        case ret of
+          Nothing -> return False
+          Just node' -> do
+             next' <- getNext node'
+             if node == next' then
+               return False
+             else do
+               match s node node'
+               return True
+
+match :: (PrimMonad m, Eq a, Hashable a, HasCallStack) => Builder (PrimState m) a -> Node (PrimState m) a -> Node (PrimState m) a -> m ()
+match s ss m = do
+  mPrev <- getPrev m
+  mNext <- getNext m
+  mNextNext <- getNext mNext
+
+  rule <- case ruleOfGuardNode mPrev of
+    Just rid | isGuardNode mNextNext -> do
+      rule <- getRule s rid
+      substitute s ss rule
+      return rule
+    _ -> do
+      rule <- newRule  s
+      ss2 <- getNext ss
+      lastNode <- getLastNodeOfRule rule
+      node1 <- insertAfter s lastNode (nodeSymbol ss)
+      node2 <- insertAfter s node1 (nodeSymbol ss2)
+      substitute s m rule
+      substitute s ss rule
+      stToPrim $ H.insert (sDigrams s) (nodeSymbol node1, nodeSymbol node2) node1
+      return rule
+
+  firstNode <- getFirstNodeOfRule rule
+  case nodeSymbol firstNode of
+    Terminal _ -> return ()
+    NonTerminal rid -> do
+      rule2 <- getRule s rid
+      freq <- readMutVar (ruleRefCounter rule2)
+      when (freq == 1) $ expand s firstNode rule2
+
+  when sanityCheck $ do
+    let loop node
+          | isGuardNode node = return ()
+          | otherwise = do
+              case nodeSymbol node of
+                Terminal _ -> return ()
+                NonTerminal rid -> do
+                  rule2 <- getRule s rid
+                  freq <- readMutVar (ruleRefCounter rule2)
+                  when (freq <= 1) $ error "Sequitur.match: non-first node with refCount <= 1"
+    loop =<< getNext firstNode
+
+deleteNode :: (PrimMonad m, Eq a, Hashable a, HasCallStack) => Builder (PrimState m) a -> Node (PrimState m) a -> m ()
+deleteNode s node = do
+  assert (not (isGuardNode node)) $ return ()
+  prev <- getPrev node
+  next <- getNext node
+  link s prev next
+  deleteDigram s node
+  case nodeSymbol node of
+    Terminal _ -> return ()
+    NonTerminal rid -> do
+      rule <- getRule s rid
+      modifyMutVar' (ruleRefCounter rule) (subtract 1)
+
+substitute :: (PrimMonad m, Eq a, Hashable a, HasCallStack) => Builder (PrimState m) a -> Node (PrimState m) a -> Rule (PrimState m) a -> m ()
+substitute s node rule = do
+  prev <- getPrev node
+  deleteNode s =<< getNext prev
+  deleteNode s =<< getNext prev
+  _ <- insertAfter s prev (NonTerminal (ruleId rule))
+  ret <- check s prev
+  unless ret $ do
+    next <- getNext prev
+    _ <- check s next
+    return ()
+
+expand :: (PrimMonad m, Eq a, Hashable a) => Builder (PrimState m) a -> Node (PrimState m) a -> Rule (PrimState m) a -> m ()
+expand s node rule = do
+  left <- getPrev node
+  right <- getNext node
+  deleteNode s node
+
+  f <- getFirstNodeOfRule rule
+  l <- getLastNodeOfRule rule
+  link s left f
+  link s l right
+
+  n <- getNext l
+  let key = (nodeSymbol l, nodeSymbol n)
+  when sanityCheck $ do
+    ret <- stToPrim $ H.lookup (sDigrams s) key
+    when (isJust ret) $ error ("Sequitur.expand: the digram is already in the table")
+  stToPrim $ H.insert (sDigrams s) key l
+  stToPrim $ H.delete (sRules s) (ruleId rule)
+
+-- -------------------------------------------------------------------
+
+-- | Construct a grammer from a given sequence of symbols using /SEQUITUR/.
+encode :: (Eq a, Hashable a) => [a] -> Grammar a
+encode xs = runST $ do
+  e <- newBuilder
+  mapM_ (add e) xs
+  build e
+
+-- | Reconstruct a input sequence from a grammar.
+--
+-- This is a left-inverse of 'encode'.
+--
+-- This function is implemented as
+--
+-- @
+-- decode = 'F.toList' . 'decodeToSeq'
+-- @
+--
+-- and provided just for convenience.
+-- For serious usage, use 'decodeToSeq' or 'decodeLazy'.
+decode :: HasCallStack => Grammar a -> [a]
+decode = F.toList . decodeToSeq
+
+-- | A variant of 'decode' with possibly better performance.
+decodeToSeq :: HasCallStack => Grammar a -> Seq a
+decodeToSeq = decodeToMonoid Seq.singleton
+
+-- | A variant of 'decode' but you can consume from the beginning
+-- before constructing entire sequence.
+decodeLazy :: HasCallStack => Grammar a -> [a]
+decodeLazy g = appEndo (decodeToMonoid (\a -> Endo (a :)) g) []
+
+-- | 'Monoid'-based folding over the decoded sequence.
+--
+-- This function is equivalent to the following definition, is more
+-- efficent due to the utilization of sharing.b
+--
+-- @
+-- decodeToMonoid f = 'mconcat' . 'map' f . 'decode'
+-- @
+decodeToMonoid :: (Monoid m, HasCallStack) => (a -> m) -> Grammar a -> m
+decodeToMonoid e g = get 0 table
+  where
+    -- depends on the fact that fmap of IntMap is lazy
+    table = fmap (mconcat . map f) g
+
+    f (Terminal a) = e a
+    f (NonTerminal r) = get r table
+
+    get r tbl =
+      case IntMap.lookup r tbl of
+        Nothing -> error ("rule " ++ show r ++ " is missing")
+        Just x -> x
+
+-- -------------------------------------------------------------------
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,84 @@
+import Control.Monad
+import qualified Data.Map.Strict as Map
+import qualified Data.IntMap.Strict as IntMap
+import qualified Data.IntSet as IntSet
+import Data.List (intercalate)
+import qualified Data.Set as Set
+import Test.Hspec
+import Test.QuickCheck
+
+import Language.Grammar.Sequitur
+
+main :: IO ()
+main = hspec $ do
+  describe "Sequitur.encode" $ do
+    let cases =
+          [ ( "abab"
+            , IntMap.fromList [(0, [NonTerminal 1, NonTerminal 1]), (1, [Terminal 'a', Terminal 'b'])]
+            )
+          , ( "abcab"
+            , IntMap.fromList [(0, [NonTerminal 1, Terminal 'c', NonTerminal 1]), (1, [Terminal 'a', Terminal 'b'])]
+            )
+          , ( "abcabc"
+            , IntMap.fromList [(0, [NonTerminal 2, NonTerminal 2]), (2, [Terminal 'a', Terminal 'b', Terminal 'c'])]
+            )
+          , ( "aaa"
+            , IntMap.fromList [(0,[Terminal 'a', Terminal 'a', Terminal 'a'])]
+            )
+          , ( "baaabacaa"
+            , IntMap.fromList [(0,[NonTerminal 1,NonTerminal 2,NonTerminal 1,Terminal 'c',NonTerminal 2]),(1,[Terminal 'b',Terminal 'a']),(2,[Terminal 'a',Terminal 'a'])]
+            )
+          ]
+    forM_ cases $ \(xs, grammar) -> do
+      it ("returns " ++ reprGrammar grammar ++ " for " ++ show xs) $ do
+        encode xs `shouldBe` grammar
+
+    it "returns a grammer with digram uniqueness property" $
+      property $ forAll simpleString $ \s ->
+        let g = encode s
+         in counterexample (reprGrammar g) $ digramUniqueness g
+
+    it "returns a grammer with rule utility property" $
+      property $ forAll simpleString $ \s ->
+        let g = encode s
+         in counterexample (reprGrammar g) $ ruleUtility g
+
+  describe "Sequitur.decode" $ do
+    it "is the inverse of encode" $
+      property $ forAll simpleString $ \s ->
+        let g = encode s
+            s' = decode g
+         in counterexample (reprGrammar g) $ counterexample s' $ s == s'
+
+simpleString :: Gen String
+simpleString = liftArbitrary (elements ['a'..'z'])
+
+reprGrammar :: Grammar Char -> String
+reprGrammar grammar = "{" ++ intercalate ", " [show nt ++ " -> " ++ intercalate " " (map reprSymbol body) | (nt, body) <- IntMap.toAscList grammar] ++ "}"
+  where
+    reprSymbol (Terminal c) = [c]
+    reprSymbol (NonTerminal x) = show x
+
+digramUniqueness :: Grammar Char -> Property
+digramUniqueness g = conjoin
+  [ counterexample (show ce) $
+      case Set.toList ps of
+        [_] -> True
+        [(i1, j1), (i2, j2)] -> i1 == i2 && (j1 == j2 + 1 || j2 == j1 + 1)
+        _ -> False
+  | ce@(_digram, ps) <- Map.toList occurrences
+  ]
+  where
+    occurrences = Map.fromListWith Set.union
+      [ (digram, Set.singleton (i,j))
+      | (i, body) <- IntMap.toList g, (j, digram) <- zip [(0::Int)..] (zip body (tail body))
+      ]
+
+ruleUtility :: Grammar Char -> Property
+ruleUtility g = 
+  conjoin [counterexample (show (r, n)) $ n >= 2 | (r, n) <- IntMap.toList occurrences]
+  .&&.
+  IntMap.keysSet g === IntSet.insert 0 (IntMap.keysSet occurrences)
+  where
+    occurrences = IntMap.fromListWith (+)
+      [(r, (1::Int)) | body <- IntMap.elems g, NonTerminal r <- body]
