diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# 3.1.0.0
+
+  * Add support for CSV via the `--csv` flag and `:set csv`
+  * Add `~?` (maybe match)
+  
 # 3.0.2.0
 
   * Fix list indexing
diff --git a/bench/Bench.hs b/bench/Bench.hs
--- a/bench/Bench.hs
+++ b/bench/Bench.hs
@@ -13,17 +13,19 @@
 main =
     defaultMain [ bgroup "eval"
                       [ bench "exprEval" $ nf exprEval "[x+' '+y]|'' split '01-23-1987' /-/" ]
+                , bgroup "csv"
+                      [ bench "succdiff" $ nfIO (silence $ runOnFile [] "~.{ix>1}{`8}" CSV "bench/data/food-prices.csv") ]
                 , bgroup "stream"
-                      [ bench "path" $ nfIO (silence $ runOnFile [] "{|[x+'\\n'+y]|>`$}" (Just ":") Nothing "bench/data/PATH")
-                      , bench "RS" $ nfIO (silence $ runOnFile [] "$0" Nothing (Just ":") "bench/data/PATH")
-                      , bench "runOnFile" $ nfIO (silence $ runOnFile [] "(+)|0 {%/Bloom/}{1}" Nothing Nothing "bench/data/ulysses.txt")
-                      , bench "runOnFile" $ nfIO (silence $ do { contents <- TIO.readFile "examples/wc.jac" ; runOnFile [] contents Nothing Nothing "bench/data/ulysses.txt" })
-                      , bench "runOnFile" $ nfIO (silence $ do { contents <- TIO.readFile "examples/span2.jac" ; runOnFile [] contents Nothing Nothing "bench/data/span.txt" })
-                      , bench "sedstream.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/sedstream.jac" ; runOnFile [] contents Nothing Nothing "bench/data/lines.txt" })
-                      , bench "gnused.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/gnused.jac" ; runOnFile [] contents Nothing Nothing "bench/data/lines.txt" })
-                      , bench "fungnused.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/fungnused.jac" ; runOnFile [] contents Nothing Nothing "bench/data/lines.txt" })
-                      , bench "hsLibversionMac.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/hsLibversionMac.jac"; runOnFile [] contents Nothing Nothing "bench/data/pandoc-mac" })
-                      , bench "sedsmtp.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/sedsmtp.jac" ; runOnFile [] contents Nothing Nothing "test/examples/data/2.txt" })
+                      [ bench "path" $ nfIO (silence $ runOnFile [] "{|[x+'\\n'+y]|>`$}" (AWK (Just ":") Nothing) "bench/data/PATH")
+                      , bench "RS" $ nfIO (silence $ runOnFile [] "$0" (AWK Nothing (Just ":")) "bench/data/PATH")
+                      , bench "runOnFile" $ nfIO (silence $ runOnFile [] "(+)|0 {%/Bloom/}{1}" (AWK Nothing Nothing) "bench/data/ulysses.txt")
+                      , bench "runOnFile" $ nfIO (silence $ do { contents <- TIO.readFile "examples/wc.jac" ; runOnFile [] contents (AWK Nothing Nothing) "bench/data/ulysses.txt" })
+                      , bench "runOnFile" $ nfIO (silence $ do { contents <- TIO.readFile "examples/span2.jac" ; runOnFile [] contents (AWK Nothing Nothing) "bench/data/span.txt" })
+                      , bench "sedstream.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/sedstream.jac" ; runOnFile [] contents (AWK Nothing Nothing) "bench/data/lines.txt" })
+                      , bench "gnused.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/gnused.jac" ; runOnFile [] contents (AWK Nothing Nothing) "bench/data/lines.txt" })
+                      -- , bench "fungnused.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/fungnused.jac" ; runOnFile [] contents (AWK Nothing Nothing) "bench/data/lines.txt" })
+                      , bench "hsLibversionMac.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/hsLibversionMac.jac"; runOnFile [] contents (AWK Nothing Nothing) "bench/data/pandoc-mac" })
+                      , bench "sedsmtp.jac" $ nfIO (silence $ do { contents <- TIO.readFile "examples/sedsmtp.jac" ; runOnFile [] contents (AWK Nothing Nothing) "test/examples/data/2.txt" })
                       ]
                 ]
 
diff --git a/doc/guide.pdf b/doc/guide.pdf
Binary files a/doc/guide.pdf and b/doc/guide.pdf differ
diff --git a/examples/diffctx.jac b/examples/diffctx.jac
--- a/examples/diffctx.jac
+++ b/examples/diffctx.jac
@@ -3,15 +3,10 @@
 @include'prelude/fn.jac'
 @include'lib/maybe.jac'
 
-fn mMatch(str, re) :=
-  if str ~ re
-    then Some str
-    else None;
-
 fn step(needle, ctx, line) :=
   let 
     val fpCtx ≔ line ~* 1 /diff --git\s+([^\s]+)/
-    val mLine ≔ mMatch line needle
+    val mLine ≔ line ~? needle
   in (alternative (ctx->1) fpCtx.mLine) end;
 
 fn process(x) :=
