diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2010, Nicolas Pouillard
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of the copyright holders nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+> import Distribution.Simple
+> main = defaultMain
diff --git a/Text/FrQuotes.hs b/Text/FrQuotes.hs
new file mode 100644
--- /dev/null
+++ b/Text/FrQuotes.hs
@@ -0,0 +1,85 @@
+module Text.FrQuotes (frQuotes) where
+
+frTop, openFrQQ', closeFrQQ', openFrQQ, closeFrQQ,
+  openFrQ, closeFrQ, openBr, closeBr :: String
+frTop = "(frTop ("
+openFrQQ'  = "[$frQQ|"
+closeFrQQ' = "|]"
+openFrQQ  = frTop ++ openFrQQ'
+closeFrQQ = closeFrQQ' ++ "))"
+openFrQ   = "\xc2\xab"
+closeFrQ  = "\xc2\xbb"
+openBr    = closeFrQQ' ++ " `mappend` frAntiq ("
+closeBr   = ") `mappend` " ++ openFrQQ'
+
+-- Substitutes UTF8 french quotes «...» for (frTop [$frQQ|...|])
+-- Antiquotations are supported via braces {...} and are
+-- substituted for frAntiq, blocks are catenated with mappend.
+-- Here String is used as UTF-8 code points.
+frQuotes :: String -> String
+frQuotes = h
+        -- All these functions follow the same style,
+        -- the first argument 'k' is the continuation, i.e. what to do next.
+        -- Each function search for a different closing token, if the closing
+        -- token is found the continuation is called with the remaining char
+        -- stream, if some opening token is found the continuation is stacked
+        -- as the continuation of a new function call that have to find this
+        -- new token first.
+
+        -- haskell context
+        -- the h function don't needs a continuation parameter
+  where h ""                   = ""
+        h ('\xc2':'\xab':'{':xs) = frTop ++ "( frAntiq (" ++ b ((closeBr++) . f ((closeFrQQ++) . (')':) . h)) xs -- avoid an empty [$frQQ||]
+        h ('\xc2':'\xab':xs)   = openFrQQ ++ f ((closeFrQQ++) . h) xs
+        h ('{':'-':xs)         = "{-" ++ c (("-}"++) . h) xs
+        h ('"':xs)             = '"' : s (('"':) . h) xs
+        h ('\'':xs)            = '\'' : a h xs
+        h ('[':'$':xs)         = '[' : '$' : startq h xs
+        h (x:xs)               = x : h xs
+
+        -- french quotes context
+        f _ ""                 = error "unterminated french quotes (expecting `\xc2\xbb')"
+        f _ ('}':_)            = error "unexpected closing brace `}'"
+        f k ('{':xs)           = openBr  ++ b ((closeBr++)  . f k) xs
+        f k ('\xc2':'\xab':xs) = openFrQ ++ f ((closeFrQ++) . f k) xs
+        f k ('\xc2':'\xbb':xs) = k xs
+        f k (x:xs)             = x : f k xs
+
+        -- braces (haskell) context
+        b _ ""                 = error "unterminated quotes hole using curly braces (expecting `}')"
+        b k ('\xc2':'\xab':xs) = openFrQQ ++ f ((closeFrQQ++) . b k) xs
+        b _ ('\xc2':'\xbb':_)  = error "unexpected closing french quote"
+        b k ('{':'-':xs)       = "{-" ++ c (("-}"++) . b k) xs
+        b k ('{':xs)           = '{' : b (('}':) . b k) xs
+        b k ('}':xs)           = k xs
+        b k ('"':xs)           = '"' : s (('"':) . b k) xs
+        b k (x:xs)             = x : b k xs
+
+        -- haskell (nested) comments
+        c _ ""                 = error "unterminated haskell comment (expecting `-}')"
+        c k ('{':'-':xs)       = "{-" ++ c (("-}"++) . c k) xs
+        c k ('-':'}':xs)       = k xs
+        c k (x:xs)             = x : c k xs
+
+        -- haskell strings literal
+        s _ ""                 = error "unterminated haskell string (expecting `\"')"
+        s k ('\\':x:xs)        = '\\' : x : s k xs
+        s k ('"':xs)           = k xs
+        s k (x:xs)             = x : s k xs
+
+        -- haskell char literal (a bit lenient)
+        a _ ""                 = error "unterminated haskell character (expecting `'')"
+        a k ('\\':x:xs)        = '\\' : x : a k xs
+        a k (x:'\'':xs)
+                   | x /= '\'' = x : '\'' : k xs
+        a k xs                 = k xs
+
+        -- haskell quasi-quotation
+        -- is there nested QQ?
+        q _ ""                 = error "unterminated haskell quasi-quotation (expecting `|]')"
+        q k ('|':']':xs)       = '|' : ']' : k xs
+        q k (x:xs)             = x : q k xs
+        startq k xs = ys ++ '|' : q k zs'
+          where (ys,zs) = break (=='|') xs
+                zs' | null zs   = error "unrecognized haskell quasi-quotation (expecting `|`)"
+                    | otherwise = drop 1 zs
diff --git a/frquotes.cabal b/frquotes.cabal
new file mode 100644
--- /dev/null
+++ b/frquotes.cabal
@@ -0,0 +1,23 @@
+Name:           frquotes
+Cabal-Version:  >= 1.2
+Version:        0.1.0
+Copyright:      (c) Nicolas Pouillard
+Author:         Nicolas Pouillard
+Maintainer:     Nicolas Pouillard <nicolas.pouillard@gmail.com>
+Category:       Text
+Synopsis:       Lexical extension for Quasi-Quotations using French-Quotes
+Description:    Translate the French-Quotes in a UTF-8 Haskell file into
+                a Quasi-Quotation that can be instantiated later on.
+License:        BSD3
+License-File:   LICENSE
+Build-Type:     Simple
+
+library
+    Build-depends: base>=3.0&&<5
+    Exposed-Modules: Text.FrQuotes
+    ghc-options: -Wall
+
+executable frquotes
+    Build-depends: base>=3.0&&<5
+    main-is: frquotes.hs
+    ghc-options: -Wall
diff --git a/frquotes.hs b/frquotes.hs
new file mode 100644
--- /dev/null
+++ b/frquotes.hs
@@ -0,0 +1,28 @@
+import System.Environment
+import System.IO
+import Text.FrQuotes (frQuotes)
+
+-- | The 'readBinaryFile' function reads a binary file and
+-- returns the contents of the file as a string.
+-- The file is read lazily, on demand, as with 'getContents'.
+
+readBinaryFile        :: FilePath -> IO String
+readBinaryFile name   =  openBinaryFile name ReadMode >>= hGetContents
+
+-- | The computation 'writeBinaryFile' @file str@ function writes the string @str@,
+-- to the file @file@.
+writeBinaryFile :: FilePath -> String -> IO ()
+writeBinaryFile f txt = withBinaryFile f WriteMode (\ hdl -> hPutStr hdl txt)
+
+interact' :: (String -> String) -> IO ()
+interact' f = do
+  args <- getArgs
+  case args of
+    [] -> interact f
+    [in1,inp,outp] ->
+          writeBinaryFile outp . g . f =<< readBinaryFile inp
+            where g = (("{-# LINE 2 \""++in1++"\" #-}\n")++)
+    _  -> fail "Usage: frquotes [<orig> <input> <output>]"
+
+main :: IO ()
+main = interact' frQuotes
