diff --git a/scan.cabal b/scan.cabal
--- a/scan.cabal
+++ b/scan.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.4
 build-type:         Simple
 name:               scan
-version:            0.1.0.5
+version:            0.1.0.6
 license:            BSD3
 license-file:       doc/LICENSE
 category:           Development
diff --git a/src/Language/Haskell/Scanner.hs b/src/Language/Haskell/Scanner.hs
--- a/src/Language/Haskell/Scanner.hs
+++ b/src/Language/Haskell/Scanner.hs
@@ -18,7 +18,7 @@
   , Opts (..)
   , defaultOpts
   , anaPosToks
-  , PosTok(..)
+  , PosTok (..)
   , Token
   , showPosTok
   , Diag (..)
@@ -60,6 +60,15 @@
 enclosedBy :: Monad m => m [a] -> m a -> m [a]
 enclosedBy p q = q <:> p <++> single q
 
+-- | a literal enclosed in quotes and a backslash as escape character
+quotedLit :: Char -> CharParser st String
+quotedLit q = enclosedBy (flat $ many $ single (noneOf $ '\\' : [q])
+                        <|> char '\\' <:> single anyChar) $ char q
+
+-- | text in double quotes
+stringLit :: CharParser st String
+stringLit = quotedLit '"'
+
 -- * parsec shortcuts
 
 -- | parse an optional list
@@ -96,17 +105,9 @@
        << notFollowedBy (oneOf "!#$%&*+./<=>?@\\^|~"))
   <++> many (noneOf "\n")
 
--- | text in double quotes
-stringLit :: CharParser st String
-stringLit = enclosedBy (flat $ many $ single (noneOf "\\\"")
-                        <|> char '\\' <:> single anyChar) $ char '\"'
-
 -- | text in single quotes
 charLit :: CharParser st String
-charLit = tryString "'''" <|>
-          enclosedBy (flat $ many $ single (noneOf "\\\'")
-                      <|> char '\\' <:> single anyChar)
-          (char '\'')
+charLit = tryString "'''" <|> quotedLit '\''
 
 -- | a precise number scanner
 number :: Parser String
@@ -330,16 +331,6 @@
 rmSp :: PosTok -> PosTok
 rmSp (PosTok p t _) = PosTok p t ""
 
--- | ensure proper amount of space
-blank :: Opts -> Token -> PosTok -> PosTok
-blank opts u (PosTok p t w) = PosTok p t
-  $ let n = maxBlanks opts in
-    if null w || noMultBlanksAfter opts t || noMultBlanksBefore opts u
-    then " " else
-        if allowMultBlanksAfter opts t || allowMultBlanksBefore opts u
-        then if n > 1 then take n w else w
-        else take n w
-
 topKeys :: [String]
 topKeys = ["import", "data", "newtype", "type", "class", "instance"]
 -- "module" may occur in export lists!
@@ -347,20 +338,25 @@
 layoutKeys :: [String]
 layoutKeys = ["do", "of", "where"]
 
-multipleBlanks :: Opts -> Token -> PosTok -> [Diag]
-multipleBlanks opts u (PosTok p t w) =
+-- | ensure proper amount of space and diagnose too many blanks
+multipleBlanks :: Opts -> Token -> PosTok -> (PosTok, [Diag])
+multipleBlanks opts u pTok@(PosTok p t w) =
   let s = showToken t
       q = updatePosString p s
       aft = noMultBlanksAfter opts t
       m = maxBlanks opts
       n = length w
