diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+## MMark 0.0.2.0
+
+* Now punctuation is stripped from header ids in
+  `Text.MMark.Extension.headerId`.
+
+* Added `scannerM` in `Text.MMark.Extension` and `runScannerM` in
+  `Text.MMark`.
+
+* Added support for block quotes.
+
+* Added support for unordered and ordered lists.
+
 ## MMark 0.0.1.1
 
 * Fixed a bug in skipping of headers (only one newline after the header line
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -174,14 +174,24 @@
 
 Block-level parsing:
 
-* Headings, thematic breaks, code blocks should be separated from paragraphs
-  by at least one empty line. This makes the parser a lot simpler and forces
-  markdown sources to be in a more readable form too.
+* Code blocks should be separated from paragraphs and lists by at least one
+  empty line.
 * If a line starts with hash signs it is expected to be a valid *non-empty*
   header (level 1–6 inclusive). If you want to start a paragraph with
   hashes, just escape the first hash with backslash and that will be enough.
+* Setext headings are not supported for the sake of simplicity.
 * Fenced code blocks must be explicitly closed by a closing fence. They are
   not closed by the end of document or by start of another block.
+* Lists and block quotes are defined by column at which their content
+  starts. Content belonging to a particular list or block quote should start
+  at the same column (or greater column, up to the column where indented
+  code blocks start). As a consequence of this, block quotes do not feature
+  “laziness”
+* Block quotes are started by a single `>` character, it's not necessary to
+  put a `>` character at beginning of every line belonging to a quote (in
+  fact, this would make every line a separate block quote).
+* Paragraphs can be interrupted by unordered list and ordered lists with any
+  valid starting index.
 
 Inline-level parsing:
 
@@ -201,9 +211,6 @@
 Not-yet-implemented things:
 
 * Separate declaration of image's source and title is not (yet?) supported.
-* Blockquotes are not implemented yet.
-* Lists (unordered and ordered) are not implemented yet.
-* Setext headings are not implemented yet.
 * Reference links are not implemented yet.
 * HTML blocks are not implemented yet.
 * HTML inlines are not implemented yet.
diff --git a/Text/MMark.hs b/Text/MMark.hs
--- a/Text/MMark.hs
+++ b/Text/MMark.hs
@@ -63,14 +63,53 @@
 -- The structure of the documentation below corresponds to these stages and
 -- should clarify the details.
 --
+-- === “Getting started” example
+--
+-- Here is a complete example of a program that reads a markdown file named
+-- @\"input.md\"@ and outputs an HTML file named @\"output.md\"@:
+--
+-- > {-# LANGUAGE OverloadedStrings #-}
+-- >
+-- > module Main (main) where
+-- >
+-- > import qualified Data.Text.IO      as T
+-- > import qualified Data.Text.Lazy.IO as TL
+-- > import qualified Lucid             as L
+-- > import qualified Text.MMark        as MMark
+-- >
+-- > main :: IO ()
+-- > main = do
+-- >   let input = "input.md"
+-- >   txt <- T.readFile input -- (1)
+-- >   case MMark.parse input txt of -- (2)
+-- >     Left errs -> putStrLn (MMark.parseErrorsPretty txt errs) -- (3)
+-- >     Right r -> TL.writeFile "output.html" -- (6)
+-- >       . L.renderText -- (5)
+-- >       . MMark.render -- (4)
+-- >       $ r
+--
+-- Let's break it down:
+--
+--     1. We read a source markdown file as strict 'Text'.
+--     2. The source is fed into the 'parse' function which does the
+--        parsing. It can either fail with a collection of parse errors
+--        or succeed returning a value of the opaque 'MMark' type.
+--     3. If parsing fails, we pretty-print the parse errors with
+--     'parseErrorsPretty'.
+--     4. Then we just render the document with `render` first to Lucid's
+--        @'Lucid.Html' ()@.
+--     5. …and then to lazy 'Data.Text.Lazy.Text' with 'Lucid.renderText'.
+--     6. Finally we write the result as @\"output.html\"@.
+--
 -- === Other modules of interest
 --
--- This module contains all the “core” functionality you may need. However,
--- one of the main selling points of MMark is that it's possible to write
--- your own extensions which stay highly composable (if done right), so
--- proliferation of third-party extensions is to be expected and encouraged.
--- To write an extension of your own import the "Text.MMark.Extension"
--- module, which has some documentation focusing on extension writing.
+-- The "Text.MMark" module contains all the “core” functionality you may
+-- need. However, one of the main selling points of MMark is that it's
+-- possible to write your own extensions which stay highly composable (if
+-- done right), so proliferation of third-party extensions is to be expected
+-- and encouraged. To write an extension of your own import the
+-- "Text.MMark.Extension" module, which has some documentation focusing on
+-- extension writing.
 
 module Text.MMark
   ( -- * Parsing
@@ -84,6 +123,7 @@
   , useExtensions
     -- * Scanning
   , runScanner
+  , runScannerM
   , projectYaml
     -- * Rendering
   , render )
diff --git a/Text/MMark/Extension.hs b/Text/MMark/Extension.hs
--- a/Text/MMark/Extension.hs
+++ b/Text/MMark/Extension.hs
@@ -53,6 +53,7 @@
   , inlineRender
     -- * Scanner construction
   , scanner
+  , scannerM
     -- * Utils
   , asPlainText
   , headerId
@@ -111,3 +112,16 @@
   -> L.Fold Bni a      -- ^ Resulting 'L.Fold'
 scanner a f = L.Fold f a id
 {-# INLINE scanner #-}
+
+-- | Create a 'L.FoldM' from an initial state and a folding function
+-- operating in monadic context.
+--
+-- @since 0.0.2.0
+
+scannerM
+  :: Monad m
+  => m a               -- ^ Initial state
+  -> (a -> Bni -> m a) -- ^ Folding function
+  -> L.FoldM m Bni a   -- ^ Resulting 'L.FoldM'
+scannerM a f = L.FoldM f a return
+{-# INLINE scannerM #-}
diff --git a/Text/MMark/Internal.hs b/Text/MMark/Internal.hs
--- a/Text/MMark/Internal.hs
+++ b/Text/MMark/Internal.hs
@@ -27,6 +27,7 @@
   , Inline (..)
     -- * Extensions
   , runScanner
+  , runScannerM
   , useExtension
   , useExtensions
     -- * Renders
@@ -46,7 +47,7 @@
 import Control.DeepSeq
 import Control.Monad
 import Data.Aeson
-import Data.Char (isSpace)
+import Data.Char (isSpace, isAlphaNum)
 import Data.Data (Data)
 import Data.Function (on)
 import Data.List.NonEmpty (NonEmpty (..))
@@ -163,8 +164,8 @@
     -- ^ Paragraph, leaf block
   | Blockquote [Block a]
     -- ^ Blockquote container block
-  | OrderedList (NonEmpty [Block a])
-    -- ^ Ordered list, container block
+  | OrderedList Word (NonEmpty [Block a])
+    -- ^ Ordered list ('Word' is the start index), container block
   | UnorderedList (NonEmpty [Block a])
     -- ^ Unordered list, container block
   | Naked a
@@ -238,6 +239,21 @@
 runScanner MMark {..} f = L.fold f mmarkBlocks
 {-# INLINE runScanner #-}
 
+-- | Like 'runScanner', but allows to run scanners with monadic context.
+--
+-- To bring 'L.Fold' and 'L.FoldM' types to the “least common denominator”
+-- use 'L.generalize' and 'L.simplify'.
+--
+-- @since 0.0.2.0
+
+runScannerM
+  :: Monad m
+  => MMark             -- ^ Document to scan
+  -> L.FoldM m Bni a   -- ^ 'L.FoldM' to use
+  -> m a               -- ^ Result of scanning
+runScannerM MMark {..} f = L.foldM f mmarkBlocks
+{-# INLINE runScannerM #-}
+
 ----------------------------------------------------------------------------
 -- Renders
 
@@ -320,24 +336,26 @@
     newline
   Paragraph (_,html) ->
     p_ html >> newline
-  Blockquote blocks ->
-    blockquote_ (mapM_ defaultBlockRender blocks)
-  OrderedList items -> do
-    ol_ $ do
+  Blockquote blocks -> do
+    blockquote_ (newline <* mapM_ defaultBlockRender blocks)
+    newline
+  OrderedList i items -> do
+    let startIndex = [start_ (T.pack $ show i) | i /= 1]
+    ol_ startIndex $ do
       newline
       forM_ items $ \x -> do
-        li_ (mapM_ defaultBlockRender x)
+        li_ (newline <* mapM_ defaultBlockRender x)
         newline
     newline
   UnorderedList items -> do
     ul_ $ do
       newline
       forM_ items $ \x -> do
-        li_ (mapM_ defaultBlockRender x)
+        li_ (newline <* mapM_ defaultBlockRender x)
         newline
     newline
   Naked (_,html) ->
-    html
+    html >> newline
   where
     mkId (Ois x) = [id_ (headerId x)]
 
@@ -405,7 +423,11 @@
 -- See also: 'headerFragment'.
 
 headerId :: NonEmpty Inline -> Text
-headerId = T.intercalate "-" . T.words . T.toLower . asPlainText
+headerId = T.intercalate "-"
+  . T.words
+  . T.filter (\x -> isAlphaNum x || isSpace x)
+  . T.toLower
+  . asPlainText
 
 -- | Generate a 'URI' with just fragment from its textual representation.
 -- Useful for getting URL from id of a header.
diff --git a/Text/MMark/Parser.hs b/Text/MMark/Parser.hs
--- a/Text/MMark/Parser.hs
+++ b/Text/MMark/Parser.hs
@@ -7,7 +7,7 @@
 -- Stability   :  experimental
 -- Portability :  portable
 --
--- MMark parser.
+-- MMark markdown parser.
 
 {-# LANGUAGE BangPatterns       #-}
 {-# LANGUAGE CPP                #-}
@@ -30,13 +30,15 @@
 import Control.Applicative
 import Control.DeepSeq
 import Control.Monad
+import Control.Monad.Reader
 import Control.Monad.State.Strict
 import Data.Bifunctor (Bifunctor (..))
 import Data.Data (Data)
 import Data.Default.Class
 import Data.List.NonEmpty (NonEmpty (..), (<|))
 import Data.Maybe (isNothing, fromJust, fromMaybe)
-import Data.Semigroup ((<>))
+import Data.Monoid (Any (..))
+import Data.Semigroup (Semigroup (..))
 import Data.Text (Text)
 import Data.Typeable (Typeable)
 import Data.Void
@@ -59,15 +61,38 @@
 ----------------------------------------------------------------------------
 -- Data types
 
--- | Parser type we use internally.
+-- | Block-level parser type.
 
-type Parser = Parsec MMarkErr Text
+type BParser = ParsecT MMarkErr Text (Reader BlockEnv)
 
+-- | Block-level parser environment.
+
+data BlockEnv = BlockEnv
+  { benvAllowNaked :: !Bool
+    -- ^ Should we consider a paragraph that does not end with a blank line
+    -- 'Naked'? It does not make sense to do so for top-level document, but
+    -- in lists, 'Naked' text is pretty common.
+  , benvRefLevel :: !Pos
+    -- ^ Current reference level: 1 column for top-level of document, column
+    -- where content starts for block quotes and lists.
+  }
+
+instance Default BlockEnv where
+  def = BlockEnv
+    { benvAllowNaked = False
+    , benvRefLevel   = pos1
+    }
+
 -- | MMark custom parse errors.
 
 data MMarkErr
   = YamlParseError String
     -- ^ YAML error that occurred during parsing of a YAML block
+  | ListStartIndexTooBig Word
+    -- ^ Ordered list start numbers must be nine digits or less
+  | ListIndexOutOfOrder Word Word
+    -- ^ The index in an ordered list is out of order, first number is the
+    -- actual index we ran into, the second number is the expected index
   | NonFlankingDelimiterRun (NonEmpty Char)
     -- ^ This delimiter run should be in left- or right- flanking position
   deriving (Eq, Ord, Show, Read, Generic, Typeable, Data)
@@ -76,24 +101,35 @@
   showErrorComponent = \case
     YamlParseError str ->
       "YAML parse error: " ++ str
+    ListStartIndexTooBig n ->
+      "Ordered list start numbers must be nine digits or less, " ++ show n
+        ++ " is too big"
+    ListIndexOutOfOrder actual expected ->
+      "List index out of order: " ++ show actual ++ ", expected "
+        ++ show expected
     NonFlankingDelimiterRun dels ->
       showTokens dels ++ " should be in left- or right- flanking position"
 
 instance NFData MMarkErr
 
--- | Parser type for inlines.
+-- | Inline-level parser type. We store type of the last consumed character
+-- in the state.
 
 type IParser = StateT CharType (Parsec MMarkErr Text)
 
 -- | 'Inline' source pending parsing.
 
-data Isp = Isp SourcePos Text
-  deriving (Eq, Ord, Show)
+data Isp
+  = IspSpan SourcePos Text
+    -- ^ We have an inline source pending parsing
+  | IspError (ParseError Char MMarkErr)
+    -- ^ We should just return this parse error
+  deriving (Eq, Show)
 
 -- | Type of last seen character.
 
 data CharType
-  = SpaceChar          -- ^ White space
+  = SpaceChar          -- ^ White space or a transparent character
   | LeftFlankingDel    -- ^ Left flanking delimiter
   | RightFlankingDel   -- ^ Right flaking delimiter
   | OtherChar          -- ^ Other character
@@ -119,7 +155,7 @@
   | DoubleFrame InlineFrame InlineFrame -- ^ Two frames to be closed
   deriving (Eq, Ord, Show)
 
--- | Configuration in inline parser.
+-- | Configuration of inline parser.
 
 data InlineConfig = InlineConfig
   { iconfigAllowEmpty :: !Bool
@@ -137,16 +173,27 @@
     , iconfigAllowImages = True
     }
 
--- | A shortcut type synonym for sub-parsers that may fail and we can
--- recover from the failure.
+-- | An auxiliary type for collapsing levels of 'Either's.
 
-type E = Either (ParseError Char MMarkErr)
+data Pair s a
+  = PairL s
+  | PairR ([a] -> [a])
 
+instance Semigroup s => Semigroup (Pair s a) where
+  (PairL l) <> (PairL r) = PairL (l <> r)
+  (PairL l) <> (PairR _) = PairL l
+  (PairR _) <> (PairL r) = PairL r
+  (PairR l) <> (PairR r) = PairR (l . r)
+
+instance Semigroup s => Monoid (Pair s a) where
+  mempty  = PairR id
+  mappend = (<>)
+
 ----------------------------------------------------------------------------
--- Block parser
+-- Top-level API
 
 -- | Parse a markdown document in the form of a strict 'Text' value and
--- either report parse errors or return a 'MMark' document. Note that the
+-- either report parse errors or return an 'MMark' document. Note that the
 -- parser has the ability to report multiple parse errors at once.
 
 parse
@@ -157,30 +204,51 @@
   -> Either (NonEmpty (ParseError Char MMarkErr)) MMark
      -- ^ Parse errors or parsed document
 parse file input =
-  case runParser ((,) <$> optional pYamlBlock <*> pBlocks) file input of
+  case runReader (runParserT pMMark file input) def of
     -- NOTE This parse error only happens when document structure on block
     -- level cannot be parsed even with recovery, which should not normally
-    -- happen.
+    -- happen except for the cases when we deal with YAML parsing errors.
     Left err -> Left (nes err)
-    Right (myaml, blocks) ->
-      let parsed = doInline <$> blocks
-          doInline = \case
-            Left err -> Naked (Left err)
-            Right x  -> first (replaceEof "end of inline block")
-              . runIsp (pInlines def <* eof) <$> x
-          getErrs (Left e) es = e : es
-          getErrs _        es = es
-          fromRight (Right x) = x
-          fromRight _         =
-            error "Text.MMark.Parser.parse: the impossible happened"
-      in case NE.nonEmpty (foldMap (foldr getErrs []) parsed) of
-           Nothing -> Right MMark
+    Right (myaml, rawBlocks) ->
+      let parsed = doInline <$> rawBlocks
+          doInline = fmap
+            $ first (nes . replaceEof "end of inline block")
+            . runIsp (pInlines def <* eof)
+          f block =
+            case foldMap e2p block of
+              PairL errs -> PairL errs
+              PairR _    -> PairR (fmap fromRight block :)
+      in case foldMap f parsed of
+           PairL errs   -> Left errs
+           PairR blocks -> Right MMark
              { mmarkYaml      = myaml
-             , mmarkBlocks    = fmap fromRight <$> parsed
+             , mmarkBlocks    = blocks []
              , mmarkExtension = mempty }
-           Just es -> Left es
 
-pYamlBlock :: Parser Yaml.Value
+----------------------------------------------------------------------------
+-- Block parser
+
+-- | Parse an MMark document on block level.
+
+pMMark :: BParser (Maybe Yaml.Value, [Block Isp])
+pMMark = do
+  meyaml <- optional pYamlBlock
+  setTabWidth (mkPos 4)
+  blocks <- pBlocks
+  eof
+  return $ case meyaml of
+    Nothing ->
+      (Nothing, blocks)
+    Just (Left (pos, err)) ->
+      (Nothing, prependErr pos (YamlParseError err) blocks)
+    Just (Right yaml) ->
+      (Just yaml, blocks)
+
+-- | Parse a YAML block. On success return the actual parsed 'Yaml.Value' in
+-- 'Right', otherwise return 'SourcePos' of parse error and 'String'
+-- describing the error as generated by the @yaml@ package in 'Left'.
+
+pYamlBlock :: BParser (Either (SourcePos, String) Yaml.Value)
 pYamlBlock = do
   dpos <- getPosition
   string "---" *> sc' *> eol
@@ -195,37 +263,56 @@
   case (Yaml.decodeEither . TE.encodeUtf8 . T.intercalate "\n") ls of
     Left err' -> do
       let (apos, err) = splitYamlError (sourceName dpos) err'
-      setPosition (fromMaybe dpos apos)
-      (fancyFailure . E.singleton . ErrorCustom . YamlParseError) err
+      return $ Left (fromMaybe dpos apos, err)
     Right v ->
-      return v
+      return (Right v)
 
-pBlocks :: Parser [E (Block Isp)]
-pBlocks = do
-  setTabWidth (mkPos 4)
-  sc *> manyTill pBlock eof
+-- | Parse several (possibly zero) blocks in a row.
 
-pBlock :: Parser (E (Block Isp))
-pBlock = choice
-  [ try (pure <$> pThematicBreak)
-  , pAtxHeading
-  , pure <$> pFencedCodeBlock
-  , try (pure <$> pIndentedCodeBlock)
-  , pure <$> pParagraph ]
+pBlocks :: BParser [Block Isp]
+pBlocks = many pBlock
 
-pThematicBreak :: Parser (Block Isp)
+-- | Parse a single block of markdown document.
+
+pBlock :: BParser (Block Isp)
+pBlock = do
+  sc
+  rlevel <- asks benvRefLevel
+  alevel <- L.indentLevel
+  done   <- atEnd
+  if done || alevel < rlevel then empty else
+    case compare alevel (ilevel rlevel) of
+      LT -> choice
+        [ pThematicBreak
+        , pAtxHeading
+        , pFencedCodeBlock
+        , pUnorderedList
+        , pOrderedList
+        , pBlockquote
+        , pParagraph ]
+      _  ->
+          pIndentedCodeBlock
+
+-- | Parse a thematic break.
+
+pThematicBreak :: BParser (Block Isp)
 pThematicBreak = do
-  void casualLevel
-  l <- lookAhead nonEmptyLine
-  if isThematicBreak l
+  l' <- lookAhead nonEmptyLine
+  let l = T.filter (not . isSpace) l'
+  if T.length l >= 3   &&
+     (T.all (== '*') l ||
+      T.all (== '-') l ||
+      T.all (== '_') l)
     then ThematicBreak <$ nonEmptyLine <* sc
     else empty
 
-pAtxHeading :: Parser (E (Block Isp))
+-- | Parse an ATX heading.
+
+pAtxHeading :: BParser (Block Isp)
 pAtxHeading = do
-  (void . lookAhead . try) start
+  (void . lookAhead . try) hashIntro
   withRecovery recover $ do
-    hlevel <- length <$> start
+    hlevel <- length <$> hashIntro
     sc1'
     ispPos <- getPosition
     r <- someTill (satisfy notNewline <?> "heading character") . try $
@@ -237,19 +324,21 @@
           4 -> Heading4
           5 -> Heading5
           _ -> Heading6
-    (Right . toBlock) (Isp ispPos (T.strip (T.pack r))) <$ sc
+    toBlock (IspSpan ispPos (T.strip (T.pack r))) <$ sc
   where
-    start = casualLevel *> count' 1 6 (char '#')
+    hashIntro = count' 1 6 (char '#')
     recover err =
-      Left err <$ takeWhileP Nothing notNewline <* sc
+      Heading1 (IspError err) <$ takeWhileP Nothing notNewline <* sc
 
-pFencedCodeBlock :: Parser (Block Isp)
+-- | Parse a fenced code block.
+
+pFencedCodeBlock :: BParser (Block Isp)
 pFencedCodeBlock = do
-  level <- casualLevel
   let p ch = try $ do
         void $ count 3 (char ch)
         n  <- (+ 3) . length <$> many (char ch)
-        ml <- optional (T.strip <$> someEscapedWith notNewline <?> "info string")
+        ml <- optional
+          (T.strip <$> someEscapedWith notNewline <?> "info string")
         guard (maybe True (not . T.any (== '`')) ml)
         return
           (ch, n,
@@ -259,60 +348,211 @@
                  if T.null l
                    then Nothing
                    else Just l)
+  alevel <- L.indentLevel
   (ch, n, infoString) <- (p '`' <|> p '~') <* eol
   let content = label "code block content" (option "" nonEmptyLine <* eol)
       closingFence = try . label "closing code fence" $ do
-        void casualLevel'
+        clevel <- ilevel <$> asks benvRefLevel
+        void $ L.indentGuard sc' LT clevel
         void $ count n (char ch)
         (void . many . char) ch
         sc'
         eof <|> eol
   ls <- manyTill content closingFence
-  CodeBlock infoString (assembleCodeBlock level ls) <$ sc
+  CodeBlock infoString (assembleCodeBlock alevel ls) <$ sc
 
-pIndentedCodeBlock :: Parser (Block Isp)
+-- | Parse an indented code block.
+
+pIndentedCodeBlock :: BParser (Block Isp)
 pIndentedCodeBlock = do
-  initialIndent <- codeBlockLevel
+  alevel <- L.indentLevel
+  clevel <- ilevel <$> asks benvRefLevel
   let go ls = do
-        immediate <- lookAhead (True <$ try codeBlockLevel' <|> pure False)
-        eventual  <- lookAhead (True <$ try codeBlockLevel  <|> pure False)
-        if not immediate && not eventual
-          then return ls
-          else do
+        immediate <- lookAhead $
+          (>= clevel) <$> (sc' *> L.indentLevel)
+        eventual  <- lookAhead $
+          (>= clevel) <$> (sc *> L.indentLevel)
+        if immediate || eventual
+          then do
             l        <- option "" nonEmptyLine
             continue <- eol'
             if continue
               then go (l:ls)
               else return (l:ls)
+          else return ls
       -- NOTE This is a bit unfortunate, but it's difficult to guarantee
       -- that preceding space is not yet consumed when we get to
       -- interpreting input as an indented code block, so we need to restore
       -- the space this way.
-      f x      = T.replicate (unPos initialIndent - 1) " " <> x
+      f x      = T.replicate (unPos alevel - 1) " " <> x
       g []     = []
       g (x:xs) = f x : xs
   ls <- g . reverse . dropWhile isBlank <$> go []
-  CodeBlock Nothing (assembleCodeBlock (mkPos 5) ls) <$ sc
+  CodeBlock Nothing (assembleCodeBlock clevel ls) <$ sc
 
-pParagraph :: Parser (Block Isp)
+-- | Parse an unorederd list.
+
+pUnorderedList :: BParser (Block Isp)
+pUnorderedList = do
+  (bullet, bulletPos, minLevel, indLevel) <-
+    pListBullet Nothing
+  x      <- innerBlocks bulletPos minLevel indLevel
+  xs     <- many $ do
+    (_, bulletPos', minLevel', indLevel') <-
+      pListBullet (Just (bullet, bulletPos))
+    innerBlocks bulletPos' minLevel' indLevel'
+  return (UnorderedList (normalizeListItems (x:|xs)))
+  where
+    innerBlocks bulletPos minLevel indLevel = do
+      p <- getPosition
+      let tooFar = sourceLine p > sourceLine bulletPos <> pos1
+          rlevel = slevel minLevel indLevel
+      if tooFar || sourceColumn p < minLevel
+        then return [if tooFar then emptyParagraph else emptyNaked]
+        else subEnv True rlevel pBlocks
+
+-- | Parse a list bullet. Return a tuple with the following components (in
+-- order):
+--
+--     * 'Char' used to represent the bullet
+--     * 'SourcePos' at which the bullet was located
+--     * the closest column position where content could start
+--     * the indentation level after the bullet
+
+pListBullet
+  :: Maybe (Char, SourcePos)
+     -- ^ Bullet 'Char' and start position of the first bullet in a list
+  -> BParser (Char, SourcePos, Pos, Pos)
+pListBullet mbullet = try $ do
+  pos     <- getPosition
+  l       <- (<> mkPos 2) <$> L.indentLevel
+  bullet  <-
+    case mbullet of
+      Nothing -> char '-' <|> char '+' <|> char '*'
+      Just (bullet, bulletPos) -> do
+        guard (sourceColumn pos >= sourceColumn bulletPos)
+        char bullet
+  eof <|> sc1
+  l'      <- L.indentLevel
+  return (bullet, pos, l, l')
+
+-- | Parse an ordered list.
+
+pOrderedList :: BParser (Block Isp)
+pOrderedList = do
+  (startIx, del, startPos, minLevel, indLevel) <-
+    pListIndex Nothing
+  x  <- innerBlocks startPos minLevel indLevel
+  xs <- manyIndexed (startIx + 1) $ \expectedIx -> do
+    (actualIx, _, startPos', minLevel', indLevel') <-
+      pListIndex (Just (del, startPos))
+    let f blocks =
+          if actualIx == expectedIx
+            then blocks
+            else prependErr
+                   startPos'
+                   (ListIndexOutOfOrder actualIx expectedIx)
+                   blocks
+    f <$> innerBlocks startPos' minLevel' indLevel'
+  return . OrderedList startIx . normalizeListItems $
+    (if startIx <= 999999999
+       then x
+       else prependErr startPos (ListStartIndexTooBig startIx) x)
+    :| xs
+  where
+    innerBlocks indexPos minLevel indLevel = do
+      p <- getPosition
+      let tooFar = sourceLine p > sourceLine indexPos <> pos1
+          rlevel = slevel minLevel indLevel
+      if tooFar || sourceColumn p < minLevel
+        then return [if tooFar then emptyParagraph else emptyNaked]
+        else subEnv True rlevel pBlocks
+
+-- | Parse a list index. Return a tuple with the following components (in
+-- order):
+--
+--     * 'Word' parsed numeric index
+--     * 'Char' used as delimiter after the numeric index
+--     * 'SourcePos' at which the index was located
+--     * the closest column position where content could start
+--     * the indentation level after the index
+
+pListIndex
+  :: Maybe (Char, SourcePos)
+     -- ^ Delimiter 'Char' and start position of the first index in a list
+  -> BParser (Word, Char, SourcePos, Pos, Pos)
+pListIndex mstart = try $ do
+  pos <- getPosition
+  i   <- L.decimal
+  del <- case mstart of
+    Nothing -> char '.' <|> char ')'
+    Just (del, startPos) -> do
+      guard (sourceColumn pos >= sourceColumn startPos)
+      char del
+  l   <- (<> pos1) <$> L.indentLevel
+  eof <|> sc1
+  l'  <- L.indentLevel
+  return (i, del, pos, l, l')
+
+-- | Parse a block quote.
+
+pBlockquote :: BParser (Block Isp)
+pBlockquote = do
+  minLevel <- try $ do
+    minLevel <- (<> pos1) <$> L.indentLevel
+    void (char '>')
+    eof <|> sc
+    l <- L.indentLevel
+    return $
+      if l > minLevel
+        then minLevel <> pos1
+        else minLevel
+  indLevel <- L.indentLevel
+  if indLevel >= minLevel
+    then do
+      let rlevel = slevel minLevel indLevel
+      xs <- subEnv False rlevel pBlocks
+      return (Blockquote xs)
+    else return (Blockquote [])
+
+-- | Parse a paragraph or naked text (is some cases).
+
+pParagraph :: BParser (Block Isp)
 pParagraph = do
-  void casualLevel
-  startPos <- getPosition
-  let go = do
-        ml <- lookAhead (optional nonEmptyLine)
-        case ml of
-          Nothing -> return []
-          Just l ->
-            if isBlank l
-              then return []
-              else do
-                void nonEmptyLine
-                continue <- eol'
-                (l :) <$> if continue then go else return []
+  startPos   <- getPosition
+  allowNaked <- asks benvAllowNaked
+  rlevel     <- asks benvRefLevel
+  let go ls = do
+        l <- lookAhead (option "" nonEmptyLine)
+        broken <- succeeds . lookAhead . try $ do
+          sc
+          alevel <- L.indentLevel
+          guard (alevel < ilevel rlevel)
+          unless (alevel < rlevel) . choice $
+            [ void (char '>')
+            , void pThematicBreak
+            , void pAtxHeading
+            , void (pListBullet Nothing)
+            , void (pListIndex  Nothing) ]
+        if isBlank l
+          then return (ls, Paragraph)
+          else if broken
+                 then return (ls, Naked)
+                 else do
+                   void nonEmptyLine
+                   continue <- eol'
+                   let ls' = ls . (l:)
+                   if continue
+                     then go ls'
+                     else return (ls', Naked)
   l        <- nonEmptyLine
   continue <- eol'
-  ls       <- if continue then go else return []
-  Paragraph (Isp startPos (assembleParagraph (l:ls))) <$ sc
+  (ls, toBlock) <-
+    if continue
+      then go id
+      else return (id, Naked)
+  (if allowNaked then toBlock else Paragraph)
+    (IspSpan startPos (assembleParagraph (l:ls []))) <$ sc
 
 ----------------------------------------------------------------------------
 -- Inline parser
@@ -323,7 +563,8 @@
   :: IParser a         -- ^ The parser to run
   -> Isp               -- ^ Input for the parser
   -> Either (ParseError Char MMarkErr) a -- ^ Result of parsing
-runIsp p (Isp startPos input) =
+runIsp _ (IspError err) = Left err
+runIsp p (IspSpan startPos input) =
   snd (runParser' (evalStateT p SpaceChar) pst)
   where
     pst = State
@@ -332,6 +573,8 @@
       , stateTokensProcessed = 0
       , stateTabWidth        = mkPos 4 }
 
+-- | Parse inlines using settings from given 'InlineConfig'.
+
 pInlines :: InlineConfig -> IParser (NonEmpty Inline)
 pInlines InlineConfig {..} =
   if iconfigAllowEmpty
@@ -348,6 +591,8 @@
       , pPlain ]
     angel = between (char '<') (char '>')
 
+-- | Parse a code span.
+
 pCodeSpan :: IParser Inline
 pCodeSpan = do
   n <- try (length <$> some (char '`'))
@@ -362,6 +607,8 @@
   put OtherChar
   return r
 
+-- | Parse a link.
+
 pInlineLink :: IParser Inline
 pInlineLink = do
   xs     <- between (char '[') (char ']') $
@@ -373,6 +620,8 @@
   put OtherChar
   return (Link xs dest mtitle)
 
+-- | Parse an image.
+
 pImage :: IParser Inline
 pImage = do
   let nonEmptyDesc = char '!' *> between (char '[') (char ']')
@@ -385,6 +634,8 @@
   put OtherChar
   return (Image alt src mtitle)
 
+-- | Parse a URI.
+
 pUri :: IParser URI
 pUri = do
   uri <- between (char '<') (char '>') URI.parser <|> naked
@@ -411,6 +662,8 @@
               fancyFailure xs
         Right x -> return x
 
+-- | Parse a title of a link or an image.
+
 pTitle :: IParser Text
 pTitle = choice
   [ p '\"' '\"'
@@ -420,6 +673,8 @@
     p start end = between (char start) (char end) $
       manyEscapedWith (/= end) "unescaped character"
 
+-- | Parse an autolink.
+
 pAutolink :: IParser Inline
 pAutolink = do
   notFollowedBy (char '>') -- empty links don't make sense
@@ -434,6 +689,9 @@
           uri' = URI.makeAbsolute mailtoScheme uri
       in Link txt uri' Nothing
 
+-- | Parse inline content inside some sort of emphasis, strikeout,
+-- superscript, and\/or subscript markup.
+
 pEnclosedInline :: IParser Inline
 pEnclosedInline = do
   let noEmpty = def { iconfigAllowEmpty = False }
@@ -467,15 +725,17 @@
           return . liftFrame thatFrame $
             liftFrame thisFrame inlines0 <| inlines1
 
+-- | Parse an opening markup sequence corresponding to given 'InlineState'
+
 pLfdr :: InlineState -> IParser InlineState
 pLfdr st = try $ do
   let dels = inlineStateDel st
-  mpos <- getNextTokenPosition
+  pos <- getPosition
   void (string dels)
   leftChar   <- get
   mrightChar <- lookAhead (optional anyChar)
   let failNow = do
-        forM_ mpos setPosition
+        setPosition pos
         (mmarkErr . NonFlankingDelimiterRun . toNesTokens) dels
   case (leftChar, isTransparent <$> mrightChar) of
     (_, Nothing)          -> failNow
@@ -487,15 +747,17 @@
   put LeftFlankingDel
   return st
 
+-- | Parse a closing markdup sequence corresponding to given 'InlineFrame'.
+
 pRfdr :: InlineFrame -> IParser InlineFrame
 pRfdr frame = try $ do
   let dels = inlineFrameDel frame
-  mpos <- getNextTokenPosition
+  pos <- getPosition
   void (string dels)
   leftChar   <- get
   mrightChar <- lookAhead (optional anyChar)
   let failNow = do
-        forM_ mpos setPosition
+        setPosition pos
         (mmarkErr . NonFlankingDelimiterRun . toNesTokens) dels
   case (leftChar, mrightChar) of
     (SpaceChar, _) -> failNow
@@ -508,6 +770,8 @@
   put RightFlankingDel
   return frame
 
+-- | Parse a hard line break.
+
 pHardLineBreak :: IParser Inline
 pHardLineBreak = do
   void (char '\\')
@@ -517,6 +781,8 @@
   put SpaceChar
   return LineBreak
 
+-- | Parse plain text.
+
 pPlain :: IParser Inline
 pPlain = Plain . T.pack <$> some
   (pEscapedChar <|> pNewline <|> pNonEscapedChar)
@@ -537,19 +803,7 @@
 ----------------------------------------------------------------------------
 -- Parsing helpers
 
-casualLevel :: Parser Pos
-casualLevel = L.indentGuard sc LT (mkPos 5)
-
-casualLevel' :: Parser Pos
-casualLevel' = L.indentGuard sc' LT (mkPos 5)
-
-codeBlockLevel :: Parser Pos
-codeBlockLevel = L.indentGuard sc GT (mkPos 4)
-
-codeBlockLevel' :: Parser Pos
-codeBlockLevel' = L.indentGuard sc' GT (mkPos 4)
-
-nonEmptyLine :: Parser Text
+nonEmptyLine :: BParser Text
 nonEmptyLine = takeWhile1P Nothing notNewline
 
 manyEscapedWith :: MonadParsec e Text m => (Char -> Bool) -> String -> m Text
@@ -583,20 +837,18 @@
 eol' :: MonadParsec e Text m => m Bool
 eol' = option False (True <$ eol)
 
-----------------------------------------------------------------------------
--- Block-level predicates
-
-isThematicBreak :: Text -> Bool
-isThematicBreak l' = T.length l >= 3 && indentLevel l' < 4 &&
-  (T.all (== '*') l ||
-   T.all (== '-') l ||
-   T.all (== '_') l)
-  where
-    l = T.filter (not . isSpace) l'
+subEnv :: Bool -> Pos -> BParser a -> BParser a
+subEnv benvAllowNaked benvRefLevel = local (const BlockEnv {..})
 
 ----------------------------------------------------------------------------
 -- Other helpers
 
+slevel :: Pos -> Pos -> Pos
+slevel a l = if l >= ilevel a then a else l
+
+ilevel :: Pos -> Pos
+ilevel = (<> mkPos 4)
+
 isSpace :: Char -> Bool
 isSpace x = x == ' ' || x == '\t'
 
@@ -648,31 +900,14 @@
 isTransparent :: Char -> Bool
 isTransparent x = Char.isSpace x || isTransparentPunctuation x
 
-nes :: a -> NonEmpty a
-nes a = a :| []
-
-assembleParagraph :: [Text] -> Text
-assembleParagraph = go
-  where
-    go []     = ""
-    go [x]    = T.dropWhileEnd isSpace x
-    go (x:xs) = x <> "\n" <> go xs
-
 assembleCodeBlock :: Pos -> [Text] -> Text
 assembleCodeBlock indent ls = T.unlines (stripIndent indent <$> ls)
 
-indentLevel :: Text -> Int
-indentLevel = T.foldl' f 0 . T.takeWhile isSpace
-  where
-    f n ch
-      | ch == ' '  = n + 1
-      | ch == '\t' = n + 4
-      | otherwise  = n
-
 stripIndent :: Pos -> Text -> Text
 stripIndent indent txt = T.drop m txt
   where
-    m = snd $ T.foldl' f (0, 0) (T.takeWhile isSpace txt)
+    m = snd $ T.foldl' f (0, 0) (T.takeWhile p txt)
+    p x = isSpace x || x == '>'
     f (!j, !n) ch
       | j  >= i    = (j, n)
       | ch == ' '  = (j + 1, n + 1)
@@ -680,6 +915,13 @@
       | otherwise  = (j, n)
     i = unPos indent - 1
 
+assembleParagraph :: [Text] -> Text
+assembleParagraph = go
+  where
+    go []     = ""
+    go [x]    = T.dropWhileEnd isSpace x
+    go (x:xs) = x <> "\n" <> go xs
+
 collapseWhiteSpace :: Text -> Text
 collapseWhiteSpace =
   T.stripEnd . T.filter (/= '\0') . snd . T.mapAccumL f True
@@ -728,12 +970,6 @@
     f EndOfInput = Label (NE.fromList altLabel)
     f x          = x
 
-mmarkErr :: MonadParsec MMarkErr s m => MMarkErr -> m a
-mmarkErr = fancyFailure . E.singleton . ErrorCustom
-
-toNesTokens :: Text -> NonEmpty Char
-toNesTokens = NE.fromList . T.unpack
-
 isEmailUri :: URI -> Maybe Text
 isEmailUri uri =
   case URI.unRText <$> URI.uriPath uri of
@@ -745,9 +981,6 @@
         else Nothing
     _ -> Nothing
 
-mailtoScheme :: URI.RText 'URI.Scheme
-mailtoScheme = fromJust (URI.mkScheme "mailto")
-
 splitYamlError :: FilePath -> String -> (Maybe SourcePos, String)
 splitYamlError file str = maybe (Nothing, str) (first pure) (parseMaybe p str)
   where
@@ -760,3 +993,64 @@
       void (string ":\n")
       r <- takeRest
       return (SourcePos file l c, r)
+
+emptyParagraph :: Block Isp
+emptyParagraph = Paragraph (IspSpan (initialPos "") "")
+
+emptyNaked :: Block Isp
+emptyNaked = Naked (IspSpan (initialPos "") "")
+
+manyIndexed :: (Alternative m, Num n) => n -> (n -> m a) -> m [a]
+manyIndexed n' m = go n'
+  where
+    go !n = liftA2 (:) (m n) (go (n + 1)) <|> pure []
+
+normalizeListItems :: NonEmpty [Block Isp] -> NonEmpty [Block Isp]
+normalizeListItems xs' =
+  if getAny $ foldMap (foldMap (Any . isParagraph)) (drop 1 x :| xs)
+    then fmap toParagraph <$> xs'
+    else case x of
+           [] -> xs'
+           (y:ys) -> r $ (toNaked y : ys) :| xs
+  where
+    (x:|xs) = r xs'
+    r = NE.reverse . fmap reverse
+    isParagraph = \case
+      OrderedList _ _ -> False
+      UnorderedList _ -> False
+      Naked         _ -> False
+      _               -> True
+    toParagraph (Naked inner) = Paragraph inner
+    toParagraph other         = other
+    toNaked (Paragraph inner) = Naked inner
+    toNaked other             = other
+
+e2p :: Either a b -> Pair a b
+e2p = \case
+  Left  a -> PairL a
+  Right b -> PairR (b:)
+
+succeeds :: Alternative m => m () -> m Bool
+succeeds m = True <$ m <|> pure False
+
+prependErr :: SourcePos -> MMarkErr -> [Block Isp] -> [Block Isp]
+prependErr pos custom blocks = Naked (IspError err) : blocks
+  where
+    err = FancyError (nes pos) (E.singleton $ ErrorCustom custom)
+
+mmarkErr :: MonadParsec MMarkErr s m => MMarkErr -> m a
+mmarkErr = fancyFailure . E.singleton . ErrorCustom
+
+mailtoScheme :: URI.RText 'URI.Scheme
+mailtoScheme = fromJust (URI.mkScheme "mailto")
+
+toNesTokens :: Text -> NonEmpty Char
+toNesTokens = NE.fromList . T.unpack
+
+nes :: a -> NonEmpty a
+nes a = a :| []
+
+fromRight :: Either a b -> b
+fromRight (Right x) = x
+fromRight _         =
+  error "Text.MMark.Parser.fromRight: the impossible happened"
diff --git a/mmark.cabal b/mmark.cabal
--- a/mmark.cabal
+++ b/mmark.cabal
@@ -1,7 +1,7 @@
 name:                 mmark
-version:              0.0.1.1
+version:              0.0.2.0
 cabal-version:        >= 1.18
-tested-with:          GHC==7.10.3, GHC==8.0.2, GHC==8.2.1
+tested-with:          GHC==7.10.3, GHC==8.0.2, GHC==8.2.2
 license:              BSD3
 license-file:         LICENSE.md
 author:               Mark Karpov <markkarpov92@gmail.com>
diff --git a/tests/Text/MMarkSpec.hs b/tests/Text/MMarkSpec.hs
--- a/tests/Text/MMarkSpec.hs
+++ b/tests/Text/MMarkSpec.hs
@@ -34,32 +34,34 @@
       it "CM3" $
         "    a\ta\n    ὐ\ta" ==->
           "<pre><code>a\ta\nὐ\ta\n</code></pre>\n"
-      xit "CM4" $ -- FIXME pending lists
+      it "CM4" $
         "  - foo\n\n\tbar" ==->
           "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n"
-      xit "CM5" $ -- FIXME pending lists
+      it "CM5" $
         "- foo\n\n\t\tbar" ==->
           "<ul>\n<li>\n<p>foo</p>\n<pre><code>  bar\n</code></pre>\n</li>\n</ul>\n"
-      xit "CM6" $ -- FIXME pending blockquotes
+      it "CM6" $
         ">\t\tfoo" ==->
           "<blockquote>\n<pre><code>  foo\n</code></pre>\n</blockquote>\n"
-      xit "CM7" $ -- FIXME pending lists
+      it "CM7" $
         "-\t\tfoo" ==->
           "<ul>\n<li>\n<pre><code>  foo\n</code></pre>\n</li>\n</ul>\n"
       it "CM8" $
         "    foo\n\tbar" ==->
           "<pre><code>foo\nbar\n</code></pre>\n"
-      xit "CM9" $ -- FIXME pending lists
+      it "CM9" $
         " - foo\n   - bar\n\t - baz" ==->
-          "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
+          "<ul>\n<li>\nfoo\n<ul>\n<li>\nbar\n<ul>\n<li>\nbaz\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
       it "CM10" $
         "#\tFoo" ==-> "<h1 id=\"foo\">Foo</h1>\n"
       it "CM11" $
         "*\t*\t*\t" ==-> "<hr>\n"
     context "3.1 Precedence" $
-      xit "CM12" $ -- FIXME pending lists
-        "- `one\n- two`" ==->
-          "<ul>\n<li>`one</li>\n<li>two`</li>\n</ul>\n"
+      it "CM12" $
+        let s = "- `one\n- two`"
+        in s ~~->
+           [ err (posN 6 s) (ueib <> etok '`' <> elabel "code span content")
+           , err (posN 13 s) (ueib <> etok '`' <> elabel "code span content") ]
     context "4.1 Thematic breaks" $ do
       it "CM13" $
         "***\n---\n___" ==-> "<hr>\n<hr>\n<hr>\n"
@@ -76,7 +78,7 @@
         "    ***" ==-> "<pre><code>***\n</code></pre>\n"
       it "CM19" $
         let s = "Foo\n    ***\n"
-        in s ~-> errFancy (posN 10 s)  (nonFlanking "*")
+        in s ~-> errFancy (posN 10 s) (nonFlanking "*")
       it "CM20" $
         "_____________________________________" ==->
           "<hr>\n"
@@ -93,21 +95,21 @@
         in s ~-> errFancy posI (nonFlanking "_")
       it "CM26" $
         " *\\-*" ==-> "<p><em>-</em></p>\n"
-      xit "CM27" $ -- FIXME pending lists
+      it "CM27" $
         "- foo\n***\n- bar" ==->
-         "<ul>\n<li>foo</li>\n</ul>\n<hr />\n<ul>\n<li>bar</li>\n</ul>\n"
+          "<ul>\n<li>\nfoo\n</li>\n</ul>\n<hr>\n<ul>\n<li>\nbar\n</li>\n</ul>\n"
       it "CM28" $
-        let s = "Foo\n***\nbar"
-        in s ~-> errFancy (posN 6 s) (nonFlanking "*")
+        "Foo\n***\nbar" ==->
+          "<p>Foo</p>\n<hr>\n<p>bar</p>\n"
       xit "CM29" $ -- FIXME pending setext headings
         "Foo\n---\nbar" ==->
           "<h2>Foo</h2>\n<p>bar</p>\n"
-      xit "CM30" $ -- FIXME pending lists
+      it "CM30" $
         "* Foo\n* * *\n* Bar" ==->
-          "<ul>\n<li>Foo</li>\n</ul>\n<hr />\n<ul>\n<li>Bar</li>\n</ul>\n"
-      xit "CM31" $ -- FIXME pending lists
+          "<ul>\n<li>\nFoo\n</li>\n<li>\n<ul>\n<li>\n<ul>\n<li>\n\n</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>\nBar\n</li>\n</ul>\n"
+      it "CM31" $
         "- Foo\n- * * *" ==->
-          "<ul>\n<li>Foo</li>\n<li>\n<hr />\n</li>\n</ul>\n"
+          "<ul>\n<li>\nFoo\n</li>\n<li>\n<hr>\n</li>\n</ul>\n"
     context "4.2 ATX headings" $ do
       it "CM32" $
         "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo" ==->
@@ -123,7 +125,7 @@
       it "CM35" $
         "\\## foo" ==-> "<p>## foo</p>\n"
       it "CM36" $
-        "# foo *bar* \\*baz\\*" ==-> "<h1 id=\"foo-bar-*baz*\">foo <em>bar</em> *baz*</h1>\n"
+        "# foo *bar* \\*baz\\*" ==-> "<h1 id=\"foo-bar-baz\">foo <em>bar</em> *baz*</h1>\n"
       it "CM37" $
         "#                  foo                     " ==->
           "<h1 id=\"foo\">foo</h1>\n"
@@ -143,18 +145,18 @@
       it "CM43" $
         "### foo ###     " ==-> "<h3 id=\"foo\">foo</h3>\n"
       it "CM44" $
-        "### foo ### b" ==-> "<h3 id=\"foo-###-b\">foo ### b</h3>\n"
+        "### foo ### b" ==-> "<h3 id=\"foo-b\">foo ### b</h3>\n"
       it "CM45" $
-        "# foo#" ==-> "<h1 id=\"foo#\">foo#</h1>\n"
+        "# foo#" ==-> "<h1 id=\"foo\">foo#</h1>\n"
       it "CM46" $
         "### foo \\###\n## foo #\\##\n# foo \\#" ==->
-          "<h3 id=\"foo-###\">foo ###</h3>\n<h2 id=\"foo-###\">foo ###</h2>\n<h1 id=\"foo-#\">foo #</h1>\n"
+          "<h3 id=\"foo\">foo ###</h3>\n<h2 id=\"foo\">foo ###</h2>\n<h1 id=\"foo\">foo #</h1>\n"
       it "CM47" $
         "****\n## foo\n****" ==->
           "<hr>\n<h2 id=\"foo\">foo</h2>\n<hr>\n"
       it "CM48" $
         "Foo bar\n# baz\nBar foo" ==->
-          "<p>Foo bar\n# baz\nBar foo</p>\n"
+          "<p>Foo bar</p>\n<h1 id=\"baz\">baz</h1>\n<p>Bar foo</p>\n"
       it "CM49" $
         let s = "## \n#\n### ###"
         in s ~~->
@@ -164,12 +166,12 @@
       it "CM76" $
         "    a simple\n      indented code block" ==->
           "<pre><code>a simple\n  indented code block\n</code></pre>\n"
-      xit "CM77" $ -- FIXME pending lists
+      it "CM77" $
         "  - foo\n\n    bar" ==->
           "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n"
-      xit "CM78" $ -- FIXME pending lists
+      it "CM78" $
         "1.  foo\n\n    - bar" ==->
-           "<ol>\n<li>\n<p>foo</p>\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ol>\n"
+           "<ol>\n<li>\n<p>foo</p>\n<ul>\n<li>\nbar\n</li>\n</ul>\n</li>\n</ol>\n"
       it "CM79" $
         "    <a/>\n    *hi*\n\n    - one" ==->
           "<pre><code>&lt;a/&gt;\n*hi*\n\n- one\n</code></pre>\n"
@@ -224,9 +226,9 @@
         let s = "`````\n\n```\naaa\n"
         in s ~-> err (posN 15 s)
            (ueof <> elabel "closing code fence" <> elabel "code block content")
-      xit "CM96" $ -- FIXME pending blockquotes
-        "> ```\n> aaa\n\nbbb" ==->
-          "<blockquote>\n<pre><code>aaa\n</code></pre>\n</blockquote>\n<p>bbb</p>\n"
+      it "CM96" $
+        let s = "> ```\n> aaa\n\nbbb\n"
+        in s ~-> err (posN 17 s) (ueof <> elabel "closing code fence" <> elabel "code block content")
       it "CM97" $
         "```\n\n  \n```" ==->
           "<pre><code>\n  \n</code></pre>\n"
@@ -311,6 +313,303 @@
       it "CM188" $
         "  \n\naaa\n  \n\n# aaa\n\n  " ==->
           "<p>aaa</p>\n<h1 id=\"aaa\">aaa</h1>\n"
+    context "5.1 Block quotes" $ do
+      it "CM189" $
+        "> # Foo\n  bar\n  baz" ==->
+          "<blockquote>\n<h1 id=\"foo\">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n"
+      it "CM190" $
+        "># Foo\n bar\n  baz" ==->
+          "<blockquote>\n<h1 id=\"foo\">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n"
+      it "CM191" $
+        "   > # Foo\n     bar\n     baz" ==->
+          "<blockquote>\n<h1 id=\"foo\">Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n"
+      it "CM192" $
+        "    > # Foo\n    > bar\n    > baz" ==->
+          "<pre><code>&gt; # Foo\n&gt; bar\n&gt; baz\n</code></pre>\n"
+      it "CM193" $
+        "> # Foo\n> bar\nbaz" ==->
+          "<blockquote>\n<h1 id=\"foo\">Foo</h1>\n</blockquote>\n<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n"
+      it "CM194" $
+        "> bar\nbaz\n> foo" ==->
+          "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n<blockquote>\n<p>foo</p>\n</blockquote>\n"
+      it "CM195" $
+        "> foo\n---" ==->
+          "<blockquote>\n<p>foo</p>\n</blockquote>\n<hr>\n"
+      it "CM196" $
+        "> - foo\n- bar" ==->
+          "<blockquote>\n<ul>\n<li>\nfoo\n</li>\n</ul>\n</blockquote>\n<ul>\n<li>\nbar\n</li>\n</ul>\n"
+      it "CM197" $
+        ">     foo\n    bar" ==->
+          "<blockquote>\n<pre><code>foo\n</code></pre>\n<p>bar</p>\n</blockquote>\n"
+      it "CM198" $
+        "> ```\nfoo\n```" ==->
+          "<blockquote>\n<pre><code>foo\n</code></pre>\n</blockquote>\n"
+      it "CM199" $
+        "> foo\n    - bar" ==->
+          "<blockquote>\n<p>foo</p>\n<ul>\n<li>\nbar\n</li>\n</ul>\n</blockquote>\n"
+      it "CM200" $
+        ">" ==->
+          "<blockquote>\n</blockquote>\n"
+      it "CM201" $
+        ">\n>  \n> " ==->
+          "<blockquote>\n</blockquote>\n<blockquote>\n</blockquote>\n<blockquote>\n</blockquote>\n"
+      it "CM202" $
+        ">\n  foo\n   " ==->
+          "<blockquote>\n<p>foo</p>\n</blockquote>\n"
+      it "CM203" $
+        "> foo\n\n> bar" ==->
+          "<blockquote>\n<p>foo</p>\n</blockquote>\n<blockquote>\n<p>bar</p>\n</blockquote>\n"
+      it "CM204" $
+        "> foo\n  bar" ==->
+          "<blockquote>\n<p>foo\nbar</p>\n</blockquote>\n"
+      it "CM205" $
+        "> foo\n\n  bar" ==->
+          "<blockquote>\n<p>foo</p>\n<p>bar</p>\n</blockquote>\n"
+      it "CM206" $
+        "foo\n> bar" ==->
+          "<p>foo</p>\n<blockquote>\n<p>bar</p>\n</blockquote>\n"
+      it "CM207" $
+        "> aaa\n***\n> bbb" ==->
+          "<blockquote>\n<p>aaa</p>\n</blockquote>\n<hr>\n<blockquote>\n<p>bbb</p>\n</blockquote>\n"
+      it "CM208" $
+        "> bar\n  baz" ==->
+          "<blockquote>\n<p>bar\nbaz</p>\n</blockquote>\n"
+      it "CM209" $
+        "> bar\n\nbaz" ==->
+          "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n"
+      it "CM210" $
+        "> bar\n\nbaz" ==->
+          "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n"
+      it "CM211" $
+        "> > > foo\nbar" ==->
+          "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo</p>\n</blockquote>\n</blockquote>\n</blockquote>\n<p>bar</p>\n"
+      it "CM212" $
+        ">>> foo\n    bar\n    baz" ==->
+          "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo\nbar\nbaz</p>\n</blockquote>\n</blockquote>\n</blockquote>\n"
+      it "CM213" $
+        ">     code\n\n>    not code" ==->
+          "<blockquote>\n<pre><code>code\n</code></pre>\n</blockquote>\n<blockquote>\n<p>not code</p>\n</blockquote>\n"
+    context "5.2 List items" $ do
+      it "CM214" $
+        "A paragraph\nwith two lines.\n\n    indented code\n\n> A block quote." ==->
+          "<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n"
+      it "CM215" $
+        "1.  A paragraph\n    with two lines.\n\n        indented code\n\n    > A block quote." ==->
+          "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n"
+      it "CM216" $
+        "- one\n\n two" ==->
+          "<ul>\n<li>\none\n</li>\n</ul>\n<p>two</p>\n"
+      it "CM217" $
+        "- one\n\n  two" ==->
+          "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n"
+      it "CM218" $
+        " -    one\n\n     two" ==->
+          "<ul>\n<li>\none\n</li>\n</ul>\n<pre><code> two\n</code></pre>\n"
+      it "CM219" $
+        " -    one\n\n      two" ==->
+          "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n"
+      it "CM220" $
+        "   > > 1.  one\n\n       two" ==->
+          "<blockquote>\n<blockquote>\n<ol>\n<li>\none\n</li>\n</ol>\n<p>two</p>\n</blockquote>\n</blockquote>\n"
+      it "CM221" $
+        ">>- one\n\n     two" ==->
+          "<blockquote>\n<blockquote>\n<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n</blockquote>\n</blockquote>\n"
+      it "CM222" $
+        "-one\n\n2.two" ==->
+          "<p>-one</p>\n<p>2.two</p>\n"
+      it "CM223" $
+        "- foo\n\n\n  bar" ==->
+          "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n"
+      it "CM224" $
+        "1.  foo\n\n    ```\n    bar\n    ```\n\n    baz\n\n    > bam" ==->
+          "<ol>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>\n<blockquote>\n<p>bam</p>\n</blockquote>\n</li>\n</ol>\n"
+      it "CM225" $
+        "- Foo\n\n      bar\n\n\n      baz" ==->
+          "<ul>\n<li>\n<p>Foo</p>\n<pre><code>bar\n\n\nbaz\n</code></pre>\n</li>\n</ul>\n"
+      it "CM226" $
+        "123456789. ok" ==->
+          "<ol start=\"123456789\">\n<li>\nok\n</li>\n</ol>\n"
+      it "CM227" $
+        let s = "1234567890. not ok\n"
+        in s ~-> errFancy posI (indexTooBig 1234567890)
+      it "CM228" $
+        "0. ok" ==->
+          "<ol start=\"0\">\n<li>\nok\n</li>\n</ol>\n"
+      it "CM229" $
+        "003. ok" ==->
+          "<ol start=\"3\">\n<li>\nok\n</li>\n</ol>\n"
+      it "CM230" $
+        "-1. not ok" ==->
+          "<p>-1. not ok</p>\n"
+      it "CM231" $
+        "- foo\n\n      bar" ==->
+          "<ul>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ul>\n"
+      it "CM232" $
+        "  10.  foo\n\n           bar" ==->
+          "<ol start=\"10\">\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ol>\n"
+      it "CM233" $
+        "    indented code\n\nparagraph\n\n    more code" ==->
+          "<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n"
+      it "CM234" $
+        "1.     indented code\n\n   paragraph\n\n       more code" ==->
+          "<ol>\n<li>\n<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n"
+      it "CM235" $
+        "1.      indented code\n\n   paragraph\n\n       more code" ==->
+          "<ol>\n<li>\n<pre><code> indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n"
+      it "CM236" $
+        "   foo\n\nbar" ==->
+          "<p>foo</p>\n<p>bar</p>\n"
+      it "CM237" $
+        "-    foo\n\n  bar" ==->
+          "<ul>\n<li>\nfoo\n</li>\n</ul>\n<p>bar</p>\n"
+      it "CM238" $
+        "-  foo\n\n   bar" ==->
+          "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n"
+      it "CM239" $
+        "-\n  foo\n-\n  ```\n  bar\n  ```\n-\n      baz" ==->
+          "<ul>\n<li>\n<p>foo</p>\n</li>\n<li>\n<pre><code>bar\n</code></pre>\n</li>\n<li>\n<pre><code>baz\n</code></pre>\n</li>\n</ul>\n"
+      it "CM240" $
+        "-   \n  foo" ==->
+          "<ul>\n<li>\nfoo\n</li>\n</ul>\n"
+      it "CM241" $
+        "-\n\n  foo" ==->
+          "<ul>\n<li>\n\n</li>\n</ul>\n<p>foo</p>\n"
+      it "CM241b" $
+        "1.\n\n   foo" ==->
+          "<ol>\n<li>\n\n</li>\n</ol>\n<p>foo</p>\n"
+      it "CM242" $
+        "- foo\n-\n- bar" ==->
+          "<ul>\n<li>\nfoo\n</li>\n<li>\n\n</li>\n<li>\nbar\n</li>\n</ul>\n"
+      it "CM243" $
+        "- foo\n-   \n- bar" ==->
+          "<ul>\n<li>\nfoo\n</li>\n<li>\n\n</li>\n<li>\nbar\n</li>\n</ul>\n"
+      it "CM244" $
+        "1. foo\n2.\n3. bar" ==->
+          "<ol>\n<li>\nfoo\n</li>\n<li>\n\n</li>\n<li>\nbar\n</li>\n</ol>\n"
+      it "CM245" $
+        "*" ==->
+          "<ul>\n<li>\n\n</li>\n</ul>\n"
+      it "CM246" $
+        "foo\n*\n\nfoo\n1." ==->
+          "<p>foo</p>\n<ul>\n<li>\n\n</li>\n</ul>\n<p>foo</p>\n<ol>\n<li>\n\n</li>\n</ol>\n"
+      it "CM247" $
+        " 1.  A paragraph\n     with two lines.\n\n         indented code\n\n     > A block quote." ==->
+          "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n"
+      it "CM248" $
+        "  1.  A paragraph\n      with two lines.\n\n          indented code\n\n      > A block quote." ==->
+          "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n"
+      it "CM249" $
+        "   1.  A paragraph\n       with two lines.\n\n           indented code\n\n       > A block quote." ==->
+          "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n"
+      it "CM250" $
+        "    1.  A paragraph\n        with two lines.\n\n            indented code\n\n        > A block quote." ==->
+          "<pre><code>1.  A paragraph\n    with two lines.\n\n        indented code\n\n    &gt; A block quote.\n</code></pre>\n"
+      it "CM251" $
+        "  1.  A paragraph\nwith two lines.\n\n          indented code\n\n      > A block quote." ==->
+          "<ol>\n<li>\nA paragraph\n</li>\n</ol>\n<p>with two lines.</p>\n<pre><code>      indented code\n\n  &gt; A block quote.\n</code></pre>\n"
+      it "CM252" $
+        "  1.  A paragraph\n    with two lines." ==->
+          "<ol>\n<li>\nA paragraph\n</li>\n</ol>\n<pre><code>with two lines.\n</code></pre>\n"
+      it "CM253" $
+        "> 1. > Blockquote\ncontinued here." ==->
+          "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote</p>\n</blockquote>\n</li>\n</ol>\n</blockquote>\n<p>continued here.</p>\n"
+      it "CM254" $
+        "> 1. > Blockquote\n  continued here." ==->
+          "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote</p>\n</blockquote>\n</li>\n</ol>\n<p>continued here.</p>\n</blockquote>\n"
+      it "CM255" $
+        "- foo\n  - bar\n    - baz\n      - boo" ==->
+          "<ul>\n<li>\nfoo\n<ul>\n<li>\nbar\n<ul>\n<li>\nbaz\n<ul>\n<li>\nboo\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
+      it "CM256" $
+        "- foo\n - bar\n  - baz\n   - boo" ==->
+          "<ul>\n<li>\nfoo\n</li>\n<li>\nbar\n</li>\n<li>\nbaz\n</li>\n<li>\nboo\n</li>\n</ul>\n"
+      it "CM257" $
+        "10) foo\n    - bar" ==->
+          "<ol start=\"10\">\n<li>\nfoo\n<ul>\n<li>\nbar\n</li>\n</ul>\n</li>\n</ol>\n"
+      it "CM258" $
+        "10) foo\n   - bar" ==->
+          "<ol start=\"10\">\n<li>\nfoo\n</li>\n</ol>\n<ul>\n<li>\nbar\n</li>\n</ul>\n"
+      it "CM259" $
+        "- - foo" ==->
+          "<ul>\n<li>\n<ul>\n<li>\nfoo\n</li>\n</ul>\n</li>\n</ul>\n"
+      it "CM260" $
+        "1. - 2. foo" ==->
+          "<ol>\n<li>\n<ul>\n<li>\n<ol start=\"2\">\n<li>\nfoo\n</li>\n</ol>\n</li>\n</ul>\n</li>\n</ol>\n"
+      it "CM261" $
+        "- # Foo\n- Bar\n  ---\n  baz" ==->
+          "<ul>\n<li>\n<h1 id=\"foo\">Foo</h1>\n</li>\n<li>\n<p>Bar</p>\n<hr>\n<p>baz</p>\n</li>\n</ul>\n"
+    context "5.3 Lists" $ do
+      it "CM262" $
+        "- foo\n- bar\n+ baz" ==->
+          "<ul>\n<li>\nfoo\n</li>\n<li>\nbar\n</li>\n</ul>\n<ul>\n<li>\nbaz\n</li>\n</ul>\n"
+      it "CM263" $
+        "1. foo\n2. bar\n3) baz" ==->
+          "<ol>\n<li>\nfoo\n</li>\n<li>\nbar\n</li>\n</ol>\n<ol start=\"3\">\n<li>\nbaz\n</li>\n</ol>\n"
+      it "CM264" $
+        "Foo\n- bar\n- baz" ==->
+          "<p>Foo</p>\n<ul>\n<li>\nbar\n</li>\n<li>\nbaz\n</li>\n</ul>\n"
+      it "CM265" $
+        "The number of windows in my house is\n14.  The number of doors is 6." ==->
+          "<p>The number of windows in my house is</p>\n<ol start=\"14\">\n<li>\nThe number of doors is 6.\n</li>\n</ol>\n"
+      it "CM266" $
+        "The number of windows in my house is\n1.  The number of doors is 6." ==->
+          "<p>The number of windows in my house is</p>\n<ol>\n<li>\nThe number of doors is 6.\n</li>\n</ol>\n"
+      it "CM267" $
+        "- foo\n\n- bar\n\n\n- baz" ==->
+          "<ul>\n<li>\n<p>foo</p>\n</li>\n<li>\n<p>bar</p>\n</li>\n<li>\n<p>baz</p>\n</li>\n</ul>\n"
+      it "CM268" $
+        "- foo\n  - bar\n    - baz\n\n\n      bim" ==->
+          "<ul>\n<li>\nfoo\n<ul>\n<li>\nbar\n<ul>\n<li>\n<p>baz</p>\n<p>bim</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n"
+      xit "CM269" $ -- FIXME pending HTML blocks
+        "- foo\n- bar\n\n<!-- -->\n\n- baz\n- bim" ==->
+          "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n<!-- -->\n<ul>\n<li>baz</li>\n<li>bim</li>\n</ul>\n"
+      xit "CM270" $ -- FIXME pending HTML blocks
+        "-   foo\n\n    notcode\n\n-   foo\n\n<!-- -->\n\n    code" ==->
+          "<ul>\n<li>\n<p>foo</p>\n<p>notcode</p>\n</li>\n<li>\n<p>foo</p>\n</li>\n</ul>\n<!-- -->\n<pre><code>code\n</code></pre>\n"
+      it "CM271" $
+        "- a\n - b\n  - c\n   - d\n    - e\n   - f\n  - g\n - h\n- i" ==->
+          "<ul>\n<li>\na\n</li>\n<li>\nb\n</li>\n<li>\nc\n</li>\n<li>\nd\n</li>\n<li>\ne\n</li>\n<li>\nf\n</li>\n<li>\ng\n</li>\n<li>\nh\n</li>\n<li>\ni\n</li>\n</ul>\n"
+      it "CM272" $
+        "1. a\n\n  2. b\n\n    3. c" ==->
+          "<ol>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ol>\n"
+      it "CM273" $
+        "- a\n- b\n\n- c" ==->
+          "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n"
+      it "CM274" $
+        "* a\n*\n\n* c" ==->
+          "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p></p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n"
+      it "CM275" $
+        "- a\n- b\n\n  c\n- d" ==->
+          "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n"
+      xit "CM276" $ -- FIXME pending reference links
+        "- a\n- b\n\n  [ref]: /url\n- d" ==->
+          "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n"
+      it "CM277" $
+        "- a\n- ```\n  b\n\n\n  ```\n- c" ==->
+          "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<pre><code>b\n\n\n</code></pre>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n"
+      it "CM278" $
+        "- a\n  - b\n\n    c\n- d" ==->
+          "<ul>\n<li>\na\n<ul>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n</ul>\n</li>\n<li>\nd\n</li>\n</ul>\n"
+      it "CM279" $
+        "* a\n  > b\n  >\n* c" ==->
+          "<ul>\n<li>\n<p>a</p>\n<blockquote>\n<p>b</p>\n</blockquote>\n<blockquote>\n</blockquote>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n"
+      it "CM280" $
+        "- a\n  > b\n  ```\n  c\n  ```\n- d" ==->
+          "<ul>\n<li>\n<p>a</p>\n<blockquote>\n<p>b</p>\n</blockquote>\n<pre><code>c\n</code></pre>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n"
+      it "CM281" $
+        "- a" ==->
+          "<ul>\n<li>\na\n</li>\n</ul>\n"
+      it "CM282" $
+        "- a\n  - b" ==->
+          "<ul>\n<li>\na\n<ul>\n<li>\nb\n</li>\n</ul>\n</li>\n</ul>\n"
+      it "CM283" $
+        "1. ```\n   foo\n   ```\n\n   bar" ==->
+          "<ol>\n<li>\n<pre><code>foo\n</code></pre>\n<p>bar</p>\n</li>\n</ol>\n"
+      it "CM284" $
+        "* foo\n  * bar\n\n  baz" ==->
+          "<ul>\n<li>\nfoo\n<ul>\n<li>\nbar\n</li>\n</ul>\nbaz\n</li>\n</ul>\n"
+      it "CM285" $
+        "- a\n  - b\n  - c\n\n- d\n  - e\n  - f" ==->
+          "<ul>\n<li>\na\n<ul>\n<li>\nb\n</li>\n<li>\nc\n</li>\n</ul>\n</li>\n<li>\nd\n<ul>\n<li>\ne\n</li>\n<li>\nf\n</li>\n</ul>\n</li>\n</ul>\n"
     context "6 Inlines" $
       it "CM286" $
         let s  = "`hi`lo`\n"
@@ -454,7 +753,7 @@
         in s ~-> errFancy (posN 9 s) (nonFlanking "*")
       it "CM345" $
         let s = "*foo bar\n*\n"
-        in s ~-> errFancy (posN 9 s) (nonFlanking "*")
+        in s ~-> err (posN 8 s) (ueib <> etok '*' <> eic <> eric)
       it "CM346" $
         let s = "*(*foo)\n"
         in s ~-> errFancy posI (nonFlanking "*")
@@ -903,8 +1202,8 @@
       it "CM544" $
         "![foo [bar](/url)](/url2)" ==->
           "<p><img src=\"/url2\" alt=\"foo bar\"></p>\n"
-      it "CM545" pending
-      it "CM546" pending
+      it "CM545" pending -- FIXME pending reference images
+      it "CM546" pending -- FIXME pending reference images
       it "CM547" $
         "![foo](train.jpg)" ==->
           "<p><img src=\"train.jpg\" alt=\"foo\"></p>\n"
@@ -916,7 +1215,7 @@
           "<p><img src=\"url\" alt=\"foo\"></p>\n"
       it "CM550" $
         "![](/url)" ==-> "<p><img src=\"/url\" alt></p>\n"
-      it "CM551-CM562" pending -- pending reference-style stuff
+      it "CM551-CM562" pending -- FIXME pending reference-style stuff
     context "6.7 Autolinks" $ do
       it "CM563" $
         "<http://foo.bar.baz>" ==->
@@ -1005,7 +1304,7 @@
       xit "CM615" $
         "foo  " ==-> "<p>foo</p>\n"
       it "CM616" $
-        "### foo\\" ==-> "<h3 id=\"foo\\\">foo\\</h3>\n"
+        "### foo\\" ==-> "<h3 id=\"foo\">foo\\</h3>\n"
       it "CM617" $
         "### foo  " ==-> "<h3 id=\"foo\">foo</h3>\n"
     context "6.10 Soft line breaks" $ do
@@ -1065,6 +1364,40 @@
         s ~~->
           [ err (posN 1 s) (utok 'M' <> etok '#' <> elabel "white space")
           , errFancy (posN 35 s) (nonFlanking "_") ]
+      describe "every block in a list gets its parse error propagated" $ do
+        context "with unordered list" $
+          it "works" $ do
+            let s = "- *foo\n\n  *bar\n- *baz\n\n  *quux\n"
+                e = ueib <> etok '*' <> eic <> eric
+            s ~~->
+              [ err (posN 6  s) e
+              , err (posN 14 s) e
+              , err (posN 21 s) e
+              , err (posN 30 s) e ]
+        context "with ordered list" $
+          it "works" $ do
+            let s = "1. *foo\n\n   *bar\n2. *baz\n\n   *quux\n"
+                e = ueib <> etok '*' <> eic <> eric
+            s ~~->
+              [ err (posN 7  s) e
+              , err (posN 16 s) e
+              , err (posN 24 s) e
+              , err (posN 34 s) e ]
+      it "too big start index of ordered list does not prevent validation of inner inlines" $ do
+        let s = "1234567890. *something\n1234567891. [\n"
+        s ~~->
+          [ errFancy posI (indexTooBig 1234567890)
+          , err (posN 22 s) (ueib <> etok '*' <> eic <> eric)
+          , err (posN 36 s) (ueib <> etok ']') ]
+      it "non-consecutive indices in ordered list do not prevent further validation" $ do
+        let s = "1. *foo\n3. *bar\n4. *baz\n"
+            e = ueib <> etok '*' <> eic <> eric
+        s ~~->
+          [ err (posN 7 s) e
+          , errFancy (posN 8 s) (indexNonCons 3 2)
+          , err (posN 15 s) e
+          , errFancy (posN 16 s) (indexNonCons 4 3)
+          , err (posN 23 s) e ]
     context "given a complete, comprehensive document" $
       it "outputs expected the HTML fragment" $
         withFiles "data/comprehensive.md" "data/comprehensive.html"
@@ -1112,17 +1445,23 @@
     context "when document contains a YAML section" $ do
       context "when it is valid" $
         it "returns the YAML section" $ do
-          doc <- mkDoc "---\nx: 100\ny: 200\n---Here we go."
+          doc <- mkDoc "---\nx: 100\ny: 200\n---\nHere we go."
           let r = object
                 [ "x" .= Number 100
                 , "y" .= Number 200 ]
           MMark.projectYaml doc `shouldBe` Just r
-      context "when it is invalid" $
+      context "when it is invalid" $ do
+        let mappingErr = fancy . ErrorCustom . YamlParseError $
+              "mapping values are not allowed in this context"
         it "signal correct parse error" $
-          let s = "---\nx: 100\ny: x:\n---Here we go."
-          in s ~-> errFancy (posN 15 s)
-             (fancy . ErrorCustom . YamlParseError $
-              "mapping values are not allowed in this context")
+          let s = "---\nx: 100\ny: x:\n---\nHere we go."
+          in s ~-> errFancy (posN 15 s) mappingErr
+        it "does not choke and can report more parse errors" $
+          let s = "---\nx: 100\ny: x:\n---\nHere we *go."
+          in s ~~->
+              [ errFancy (posN 15 s) mappingErr
+              , err (posN 33 s) (ueib <> etok '*' <> eic <> eric)
+              ]
 
 ----------------------------------------------------------------------------
 -- Testing extensions
@@ -1200,3 +1539,16 @@
 
 nonFlanking :: Text -> EF MMarkErr
 nonFlanking = fancy . ErrorCustom . NonFlankingDelimiterRun . NE.fromList . T.unpack
+
+-- | Create a error component complaining that the given starting index of
+-- an ordered list is too big.
+
+indexTooBig :: Word -> EF MMarkErr
+indexTooBig = fancy . ErrorCustom . ListStartIndexTooBig
+
+-- | Create a error component complaining about non-consecutive indices in
+-- an ordered list.
+
+indexNonCons :: Word -> Word -> EF MMarkErr
+indexNonCons actual expected = fancy . ErrorCustom $
+  ListIndexOutOfOrder actual expected
