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 +21/−0
- Setup.hs +2/−0
- sjsp.cabal +33/−0
- src/Config.hs +35/−0
- src/Injector.hs +190/−0
- src/Main.hs +74/−0
- src/Profiler.hs +77/−0
+ 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); "+ ]