-  in [ Diag q $ "up to column " ++ show (sourceColumn q + n)
+      diag = Diag q $ "up to column " ++ show (sourceColumn q + n)
        ++ " multiple (" ++ show n ++ ") blanks"
        ++ if aft then " after " ++ show s else ""
-     | n > m && m > 1 || n > 1 && (aft || noMultBlanksBefore opts u)
-       || n > 1 && m == 1
-       && not (allowMultBlanksAfter opts t || allowMultBlanksBefore opts u)
-     ]
+      bTok = PosTok p t " "
+  in case w of
+    "" -> (bTok, []) -- insert a missing blank (without diag)
+    _ | allowMultBlanksBefore opts u
+      -> (pTok, []) -- no change
+    _ | noMultBlanksAfter opts t
+      -> (bTok, [diag | n > 1])
+    _ -> (PosTok p t $ take m w, [diag | n > m])
 
 -- | tidy up line comments
 adjustLineComment :: String -> String
@@ -407,6 +403,7 @@
     sr = take 5 $ reverse s
     in (PosTok p (Token BlockComment n) w,
        [ Diag p $ "non-conventional comment start: " ++ s5 | s5 /= take 5 n ]
+       ++ [ Diag p "avoid nested comments" | isInfixOf "{-" $ drop 2 s ]
        ++ [ Diag (updatePosString p s) $ "non-conventional comment end: "
             ++ reverse sr | sr /= nr ])
   Token LineComment s -> let
@@ -419,35 +416,69 @@
           ++ "one blank after --"
           ++ if null sr then "" else " in --" ++ sr
         | s4 /= take 4 n ]
-        ++ [ Diag q "missing comment after --" | n == "--" ])
+        ++ [ Diag q "missing comment after --" | n == "--" ]
+        ++ [ Diag q "line comment contains block comment start"
+             | isInfixOf "{-" n ]
+        ++ [ Diag q "line comment contains block comment end"
+             | isInfixOf "-}" n ])
   _ -> (t, [ Diag p "use layout instead of ;" | isSepIn ";" u ])
 
+-- | a comment string that is no pragma or directive
+isNormalCommentString :: String -> Bool
+isNormalCommentString s = elem (drop 2 $ take 3 s) [" ", "\n"]
+
+-- | a comment that is no pragma or directive
+isNormalComment :: PosTok -> Bool
+isNormalComment p@(PosTok _ u _) =
+  isComment u && isNormalCommentString (showPosTok $ fst $ anaPosTok p)
+
+-- | check if no other normal comment follows
+noOtherComment :: [PosTok] -> Bool
+noOtherComment ps = case ps of
+  [] -> True
+  p : _ -> not $ isNormalComment p
+
 -- | the data type for options passed to the analysis
 data Opts = Opts
   { tempHask :: Bool
+  , windowsOutput :: Bool
+  , lineLength :: Int
+  , maxBlankLines :: Int
   , maxBlanks :: Int
   , allowMultBlanksBefore :: Token -> Bool
-  , allowMultBlanksAfter :: Token -> Bool
-  , noMultBlanksBefore :: Token -> Bool
   , noMultBlanksAfter :: Token -> Bool
+  , makeLineComments :: Bool
+  , joinComments :: Bool
+  , ignoreComments :: Bool
+  , commentGap :: Int
+  , checkSpacing :: Bool
   }
 
 -- | the default options to use
 defaultOpts :: Opts
 defaultOpts = Opts
   { tempHask = False
+  , windowsOutput = False
+  , lineLength = 80
+  , maxBlankLines = 2
   , maxBlanks = 1
   , allowMultBlanksBefore = isComment
-  , allowMultBlanksAfter = const False
-  , noMultBlanksBefore = const False
   , noMultBlanksAfter = isKeyw $ "let" : layoutKeys
+  , makeLineComments = True
+  , joinComments = True
+  , ignoreComments = False
+  , commentGap = 0
+  , checkSpacing = True
   }
 
 -- | analyse consecutive tokens
 anaPosToks :: Opts -> [PosTok] -> ([PosTok], [Diag])
-anaPosToks opts l = case l of
+anaPosToks opts l = let ignoreC = ignoreComments opts in case l of
   [] -> ([], [])
