diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,7 @@
 Changelog for record-dot-preprocessor
 
+0.2.12, released 2021-09-01
+    #45, re-export preprocessor internals as library
 0.2.11, released 2021-05-28
     #41, use qualified names in the plugin
 0.2.10, released 2021-03-01
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# record-dot-preprocessor [![Hackage version](https://img.shields.io/hackage/v/record-dot-preprocessor.svg?label=Hackage)](https://hackage.haskell.org/package/record-dot-preprocessor) [![Stackage version](https://www.stackage.org/package/record-dot-preprocessor/badge/nightly?label=Stackage)](https://www.stackage.org/package/record-dot-preprocessor) [![Build status](https://img.shields.io/github/workflow/status/ndmitchell/record-dot-preprocessor/ci.svg)](https://github.com/ndmitchell/record-dot-preprocessor/actions)
+# record-dot-preprocessor [![Hackage version](https://img.shields.io/hackage/v/record-dot-preprocessor.svg?label=Hackage)](https://hackage.haskell.org/package/record-dot-preprocessor) [![Stackage version](https://www.stackage.org/package/record-dot-preprocessor/badge/nightly?label=Stackage)](https://www.stackage.org/package/record-dot-preprocessor) [![Build status](https://img.shields.io/github/workflow/status/ndmitchell/record-dot-preprocessor/ci/master.svg)](https://github.com/ndmitchell/record-dot-preprocessor/actions)
 
 In almost every programming language `a.b` will get the `b` field from the `a` data type, and many different data types can have a `b` field. The reason this feature is ubiquitous is because it's _useful_. This feature has been [proposed for Haskell](https://github.com/ghc-proposals/ghc-proposals/pull/282) as `RecordDotSyntax`. The `record-dot-preprocessor` brings this feature to Haskell today. Some examples:
 
diff --git a/plugin/RecordDotPreprocessor/Lib.hs b/plugin/RecordDotPreprocessor/Lib.hs
new file mode 100644
--- /dev/null
+++ b/plugin/RecordDotPreprocessor/Lib.hs
@@ -0,0 +1,3 @@
+module RecordDotPreprocessor.Lib (module X) where
+
+import Edit as X
diff --git a/preprocessor/Edit.hs b/preprocessor/Edit.hs
--- a/preprocessor/Edit.hs
+++ b/preprocessor/Edit.hs
@@ -1,6 +1,6 @@
 {-# LANGUAGE PatternSynonyms, ViewPatterns #-}
 
-module Edit(edit) where
+module Edit(recordDotPreprocessor, recordDotPreprocessorOnFragment) where
 
 import Lexer
 import Paren
@@ -9,9 +9,14 @@
 import Data.List.Extra
 import Control.Monad.Extra
 
+recordDotPreprocessor :: FilePath -> String -> String
+recordDotPreprocessor original = unlexerFile (Just original) . unparens . edit . parens . lexer
+    where
+        edit :: [PL] -> [PL]
+        edit = editAddPreamble . editAddInstances . editLoop
 
-edit :: [PL] -> [PL]
-edit = editAddPreamble . editAddInstances . editLoop
+recordDotPreprocessorOnFragment :: String -> String
+recordDotPreprocessorOnFragment = unlexerFile Nothing . unparens . editLoop . parens . lexer
 
 
 ---------------------------------------------------------------------
diff --git a/preprocessor/Lexer.hs b/preprocessor/Lexer.hs
--- a/preprocessor/Lexer.hs
+++ b/preprocessor/Lexer.hs
@@ -96,7 +96,7 @@
 seen xs = first (xs++)
 
 
-unlexerFile :: FilePath -> [Lexeme] -> String
+unlexerFile :: Maybe FilePath -> [Lexeme] -> String
 unlexerFile src xs =
     dropping 1 ++
     -- we split the whitespace up to increase the chances of startLine being true below
@@ -122,4 +122,6 @@
         go _ _ [] = ""
 
         -- write out a line marker with a trailing newline
-        dropping n = "{-# LINE " ++ show n ++ " " ++ show src ++ " #-}\n"
+        dropping n = case src of
+          Just src' -> "{-# LINE " ++ show n ++ " " ++ show src' ++ " #-}\n"
+          Nothing -> ""
diff --git a/preprocessor/Paren.hs b/preprocessor/Paren.hs
--- a/preprocessor/Paren.hs
+++ b/preprocessor/Paren.hs
@@ -1,9 +1,10 @@
 {-# LANGUAGE ScopedTypeVariables, DeriveFunctor #-}
 
 -- Most of this module follows the Haskell report, https://www.haskell.org/onlinereport/lexemes.html
-module Paren(Paren(..), parenOn, unparen, unparens) where
+module Paren(Paren(..), parens, unparens) where
 
 import Data.Tuple.Extra
+import Lexer(Lexeme(..))
 
 -- | A list of items which are paranthesised.
 data Paren a
@@ -26,6 +27,8 @@
         go close (x:xs) = first (Item x :) $ go close xs
         go close [] = ([], Nothing)
 
+parens :: [Lexeme] -> [Paren Lexeme]
+parens = parenOn lexeme [("(",")"),("[","]"),("{","}"),("`","`")]
 
 unparens :: [Paren a] -> [a]
 unparens = concatMap unparen
diff --git a/preprocessor/Preprocessor.hs b/preprocessor/Preprocessor.hs
--- a/preprocessor/Preprocessor.hs
+++ b/preprocessor/Preprocessor.hs
@@ -1,8 +1,6 @@
 
 module Preprocessor(main) where
 
-import Lexer
-import Paren
 import Edit
 import System.IO.Extra
 import System.Environment
@@ -23,6 +21,5 @@
 
 runConvert :: FilePath -> FilePath -> FilePath -> IO ()
 runConvert original input output = do
-    res <- unlexerFile original . unparens . edit . paren . lexer <$> readFileUTF8' input
+    res <- recordDotPreprocessor original <$> readFileUTF8' input
     if output == "-" then putStrLn res else writeFileUTF8 output res
-    where paren = parenOn lexeme [("(",")"),("[","]"),("{","}"),("`","`")]
diff --git a/record-dot-preprocessor.cabal b/record-dot-preprocessor.cabal
--- a/record-dot-preprocessor.cabal
+++ b/record-dot-preprocessor.cabal
@@ -1,7 +1,7 @@
-cabal-version:      >= 1.18
+cabal-version:      1.18
 build-type:         Simple
 name:               record-dot-preprocessor
-version:            0.2.11
+version:            0.2.12
 license:            BSD3
 x-license:          BSD-3-Clause OR Apache-2.0
 license-file:       LICENSE
@@ -32,7 +32,9 @@
 
 library
     default-language:   Haskell2010
-    hs-source-dirs:     plugin
+    hs-source-dirs:
+        plugin
+        preprocessor
     build-depends:
         base >= 4.8 && < 5,
         uniplate,
@@ -42,8 +44,12 @@
         buildable: False
     exposed-modules:
         RecordDotPreprocessor
+        RecordDotPreprocessor.Lib
     other-modules:
         Compat
+        Edit
+        Lexer
+        Paren
 
 executable record-dot-preprocessor
     default-language:   Haskell2010