diff --git a/examples/nmCtx.jac b/examples/nmCtx.jac
--- a/examples/nmCtx.jac
+++ b/examples/nmCtx.jac
@@ -3,9 +3,6 @@
 @include'prelude/fn.jac'
 @include'lib/maybe.jac'
 
-fn mMatch(str, re) :=
-  ?str ~ re; Some str; None;
-
 fn step(ctx, line) :=
   let
     val fpCtx := line ~* 1 /(.*\.o):$/
diff --git a/examples/slow.jac b/examples/slow.jac
deleted file mode 100644
--- a/examples/slow.jac
+++ /dev/null
@@ -1,1 +0,0 @@
-(+)|> ([x+'\n'+y]|>)¨{|captures `0 1 /-lHS([A-Aa-z][A-Za-z0-9\-]*\d+(\.\d+)*)/}
diff --git a/examples/tagsex.jac b/examples/tagsex.jac
new file mode 100644
--- /dev/null
+++ b/examples/tagsex.jac
@@ -0,0 +1,12 @@
+fn mkEx(s) :=
+  '/^' + s + '$/;';
+
+{. TODO: insert \zs at precise identifier! https://stackoverflow.com/a/31089753/11296354
+
+fn processStr(s) :=
+  let
+    val line := split s /[ \(:]+/
+    val outLine := sprintf '%s\t%s\t%s' (line.3 . fp . mkEx s)
+  in outLine end;
+
+processStr¨{%/fn +[[:lower:]][[:latin:]]*.*:=/}{`0}
diff --git a/jacinda.cabal b/jacinda.cabal
--- a/jacinda.cabal
+++ b/jacinda.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               jacinda
-version:            3.0.2.0
+version:            3.1.0.0
 license:            AGPL-3.0-only
 license-file:       COPYING
 maintainer:         vamchale@gmail.com