-  t0 : r1 -> let (t1@(PosTok p1 u1 w1), cs) = anaPosTok t0 in case r1 of
+  t0 : r1 ->
+    let (tC@(PosTok p1 u1 w1), cC) = anaPosTok t0
+        (t1, cs) = if ignoreC then (t0, []) else (tC, cC)
+    in case r1 of
     [] -> ([t1], cs)
     t2@(PosTok p2 u2 w2) : r2 -> let
         (ar1, rds) = anaPosToks opts r1
@@ -462,11 +493,9 @@
         lt = length s1 <= length s2
         both = s1 ++ s2
         nw1 = null w1
-        bt1 = blank opts u2 t1
+        (bt1, m1) = multipleBlanks opts u2 t1
         al = t1 : ar1
         bl = bt1 : ar1
-        rl = rmSp t1 : ar1
-        m1 = multipleBlanks opts u2 t1
         aS = "after "
         bS = "before "
         aft = case () of
@@ -482,32 +511,63 @@
           _ | o1 -> aS
             | c2 -> bS
           _ -> "here "
-        omitSpace = o1 && (isInfixOp u2 || c2)
+        checkS = checkSpacing opts
+        joinC = joinComments opts
+        omitSpace = checkS && o1 && (isInfixOp u2 || c2)
           || isInfixOp u1 && c2 && not (isSymb [".."] u1 && isSepIn "]" u2)
-        addSpace = ni1 && nw1
+        addSpace = checkS && ni1 && nw1
           && (not (o1 || isSymb (map (: []) "!#-@~") u1) || isComment u2)
           && spaceNeededBefore u2
           && if tempHask opts then not $ isSymb ["$"] u1 else True
+        s1C = take (length s1 - 3) s1
         in case r2 of
-      _ | isComment u1 && isIndent u2
-        -> (al, cs ++ [ Diag p1 "could be a line comment"
-             | isPrefixOf "{- " s1 && not (any (== '\n') s1) ] ++ rds)
-      PosTok _ u3 _ : _
-        | isFstInfixMinusArg u1 && isSymb ["-"] u2 && spaceNeededBefore u3
-        -> (bt1 : blank opts u3 t2 : tail ar1, cs ++ m1
+      _ | makeLineComments opts && isComment u1 && isIndent u2
+          && noOtherComment r2 && isPrefixOf "{- " s1C
+          && not (any (== '\n') s1C)
+        -> ( PosTok p1 (Token LineComment $ "-- " ++ drop 3 s1C) "" : ar1
+           , cs ++ Diag p1 "could be a line comment" : rds)
+        | joinC && isNormalComment tC && isNormalComment t2
+        -> (al, cs ++ Diag p2 "consecutive comments" : rds)
+      t3@(PosTok _ u3 _) : r3
+        | joinC && isNormalComment tC && isIndent u2 && isNormalComment t3
+        -> let (PosTok p3 nu3 w3, cs3) = anaPosTok t3
+               (f3, h3) = splitAt 3 (showToken nu3)
+               hh3 = head h3
+               isHaddockStart = not (null h3) && any (hh3 ==) "$*^|"
+               s3 = if isHaddockStart
+                    then dropWhile isSpace
+                      $ if hh3 == '*' then dropWhile (== '*') h3 else tail h3
+                    else h3
+               nt3 = PosTok p1 (Token BlockComment
+                 $ (if noLineComment u1 then s1C
+                   else "{- " ++ drop 3 s1)
+                   ++ w1 ++ s2 ++ w2 ++ replicate (commentGap opts) ' '
+                   ++ if noLineComment nu3 then s3
+                      else s3 ++ " -}") w3
+               (ar3, rds3) = anaPosToks opts $ nt3 : r3
+           in (ar3
+              , cs ++ Diag p3 "join (or separate) consecutive comments"
+                : [ Diag (updatePosString p3 f3)
+                     $ "unexpected haddock marker " ++ [hh3]
+                     | isHaddockStart ]
+                ++ (if ignoreC then [] else cs3) ++ rds3)
+        | checkS && isFstInfixMinusArg u1 && isSymb ["-"] u2
+          && spaceNeededBefore u3
+        -> let (bt2, m2) = multipleBlanks opts u3 t2 in
+           (bt1 : bt2 : tail ar1, cs ++ m1
              ++ [ Diag p2 "put blanks around infix -" | nw1 || null w2 ]
-             ++ multipleBlanks opts u3 t2 ++ rds)
+             ++ m2 ++ rds)
         | ni1 && isKeyw layoutKeys u2
           && not (isSepIn "{" u3) && noLineComment u3 && not (isIndent u3)
