diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 itchyny
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/sjsp.cabal b/sjsp.cabal
new file mode 100644
--- /dev/null
+++ b/sjsp.cabal
@@ -0,0 +1,33 @@
+name:                   sjsp
+version:                0.1.0
+author:                 itchyny <https://github.com/itchyny>
+maintainer:             itchyny <https://github.com/itchyny>
+license:                MIT
+license-file:           LICENSE
+category:               Compiler
+build-type:             Simple
+cabal-version:          >=1.6
+synopsis:               Simple JavaScript Profiler
+description:            This is a JavaScript profiler, injecting profiling
+                        codes into your JavaScript files.
+                        See https://github.com/itchyny/sjsp for details.
+
+executable sjsp
+  hs-source-dirs:       src
+  main-is:              Main.hs
+  other-modules:        Injector
+                      , Profiler
+                      , Config
+  build-depends:        base >= 4.0 && < 5
+                      , ghc-prim
+                      , syb >= 0.4
+                      , language-javascript
+                      , bytestring >= 0.10.6
+                      , filepath
+                      , blaze-builder >= 0.3.3
+                      , unordered-containers
+  build-tools:          happy >= 1.19, alex >= 3.1
+
+source-repository head
+  type:                 git
+  location:             git@github.com:itchyny/sjsp.git
diff --git a/src/Config.hs b/src/Config.hs
new file mode 100644
--- /dev/null
+++ b/src/Config.hs
@@ -0,0 +1,35 @@
+module Config where
+
+data Flag = Interval String
+          | Number String
+          | Accurate
+          | Time
+          | Count
+          | Print
+          | Version
+          | Help
+          deriving (Eq, Show)
+
+data Config
+  = Config { interval :: Integer
+           , number :: Integer
+           , accurate :: Bool
+           , time :: Bool
+           , count :: Bool
+           }
+
+isInterval :: Flag -> Bool
+isInterval (Interval _) = True
+isInterval _ = False
+
+getInterval :: Flag -> Integer
+getInterval (Interval x) = read x
+getInterval _ = undefined
+
+isNumber :: Flag -> Bool
+isNumber (Number _) = True
+isNumber _ = False
+
+getNumber :: Flag -> Integer
+getNumber (Number x) = read x
+getNumber _ = undefined
diff --git a/src/Injector.hs b/src/Injector.hs
new file mode 100644
--- /dev/null
+++ b/src/Injector.hs
@@ -0,0 +1,190 @@
+module Injector (inject) where
+
+import Data.Char (isSpace)
+import Data.Generics (everywhere, mkT)
+import Data.List (intersperse)
+import Language.JavaScript.Parser
+
+import Config
+import Profiler
+
+inject :: Config -> String -> [String] -> JSNode -> JSNode
+inject config fname contents = prepend (profiler config)
+                             . everywhere (mkT $ f fname contents)
+
+f :: String -> [String] -> Node -> Node
+-- function test() { body; } -> function test() { start("test"); body; end(); }
+f fname contents (JSFunction fn name lb args rb (NN (JSBlock a b c)))
+  = JSFunction fn name lb args rb
+      $ NN (JSBlock a (start fname contents (getPos fn) (extractName [name]) : b ++ [ jssemicolon, end ]) c)
+-- function() { body; }; -> function() { start("anonymous"); body; end(); };
+f fname contents (JSFunctionExpression fn name lb args rb (NN (JSBlock a b c)))
+  = JSFunctionExpression fn name lb args rb
+      $ NN (JSBlock a (start fname contents (getPos fn) (extractName name) : b ++ [ jssemicolon, end ]) c)
+-- var test = function() { start("anonymous"); body; end(); }; -> function() { start("test"); body; end(); };
+f fname contents
+  (JSVarDecl
+     variable
+     [ equal@(NT (JSLiteral "=") _ _),
+       NN (JSFunctionExpression fn name lb args rb
+             (NN (JSBlock a (NN (JSVariables _ (NN (JSVarDecl name' _):_) _):b) c))) ])
+  | extractName [name'] == identifier "state"
+    = JSVarDecl
+        variable
+        [ equal,
+          NN (JSFunctionExpression fn name lb args rb
+               $ NN (JSBlock a (start fname contents (getPos fn) (extractName [variable])
+                               : b ++ [ jssemicolon ]) c)) ]
+-- var test = function() { body; }; -> function() { start("test"); body; end(); };
+f fname contents
+  (JSVarDecl
+    variable
+    [ equal@(NT (JSLiteral "=") _ _),
+      NN (JSFunctionExpression fn name lb args rb
+            (NN (JSBlock a b c))) ])
+  = JSVarDecl
+      variable
+      [ equal,
+        NN (JSFunctionExpression fn name lb args rb
+           $ NN (JSBlock a (start fname contents (getPos fn) (extractName [variable])
+                           : b ++ [ jssemicolon, end ]) c)) ]
+-- throw expr; -> throw (function(arguments) { var value = expr; end(); return value; }).call(this, arguments);
+f _ _ (JSThrow throw expr)
+  = JSThrow throw
+      $ jscallNoSemicolon
+        (jsmemberdot "call" $
+          jsparen $
+            jsfunction ["arguments"]
+              [ jsvar (identifier "return") [expr],
+                end,
+                jsreturn (jsexpr $ jsidentifier (identifier "return")) ])
+              [jsliteral "this", jsliteralSpace "arguments"]
+-- return; -> return (function() { end(); })();
+f _ _ (JSReturn ret [] _)
+  = JSReturn ret
+      [ jscallNoSemicolon
+        (jsparen $
+            jsfunction [] [ end ]) [] ] jssemicolon
+-- return expr; -> return (function(arguments) { var value = expr; end(); return value; }).call(this, arguments);
+f _ _ (JSReturn ret expr _)
+  = JSReturn ret
+      [ jscallNoSemicolon
+        (jsmemberdot "call" $
+          jsparen $
+            jsfunction ["arguments"]
+              [ jsvar (identifier "return") expr,
+                end,
+                jsreturn (jsexpr $ jsidentifier (identifier "return")) ])
+              [jsliteral "this", jsliteralSpace "arguments"] ] jssemicolon
+f _ _ x = x
+
+identifier :: String -> String
+identifier name = "sjsp__" ++ name
+
+prepend :: String -> JSNode -> JSNode
+prepend code node = NN $ JSExpression [ fromRight $ parse code "", node ]
+
+start :: String -> [String] -> TokenPosn -> String -> JSNode
+start fname contents (TokenPn _ line col) name
+  = jsvar (identifier "state") [ jscallNoSemicolon (jsidentifier (identifier "start"))
+                                 [ jsstring fname,
+                                   jsnumber line,
+                                   jsnumber col,
+                                   jsstring name,
+                                   jsstring (take 200 $ dropWhile isSpace $ drop (col - 120) $ contents !! (line - 1))] ]
+
+end :: JSNode
+end
+  = NN $ JSExpression
+           [ jsidentifier (identifier "end"),
+             NN $ JSArguments (jsliteral "(") [jsidentifierNoSpace (identifier "state")] (jsliteral ")"),
+             jssemicolon ]
+
+extractName :: [JSNode] -> String
+extractName [NT (JSIdentifier name) _ _] = name
+extractName [NN (JSIdentifier name)] = name
+extractName _ = "anonymous"
+
+jsnumber :: Int -> JSNode
+jsnumber n = NT (JSDecimal (show n)) pos []
+
+jsstring :: String -> JSNode
+jsstring xs = NT (JSStringLiteral '"' (tail $ init $ show xs)) pos []
+
+jsfunction :: [String] -> [JSNode] -> JSNode
+jsfunction xs node
+  = NN $ JSFunctionExpression
+           (jsliteral "function")
+           []
+           (jsliteral "(")
+           (jscommas [NT (JSIdentifier x) pos [] | x <- xs ])
+           (jsliteral ")")
+           (NN $ JSBlock [jsliteral "{"] node [jsliteralSpace "}"])
+
+jscommas :: [JSNode] -> [JSNode]
+jscommas = intersperse $ jsliteral ","
+
+jsvar :: String -> [JSNode] -> JSNode
+jsvar name expr
+  = NN $ JSVariables
+           (jsliteralSpace "var")
+           [NN $ JSVarDecl
+                  (NT (JSIdentifier name) pos jsspace)
+                  (if null expr
+                      then []
+                      else [jsliteralSpace "=", NN (JSExpression expr)])]
+           jssemicolon
+
+jsidentifier :: String -> JSNode
+jsidentifier name = NT (JSIdentifier name) pos jsspace
+
+jsidentifierNoSpace :: String -> JSNode
+jsidentifierNoSpace name = NT (JSIdentifier name) pos []
+
+jsexpr :: JSNode -> JSNode
+jsexpr = NN . JSExpression . (:[])
+
+jsparen :: JSNode -> JSNode
+jsparen expr
+  = NN $ JSExpressionParen
+           (jsliteralSpace "(")
+           expr
+           (jsliteral ")")
+
+jscallNoSemicolon :: JSNode -> [JSNode] -> JSNode
+jscallNoSemicolon expr args
+  = NN $ JSExpression
+         [ expr, NN (JSArguments (jsliteral "(") (jscommas args) (jsliteral ")")) ]
+
+jsreturn :: JSNode -> JSNode
+jsreturn expr
+  = NN $ JSReturn (jsliteralSpace "return") [expr] jssemicolon
+
+jsmemberdot :: String -> JSNode -> JSNode
+jsmemberdot name expr
+  = NN $ JSMemberDot [expr]
+         (jsliteral ".")
+         (NT (JSIdentifier name) pos [])
+
+jssemicolon :: JSNode
+jssemicolon = jsliteral ";"
+
+jsliteral :: String -> JSNode
+jsliteral name = NT (JSLiteral name) pos []
+
+jsliteralSpace :: String -> JSNode
+jsliteralSpace name = NT (JSLiteral name) pos jsspace
+
+jsspace :: [CommentAnnotation]
+jsspace = [WhiteSpace pos " "]
+
+getPos :: JSNode -> TokenPosn
+getPos (NT _ p _) = p
+getPos _ = pos
+
+pos :: TokenPosn
+pos = TokenPn 0 0 0
+
+fromRight :: Either a b -> b
+fromRight (Right x) = x
+fromRight _ = undefined
diff --git a/src/Main.hs b/src/Main.hs
new file mode 100644
--- /dev/null
+++ b/src/Main.hs
@@ -0,0 +1,74 @@
+module Main where
+
+import Control.Applicative ((<$>), (<*>))
+import Data.Maybe (fromMaybe, listToMaybe)
+import Data.Version (showVersion)
+import Language.JavaScript.Parser
+import System.Console.GetOpt
+import System.Environment (getArgs)
+import System.FilePath.Posix (replaceExtension, takeFileName)
+import qualified Data.ByteString.Builder as BS
+import qualified Data.ByteString.Lazy.Char8 as BS
+
+import Config
+import Injector (inject)
+import Paths_sjsp (version)
+
+main :: IO ()
+main = command <$> getOpt Permute options =<< getArgs
+
+command :: ([Flag], [String], [String]) -> IO ()
+command (opt, nonopt, err)
+  | Help `elem` opt || null opt && null nonopt && null err = mapM_ putStrLn help
+  | Version `elem` opt = putStrLn info
+  | null err && not (null nonopt) = mapM_ (process opt) nonopt
+  | otherwise = ioError (userError (concat err ++ usageInfo usage options))
+
+process :: [Flag] -> String -> IO ()
+process opt name
+  = output
+  . BS.toLazyByteString
+  . renderJS
+  =<< inject config (takeFileName name) <$> (lines <$> readFile name) <*> parseFile name
+  where output | Print `elem` opt = BS.putStrLn
+               | otherwise = BS.writeFile (replaceExtension name ".sjsp.js")
+        config = Config { interval = get 10 getInterval isInterval opt
+                        , number = get 20 getNumber isNumber opt
+                        , accurate = Accurate `elem` opt
+                        , time = Time `elem` opt || Time `notElem` opt && Count `notElem` opt
+                        , count = Count `elem` opt || Time `notElem` opt && Count `notElem` opt
+                        }
+        get :: a -> (Flag -> a) -> (Flag -> Bool) -> [Flag] -> a
+        get x f g = maybe x f . listToMaybe . reverse . filter g
+
+options :: [OptDescr Flag]
+options =
+  [ Option "i"  ["interval"] (OptArg (Interval . fromMaybe "10") "INTERVAL") "interval time of logging the result in seconds (default 10)"
+  , Option "n"  ["number"]   (OptArg (Number . fromMaybe "20") "NUMBER") "number of the results in the ranking (default 20)"
+  , Option "a"  ["accurate"] (NoArg Accurate) "measure the time in accurate precision using performance.now() (default off)"
+  , Option "t"  ["time"]     (NoArg Time)     "reports the result sorted by consumed time (default on; use only -c/--count to disable this flag)"
+  , Option "c"  ["count"]    (NoArg Count)    "reports the result sorted by the number of times the function is called (default on; use only -t/--time to disable this flag)"
+  , Option "p"  ["print"]    (NoArg Print)    "print out the compiled result to stdout"
+  , Option "vV" ["version"]  (NoArg Version)  "display the version number"
+  , Option "h?" ["help"]     (NoArg Help)     "display this help"
+  ]
+
+info :: String
+info = "sjsp " ++ showVersion version ++ "     Simple JavaScript Profiler"
+
+usage :: String
+usage = "Usage: sjsp [OPTIONS...] file\n"
+
+help :: [String]
+help = [ info
+       , ""
+       , usageInfo usage options
+       , "Example:"
+       , "  sjsp test.js     # generates test.sjsp.js"
+       , ""
+       , ""
+       , "This software is released under the MIT License."
+       , "This software is distributed at https://github.com/itchyny/sjsp."
+       , "Report a bug of this software at https://github.com/itchyny/sjsp/issues."
+       , "The author is itchyny <https://github.com/itchyny>."
+       ]
diff --git a/src/Profiler.hs b/src/Profiler.hs
new file mode 100644
--- /dev/null
+++ b/src/Profiler.hs
@@ -0,0 +1,77 @@
+module Profiler (profiler) where
+
+import Data.Char (isSpace, toLower)
+
+import Config
+
+profiler :: Config -> String
+profiler config = concatMap (dropWhile isSpace)
+ [ ";(function(global) { "
+ , "  var interval = " ++ show (interval config) ++ "; "
+ , "  var number = " ++ show (number config) ++ "; "
+ , "  var accurate = " ++ map toLower (show (accurate config)) ++ "; "
+ , "  var time = " ++ map toLower (show (time config)) ++ "; "
+ , "  var count = " ++ map toLower (show (count config)) ++ "; "
+ , "  global.sjsp__result = global.sjsp__result || {}; "
+ , "  var sjsp__state = global.sjsp__state = { time: 0, count: 0, line: 0, col: 0, name: '', fname: '', linestr: '' }; "
+ , "  var now = accurate ? function() { return performance.now(); } : function() { return Date.now(); }; "
+ , "  global.sjsp__start = function(fname, line, col, name, linestr) { "
+ , "    return { time: now(), line: line, col: col, name: name, fname: fname, linestr: linestr }; "
+ , "  }; "
+ , "  global.sjsp__end = function(x) { "
+ , "    if (!x.time) { "
+ , "      return; "
+ , "    } "
+ , "    var key = [ x.fname, x.line, x.col, x.name ].join('/'); "
+ , "    global.sjsp__result[key] = global.sjsp__result[key] "
+ , "      || { time: 0, count: 0, line: x.line, col: x.col, name: x.name, fname: x.fname, linestr: x.linestr }; "
+ , "    global.sjsp__result[key].time += (now() - x.time); "
+ , "    global.sjsp__result[key].count += 1; "
+ , "  }; "
+ , "  global.sjsp__result_time = global.sjsp__result_time || []; "
+ , "  global.sjsp__result_count = global.sjsp__result_count || []; "
+ , "  if (global.hasOwnProperty('sjsp__interval')) { "
+ , "    return; "
+ , "  } "
+ , "  var space = function(x, n) { "
+ , "    return Array(Math.max(0, n - x.toString().length + 1)).join(' ') + x; "
+ , "  }; "
+ , "  var format = function(x, y) { "
+ , "    return [ 'time: ' + space((x.time / (accurate ? 1 : 1000)).toFixed(3), y.time) + (accurate ? 'm' : '') + 'sec' "
+ , "           , 'count: ' + space(x.count, y.count)"
+ , "           , space(x.name, y.name)"
+ , "           , space(x.fname, y.fname)"
+ , "           , '(line: ' + space(x.line, y.line) + ', col: ' + space(x.col, y.col) + ')' "
+ , "           , x.linestr ].join('  '); }; "
+ , "  var max = function(x) { "
+ , "    return Math.max.apply(null, x); "
+ , "  }; "
+ , "  var lengths = function(result) { "
+ , "    return { "
+ , "      time:  max(result.map(function(x) { return (x.time / (accurate ? 1 : 1000)).toFixed(3).length; })), "
+ , "      count: max(result.map(function(x) { return x.count.toString().length; })), "
+ , "      fname: max(result.map(function(x) { return x.fname.length; })), "
+ , "      name:  max(result.map(function(x) { return x.name.length; })), "
+ , "      line:  max(result.map(function(x) { return x.line.toString().length; })), "
+ , "      col:   max(result.map(function(x) { return x.col.toString().length; })) "
+ , "    }; "
+ , "  }; "
+ , "  var gather = function(sorter) { "
+ , "    var result = "
+ , "      Object.keys(global.sjsp__result)"
+ , "            .map(function(key) { return global.sjsp__result[key]; })"
+ , "            .sort(sorter)"
+ , "            .slice(0, number); "
+ , "    var l = lengths(result); "
+ , "    return result.map(function(x) { return format(x, l); }); "
+ , "  }; "
+ , "  global.sjsp__interval = setInterval(function() { "
+ , "    global.sjsp__result_time = gather(function(x, y) { return y.time - x.time; }); "
+ , "    global.sjsp__result_count = gather(function(x, y) { return y.count - x.count; }); "
+ , "    console.log((time ? '========== SORT BY TIME ==========\\n'"
+ , "                      + global.sjsp__result_time.join('\\n') + (time && count ? '\\n' : '') : '')"
+ , "              + (count ? '========== SORT BY COUNT ==========\\n' "
+ , "                      + global.sjsp__result_count.join('\\n') : '')); "
+ , "  }, interval * 1000); "
+ , "})(typeof window !== 'undefined' ? window : this); "
+ ]