@@ -18,8 +18,10 @@
     lib/fs/*.jac
     prelude/*.jac
 
-extra-source-files:
+extra-doc-files:
     CHANGELOG.md
+
+extra-source-files:
     README.md
     man/ja.1
     doc/guide.pdf
@@ -37,6 +39,7 @@
 
 common warnings
     ghc-options:
+        -Wall
         -Wincomplete-uni-patterns -Wincomplete-record-updates
         -Wredundant-constraints -Widentities -Wcpp-undef
         -Wmissing-export-lists -Wunused-packages
@@ -57,7 +60,6 @@
         A.I
         A.E
         L
-        Jacinda.Fuse
         Nm
         U
         R
@@ -71,10 +73,10 @@
 
     autogen-modules:  Paths_jacinda
     default-language: Haskell2010
-    ghc-options:      -Wall -O2 -Wno-missing-signatures -Wno-x-partial
+    ghc-options:      -O2 -Wno-missing-signatures -Wno-x-partial
     build-depends:
         base >=4.11.0.0 && <5,
-        bytestring >=0.11.0.0,
+        bytestring >=0.11.2.0,
         text,
         prettyprinter >=1.7.0,
         containers >=0.6.0.1,
@@ -89,7 +91,8 @@
         vector >=0.12.2.0,
         recursion >=1.0.0.0,
         split,
-        deepseq
+        deepseq,
+        lazy-csv
 
     other-extensions:
         OverloadedStrings
@@ -110,7 +113,7 @@
     other-modules:    Paths_jacinda
     autogen-modules:  Paths_jacinda
     default-language: Haskell2010
-    ghc-options:      -Wall -rtsopts "-with-rtsopts=-A200k -k32k"
+    ghc-options:      -rtsopts "-with-rtsopts=-A200k -k32k"
     build-depends:
         base,
         jacinda-lib,
@@ -128,7 +131,7 @@
     main-is:          Spec.hs
     hs-source-dirs:   test
     default-language: Haskell2010
-    ghc-options:      -Wall -threaded -rtsopts "-with-rtsopts=-N -K1K" -Wall
+    ghc-options:      -threaded -rtsopts "-with-rtsopts=-N -K1K"
     build-depends:
         base,
         jacinda-lib,
@@ -143,7 +146,7 @@
     main-is:          Bench.hs
     hs-source-dirs:   bench
     default-language: Haskell2010
-    ghc-options:      -Wall -rtsopts "-with-rtsopts=-A200k -k32k"
+    ghc-options:      -rtsopts "-with-rtsopts=-A200k -k32k"
     build-depends:
         base,
         criterion,
diff --git a/lib/gitCtx.jac b/lib/gitCtx.jac
--- a/lib/gitCtx.jac
+++ b/lib/gitCtx.jac
@@ -1,15 +1,10 @@
 @include'prelude/fn.jac'
 @include'lib/maybe.jac'
 
-fn mMatch(p, str) :=
-  if p str
-    then Some str
-    else None;
-
 fn step(p, ctx, line) :=
   let
     val fpCtx ≔ line ~* 1 /diff --git\s+([^\s]+)/
-    val mLine ≔ mMatch p line
+    val mLine ≔ p ~? line
   in (alternative (ctx->1) fpCtx.mLine) end;
 
 fn process(x) :=
diff --git a/man/ja.1 b/man/ja.1
--- a/man/ja.1
+++ b/man/ja.1
@@ -35,6 +35,9 @@
 .TP
 \f[B]\-R\f[R]
 Record separator regex
+.TP
+\f[B]\-\-csv\f[R]
+Process as CSV
 .SH LANGUAGE
 .SS REGEX
 Regular expressions follow Rust\[cq]s regex library:
@@ -134,6 +137,9 @@
 Str \-> Regex \-> Option (Int .
 Int)
 .TP
+\f[B]\[ti]?\f[R] Maybe match: return string if it is a match, otherwise None
+Str \-> Regex \-> Option Str
+.TP
 \f[B]\[ti]*\f[R] Match, returning nth capture group
 Str \-> Int \-> Regex \-> Option Str
 .TP
@@ -207,6 +213,8 @@
 .PP
 \f[B]:set rs=/REGEX/;\f[R] Set record separator
 .PP
+\f[B]:set csv;\f[R] Process as CSV
+.PP
 \f[B]:flush;\f[R] Flush stdout for every line
 .SH INFLUENTIAL ENVIRONMENT VARIABLES
 \f[CR]JAC_PATH\f[R] \- colon\-separated list of directories to search
@@ -227,7 +235,7 @@
 :set fs:=/,[ \[rs]t]*|[ \[rs]t]+/; {|sprintf \[aq]%i %i\[aq] (\[ga]2 . \[ga]1)}
 Same, with input fields separated by comma and/or blanks and tabs.
 .TP
-:set fs:=/,/; {ix=1}{[x+`\[rs]n'+y]|>\[ga]$}
+:set csv; {ix=1}{[x+`\[rs]n'+y]|>\[ga]$}
 Present column names of a .csv file, one per line
 .TP
 (+)|0 $1:i
diff --git a/src/A.hs b/src/A.hs
--- a/src/A.hs
+++ b/src/A.hs
@@ -8,6 +8,7 @@
          , L (..), N (..), BBin (..), BTer (..)
          , BUn (..), DfnVar (..)
          , D (..), Program (..)
+         , Mode (..)
          , mapExpr
          , getS, flushD
          -- * Base functors
@@ -148,7 +149,7 @@
           | Eq | Neq | Geq | Gt | Lt | Leq
           | Map
           | Matches -- ^ @'string' ~ /pat/@
-          | NotMatches
+          | NotMatches | MMatch
           | And | Or
           | Min | Max
           | Split | Splitc
@@ -163,7 +164,7 @@
     pretty Plus = "+"; pretty Times = "*"; pretty Div = "%"; pretty Minus = "-"
     pretty Eq = "="; pretty Gt = ">"; pretty Lt = "<"; pretty Geq = ">="
     pretty Leq = "<="; pretty Neq = "!="; pretty Map = "¨"; pretty Matches = "~"
-    pretty NotMatches = "!~"; pretty And = "&"; pretty Or = "||"
+    pretty MMatch = "~?"; pretty NotMatches = "!~"; pretty And = "&"; pretty Or = "||"
     pretty Max = "max"; pretty Min = "min"; pretty Prior = "\\."; pretty Filter = "#."
     pretty Split = "split"; pretty Splitc = "splitc"; pretty Sprintf = "sprintf"
     pretty Match = "match"; pretty MapMaybe = ":?"; pretty Fold1 = "|>"
@@ -274,8 +275,9 @@
 mPrec Gt         = Just 4
 mPrec Lt         = Just 4
 mPrec Leq        = Just 4
-mPrec Matches    = Just 4
-mPrec NotMatches = Just 4
+mPrec Matches    = Just 5
+mPrec NotMatches = Just 5
+mPrec MMatch     = Just 5
 mPrec Report     = Just 4
 mPrec And        = Just 3
 mPrec Or         = Just 2
@@ -393,7 +395,8 @@
 data D a = SetFS T.Text | SetRS T.Text
          | FunDecl (Nm a) [Nm a] (E a)
          | FlushDecl
-         | SetAsv | SetUsv | SetOFS T.Text | SetORS T.Text
+         | SetAsv | SetUsv | SetCsv
+         | SetOFS T.Text | SetORS T.Text
          deriving (Functor)
 
 instance Pretty (D a) where
@@ -416,13 +419,16 @@
 flushD :: Program a -> Bool
 flushD (Program ds _) = any p ds where p FlushDecl = True; p _ = False
 
-getS :: Program a -> (Maybe T.Text, Maybe T.Text)
-getS (Program ds _) = foldl' go (Nothing, Nothing) ds where
-    go (_, rs) (SetFS bs) = (Just bs, rs)
-    go _ SetAsv           = (Just "\\x1f", Just "\\x1e")
-    go _ SetUsv           = (Just "␞",Just "␟")
-    go (fs, _) (SetRS bs) = (fs, Just bs)
-    go next _             = next
+data Mode = CSV | AWK (Maybe T.Text) (Maybe T.Text) -- field, record
+
+getS :: Program a -> Mode
+getS (Program ds _) = foldl' go (AWK Nothing Nothing) ds where
+    go (AWK _ rs) (SetFS bs) = AWK (Just bs) rs
+    go _ SetAsv              = AWK (Just "\\x1f") (Just "\\x1e")
+    go _ SetUsv              = AWK (Just "␞") (Just "␟")
+    go _ SetCsv              = CSV
+    go (AWK fs _) (SetRS bs) = AWK fs (Just bs)
+    go next _                = next
 
 mapExpr :: (E a -> E a) -> Program a -> Program a
 mapExpr f (Program ds e) = Program ds (f e)
diff --git a/src/File.hs b/src/File.hs
--- a/src/File.hs
+++ b/src/File.hs
@@ -17,12 +17,13 @@
 import qualified Data.ByteString            as BS
 import qualified Data.ByteString.Lazy       as BSL
 import qualified Data.ByteString.Lazy.Char8 as ASCIIL
-import           Data.Foldable              (traverse_)
+import           Data.Foldable              (fold, traverse_)
 import           Data.Functor               (($>))
 import qualified Data.Text                  as T
 import           Data.Text.Encoding         (encodeUtf8)
 import qualified Data.Text.IO               as TIO
 import           Data.Tuple                 (swap)
+import qualified Data.Vector                as V
 import           Include
 import           Jacinda.Backend.Const
 import           Jacinda.Backend.T
@@ -34,8 +35,22 @@
 import           R
 import           Regex.Rure                 (RurePtr)
 import           System.IO                  (stdin)
+import           Text.CSV.Lazy.ByteString   (CSVField (..), parseCSV)
 import           Ty
 
+csvCtx :: BSL.ByteString -> [LineCtx]
+csvCtx = go Nothing . parseCSV where
+    go _ []           = []
+    go _ (Left err:_) = error (show err)
+    -- TODO: re-csv it?
+    go (Just n) (Right r:rs) = let fs=mB<$>r in (fold fs, V.fromListN n fs, fromIntegral (line r)):go (Just n) rs
+    go Nothing (Right r:rs)  = let fs=mB<$>r; n=length fs in (fold fs, V.fromListN n fs, fromIntegral (line r)):go (Just n) rs
+    mB f@CSVField{} = BSL.toStrict (csvFieldContent f)
+    mB f            = error (show f)
+    line (f@CSVField{}:_) = csvRowNum f
+    line (f:_)            = error (show f)
+    line []               = error "empty record in csv"
+
 parseLib :: [FilePath] -> FilePath -> StateT AlexUserState IO [D AlexPosn]
 parseLib incls fp = do
     contents <- liftIO $ TIO.readFile =<< resolveImport incls fp
@@ -43,7 +58,7 @@
     case parseLibWithCtx contents st of
         Left err              -> liftIO (throwIO err)
         Right (st', ([], ds)) -> put st' $> (rwD <$> ds)
-        Right (st', (is, ds)) -> do { put st' ; dss <- traverse (parseLib incls) is ; pure (concat dss ++ fmap rwD ds) }
+        Right (st', (is, ds)) -> do {put st'; dss <- traverse (parseLib incls) is; pure (concat dss ++ fmap rwD ds)}
 
 parseE :: [FilePath] -> T.Text -> StateT AlexUserState IO (Program AlexPosn)
 parseE incls bs = do
@@ -90,37 +105,40 @@
 runOnBytes :: [FilePath]
            -> FilePath -- ^ Data file name, for @nf@
            -> T.Text -- ^ Program
-           -> Maybe T.Text -- ^ Field separator
-           -> Maybe T.Text -- ^ Record separator
+           -> Mode
            -> BSL.ByteString
            -> IO ()
-runOnBytes incls fp src cliFS cliRS contents = do
+runOnBytes incls fp src mode contents = do
     incls' <- defaultIncludes <*> pure incls
     (ast, m) <- parseEWithMax incls' src
     (typed, i) <- yIO $ runTyM m (tyP ast)
     let (eI, j) = ib i typed
     m'Throw $ cF eI
-    let ~(afs, ars) = getS ast
+    let ~(AWK afs ars) = getS ast
         (e', k) = runState (eta eI) j
-        cont=run (compileFS (cliFS <|> afs)) (flushD typed) k (compileR (encodeUtf8 $ T.pack fp) e')
-    case cliRS <|> ars of
-        Nothing -> cont $ fmap BSL.toStrict (ASCIIL.lines contents)
-        Just rs -> cont $ lazySplit (tcompile rs) contents
+        cont=run (flushD typed) k (compileR (encodeUtf8 $ T.pack fp) e')
+    case mode of
+        AWK cliFS cliRS ->
+            let r=compileFS (cliFS <|> afs)
+                bs=case cliRS <|> ars of
+                    Nothing -> fmap BSL.toStrict (ASCIIL.lines contents)
+                    Just rs -> lazySplit (tcompile rs) contents
+                ctxs=zipWith (\ ~(x,y) z -> (x,y,z)) [(b, splitBy r b) | b <- bs] [1..]
+            in cont ctxs
+        CSV -> let ctxs = csvCtx contents in cont ctxs
 
 runStdin :: [FilePath]
          -> T.Text -- ^ Program
-         -> Maybe T.Text -- ^ Field separator
-         -> Maybe T.Text -- ^ Record separator
+         -> Mode
          -> IO ()
-runStdin is src cliFS cliRS = runOnBytes is "(stdin)" src cliFS cliRS =<< BSL.hGetContents stdin
+runStdin is src m = runOnBytes is "(stdin)" src m =<< BSL.hGetContents stdin
 
 runOnFile :: [FilePath]
           -> T.Text
-          -> Maybe T.Text
-          -> Maybe T.Text
+          -> Mode
           -> FilePath
           -> IO ()
-runOnFile is e fs rs fp = runOnBytes is fp e fs rs =<< BSL.readFile fp
+runOnFile is e m fp = runOnBytes is fp e m =<< BSL.readFile fp
 
 tcIO :: [FilePath] -> T.Text -> IO ()
 tcIO incls src = do
diff --git a/src/Jacinda/Backend/T.hs b/src/Jacinda/Backend/T.hs
--- a/src/Jacinda/Backend/T.hs
+++ b/src/Jacinda/Backend/T.hs
@@ -1,6 +1,6 @@
 {-# LANGUAGE OverloadedStrings #-}
 
-module Jacinda.Backend.T ( run, eB ) where
+module Jacinda.Backend.T ( LineCtx, run, eB ) where
 
 import           A
 import           A.I
@@ -91,25 +91,22 @@
     pSF flush c es
 pSF _ _ [] = pure ()
 
-run :: RurePtr -> Bool -> Int -> E T -> [BS.ByteString] -> IO ()
-run r flush j e bs | TyB TyUnit <- eLoc e =  (\(s, f, env) -> pSF flush (s,f) env) $ flip evalState j $ do
+run :: Bool -> Int -> E T -> [LineCtx] -> IO ()
+run flush j e ctxs | TyB TyUnit <- eLoc e =  (\(s, f, env) -> pSF flush (s,f) env) $ flip evalState j $ do
     (res, tt, iEnv, μ) <- unit e
     u <- nI
-    let ctxs=zipWith (\ ~(x,y) z -> (x,y,z)) [(b, splitBy r b) | b <- bs] [1..]
-        outs=μ<$>ctxs; es'=scanl' (&) (Σ u iEnv IM.empty IM.empty IM.empty IS.empty) outs
+    let outs=μ<$>ctxs; es'=scanl' (&) (Σ u iEnv IM.empty IM.empty IM.empty IS.empty) outs
     pure (res, tt, gE<$>es')
-run r flush j e bs | TyB TyStream:$_ <- eLoc e = traverse_ (traverse_ (pS flush)).flip evalState j $ do
+run flush j e ctxs | TyB TyStream:$_ <- eLoc e = traverse_ (traverse_ (pS flush)).flip evalState j $ do
     t <- nI
     (iEnv, μ) <- ctx e t
     u <- nI
-    let ctxs=zipWith (\ ~(x,y) z -> (x,y,z)) [(b, splitBy r b) | b <- bs] [1..]
-        outs=μ<$>ctxs; es=scanl' (&) (Σ u iEnv IM.empty IM.empty IM.empty IS.empty) outs
+    let outs=μ<$>ctxs; es=scanl' (&) (Σ u iEnv IM.empty IM.empty IM.empty IS.empty) outs
     pure ((! t).gE<$>es)
-run r _ j e bs = pDocLn $ flip evalState j $ do
+run _ j e ctxs = pDocLn $ flip evalState j $ do
     (iEnv, g, e0) <- collect e
     u <- nI
-    let ctxs=zipWith (\ ~(x,y) z -> (x,y,z)) [(b, splitBy r b) | b <- bs] [1..]
-        updates=g<$>ctxs
+    let updates=g<$>ctxs
         finEnv=foldl' (&) (Σ u iEnv IM.empty IM.empty IM.empty IS.empty) updates
     e0@>(fromMaybe (throw EmptyFold)<$>gE finEnv)
 
@@ -363,6 +360,9 @@
 (EApp _ (EApp _ (BB _ NotMatches) s) r) @> b = do
     se <- s@>b; re <- r@>b
     pure (mkB (not$isMatch' (asR re) (asS se)))
+(EApp ty (EApp _ (BB _ MMatch) s) r) @> b = do
+    se <- s@>b; re <- r@>b
+    pure (if isMatch' (asR re) (asS se) then OptionVal ty (Just$!se) else OptionVal ty Nothing)
 (EApp ty (EApp _ (BB _ Take) n) x) @> b = do
     n' <- asI<$>(n@>b); x' <- asV<$>(x@>b)
     pure $ Arr ty (V.take (fromIntegral n') x')
diff --git a/src/Jacinda/Fuse.hs b/src/Jacinda/Fuse.hs
deleted file mode 100644
--- a/src/Jacinda/Fuse.hs
+++ /dev/null
@@ -1,59 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-module Jacinda.Fuse ( fuse ) where
-
-import           A
-import           A.E
-import           Control.Monad.State.Strict (runState)
-import           Ty.Const
-
-fuse :: Int -> E T -> (E T, Int)
-fuse i = flip runState i.fM
-
-fM :: E T -> M (E T)
-fM (EApp t0 (EApp t1 (EApp t2 ho@(TB _ Fold) op) seed) stream) | TyB TyStream :$ _ <- eLoc stream = do
-    stream' <- fM stream
-    case stream' of
-        (EApp _ (EApp _ (BB _ Filter) p) xs) -> do
-            let opTy@(TyArr sTy popTy@(TyArr xTy _)) = eLoc op
-            s <- nN "seed" sTy; x <- nN "x" xTy
-            let sE=Var sTy s; xE=Var xTy x
-            let fop=Lam opTy s (Lam popTy x (Cond sTy (EApp tyB p xE) (EApp sTy (EApp popTy op sE) xE) sE)) in pure (EApp t0 (EApp t1 (EApp t2 ho fop) seed) xs)
-        (Guarded t p e) -> do
-            let opTy@(TyArr sTy popTy@(TyArr xTy _)) = eLoc op
-            s <- nN "seed" sTy; x <- nN "x" xTy
-            let sE=Var sTy s; xE=Var xTy x
-            let fop=Lam opTy s (Lam popTy x (Cond sTy p (EApp sTy (EApp popTy op sE) xE) sE)) in pure (EApp t0 (EApp t1 (EApp t2 ho fop) seed) (Implicit t e))
-            -- FIXME: does this evaluate e? (could be exception)
-        (EApp _ (EApp _ (BB _ Map) f) xs) -> do
-            let (TyArr xTy yTy) = eLoc f
-                (TyArr sTy _) = eLoc op
-            s <- nN "seed" sTy; x <- nN "x" xTy
-            let sE=Var sTy s; xE=Var xTy x
-                popT=xTy ~> sTy; fopT=sTy ~> popT
-                fop=Lam fopT s (Lam popT x (EApp undefined (EApp undefined op sE) (EApp yTy f xE)))
-            fM (EApp sTy (EApp undefined (EApp undefined (TB (fopT ~> (sTy ~> TyArr (TyB TyStream :$ xTy) sTy)) Fold) fop) seed) xs)
-        (EApp _ (EApp _ (BB _ MapMaybe) f) xs) -> do
-            -- op | seed (f:?xs) -> [option x (x `op`) (f y)] | seed xs
-            let TyArr xT yT=eLoc f
-                sT=eLoc seed
-            s <- nN "seed" sT; x <- nN "x" xT
-            let sE=Var sT s; xE=Var xT x
-                popT=xT ~> sT; fopT=sT ~> popT
-                fop=Lam fopT s (Lam popT x (EApp sT (EApp undefined (EApp undefined (TB (sT ~> TyArr undefined (yT ~> sT)) Option) sE) (EApp undefined op sE)) (EApp yT f xE)))
-            fM (EApp sT (EApp undefined (EApp undefined (TB (TyArr fopT (TyArr sT (TyArr (TyB TyStream :$ xT) sT))) Fold) fop) seed) xs)
-        (EApp _ (UB _ CatMaybes) xs) -> do
-            -- op | seed (.? xs) -> [option x (x `op`) y] | seed xs
-            let TyArr _ (TyArr xTy _)=eLoc op
-                xMT=tyOpt xTy
-                sTy=eLoc seed
-            s <- nN "seed" sTy; x <- nN "x" xMT
-            let sE=Var sTy s; xE=Var xMT x
-                popT=xMT ~> sTy; fopT=sTy ~> popT
-                fop=Lam fopT s (Lam popT x (EApp sTy (EApp undefined (EApp undefined (TB (sTy ~> TyArr undefined (xMT ~> sTy)) Option) sE) (EApp undefined op sE)) xE))
-            fM (EApp sTy (EApp undefined (EApp undefined (TB (fopT ~> (sTy ~> TyArr (TyB TyStream :$ xMT) sTy)) Fold) fop) seed) xs)
-        _ -> pure (EApp t0 (EApp t1 (EApp t2 ho op) seed) stream')
-fM (Tup t es) = Tup t <$> traverse fM es
-fM (EApp t e0 e1) = EApp t <$> fM e0 <*> fM e1
-fM (Lam t n e) = Lam t n <$> fM e
-fM e = pure e
diff --git a/src/L.x b/src/L.x
--- a/src/L.x
+++ b/src/L.x
@@ -105,6 +105,7 @@
         "]"                      { mkSym RSqBracket `andBegin` 0 }
         "~"                      { mkSym Tilde }
         "!~"                     { mkSym NotMatchTok }
+        "~?"                     { mkSym MMatch }
         ","                      { mkSym Comma }
         ",,"                     { mkSym DoubleComma }
         "."                      { mkSym Dot }
@@ -146,6 +147,7 @@
         else                     { mkKw KwElse }
         asv                      { mkKw KwAsv }
         usv                      { mkKw KwUsv }
+        csv                      { mkKw KwCsv }
 
         fs                       { mkRes VarFs }
         rs                       { mkRes VarRs }
@@ -257,9 +259,7 @@
 
 alexEOF = EOF <$> get_pos
 
-data Sym = PlusTok
-         | MinusTok
-         | PercentTok
+data Sym = PlusTok | MinusTok | PercentTok
          | ExpTok
          | FoldTok
          | IceCreamCone
@@ -279,12 +279,10 @@
          | GtTok | GeqTok
          | LtTok | LeqTok
          | AndTok | OrTok
-         | Tilde | NotMatchTok
-         | Comma
+         | Tilde | NotMatchTok | MMatch
+         | Comma | Dot
          | DoubleComma
-         | Dot
-         | TallyTok
-         | LengthTok
+         | TallyTok | LengthTok
          | ConstTok
          | LBracePercent
          | LBraceBar
@@ -295,12 +293,9 @@
          | BackslashDot
          | QuestionMark
          | FilterTok
-         | FloorSym
-         | CeilSym
-         | DedupTok
-         | DedupOnTok
-         | CatMaybesTok
-         | MapMaybeTok
+         | FloorSym | CeilSym
+         | DedupTok | DedupOnTok
+         | CatMaybesTok | MapMaybeTok
          | CapTok
          | NegTok
          | LastFieldTok
@@ -335,6 +330,7 @@
     pretty RSqBracket    = "]"
     pretty Tilde         = "~"
     pretty NotMatchTok   = "!~"
+    pretty MMatch        = "~?"
     pretty Comma         = ","
     pretty DoubleComma   = ",,"
     pretty Dot           = "."
@@ -372,7 +368,7 @@
              | KwFn
              | KwInclude
              | KwIf | KwThen | KwElse
-             | KwAsv | KwUsv
+             | KwAsv | KwUsv | KwCsv
 
 -- | Reserved/special variables
 data Var = VarX | VarY
@@ -407,6 +403,7 @@
     pretty KwElse    = "else"
     pretty KwUsv     = "usv"
     pretty KwAsv     = "asv"
+    pretty KwCsv     = "csv"
 
 data Builtin = BuiltinIParse | BuiltinFParse
              | BuiltinSubstr
diff --git a/src/Parser.y b/src/Parser.y
--- a/src/Parser.y
+++ b/src/Parser.y
@@ -83,6 +83,7 @@
     mapMaybe { TokSym $$ MapMaybeTok }
     catMaybes { TokSym $$ CatMaybesTok }
     capture { TokSym $$ CapTok }
+    mmatch { TokSym $$ L.MMatch }
     neg { TokSym $$ NegTok }
 
     eq { TokSym $$ EqTok }
@@ -122,6 +123,7 @@
     else { TokKeyword $$ KwElse }
     usv { TokKeyword $$ KwUsv }
     asv { TokKeyword $$ KwAsv }
+    csv { TokKeyword $$ KwCsv }
 
     x { TokResVar $$ VarX }
     y { TokResVar $$ VarY }
@@ -197,16 +199,13 @@
      | times { Times }
      | minus { Minus }
      | percent { Div }
-     | gt { Gt }
-     | lt { Lt }
-     | geq { Geq }
-     | leq { Leq }
-     | eq { Eq }
-     | neq { Neq }
+     | gt { Gt } | lt { Lt }
+     | geq { Geq } | leq { Leq }
+     | eq { Eq } | neq { Neq }
      | quot { Map }
      | mapMaybe { MapMaybe }
-     | tilde { Matches }
-     | notMatch { NotMatches }
+     | tilde { Matches } | notMatch { NotMatches }
+     | mmatch { A.MMatch }
      | and { And }
      | or { Or }
      | backslashdot { Prior }
@@ -231,6 +230,7 @@
   | set ors defEq strLit semicolon { SetORS (strTok $4) }
   | set asv semicolon { SetAsv }
   | set usv semicolon { SetUsv }
+  | set csv semicolon { SetCsv }
   | flush semicolon { FlushDecl }
   | fn name Args defEq E semicolon { FunDecl $2 $3 $5 }
   | fn name defEq E semicolon { FunDecl $2 [] $4 }
diff --git a/src/Parser/Rw.hs b/src/Parser/Rw.hs
--- a/src/Parser/Rw.hs
+++ b/src/Parser/Rw.hs
@@ -32,6 +32,7 @@
 mFi Filter     = Just 5
 mFi Fold1      = Just 5
 mFi Matches    = Just 5
+mFi MMatch     = Just 5
 mFi NotMatches = Just 5
 mFi Min        = Nothing
 mFi Max        = Nothing
diff --git a/src/Ty.hs b/src/Ty.hs
--- a/src/Ty.hs
+++ b/src/Ty.hs
@@ -384,6 +384,7 @@
 tyES s (BB _ Splitc) = pure (BB (tyStr ~> tyStr ~> tyV tyStr) Splitc, s)
 tyES s (BB _ Matches) = pure (BB (tyStr ~> tyR ~> tyB) Matches, s)
 tyES s (BB _ NotMatches) = pure (BB (tyStr ~> tyR ~> tyB) NotMatches, s)
+tyES s (BB _ MMatch) = pure (BB (tyStr ~> tyR ~> tyOpt tyStr) MMatch, s)
 tyES s (UB _ Tally) = pure (UB (tyStr ~> tyI) Tally, s)
 tyES s (BB _ Take) = do {a <- var<$>freshN "a"; pure (BB (tyI ~> tyV a ~> tyV a) Take, s)}
 tyES s (BB _ Drop) = do {a <- var<$>freshN "a"; pure (BB (tyI ~> tyV a ~> tyV a) Drop, s)}
diff --git a/test/examples/bookend.jac b/test/examples/bookend.jac
deleted file mode 100644
--- a/test/examples/bookend.jac
+++ /dev/null
@@ -1,1 +0,0 @@
-/Disassembly of section \.text:/,,/Disassembly of section/$0
diff --git a/x/Main.hs b/x/Main.hs
--- a/x/Main.hs
+++ b/x/Main.hs
@@ -2,6 +2,7 @@
 
 module Main (main) where
 
+import           A                   (Mode (..))
 import qualified Data.Text           as T
 import qualified Data.Text.IO        as TIO
 import qualified Data.Version        as V
@@ -11,7 +12,7 @@
 
 data Command = TypeCheck !FilePath ![FilePath]
              | Run !FilePath !(Maybe T.Text) !(Maybe T.Text) !(Maybe FilePath) ![FilePath]
-             | Expr !T.Text !(Maybe FilePath) !(Maybe T.Text) !Bool !Bool !(Maybe T.Text) ![FilePath]
+             | Expr !T.Text !(Maybe FilePath) !(Maybe T.Text) !Bool !Bool !Bool !(Maybe T.Text) ![FilePath]
              | Eval !T.Text
 
 jacFile :: Parser FilePath
@@ -20,6 +21,11 @@
     <> help "Source code"
     <> jacCompletions)
 
+csv :: Parser Bool
+csv = switch
+    (long "csv"
+    <> help "Process as CSV")
+
 asv :: Parser Bool
 asv = switch
     (long "asv"
@@ -66,7 +72,7 @@
     where
         tcP = TypeCheck <$> jacFile <*> includes
         runP = Run <$> jacFile <*> jacFs <*> jacRs <*> inpFile <*> includes
-        exprP = Expr <$> jacExpr <*> inpFile <*> jacFs <*> asv <*> usv <*> jacRs <*> includes
+        exprP = Expr <$> jacExpr <*> inpFile <*> jacFs <*> asv <*> usv <*> csv <*> jacRs <*> includes
         eP = Eval <$> jacExpr
 
 includes :: Parser [FilePath]
@@ -92,20 +98,25 @@
 main :: IO ()
 main = run =<< execParser wrapper
 
-ap :: Bool -> Bool -> Maybe T.Text -> Maybe T.Text -> (Maybe T.Text, Maybe T.Text)
-ap True True _ _          = errorWithoutStackTrace "--asv and --usv both specified."
-ap _ True Just{} _        = errorWithoutStackTrace "--usv and field separator both speficied."
-ap _ True _ Just{}        = errorWithoutStackTrace "--usv and record separator both speficied."
-ap True _ Just{} _        = errorWithoutStackTrace "--asv and field separator both speficied."
-ap True _ _ Just{}        = errorWithoutStackTrace "--asv and record separator both speficied."
-ap True _ Nothing Nothing = (Just "\\x1f", Just "\\x1e")
-ap _ True Nothing Nothing = (Just "␟", Just "␞")
-ap _ _ fs rs              = (fs,rs)
+ap :: Bool -> Bool -> Bool -> Maybe T.Text -> Maybe T.Text -> Mode
+ap True True _ _ _          = errorWithoutStackTrace "--asv and --usv both specified."
+ap True _ True _ _          = errorWithoutStackTrace "--asv and --csv both specified."
+ap _ True True _ _          = errorWithoutStackTrace "--usv and --csv both specified."
+ap _ True _ Just{} _        = errorWithoutStackTrace "--usv and field separator both speficied."
+ap _ True _ _ Just{}        = errorWithoutStackTrace "--usv and record separator both speficied."
+ap True _ _ Just{} _        = errorWithoutStackTrace "--asv and field separator both speficied."
+ap True _ _ _ Just{}        = errorWithoutStackTrace "--asv and record separator both speficied."
+ap _ _ True Just{} _        = errorWithoutStackTrace "--csv and field separator both speficied."
+ap _ _ True _ Just{}        = errorWithoutStackTrace "--csv and record separator both speficied."
+ap _ _ True Nothing Nothing = CSV
+ap True _ _ Nothing Nothing = AWK (Just "\\x1f") (Just "\\x1e")
+ap _ True _ Nothing Nothing = AWK (Just "␟") (Just "␞")
+ap _ _ _ fs rs              = AWK fs rs
 
 run :: Command -> IO ()
-run (TypeCheck fp is)                = tcIO is =<< TIO.readFile fp
-run (Run fp fs rs Nothing is)        = do { contents <- TIO.readFile fp ; runStdin is contents fs rs }
-run (Run fp fs rs (Just dat) is)     = do { contents <- TIO.readFile fp ; runOnFile is contents fs rs dat }
-run (Expr eb Nothing fs a u rs is)   = let (fs',rs') = ap a u fs rs in runStdin is eb fs' rs'
-run (Expr eb (Just fp) fs a u rs is) = let (fs',rs') = ap a u fs rs in runOnFile is eb fs' rs' fp
-run (Eval e)                         = print (exprEval e)
+run (TypeCheck fp is)                  = tcIO is =<< TIO.readFile fp
+run (Run fp fs rs Nothing is)          = do { contents <- TIO.readFile fp ; runStdin is contents (AWK fs rs) }
+run (Run fp fs rs (Just dat) is)       = do { contents <- TIO.readFile fp ; runOnFile is contents (AWK fs rs) dat }
+run (Expr eb Nothing fs a u c rs is)   = let m = ap a u c fs rs in runStdin is eb m
+run (Expr eb (Just fp) fs a u c rs is) = let m = ap a u c fs rs in runOnFile is eb m fp
+run (Eval e)                           = print (exprEval e)
