packages feed

sjsp (empty) → 0.1.0

raw patch · 7 files changed

+432/−0 lines, 7 filesdep +basedep +blaze-builderdep +bytestringsetup-changed

Dependencies added: base, blaze-builder, bytestring, filepath, ghc-prim, language-javascript, syb, unordered-containers

Files

+ LICENSE view
@@ -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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ sjsp.cabal view
@@ -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
+ src/Config.hs view
@@ -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
+ src/Injector.hs view
@@ -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
+ src/Main.hs view
@@ -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>."+       ]
+ src/Profiler.hs view
@@ -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); "+ ]