-        -> (bl, cs ++ m1 ++ Diag p2 ("break line after " ++ show s2) : rds)
+        -> (al, cs ++ m1 ++ Diag p2 ("break line after " ++ show s2) : rds)
       _ | addSpace
         -> (bl, cs ++ Diag p2
              ("put blank " ++ if aft then aS ++ s1 else bS ++ s2) : rds)
       _ | not nw1 && omitSpace
-        -> (rl, cs
+        -> (rmSp t1 : ar1, cs
            ++ Diag p2 ("no space needed " ++ pos ++ parsOfBoth) : rds)
       _ | not nw1 && ii1 && isKeyw topKeys u2
-        -> (rl, cs ++ Diag p2 ("start in column 1 with keyword " ++ s2) : rds)
+        -> (al, cs ++ Diag p2 ("start in column 1 with keyword " ++ s2) : rds)
       _ | nw1 || ii1
         -> (al, cs ++ rds)
       _ -> (bl, cs ++ m1 ++ rds)
diff --git a/src/scan.hs b/src/scan.hs
--- a/src/scan.hs
+++ b/src/scan.hs
@@ -35,16 +35,25 @@
 
 -- | the hard-coded version string.
 version :: String
-version = "scan-0.1.0.5 http://projects.haskell.org/style-scanner/"
+version = "scan-0.1.0.6 http://projects.haskell.org/style-scanner/"
 
 data Flag =
     Help
   | Version
   | TempHask
+  | WindowsOutput
   | LineLen Int
   | MaxBlanks Int
   | BackupExt String
+  | OutputExt String
   | OutputFile FilePath
+  | OutputDir FilePath
+  | MaxBlankLines Int
+  | CheckSpacing Bool
+  | CheckComments Bool
+  | MakeLineComments Bool
+  | JoinComments Bool
+  | CommentGap Int
   deriving (Show, Eq)
 
 -- | describe all available options
@@ -54,37 +63,75 @@
       "show usage message and exit"
     , Option "v" ["version"] (NoArg Version)
       "show version and exit"
+    , Option "w" ["windows"] (NoArg WindowsOutput)
+      "create windows (CRLF) file"
     , Option "t" ["template-haskell"] (NoArg TempHask)
       "no hints for $ in template haskell"
-    , Option "l" ["line-length"] (ReqArg (LineLen . read) "<n>")
+    , Option "l" ["line-length"] (ReqArg (LineLen . readN) "<n>")
       "report lines longer than <n> (default -l80)"
-    , Option "m" ["multiple-blanks"] (ReqArg (MaxBlanks . read) "<n>")
+    , Option "m" ["multiple-blanks"] (ReqArg (MaxBlanks . readN) "<n>")
       "report more than <n> blanks (default -m1)"
+    , Option "s" ["check-spacing"] (ReqArg (CheckSpacing . readB) "<b>")
+      "check spacing around symbols (default True)"
+    , Option "c" ["check-comments"] (ReqArg (CheckComments . readB) "<b>")
+      "check comment delimiters (default True)"
+    , Option "C" ["change-comments"] (ReqArg (MakeLineComments . readB) "<b>")
+      "change to some line comments (default True)"
+    , Option "j" ["join-comments"] (ReqArg (JoinComments . readB) "<b>")
+      "join consecutive comments (default True)"
+    , Option "g" ["comment-gap"] (ReqArg (CommentGap . readN) "<n>")
+      "spaces between joined comments (default -g0)"
+    , Option "b" ["blank-lines"] (ReqArg (MaxBlankLines . readN) "<n>")
+      "remove more than <n> blank lines (default -r2)"
     , Option "i" ["inplace-modify"] (OptArg (BackupExt . fromMaybe "") "<ext>")
       "modify file in place (backup if <ext> given)"
+    , Option "e" ["extension"] (ReqArg OutputExt "<ext>")
+      "create output file with given extension <ext>"
     , Option "o" ["output-file"] (ReqArg OutputFile "<file>")
       "output modified input to <file>"
-    ]
+    , Option "O" ["output-directory"] (ReqArg OutputDir "<dir>")
+      "output modified file to <dir>"
+   ]
 
 output :: FilePath -> [Flag] -> Maybe (FilePath, Maybe FilePath)
