diff --git a/Language/Symantic/Grammar/ContextFree.hs b/Language/Symantic/Grammar/ContextFree.hs
--- a/Language/Symantic/Grammar/ContextFree.hs
+++ b/Language/Symantic/Grammar/ContextFree.hs
@@ -17,7 +17,7 @@
 -- * Type 'CF'
 -- | Context-free grammar.
 newtype CF g a = CF { unCF :: g a }
- deriving (IsString, Functor, Gram_Terminal, Applicative, Gram_App)
+ deriving (IsString, Functor, Gram_Char, Gram_String, Applicative, Gram_App)
 deriving instance Gram_Error err g => Gram_Error err (CF g)
 deriving instance Gram_Reader st g => Gram_Reader st (CF g)
 deriving instance Gram_State st g => Gram_State st (CF g)
@@ -111,7 +111,8 @@
 -- * Class 'Gram_Comment'
 -- | Symantics for handling comments after each 'lexeme'.
 class
- ( Gram_Terminal g
+ ( Gram_Char g
+ , Gram_String g
  , Gram_Rule g
  , Gram_Alt g
  , Gram_App g
@@ -119,22 +120,22 @@
  , Gram_CF g
  ) => Gram_Comment g where
 	commentable :: g () -> g () -> g () -> g ()
-	commentable = rule3 "commentable" $ \space line block ->
-		manySkip $ choice [space, line, block]
-	comment_line :: CF g String -> CF g String
-	comment_line prefix = rule "comment_line" $
-		prefix *> many (any `minus` (void (char '\n') <+> eoi))
-	comment_block :: CF g String -> Reg lr g String -> CF g String
-	comment_block begin end = rule "comment_block" $
+	commentable = rule3 "Commentable" $ \sp line block ->
+		manySkip $ choice [sp, line, block]
+	commentLine :: CF g String -> CF g String
+	commentLine prefix = rule "CommentLine" $
+		prefix *> many (any `minus` (void eol <+> eoi))
+	commentBlock :: CF g String -> Reg lr g String -> CF g String
+	commentBlock begin end = rule "CommentBlock" $
 		begin *> many (any `minus` end) <* cfOf end
 	lexeme :: CF g a -> CF g a
-	lexeme = rule1 "lexeme" $ \g ->
+	lexeme = rule1 "Lexeme" $ \g ->
 		g <* commentable
-		 (void $ string " " <+> string "\n ")
-		 (void $ comment_line (string "--"))
-		 (void $ comment_block (string "{-") (string "-}"))
+		 (void $ space <+> (eol *> space))
+		 (void $ commentLine (string "--"))
+		 (void $ commentBlock (string "{-") (string "-}"))
 	parens :: CF g a -> CF g a
-	parens = rule1 "parens" $
+	parens = rule1 "Parens" $
 		between
 		 (lexeme $ char '(')
 		 (lexeme $ char ')')
@@ -147,8 +148,8 @@
 gram_comment :: forall g. (Gram_Comment g, Gram_RuleEBNF g) => [CF g ()]
 gram_comment =
  [ void $ commentable (void $ argEBNF "space") (void $ argEBNF "line") (void $ argEBNF "block")
- , void $ comment_line (argEBNF "prefix")
- , void $ comment_block (argEBNF "begin") (argEBNF "end" :: RegL g String)
+ , void $ commentLine (argEBNF "prefix")
+ , void $ commentBlock (argEBNF "begin") (argEBNF "end" :: RegL g String)
  , void $ lexeme (argEBNF "g")
  , void $ parens (argEBNF "g")
  , void $ inside id (argEBNF "begin") (argEBNF "in") (argEBNF "end") (argEBNF "next")
diff --git a/Language/Symantic/Grammar/EBNF.hs b/Language/Symantic/Grammar/EBNF.hs
--- a/Language/Symantic/Grammar/EBNF.hs
+++ b/Language/Symantic/Grammar/EBNF.hs
@@ -1,10 +1,8 @@
 module Language.Symantic.Grammar.EBNF where
 
 import Control.Applicative (Applicative(..))
-import Control.Monad
-import Data.Semigroup hiding (option)
+import Data.Semigroup
 import Data.Text (Text)
-import Prelude hiding (any)
 import qualified Data.Text as Text
 
 import Language.Symantic.Grammar.Meta
diff --git a/Language/Symantic/Grammar/Fixity.hs b/Language/Symantic/Grammar/Fixity.hs
--- a/Language/Symantic/Grammar/Fixity.hs
+++ b/Language/Symantic/Grammar/Fixity.hs
@@ -1,9 +1,8 @@
 module Language.Symantic.Grammar.Fixity where
 
 import Data.Bool as Bool
-import Data.Semigroup hiding (option)
+import Data.Semigroup
 import Data.String (IsString(..))
-import Prelude hiding (any)
 
 -- * Type 'Fixity'
 data Fixity
diff --git a/Language/Symantic/Grammar/Operators.hs b/Language/Symantic/Grammar/Operators.hs
--- a/Language/Symantic/Grammar/Operators.hs
+++ b/Language/Symantic/Grammar/Operators.hs
@@ -4,8 +4,7 @@
 
 import Control.Applicative (Applicative(..))
 import Control.Monad (void)
-import Data.Foldable hiding (any)
-import Prelude hiding (any)
+import Data.Foldable
 
 import Language.Symantic.Grammar.Fixity
 import Language.Symantic.Grammar.EBNF
@@ -16,7 +15,8 @@
 -- * Class 'Gram_Op'
 -- | Symantics for operators.
 class
- ( Gram_Terminal g
+ ( Gram_Char g
+ , Gram_String g
  , Gram_Rule g
  , Gram_Alt g
  , Gram_Try g
diff --git a/Language/Symantic/Grammar/Regular.hs b/Language/Symantic/Grammar/Regular.hs
--- a/Language/Symantic/Grammar/Regular.hs
+++ b/Language/Symantic/Grammar/Regular.hs
@@ -13,7 +13,7 @@
 -- * Type 'Reg'
 -- | Left or right regular grammar.
 newtype Reg (lr::Side) g a = Reg { unReg :: g a }
- deriving (IsString, Functor, Gram_Terminal)
+ deriving (IsString, Functor, Gram_Char, Gram_String)
 deriving instance Gram_Alt g => Gram_Alt (Reg lr g)
 deriving instance Gram_Try g => Gram_Try (Reg lr g)
 deriving instance Gram_Rule g => Gram_Rule (Reg lr g)
diff --git a/Language/Symantic/Grammar/Terminal.hs b/Language/Symantic/Grammar/Terminal.hs
--- a/Language/Symantic/Grammar/Terminal.hs
+++ b/Language/Symantic/Grammar/Terminal.hs
@@ -2,14 +2,13 @@
 -- | Symantics for terminal grammars.
 module Language.Symantic.Grammar.Terminal where
 
-import Control.Monad
 import Data.Semigroup (Semigroup(..))
 import Data.String (IsString(..))
-import Prelude hiding (any)
 import qualified Data.Bool as Bool
 import qualified Data.Char as Char
 import qualified Data.List as List
 import qualified Data.Text as Text
+import qualified Data.Text.Lazy as TL
 
 import Language.Symantic.Grammar.Fixity
 import Language.Symantic.Grammar.EBNF
@@ -18,51 +17,42 @@
 -- | Terminal grammar.
 newtype Terminal g a
  =      Terminal { unTerminal :: g a }
- deriving (Functor, Gram_Terminal)
+ deriving (Functor, Gram_Char, Gram_String)
 deriving instance Gram_Rule g => Gram_Rule (Terminal g)
 
--- ** Class 'Gram_Terminal'
+-- ** Class 'Gram_Char'
 -- | Symantics for terminal grammars.
-class Gram_Terminal g where
+class Gram_Rule g => Gram_Char g where
 	any    :: g Char
 	but    :: Terminal g Char -> Terminal g Char -> Terminal g Char
 	eoi    :: g ()
+	eol    :: g Char
+	space  :: g Char
 	char   :: Char -> g Char
-	string :: String -> g String
 	unicat :: Unicat -> g Char
 	range  :: (Char, Char) -> g Char
-	-- string = foldr (\c -> (<*>) ((:) <$> char c)) (pure "")
-	-- string [] = pure []
-	-- string (c:cs) = (:) <$> char c <*> string cs
-deriving instance Gram_Terminal RuleEBNF
-instance Gram_Terminal EBNF where
+	eol   = rule "NewLine" $ char '\n'
+	space = rule "Space" $ char ' '
+deriving instance Gram_Char RuleEBNF
+instance Gram_Char EBNF where
 	any  = ebnf_const "_"
 	Terminal (EBNF f) `but` Terminal (EBNF g) =
 		Terminal $ EBNF $ \bo po -> parenInfix po op $
 			f bo (op, SideL) <> " - " <> g bo (op, SideR)
 		where op = infixL 6
-	eoi  = ebnf_const "eoi"
-	char = ebnf_const . escape
+	eoi   = ebnf_const "eoi"
+	eol   = ebnf_const "↵"
+	space = ebnf_const "␣"
+	char  = ebnf_const . escape
 		where
 		escape c | Char.isPrint c && c /= '"' = Text.concat $ ["\"", Text.singleton c, "\""]
 		escape c = Text.concat ["U+", Text.pack $ show $ Char.ord c]
-	string s =
-		case List.break (\c -> Bool.not (Char.isPrint c) || c == '"') s of
-		 (ps, "")   -> raw ps
-		 ("", [c])  -> "" <$ char c
-		 (ps, [c])  -> "" <$ raw ps <* char c
-		 ("", c:rs) -> "" <$ char c <* string rs
-		 (ps, c:rs) -> "" <$ raw ps <* char c <* string rs
-		where
-		raw cs = ebnf_const $ Text.concat $ ["\"", Text.pack cs, "\""]
 	unicat = ebnf_const . Text.pack . show
 	range (l, h) = ebnf_const $ Text.concat
 	 [ runEBNF $ char l
 	 , "…"
 	 , runEBNF $ char h
 	 ]
-instance IsString (EBNF String) where
-	fromString = string
 
 -- *** Type 'Unicat'
 -- | Unicode category.
@@ -109,3 +99,29 @@
 		 , Char.OtherSymbol
 		 ]
 	 Unicat cat -> [cat]
+
+-- ** Class 'Gram_String'
+class Functor g => Gram_String g where
+	string   :: String -> g String
+	{-
+	string = foldr (\c -> (<*>) ((:) <$> char c)) (pure "")
+	string [] = pure []
+	string (c:cs) = (:) <$> char c <*> string cs
+	-}
+	text     :: Text.Text -> g Text.Text
+	textLazy :: TL.Text -> g TL.Text
+	text t     = Text.pack <$> string (Text.unpack t)
+	textLazy t = TL.pack   <$> string (TL.unpack t)
+deriving instance Gram_String RuleEBNF
+instance Gram_String EBNF where
+	string s =
+		case List.break (\c -> Bool.not (Char.isPrint c) || c == '"') s of
+		 (ps, "")   -> raw ps
+		 ("", [c])  -> "" <$ char c
+		 (ps, [c])  -> "" <$ raw ps <* char c
+		 ("", c:rs) -> "" <$ char c <* string rs
+		 (ps, c:rs) -> "" <$ raw ps <* char c <* string rs
+		where
+		raw cs = ebnf_const $ Text.concat $ ["\"", Text.pack cs, "\""]
+instance IsString (EBNF String) where
+	fromString = string
diff --git a/Language/Symantic/Grammar/Test.hs b/Language/Symantic/Grammar/Test.hs
deleted file mode 100644
--- a/Language/Symantic/Grammar/Test.hs
+++ /dev/null
@@ -1,113 +0,0 @@
-{-# LANGUAGE ConstraintKinds #-}
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-module Test where
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import Control.Applicative (Applicative(..))
-import Control.Monad
-import Data.Semigroup ((<>))
-import Data.String (IsString(..))
-import Prelude hiding (any, (^), exp)
-import qualified Control.Applicative as Gram_AltApp
-import qualified Data.Char as Char
-import qualified Data.Text as Text
-import qualified Text.Megaparsec as P
-
-import Language.Symantic.Grammar
-
--- * Type 'ParsecT'
-type ParsecC e s = (P.Token s ~ Char, P.Stream s, P.ErrorComponent e)
-instance ParsecC e s => IsString (P.ParsecT e s m [Char]) where
-	fromString = P.string
-instance ParsecC e s => Gram_Rule (P.ParsecT e s m) where
-	rule = P.label . Text.unpack
-instance ParsecC e s => Gram_Terminal (P.ParsecT e s m) where
-	any          = P.anyChar
-	eoi          = P.eof
-	char         = P.char
-	string       = P.string
-	unicat cat   = P.satisfy $ (`elem` cats) . Char.generalCategory
-		where cats = unicode_categories cat
-	range (l, h) = P.satisfy $ \c -> l <= c && c <= h
-	but (Terminal f) (Terminal p) = Terminal $ P.notFollowedBy (P.try p) *> f
-instance ParsecC e s => Gram_Alt (P.ParsecT e s m) where
-	empty  = Gram_AltApp.empty
-	(<+>)  = (Gram_AltApp.<|>)
-	choice = P.choice
-instance ParsecC e s => Gram_Try (P.ParsecT e s m) where
-	try = P.try
-instance ParsecC e s => Gram_RegR (P.ParsecT e s m) where
-	Terminal f .*> Reg x = Reg $ f <*> x
-instance ParsecC e s => Gram_RegL (P.ParsecT e s m) where
-	Reg f <*. Terminal x = Reg $ f <*> x
-instance ParsecC e s => Gram_App (P.ParsecT e s m)
-instance ParsecC e s => Gram_AltApp (P.ParsecT e s m)
-instance ParsecC e s => Gram_CF (P.ParsecT e s m) where
-	CF f <& Reg p      = CF $ P.lookAhead f <*> p
-	Reg f &> CF p      = CF $ P.lookAhead f <*> p
-	CF f `minus` Reg p = CF $ P.notFollowedBy (P.try p) *> f
-instance ParsecC e s => Gram_Comment (P.ParsecT e s m)
-
-elide :: Text.Text -> String
-elide s | Text.length s > 42 = take 42 (Text.unpack s) <> ['…']
-elide s = Text.unpack s
-
-tests :: TestTree
-tests = testGroup "Grammar"
- [ testGroup "Terminal" $
-	let (==>) inp exp =
-		testCase (elide exp) $
-		runEBNF (unTerminal (void inp)) @?= exp
-		; infix 1 ==> in
-	 [ string "" ==> "\"\""
-	 , string "abé\"to" ==> "\"abé\", U+34, \"to\""
-	 , string "\"" ==> "U+34"
-	 , range ('a', 'z') ==> "\"a\"…\"z\""
-	 , unicat Unicat_Letter ==> "Unicat_Letter"
-	 , unicat (Unicat Char.LowercaseLetter) ==> "Unicat LowercaseLetter"
-	 ]
- , testGroup "Reg" $
-	let (==>) inp exp =
-		testCase (elide exp) $
-		runEBNF (unReg (void inp)) @?= exp
-		; infix 1 ==> in
-	 [ (<>) <$> string "0" .*> someR (char '1') ==> "\"0\", {\"1\"}-"
-	 , (<>) <$> someL (char '1') <*. string "0" ==> "{\"1\"}-, \"0\""
-	 ]
- , testGroup "CF" $
-	let (==>) inp exp =
-		testCase (elide exp) $
-		runEBNF (unCF (void inp)) @?= exp
-		; infix 1 ==> in
-	 [ (<>) <$> string "0" <*> string "1" ==> "\"0\", \"1\""
-	 , (<>) <$> string "0" <* string "X" <*> string "1" ==> "\"0\", \"X\", \"1\""
-	 , (<>) <$> (string "0" <+> string "1") <*> string "2" ==> "(\"0\" | \"1\"), \"2\""
-	 , (<>) <$> string "0" <*> (string "1" <+> string "2") ==> "\"0\", (\"1\" | \"2\")"
-	 , string "0" <+> string "1" <+> string "2" ==> "\"0\" | \"1\" | \"2\""
-	 , choice [string "0", string "1", string "2"] ==> "\"0\" | \"1\" | \"2\""
-	 , (<>) <$> choice
-		 [ (<>) <$> string "0" <*> string "1"
-		 , string "2" <+> string "3"
-		 , string "4"
-		 ] <*> string "5" ==> "(\"0\", \"1\" | \"2\" | \"3\" | \"4\"), \"5\""
-	 , concat <$> many (string "0") ==> "{\"0\"}"
-	 , () <$ char 'a' <* char 'b' <* char 'c' ==> "\"a\", \"b\", \"c\""
-	 ,let g0 = (<>) <$> string "0" .*> someR (char '1') in
-		(<>) <$> string "0" <& g0 ==> "\"0\" & \"0\", {\"1\"}-"
-	 ,let g0 = (<>) <$> string "0" .*> someR (char '1') in
-		let g1 = (<>) <$> someL (char '1') <*. string "0" in
-		string "0" `minus` g0 `minus` g1 ==>
-		"\"0\" - \"0\", {\"1\"}- - {\"1\"}-, \"0\""
-	 , (<>)
-		 <$> many (string "0" <+> string "1")
-		 <*> some (string "2") ==> "{\"0\" | \"1\"}, {\"2\"}-"
-	 ]
- ]
-
-main :: IO ()
-main =
-	defaultMain $
-	testGroup "Language.Symantic"
-	 [tests]
diff --git a/stack.yaml b/stack.yaml
new file mode 100644
--- /dev/null
+++ b/stack.yaml
@@ -0,0 +1,3 @@
+resolver: lts-10.5
+packages:
+- '.'
diff --git a/symantic-grammar.cabal b/symantic-grammar.cabal
--- a/symantic-grammar.cabal
+++ b/symantic-grammar.cabal
@@ -1,50 +1,36 @@
-author: Julien Moutinho <julm+symantic@autogeree.net>
+name: symantic-grammar
+-- PVP:  +-+------- breaking API changes
+--       | | +----- non-breaking API additions
+--       | | | +--- code changes with no API change
+version: 0.3.0.20180213
+category: Language
+synopsis: Library for symantic grammars.
+description: This library defines an embedded DSL for regular or context-free grammars,
+             in the <http://okmij.org/ftp/tagless-final/ Tagless-Final> way (aka. the /symantic/ way).
+             .
+             See @Test.hs@ or source code of <https://hackage.haskell.org/package/symantic symantic>
+             and <https://hackage.haskell.org/package/symantic-lib symantic-lib> for examples of use.
+extra-doc-files:
+license: GPL-3
+license-file: COPYING
+stability: experimental
+author:      Julien Moutinho <julm+symantic@autogeree.net>
+maintainer:  Julien Moutinho <julm+symantic@autogeree.net>
 bug-reports: Julien Moutinho <julm+symantic@autogeree.net>
+-- homepage:
+
 build-type: Simple
 cabal-version: >= 1.24
-category: Language
-description:
-  This library defines an embedded DSL for regular or context-free grammars,
-  in the <http://okmij.org/ftp/tagless-final/ Tagless-Final> way (aka. the /symantic/ way).
-  .
-  See @Test.hs@ or source code of <https://hackage.haskell.org/package/symantic symantic>
-  and <https://hackage.haskell.org/package/symantic-lib symantic-lib> for examples of use.
+tested-with: GHC==8.2.2
 extra-source-files:
+  stack.yaml
 extra-tmp-files:
--- homepage: 
-license: GPL-3
-license-file: COPYING
-maintainer: Julien Moutinho <julm+symantic@autogeree.net>
-name: symantic-grammar
-stability: experimental
-synopsis: Library for symantic grammars.
-tested-with: GHC==8.0.2
--- PVP:  +-+------- breaking API changes
---       | | +----- non-breaking API additions
---       | | | +--- code changes with no API change
-version: 0.2.0.20170709
 
 Source-Repository head
   location: git://git.autogeree.net/symantic
   type:     git
 
 Library
-  default-extensions:
-    DataKinds
-    FlexibleContexts
-    FlexibleInstances
-    GeneralizedNewtypeDeriving
-    KindSignatures
-    LambdaCase
-    MultiParamTypeClasses
-    OverloadedStrings
-    ScopedTypeVariables
-    StandaloneDeriving
-  ghc-options: -Wall
-               -fwarn-incomplete-patterns
-               -fno-warn-tabs
-               -fprint-explicit-kinds
-  default-language: Haskell2010
   exposed-modules:
     Language.Symantic.Grammar
     Language.Symantic.Grammar.BinTree
@@ -57,12 +43,35 @@
     Language.Symantic.Grammar.Terminal
     Language.Symantic.Grammar.Source
     Language.Symantic.Grammar.Error
+  default-language: Haskell2010
+  default-extensions:
+    DataKinds
+    FlexibleContexts
+    FlexibleInstances
+    GeneralizedNewtypeDeriving
+    KindSignatures
+    LambdaCase
+    MultiParamTypeClasses
+    OverloadedStrings
+    ScopedTypeVariables
+    StandaloneDeriving
+  ghc-options:
+    -Wall
+    -Wincomplete-uni-patterns
+    -Wincomplete-record-updates
+    -fno-warn-tabs
+    -fhide-source-paths
   build-depends:
-    base >= 4.6 && < 5
-    , text
+      base >= 4.6 && < 5
+    , text >= 1.2
 
 Test-Suite symantic-grammar-test
   type: exitcode-stdio-1.0
+  hs-source-dirs: test
+  main-is: Main.hs
+  other-modules:
+    HUnit
+  default-language: Haskell2010
   default-extensions:
     FlexibleContexts
     FlexibleInstances
@@ -70,18 +79,17 @@
     OverloadedStrings
     ScopedTypeVariables
     TypeFamilies
-  default-language: Haskell2010
-  ghc-options: -Wall
-               -fno-warn-tabs
-               -main-is Test
-  hs-source-dirs: Language/Symantic
-  main-is: Grammar/Test.hs
-  other-modules:
+  ghc-options:
+    -Wall
+    -Wincomplete-uni-patterns
+    -Wincomplete-record-updates
+    -fno-warn-tabs
+    -fhide-source-paths
   build-depends:
-    base >= 4.6 && < 5
-    , megaparsec
-    , symantic-grammar
-    , tasty >= 0.11
-    , tasty-hunit
-    , text
-    , transformers
+      symantic-grammar
+    , base         >= 4.6 && < 5
+    , megaparsec   >= 6.3
+    , tasty        >= 0.11
+    , tasty-hunit  >= 0.9
+    , text         >= 1.2
+    , transformers >= 0.5
diff --git a/test/HUnit.hs b/test/HUnit.hs
new file mode 100644
--- /dev/null
+++ b/test/HUnit.hs
@@ -0,0 +1,108 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module HUnit where
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Control.Applicative (Applicative(..))
+import Control.Monad
+import Data.Semigroup ((<>))
+import Data.String (IsString(..))
+import qualified Control.Applicative as Applicative
+import qualified Data.Char as Char
+import qualified Data.Text as Text
+import qualified Text.Megaparsec as P
+import qualified Text.Megaparsec.Char as P
+
+import Language.Symantic.Grammar
+
+-- * Type 'ParsecT'
+type ParsecC e s = (P.Token s ~ Char, P.Stream s, Ord e)
+instance (ParsecC e s, Gram_String (P.ParsecT e s m)) => IsString (P.ParsecT e s m String) where
+	fromString = string
+instance ParsecC e s => Gram_Rule (P.ParsecT e s m) where
+	rule = P.label . Text.unpack
+instance ParsecC e s => Gram_Char (P.ParsecT e s m) where
+	any          = P.anyChar
+	eoi          = P.eof
+	char         = P.char
+	unicat cat   = P.satisfy $ (`elem` cats) . Char.generalCategory
+		where cats = unicode_categories cat
+	range (l, h) = P.satisfy $ \c -> l <= c && c <= h
+	but (Terminal f) (Terminal p) = Terminal $ P.notFollowedBy (P.try p) *> f
+instance ParsecC e String => Gram_String (P.ParsecT e String m) where
+	string = P.string
+instance ParsecC e s => Gram_Alt (P.ParsecT e s m) where
+	empty  = Applicative.empty
+	(<+>)  = (Applicative.<|>)
+	choice = P.choice
+instance ParsecC e s => Gram_Try (P.ParsecT e s m) where
+	try = P.try
+instance ParsecC e s => Gram_RegR (P.ParsecT e s m) where
+	Terminal f .*> Reg x = Reg $ f <*> x
+instance ParsecC e s => Gram_RegL (P.ParsecT e s m) where
+	Reg f <*. Terminal x = Reg $ f <*> x
+instance ParsecC e s => Gram_App (P.ParsecT e s m)
+instance ParsecC e s => Gram_AltApp (P.ParsecT e s m)
+instance ParsecC e s => Gram_CF (P.ParsecT e s m) where
+	CF f <& Reg p      = CF $ P.lookAhead f <*> p
+	Reg f &> CF p      = CF $ P.lookAhead f <*> p
+	CF f `minus` Reg p = CF $ P.notFollowedBy (P.try p) *> f
+instance ParsecC e String => Gram_Comment (P.ParsecT e String m)
+
+elide :: Text.Text -> String
+elide s | Text.length s > 42 = take 42 (Text.unpack s) <> ['…']
+elide s = Text.unpack s
+
+hunits :: TestTree
+hunits = testGroup "Grammar"
+ [ testGroup "Terminal" $
+	let (==>) input expected =
+		testCase (elide expected) $
+		runEBNF (unTerminal (void input)) @?= expected
+		; infix 1 ==> in
+	 [ string "" ==> "\"\""
+	 , string "abé\"to" ==> "\"abé\", U+34, \"to\""
+	 , string "\"" ==> "U+34"
+	 , range ('a', 'z') ==> "\"a\"…\"z\""
+	 , unicat Unicat_Letter ==> "Unicat_Letter"
+	 , unicat (Unicat Char.LowercaseLetter) ==> "Unicat LowercaseLetter"
+	 ]
+ , testGroup "Reg" $
+	let (==>) input expected =
+		testCase (elide expected) $
+		runEBNF (unReg (void input)) @?= expected
+		; infix 1 ==> in
+	 [ (<>) <$> string "0" .*> someR (char '1') ==> "\"0\", {\"1\"}-"
+	 , (<>) <$> someL (char '1') <*. string "0" ==> "{\"1\"}-, \"0\""
+	 ]
+ , testGroup "CF" $
+	let (==>) input expected =
+		testCase (elide expected) $
+		runEBNF (unCF (void input)) @?= expected
+		; infix 1 ==> in
+	 [ (<>) <$> string "0" <*> string "1" ==> "\"0\", \"1\""
+	 , (<>) <$> string "0" <* string "X" <*> string "1" ==> "\"0\", \"X\", \"1\""
+	 , (<>) <$> (string "0" <+> string "1") <*> string "2" ==> "(\"0\" | \"1\"), \"2\""
+	 , (<>) <$> string "0" <*> (string "1" <+> string "2") ==> "\"0\", (\"1\" | \"2\")"
+	 , string "0" <+> string "1" <+> string "2" ==> "\"0\" | \"1\" | \"2\""
+	 , choice [string "0", string "1", string "2"] ==> "\"0\" | \"1\" | \"2\""
+	 , (<>) <$> choice
+		 [ (<>) <$> string "0" <*> string "1"
+		 , string "2" <+> string "3"
+		 , string "4"
+		 ] <*> string "5" ==> "(\"0\", \"1\" | \"2\" | \"3\" | \"4\"), \"5\""
+	 , concat <$> many (string "0") ==> "{\"0\"}"
+	 , () <$ char 'a' <* char 'b' <* char 'c' ==> "\"a\", \"b\", \"c\""
+	 ,let g0 = (<>) <$> string "0" .*> someR (char '1') in
+		(<>) <$> string "0" <& g0 ==> "\"0\" & \"0\", {\"1\"}-"
+	 ,let g0 = (<>) <$> string "0" .*> someR (char '1') in
+		let g1 = (<>) <$> someL (char '1') <*. string "0" in
+		string "0" `minus` g0 `minus` g1 ==>
+		"\"0\" - \"0\", {\"1\"}- - {\"1\"}-, \"0\""
+	 , (<>)
+		 <$> many (string "0" <+> string "1")
+		 <*> some (string "2") ==> "{\"0\" | \"1\"}, {\"2\"}-"
+	 ]
+ ]
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,10 @@
+module Main where
+
+import Test.Tasty
+import HUnit
+
+main :: IO ()
+main =
+	defaultMain $
+	testGroup "Language.Symantic"
+	 [hunits]