-output p = foldl (\ m f -> case f of
-  OutputFile str -> Just (str, maybe Nothing snd m)
-  BackupExt ext ->
-    Just (p, if null ext then Nothing else Just $ p ++ "." ++ ext)
+output p = foldl (\ m f -> let o = maybe Nothing snd m in case f of
+  OutputFile s -> Just (s, o)
+  BackupExt e ->
+    Just (p, if null e then o else Just $ p ++ '.' : e)
+  OutputExt e ->
+    Just (p ++ '.' : e, o)
+  OutputDir d ->
+    Just (d ++ '/' : p, o)
   _ -> m) Nothing
 
-lineLength :: [Flag] -> Int
-lineLength = foldl (\ m f -> case f of
-  LineLen n -> n
-  _ -> m) 80
+readN :: String -> Int
+readN s = if not (null s) && all isDigit s && length s < 4 then read s
+  else error $ "expected small number argument but got: " ++ s
 
+readB :: String -> Bool
+readB s = case map toLower s of
+  t@(_ : _)
+    | elem t $ inits "true" -> True
+    | elem t $ inits "false" -> False
+  _ -> error $
+    "expected case-insensitive partial boolean argument but got: " ++ s
+
 anaOpts :: [Flag] -> Opts
 anaOpts = foldl (\ m f -> case f of
   TempHask -> m { tempHask = True }
+  WindowsOutput -> m { windowsOutput = True }
+  LineLen n -> m { lineLength = n }
+  MaxBlankLines n -> m { maxBlankLines = n }
   MaxBlanks n -> if n <= 0 then
     m { maxBlanks = maxBound
       , noMultBlanksAfter = const False }
     else m { maxBlanks = n }
+
+  CheckSpacing b -> m { checkSpacing = b }
+  CheckComments b -> m { ignoreComments = not b }
+  MakeLineComments b -> m { makeLineComments = b }
+  JoinComments b -> m { joinComments = b }
+  CommentGap n -> m { commentGap = n }
   _ -> m) defaultOpts
 
 {- arguments starting with a minus sign are treated as options. files that
@@ -107,9 +154,11 @@
         unless (null errs) exitFailure
     _ | elem Version flags
       -> putStrLn version
+    _ | length (show flags) < 0 -- force evaluation of flags
+      -> exitFailure
     file : r -> let
       out = output file flags
-      proc = process out (lineLength flags) (anaOpts flags)
+      proc = process out (anaOpts flags)
       in case out of
       Just _ | null r -> proc file
       Nothing -> mapM_ proc files
@@ -124,13 +173,17 @@
 {- | process a file. A first true arguments only shows diagnostics.
 A first false argument writes back a modified file, but only if there are
 modifications. -}
-process :: Maybe (FilePath, Maybe FilePath) -> Int -> Opts -> FilePath -> IO ()
-process out ll opts f = do
+process :: Maybe (FilePath, Maybe FilePath) -> Opts -> FilePath -> IO ()
+process out opts f = do
   str <- readFile f
   let ls = lines str
-      wcsr = map (checkLine ll f) (zip [1 ..] ls)
+      wcsr = map (checkLine (lineLength opts) f) (zip [1 ..] ls)
       wcs = map fst wcsr
-      wfile = [ Diag (diagLinePos f 2) "windows (CRLF) file" ]
+      isWin = windowsOutput opts
+      m = maxBlankLines opts
+      crPos = diagLinePos f 2
+      wfile = if isWin then [] else
+                  [ Diag crPos "windows (CRLF) file" ]
       noNL = not $ isSuffixOf "\n" str
       cs = case group $ map fst wcs of
              [True : _, [False]] | noNL -> wfile -- with missing newline
@@ -138,8 +191,10 @@
                [ Diag (diagLinePos f (length x + 1))
                  "inconsistent unix (LF) or windows (CRLF) file" ]
              [True : _] -> wfile
+             [False : _] -> [ Diag crPos "unix (LF) file" | isWin ]
              _ -> []
-        ++ concatMap snd wcs ++ checkBlankLines f 0 0 ls
+        ++ concatMap snd wcs
+        ++ if m > 0 then checkBlankLines f m 0 0 ls else []
       newStr = unlines $ map snd wcsr
       fs = [ Diag (diagLinePos f $ length ls) "missing final newline" | noNL ]
       prDiags = mapM_ (putStrLn . showDiag)
@@ -147,7 +202,10 @@
     Right ts -> let (nts, ds) = anaPosToks opts ts in case out of
       Nothing -> prDiags $ cs ++ ds ++ fs
       Just (ofile, mbak) ->
-        let rstr = unlines . removeBlankLines 0 null . lines
+        let rstr =
+              (if isWin then concatMap (++ "\r\n") else unlines)
+              . (if m > 0 then removeBlankLines m 0 null else id)
+              . lines
               $ concatMap showPosTok nts in
         if rstr == str then putStrLn $ "no change for file: " ++ f else do
           case mbak of
@@ -169,26 +227,27 @@
          "expecting" "unexpected" "end of input"
          (errorMessages err)
 
--- | check for more than two consecutive lines
+-- | check for more than n consecutive lines
 checkBlankLines :: FilePath
+  -> Int -- ^ maximal allowed number of blank lines
   -> Int -- ^ current number of blank lines
   -> Int -- ^ current line number
   -> [String] -> [Diag]
-checkBlankLines f c n l = let p = diagLinePos f n in case l of
+checkBlankLines f m c n l = let p = diagLinePos f n in case l of
   [] -> [Diag p $ "trailing (" ++ show c ++ ") blank lines" | c > 0]
   s : r -> let n1 = n + 1 in
-    if null $ filter (not . isSpace) s then checkBlankLines f (c + 1) n1 r
+    if null $ filter (not . isSpace) s then checkBlankLines f m (c + 1) n1 r
     else [ Diag p $ "too many (" ++ show c ++ ") consecutive blank lines"
-         | c > 2 ] ++ checkBlankLines f 0 n1 r
+         | c > m ] ++ checkBlankLines f m 0 n1 r
 
 -- | removing more than two consecutive lists fulfilling the predicate
-removeBlankLines :: Int -> ([a] -> Bool) -> [[a]] -> [[a]]
-removeBlankLines c p l = case l of
+removeBlankLines :: Int -> Int -> ([a] -> Bool) -> [[a]] -> [[a]]
+removeBlankLines m c p l = case l of
   [] -> []
   x : r ->
     if p x
-    then removeBlankLines (c + 1) p r
-    else replicate (min 2 c) [] ++ x : removeBlankLines 0 p r
+    then removeBlankLines m (c + 1) p r
+    else replicate (min m c) [] ++ x : removeBlankLines m 0 p r
 
 -- | create a position from a file and a line number
 diagLinePos :: FilePath -> Int -> SourcePos
