├── .gitignore ├── test ├── code.js └── code.jsw ├── scripts ├── run ├── compile └── runw ├── uglify ├── tojsw │ ├── exports.js │ ├── node.js │ ├── transform.js │ ├── utils.js │ ├── scope.js │ └── ast.js └── fromjsw │ ├── exports.js │ ├── node.js │ ├── transform.js │ ├── utils.js │ ├── scope.js │ └── ast.js ├── src ├── utils.coffee ├── args.coffee ├── jsw.coffee └── meta.coffee ├── package.json ├── LICENSE ├── lib ├── utils.js ├── args.js ├── jsw.js └── meta.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | 4 | -------------------------------------------------------------------------------- /test/code.js: -------------------------------------------------------------------------------- 1 | a=1; 2 | b = 2; 3 | a = 1; 4 | c = 5 | 3; 6 | a = 1; -------------------------------------------------------------------------------- /scripts/run: -------------------------------------------------------------------------------- 1 | # -*- grammar-ext: sh -*- 2 | 3 | set -e 4 | scripts/compile 5 | export DEBUG=* 6 | node lib/jsw.js $@ 7 | exit $? -------------------------------------------------------------------------------- /scripts/compile: -------------------------------------------------------------------------------- 1 | # -*- grammar-ext: sh -*- 2 | 3 | set -e 4 | cd ~/dev/apps/jsw 5 | # echo "Compiling JSW" 6 | coffee -co lib src/*.coffee 7 | -------------------------------------------------------------------------------- /scripts/runw: -------------------------------------------------------------------------------- 1 | # -*- grammar-ext: sh -*- 2 | 3 | function block_for_change { 4 | inotifywait -qe modify \ 5 | src/* \ 6 | uglify/tojsw/* \ 7 | uglify/fromjsw/* 8 | } 9 | bash scripts/run $@ 10 | while block_for_change; do 11 | bash scripts/run $@ 12 | done 13 | -------------------------------------------------------------------------------- /test/code.jsw: -------------------------------------------------------------------------------- 1 | a = 1; 2 | b = 2; 3 | a = 1; 4 | c = 3; 5 | a = 1; 6 | 7 | ### metadata to restore jsw to js losslessly (vers 1) ### 8 | #eJxlj0FrwzAMhf9K0DkFSXZsJ6Wn/oTutuxgxQpstElpTRmU/Pd5bC1hO70nwfuedIebXq7QUQ0f1/2c 9 | #FDqIO9r2k1S7iovGotX3Yiimn8wWaji8n85HPeSY9aRT3jixqUmNej+KYlE7UorokzMS0DJ0r3e45njJ 10 | #0GENOiXobA15Pu/zZ2ksSJnzj++nikvPFpb6mSH+DVG7SvUTV/QnamB5+3+f0SDMJopabCUE79E7legS 11 | #kXNphO5Z1TyaaPnPSYkNt0hhEBuIWUxoENWWeYg+xRWHH3+yL6CXuXBuetxYh8OAQqHVZkSHguSSaWO0 12 | #oydq/YqwAixfUyp8nw== 13 | -------------------------------------------------------------------------------- /uglify/tojsw/exports.js: -------------------------------------------------------------------------------- 1 | // exports["Compressor"] = Compressor; 2 | // exports["DefaultsError"] = DefaultsError; 3 | // exports["Dictionary"] = Dictionary; 4 | // exports["JS_Parse_Error"] = JS_Parse_Error; 5 | // exports["MAP"] = MAP; 6 | exports["OutputStream"] = OutputStream; 7 | // exports["SourceMap"] = SourceMap; 8 | exports["TreeTransformer"] = TreeTransformer; 9 | // exports["TreeWalker"] = TreeWalker; 10 | // exports["base54"] = base54; 11 | // exports["defaults"] = defaults; 12 | // exports["mangle_properties"] = mangle_properties; 13 | // exports["merge"] = merge; 14 | exports["parse"] = parse; 15 | // exports["push_uniq"] = push_uniq; 16 | // exports["string_template"] = string_template; 17 | // exports["is_identifier"] = is_identifier; 18 | -------------------------------------------------------------------------------- /uglify/fromjsw/exports.js: -------------------------------------------------------------------------------- 1 | // exports["Compressor"] = Compressor; 2 | // exports["DefaultsError"] = DefaultsError; 3 | // exports["Dictionary"] = Dictionary; 4 | // exports["JS_Parse_Error"] = JS_Parse_Error; 5 | // exports["MAP"] = MAP; 6 | exports["OutputStream"] = OutputStream; 7 | // exports["SourceMap"] = SourceMap; 8 | // exports["TreeTransformer"] = TreeTransformer; 9 | // exports["TreeWalker"] = TreeWalker; 10 | // exports["base54"] = base54; 11 | // exports["defaults"] = defaults; 12 | // exports["mangle_properties"] = mangle_properties; 13 | // exports["merge"] = merge; 14 | exports["parse"] = parse; 15 | // exports["push_uniq"] = push_uniq; 16 | // exports["string_template"] = string_template; 17 | // exports["is_identifier"] = is_identifier; 18 | -------------------------------------------------------------------------------- /src/utils.coffee: -------------------------------------------------------------------------------- 1 | 2 | log = require('debug') 'utils' 3 | fs = require 'fs' 4 | path = require 'path' 5 | UglifyT0 = require '../uglify/tojsw/node' 6 | 7 | exports.checkFileExt = (file, ext) -> 8 | if path.extname(file).toLowerCase() isnt ext 9 | throw "jsw error: #{file} is not a #{ext} file" 10 | fileBase = path.basename file, ext 11 | [path.dirname(file) + '/' + fileBase, fileBase] 12 | 13 | exports.dumpAst = (ast, file) -> 14 | tt = new UglifyT0.TreeTransformer null, (node) -> 15 | node.startPos = node.start.pos 16 | node.endPos = node.end.pos 17 | delete node.start 18 | delete node.end 19 | node.type = node.TYPE 20 | if not node.body? or node.body.length is 0 21 | delete node.body 22 | node 23 | fs.writeFileSync file, JSON.stringify ast.transform tt 24 | ast 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsw", 3 | "version": "0.2.0", 4 | "description": "Alternative Javascript syntax that uses significant whitespace", 5 | "main": "lib/jsw.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/mark-hahn/jsw" 9 | }, 10 | "keywords": [ 11 | "jsw", 12 | "es6", 13 | "javascript", 14 | "coffeescript", 15 | "translator" 16 | ], 17 | "author": "Mark Hahn", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/mark-hahn/jsw/issues" 21 | }, 22 | "scripts": { 23 | "prepublish": "scripts/compile" 24 | }, 25 | "homepage": "https://github.com/mark-hahn/jsw", 26 | "dependencies": { 27 | "argparse": "^1.0.2", 28 | "chalkline": "0.0.4", 29 | "debug": "^2.2.0", 30 | "uglify-js2": "^2.1.11" 31 | }, 32 | "bin": { 33 | "jsw": "scripts/run" 34 | }, 35 | "preferGlobal": "true" 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mark Hahn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.3 2 | (function() { 3 | var UglifyT0, fs, log, path; 4 | 5 | log = require('debug')('utils'); 6 | 7 | fs = require('fs'); 8 | 9 | path = require('path'); 10 | 11 | UglifyT0 = require('../uglify/tojsw/node'); 12 | 13 | exports.checkFileExt = function(file, ext) { 14 | var fileBase; 15 | if (path.extname(file).toLowerCase() !== ext) { 16 | throw "jsw error: " + file + " is not a " + ext + " file"; 17 | } 18 | fileBase = path.basename(file, ext); 19 | return [path.dirname(file) + '/' + fileBase, fileBase]; 20 | }; 21 | 22 | exports.dumpAst = function(ast, file) { 23 | var tt; 24 | tt = new UglifyT0.TreeTransformer(null, function(node) { 25 | node.startPos = node.start.pos; 26 | node.endPos = node.end.pos; 27 | delete node.start; 28 | delete node.end; 29 | node.type = node.TYPE; 30 | if ((node.body == null) || node.body.length === 0) { 31 | delete node.body; 32 | } 33 | return node; 34 | }); 35 | fs.writeFileSync(file, JSON.stringify(ast.transform(tt))); 36 | return ast; 37 | }; 38 | 39 | }).call(this); 40 | -------------------------------------------------------------------------------- /src/args.coffee: -------------------------------------------------------------------------------- 1 | 2 | getArgs = -> 3 | 4 | parser = new (require('argparse').ArgumentParser) 5 | version: JSON.parse(require('fs').readFileSync 'package.json', 'utf8').version 6 | addHelp: true 7 | description: 8 | "A translator for an alternate Javascript syntax that uses significant whitespace. 9 | A .js file is a javascript file up to es6. 10 | A .jsw file is the alternate syntax version. 11 | This utility translates to/from these two types. " 12 | 13 | parser.addArgument ['-t', '--tojsw' ], 14 | nargs: 0 15 | action: 'storeTrue' 16 | help: 'Translate js file to a jsw file.' 17 | 18 | parser.addArgument ['-f', '--fromjsw' ], 19 | nargs: 0 20 | action: 'storeTrue' 21 | help: 'Translate jsw file to a js file.' 22 | 23 | parser.addArgument ['-m', '--map' ], 24 | nargs: 0 25 | action: 'storeTrue' 26 | help: 'Create or use map to enable lossless restore to js.' 27 | 28 | parser.addArgument ['-b', '--beautifyjs' ], 29 | nargs: 0 30 | action: 'storeTrue' 31 | help: 'Beautify a js file into another js file (for testing).' 32 | 33 | parser.addArgument ['files'], 34 | nargs: '*' 35 | help: 'Files to translate.' 36 | 37 | parser.parseArgs() 38 | 39 | module.exports = getArgs() 40 | -------------------------------------------------------------------------------- /lib/args.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.3 2 | (function() { 3 | var getArgs; 4 | 5 | getArgs = function() { 6 | var parser; 7 | parser = new (require('argparse').ArgumentParser)({ 8 | version: JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version, 9 | addHelp: true, 10 | description: "A translator for an alternate Javascript syntax that uses significant whitespace. A .js file is a javascript file up to es6. A .jsw file is the alternate syntax version. This utility translates to/from these two types. " 11 | }); 12 | parser.addArgument(['-t', '--tojsw'], { 13 | nargs: 0, 14 | action: 'storeTrue', 15 | help: 'Translate js file to a jsw file.' 16 | }); 17 | parser.addArgument(['-f', '--fromjsw'], { 18 | nargs: 0, 19 | action: 'storeTrue', 20 | help: 'Translate jsw file to a js file.' 21 | }); 22 | parser.addArgument(['-m', '--map'], { 23 | nargs: 0, 24 | action: 'storeTrue', 25 | help: 'Create or use map to enable lossless restore to js.' 26 | }); 27 | parser.addArgument(['-b', '--beautifyjs'], { 28 | nargs: 0, 29 | action: 'storeTrue', 30 | help: 'Beautify a js file into another js file (for testing).' 31 | }); 32 | parser.addArgument(['files'], { 33 | nargs: '*', 34 | help: 'Files to translate.' 35 | }); 36 | return parser.parseArgs(); 37 | }; 38 | 39 | module.exports = getArgs(); 40 | 41 | }).call(this); 42 | -------------------------------------------------------------------------------- /src/jsw.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | jsw.coffee 3 | A translator for an alternate Javascript syntax that uses significant whitespace 4 | ### 5 | 6 | log = require('debug') 'jsw' 7 | fs = require 'fs' 8 | utils = require './utils' 9 | args = require './args' 10 | UglifyT0 = require '../uglify/tojsw/node' 11 | UglifyFrom = require '../uglify/fromjsw/node' 12 | meta = require './meta' 13 | chlklin = require 'chalkline' 14 | 15 | for file in args.files 16 | chlklin.magenta() 17 | 18 | if args.tojsw 19 | [fileNoExt, fileBase] = utils.checkFileExt file, '.js' 20 | jsCode = fs.readFileSync file, 'utf8' 21 | ast = UglifyT0.parse jsCode 22 | utils.dumpAst ast, 'test/ast-dump.json' 23 | fs.writeFileSync 'test/ast.json', JSON.stringify ast 24 | 25 | opts = beautify:yes, indent_level: 2 26 | if args.map 27 | jswMappings = [] 28 | opts.node_map = add: (node_gen_map) -> jswMappings.push node_gen_map 29 | jswCode = ast.print_to_string opts 30 | metaStr = (if args.map then meta.encode jsCode, jswCode, jswMappings else '') 31 | fs.writeFileSync fileNoExt + '.jsw', jswCode + metaStr 32 | 33 | ## for debug only 34 | if args.beautifyjs 35 | Uglify = require 'uglify-js2' 36 | [fileNoExt, fileBase] = utils.checkFileExt file, '.js' 37 | jsCode = fs.readFileSync file, 'utf8' 38 | ast = Uglify.parse jsCode 39 | utils.dumpAst ast, 'test/ast-dump' + fileBase + '.json' 40 | fs.writeFileSync 'test/b-ast.json', JSON.stringify ast 41 | jsCode = ast.print_to_string beautify:yes 42 | fs.writeFileSync 'test/b-out.js', jsCode 43 | 44 | if args.fromjsw 45 | [fileNoExt, fileBase] = utils.checkFileExt file, '.jsw' 46 | jswCode = fs.readFileSync file, 'utf8' 47 | if args.map 48 | [jswCode, metaObj] = meta.decode jswCode 49 | metaObj.jswCode = jswCode 50 | if not jswCode 51 | throw 'jsw metadata is missing, corrupted, or unknown version' 52 | fs.writeFileSync 'test/meta.json', JSON.stringify metaObj ? {} 53 | 54 | ast = UglifyFrom.parse jswCode 55 | utils.dumpAst ast, 'test/ast-dump.json' 56 | fs.writeFileSync 'test/ast.json', JSON.stringify ast 57 | 58 | opts = beautify:yes, indent_level: 2 59 | if args.map 60 | opts.jsw_out_meta = metaObj 61 | jsCode = ast.print_to_string opts 62 | fs.writeFileSync fileNoExt + '.js', jsCode 63 | 64 | chlklin.blue() 65 | 66 | -------------------------------------------------------------------------------- /lib/jsw.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.3 2 | 3 | /* 4 | jsw.coffee 5 | A translator for an alternate Javascript syntax that uses significant whitespace 6 | */ 7 | 8 | (function() { 9 | var Uglify, UglifyFrom, UglifyT0, args, ast, chlklin, file, fileBase, fileNoExt, fs, i, jsCode, jswCode, jswMappings, len, log, meta, metaObj, metaStr, opts, ref, ref1, ref2, ref3, ref4, utils; 10 | 11 | log = require('debug')('jsw'); 12 | 13 | fs = require('fs'); 14 | 15 | utils = require('./utils'); 16 | 17 | args = require('./args'); 18 | 19 | UglifyT0 = require('../uglify/tojsw/node'); 20 | 21 | UglifyFrom = require('../uglify/fromjsw/node'); 22 | 23 | meta = require('./meta'); 24 | 25 | chlklin = require('chalkline'); 26 | 27 | ref = args.files; 28 | for (i = 0, len = ref.length; i < len; i++) { 29 | file = ref[i]; 30 | chlklin.magenta(); 31 | if (args.tojsw) { 32 | ref1 = utils.checkFileExt(file, '.js'), fileNoExt = ref1[0], fileBase = ref1[1]; 33 | jsCode = fs.readFileSync(file, 'utf8'); 34 | ast = UglifyT0.parse(jsCode); 35 | utils.dumpAst(ast, 'test/ast-dump.json'); 36 | fs.writeFileSync('test/ast.json', JSON.stringify(ast)); 37 | opts = { 38 | beautify: true, 39 | indent_level: 2 40 | }; 41 | if (args.map) { 42 | jswMappings = []; 43 | opts.node_map = { 44 | add: function(node_gen_map) { 45 | return jswMappings.push(node_gen_map); 46 | } 47 | }; 48 | } 49 | jswCode = ast.print_to_string(opts); 50 | metaStr = (args.map ? meta.encode(jsCode, jswCode, jswMappings) : ''); 51 | fs.writeFileSync(fileNoExt + '.jsw', jswCode + metaStr); 52 | } 53 | if (args.beautifyjs) { 54 | Uglify = require('uglify-js2'); 55 | ref2 = utils.checkFileExt(file, '.js'), fileNoExt = ref2[0], fileBase = ref2[1]; 56 | jsCode = fs.readFileSync(file, 'utf8'); 57 | ast = Uglify.parse(jsCode); 58 | utils.dumpAst(ast, 'test/ast-dump' + fileBase + '.json'); 59 | fs.writeFileSync('test/b-ast.json', JSON.stringify(ast)); 60 | jsCode = ast.print_to_string({ 61 | beautify: true 62 | }); 63 | fs.writeFileSync('test/b-out.js', jsCode); 64 | } 65 | if (args.fromjsw) { 66 | ref3 = utils.checkFileExt(file, '.jsw'), fileNoExt = ref3[0], fileBase = ref3[1]; 67 | jswCode = fs.readFileSync(file, 'utf8'); 68 | if (args.map) { 69 | ref4 = meta.decode(jswCode), jswCode = ref4[0], metaObj = ref4[1]; 70 | metaObj.jswCode = jswCode; 71 | if (!jswCode) { 72 | throw 'jsw metadata is missing, corrupted, or unknown version'; 73 | } 74 | fs.writeFileSync('test/meta.json', JSON.stringify(metaObj != null ? metaObj : {})); 75 | } 76 | ast = UglifyFrom.parse(jswCode); 77 | utils.dumpAst(ast, 'test/ast-dump.json'); 78 | fs.writeFileSync('test/ast.json', JSON.stringify(ast)); 79 | opts = { 80 | beautify: true, 81 | indent_level: 2 82 | }; 83 | if (args.map) { 84 | opts.jsw_out_meta = metaObj; 85 | } 86 | jsCode = ast.print_to_string(opts); 87 | fs.writeFileSync(fileNoExt + '.js', jsCode); 88 | } 89 | chlklin.blue(); 90 | } 91 | 92 | }).call(this); 93 | -------------------------------------------------------------------------------- /src/meta.coffee: -------------------------------------------------------------------------------- 1 | 2 | log = require('debug') 'meta' 3 | fs = require 'fs' 4 | crypto = require 'crypto' 5 | zlib = require 'zlib' 6 | 7 | exports.encode = (jsCode, jswCode, jswMappings) -> 8 | 9 | addContexts = (map) -> 10 | addOneCtx = (key, pos, dir) -> 11 | ctx = '' 12 | for i in [1..9e9] 13 | pos += i * dir 14 | if not (chr = jswCode[pos])? then break 15 | ctx += chr 16 | map[key] = ctx 17 | addOneCtx 'topCtx', map.gen_start_pos, -1 18 | addOneCtx 'botCtx', map.gen_end_pos-1, +1 19 | map 20 | 21 | meta = {vers: 1, jsCode} 22 | for jswMap in jswMappings then do -> 23 | {type, orig_start_pos, orig_end_pos, gen_start_pos, gen_end_pos} = jswMap 24 | hash = crypto.createHash 'md5' 25 | hash.update jswCode[gen_start_pos...gen_end_pos] 26 | key = type + '-' + hash.digest 'hex' 27 | metaMap = {start: orig_start_pos, end: orig_end_pos, gen_start_pos, gen_end_pos} 28 | if not (val = meta[key]) 29 | meta[key] = metaMap 30 | else 31 | if not Array.isArray val 32 | meta[key] = [ addContexts val ] 33 | meta[key].push addContexts metaMap 34 | for key, val of meta 35 | if not Array.isArray val 36 | delete val.gen_start_pos 37 | delete val.gen_end_pos 38 | else 39 | for val2 in val 40 | delete val2.gen_start_pos 41 | delete val2.gen_end_pos 42 | 43 | metaJson = JSON.stringify meta 44 | fs.writeFileSync 'test/meta.json', metaJson 45 | 46 | base64 = zlib.deflateSync(metaJson).toString 'base64' 47 | metaLines = '' 48 | for idx in [0..9e9] by 80 when idx < base64.length 49 | metaLines += '#' + base64[idx...idx+80] + '\n' 50 | '\n\n### metadata to restore jsw to js losslessly (vers 1) ###\n' + metaLines 51 | 52 | lookup = (node) -> 53 | chkSpan = (ofs, dir, ctx, start) => 54 | log 'chkSpan1', {ofs, dir, ctx, start} 55 | for i in [0...ctx.length] 56 | ofs += i * dir 57 | if ctx[i] isnt @jswCode[start + ofs] then break 58 | if i is ctx.length 59 | ofs += i * dir 60 | if not @jswCode[start + ofs] 61 | log 'chkSpan2', 62 | {i, start, ofs, chr: @jswCode[start + ofs], @jswCode} 63 | return yes 64 | log 'chkSpan3', {i, clen: ctx.length} 65 | return i 66 | 67 | hash = crypto.createHash 'md5' 68 | hash.update @jswCode[node.start.pos...node.end.endpos] 69 | key = node.TYPE + '-' + hash.digest 'hex' 70 | log 'key', key, @[key] 71 | if not (metaMap = @[key]) then return null 72 | if not Array.isArray metaMap 73 | log 'not Array.isArray', @jsCode[metaMap.start...metaMap.end] 74 | return @jsCode[metaMap.start...metaMap.end] 75 | bestMap = null 76 | bestSpan = -1 77 | for map in metaMap 78 | log 'map', map 79 | {start, end, topCtx, botCtx} = map 80 | 81 | spanTop = chkSpan -1, -1, topCtx, node.start.pos 82 | if spanTop is yes then return @jsCode[start...end] 83 | 84 | spanBot = chkSpan 0, 1, botCtx, node.end.endpos 85 | if spanBot is yes then return @jsCode[start...end] 86 | 87 | if spanTop + spanBot > bestSpan 88 | bestSpan = spanTop + spanBot 89 | bestMap = map 90 | log 'bestMap', bestMap 91 | return if bestMap then @jsCode[bestMap.start...bestMap.end] 92 | 93 | exports.decode = (jswCode, metaText) -> 94 | try 95 | if not metaText 96 | if not (match = 97 | /([\s\S]*)###\smetadata\sto\srestore\sjsw\sto\sjs.*\s(\d+)\)([\s\S]*)/ 98 | .exec jswCode) or +match[2] isnt 1 99 | return [] 100 | metaText = match[3] 101 | base64 = metaText.replace /[\s\n\r#]/g, '' 102 | meta = JSON.parse zlib.inflateSync(new Buffer base64, 'base64').toString() 103 | meta.lookup = lookup 104 | [match[1], meta] 105 | catch e 106 | log e 107 | [] 108 | 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSW 2 | 3 | An alternate version of Javascript syntax that uses significant whitespace 4 | 5 | JSW (Javascript with Significant Whitespace) is a syntax for Javascript, and especially ES6, that allows editing real JS with Coffeescript-like syntax. 6 | 7 | The utility in this repository is a bi-directional translator between JS and JSW. 8 | 9 | Unlike Coffeescript, JSW does not provide a new language with differing semantics. It is a thin skin over Javascript that only changes the syntax. This JSW utility is a syntax translator, not a compiler or transpiler. 10 | 11 | JSW is a great way for Coffeescript users to migrate to real Javascript with ES6. 12 | 13 | ### Sample JSW with translated Javascript 14 | 15 | ```coffee 16 | # JSW 17 | -> func1 arg1, arg2 18 | var hash1 = key1: val1 19 | key2: val2 20 | key3: 21 | block 22 | let x = y 23 | func2 x 24 | if q and z 25 | func1 "This is text spread 26 | over two lines." 27 | ``` 28 | ```javascript 29 | // Javascript 30 | function func1 (arg1, arg2) { 31 | var hash1 = { 32 | key1: val1, 33 | key2: val2, 34 | key3: key3 35 | } 36 | { 37 | let x = y; 38 | func2(x); 39 | if (q && z) { 40 | func1("This is text spread " + 41 | "over two lines."); 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ### Installation 48 | 49 | This is not published in npm yet but installation is easy. Just clone this repo and run `npm install` in the repo folder. 50 | 51 | ### Usage 52 | 53 | From the repo folder, run `scripts/run -tm test.js` to translate `test.js` *to* `test.jsw` with a map. Run `scripts/run -fm test.js` to translate *from* the new test.jsw to test.js. Run `scripts/run -h` to see all run options. 54 | 55 | ### JSW Overall Features 56 | 57 | - JSW syntax is much less noisy than the JS syntax, just like Coffeescript. No more unnecessary parens and braces. No typing of `function`. 58 | 59 | - Some syntax features, like simple line continuations and blocks, are an improvement over Coffeescript. 60 | 61 | - The translation of JSW to/from JS is one-to-one and reversible. Changing JS to JSW and back preserves the original text. This means that JS commits to GIT will have minimal lines changed, only where JSW changed. 62 | 63 | - The JSW utility in this repository includes translation to/from JS and JSW. Both directions are equally supported. 64 | 65 | - The JS produced by translating JSW is easily predictable. It consists of simple short mappings like `function` to `->`. Writing JSW and debugging with the translated JS takes no mental effort. 66 | 67 | - JSW takes advantage of new ES6 features. The syntax `->` is a replacement for `function` while ES6 provides `=>` with no change in JSW. The combination of the two matches coffescript. 68 | 69 | - You can easily convert from Coffeescript to JSW by first converting to JS and then to JSW. 70 | 71 | - Coffeescript highlighting works pretty well with JSW (see sample above) 72 | 73 | - An Atom editor extension, coffee-js-translator, is planned that allows opening normal JS, editing in JSW, and saving in the original JS. This allows working with others who don't even know about JSW. One does not even need to have JSW files. 74 | 75 | ### JSW features not in Coffeescript 76 | 77 | - Most of ES6 78 | - Simple continuations that can break anywhere, including strings 79 | - Blocks 80 | - No hashes require braces 81 | 82 | ### Coffeescript features not in JSW 83 | 84 | - Some ES6 replacements not as complete (e.g. Classes) 85 | - All statements are expressions 86 | - Ranges 87 | - `this` reference in params and object declrations 88 | - Matching ES6 features often have different syntax (e.g. string interpolation) 89 | - Combined comparison (0 < x < 1) 90 | 91 | ### JSW to JS Text Mapping 92 | 93 | When JS is converted to JSW, a compact set of metadata is added to the bottom of JSW as a comment. This is very similar to how Coffeescript provides a source map. If support for exact JS text matching is not required this can be disabled. In general it can be ignored and the Atom package JSW will hide that metadata when editing. 94 | 95 | ### Status is Pre-Alpha 96 | 97 | Not useful yet. The round-trip translation works with format preservation. But the only language translation so far is `->` to/from `function`. 98 | 99 | The only thing left to do is hack the parser/generator to finish the new syntax. I use the term "only" liberally (grin). 100 | 101 | ### Helping Development 102 | 103 | Clone this repo and run `npm install` in the repo folder. Edit the code in the `src` or `uglify` folder. When you run `scripts/run` using the Usage instructions above it will automatically compile the coffeescript to javascript before running. This is obviously temporary until the pre-publish build process is added. 104 | 105 | Use `scripts/runw` (run and watch) instead of `scripts/run` and the test will be run again whenever and source file changes. 106 | 107 | ### Worried about developing in Coffeescript? 108 | 109 | Don't be! The whole point of this project is to allow you to work in either coffeescript-like syntax or in real Javascript. As soon as this utility is mature, it will be used to change this project to pure Javascript. Everyone will then be able to work on it the way they want. 110 | 111 | ### License 112 | 113 | Copyright by Mark Hahn using the MIT license. 114 | 115 | -------------------------------------------------------------------------------- /lib/meta.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.9.3 2 | (function() { 3 | var crypto, fs, log, lookup, zlib; 4 | 5 | log = require('debug')('meta'); 6 | 7 | fs = require('fs'); 8 | 9 | crypto = require('crypto'); 10 | 11 | zlib = require('zlib'); 12 | 13 | exports.encode = function(jsCode, jswCode, jswMappings) { 14 | var addContexts, base64, fn, idx, j, jswMap, k, key, l, len, len1, meta, metaJson, metaLines, val, val2; 15 | addContexts = function(map) { 16 | var addOneCtx; 17 | addOneCtx = function(key, pos, dir) { 18 | var chr, ctx, i, j; 19 | ctx = ''; 20 | for (i = j = 1; j <= 9000000000; i = ++j) { 21 | pos += i * dir; 22 | if ((chr = jswCode[pos]) == null) { 23 | break; 24 | } 25 | ctx += chr; 26 | } 27 | return map[key] = ctx; 28 | }; 29 | addOneCtx('topCtx', map.gen_start_pos, -1); 30 | addOneCtx('botCtx', map.gen_end_pos - 1, +1); 31 | return map; 32 | }; 33 | meta = { 34 | vers: 1, 35 | jsCode: jsCode 36 | }; 37 | fn = function() { 38 | var gen_end_pos, gen_start_pos, hash, key, metaMap, orig_end_pos, orig_start_pos, type, val; 39 | type = jswMap.type, orig_start_pos = jswMap.orig_start_pos, orig_end_pos = jswMap.orig_end_pos, gen_start_pos = jswMap.gen_start_pos, gen_end_pos = jswMap.gen_end_pos; 40 | hash = crypto.createHash('md5'); 41 | hash.update(jswCode.slice(gen_start_pos, gen_end_pos)); 42 | key = type + '-' + hash.digest('hex'); 43 | metaMap = { 44 | start: orig_start_pos, 45 | end: orig_end_pos, 46 | gen_start_pos: gen_start_pos, 47 | gen_end_pos: gen_end_pos 48 | }; 49 | if (!(val = meta[key])) { 50 | return meta[key] = metaMap; 51 | } else { 52 | if (!Array.isArray(val)) { 53 | meta[key] = [addContexts(val)]; 54 | } 55 | return meta[key].push(addContexts(metaMap)); 56 | } 57 | }; 58 | for (j = 0, len = jswMappings.length; j < len; j++) { 59 | jswMap = jswMappings[j]; 60 | fn(); 61 | } 62 | for (key in meta) { 63 | val = meta[key]; 64 | if (!Array.isArray(val)) { 65 | delete val.gen_start_pos; 66 | delete val.gen_end_pos; 67 | } else { 68 | for (k = 0, len1 = val.length; k < len1; k++) { 69 | val2 = val[k]; 70 | delete val2.gen_start_pos; 71 | delete val2.gen_end_pos; 72 | } 73 | } 74 | } 75 | metaJson = JSON.stringify(meta); 76 | fs.writeFileSync('test/meta.json', metaJson); 77 | base64 = zlib.deflateSync(metaJson).toString('base64'); 78 | metaLines = ''; 79 | for (idx = l = 0; l <= 9e9; idx = l += 80) { 80 | if (idx < base64.length) { 81 | metaLines += '#' + base64.slice(idx, idx + 80) + '\n'; 82 | } 83 | } 84 | return '\n\n### metadata to restore jsw to js losslessly (vers 1) ###\n' + metaLines; 85 | }; 86 | 87 | lookup = function(node) { 88 | var bestMap, bestSpan, botCtx, chkSpan, end, hash, j, key, len, map, metaMap, spanBot, spanTop, start, topCtx; 89 | chkSpan = (function(_this) { 90 | return function(ofs, dir, ctx, start) { 91 | var i, j, ref; 92 | log('chkSpan1', { 93 | ofs: ofs, 94 | dir: dir, 95 | ctx: ctx, 96 | start: start 97 | }); 98 | for (i = j = 0, ref = ctx.length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { 99 | ofs += i * dir; 100 | if (ctx[i] !== _this.jswCode[start + ofs]) { 101 | break; 102 | } 103 | } 104 | if (i === ctx.length) { 105 | ofs += i * dir; 106 | if (!_this.jswCode[start + ofs]) { 107 | log('chkSpan2', { 108 | i: i, 109 | start: start, 110 | ofs: ofs, 111 | chr: _this.jswCode[start + ofs], 112 | jswCode: _this.jswCode 113 | }); 114 | return true; 115 | } 116 | } 117 | log('chkSpan3', { 118 | i: i, 119 | clen: ctx.length 120 | }); 121 | return i; 122 | }; 123 | })(this); 124 | hash = crypto.createHash('md5'); 125 | hash.update(this.jswCode.slice(node.start.pos, node.end.endpos)); 126 | key = node.TYPE + '-' + hash.digest('hex'); 127 | log('key', key, this[key]); 128 | if (!(metaMap = this[key])) { 129 | return null; 130 | } 131 | if (!Array.isArray(metaMap)) { 132 | log('not Array.isArray', this.jsCode.slice(metaMap.start, metaMap.end)); 133 | return this.jsCode.slice(metaMap.start, metaMap.end); 134 | } 135 | bestMap = null; 136 | bestSpan = -1; 137 | for (j = 0, len = metaMap.length; j < len; j++) { 138 | map = metaMap[j]; 139 | log('map', map); 140 | start = map.start, end = map.end, topCtx = map.topCtx, botCtx = map.botCtx; 141 | spanTop = chkSpan(-1, -1, topCtx, node.start.pos); 142 | if (spanTop === true) { 143 | return this.jsCode.slice(start, end); 144 | } 145 | spanBot = chkSpan(0, 1, botCtx, node.end.endpos); 146 | if (spanBot === true) { 147 | return this.jsCode.slice(start, end); 148 | } 149 | if (spanTop + spanBot > bestSpan) { 150 | bestSpan = spanTop + spanBot; 151 | bestMap = map; 152 | } 153 | } 154 | log('bestMap', bestMap); 155 | if (bestMap) { 156 | return this.jsCode.slice(bestMap.start, bestMap.end); 157 | } 158 | }; 159 | 160 | exports.decode = function(jswCode, metaText) { 161 | var base64, e, match, meta; 162 | try { 163 | if (!metaText) { 164 | if (!(match = /([\s\S]*)###\smetadata\sto\srestore\sjsw\sto\sjs.*\s(\d+)\)([\s\S]*)/.exec(jswCode)) || +match[2] !== 1) { 165 | return []; 166 | } 167 | metaText = match[3]; 168 | } 169 | base64 = metaText.replace(/[\s\n\r#]/g, ''); 170 | meta = JSON.parse(zlib.inflateSync(new Buffer(base64, 'base64')).toString()); 171 | meta.lookup = lookup; 172 | return [match[1], meta]; 173 | } catch (_error) { 174 | e = _error; 175 | log(e); 176 | return []; 177 | } 178 | }; 179 | 180 | }).call(this); 181 | -------------------------------------------------------------------------------- /uglify/tojsw/node.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var fs = require("fs"); 3 | 4 | var FILES = exports.FILES = [ 5 | "./utils.js", 6 | "./ast.js", 7 | "./parse.js", 8 | "./transform.js", 9 | "./scope.js", 10 | "./output.js", 11 | // "./compress.js", 12 | // "./sourcemap.js", 13 | // "./mozilla-ast.js", 14 | // "./propmangle.js", 15 | "./exports.js", 16 | ].map(function(file){ 17 | return fs.realpathSync(path.join(path.dirname(__filename), file)); 18 | }); 19 | 20 | var UglifyJS = exports; 21 | 22 | new Function("exports", FILES.map(function(file){ 23 | return fs.readFileSync(file, "utf8"); 24 | }).join("\n\n"))( UglifyJS ); 25 | 26 | UglifyJS.AST_Node.warn_function = function(txt) { 27 | console.error("WARN: %s", txt); 28 | }; 29 | 30 | exports.minify = function(files, options) { 31 | options = UglifyJS.defaults(options, { 32 | spidermonkey : false, 33 | outSourceMap : null, 34 | sourceRoot : null, 35 | inSourceMap : null, 36 | fromString : false, 37 | warnings : false, 38 | mangle : {}, 39 | output : null, 40 | compress : {} 41 | }); 42 | UglifyJS.base54.reset(); 43 | 44 | // 1. parse 45 | var haveScope = false; 46 | var toplevel = null, 47 | sourcesContent = {}; 48 | 49 | if (options.spidermonkey) { 50 | toplevel = UglifyJS.AST_Node.from_mozilla_ast(files); 51 | } else { 52 | if (typeof files == "string") 53 | files = [ files ]; 54 | files.forEach(function(file, i){ 55 | var code = options.fromString 56 | ? file 57 | : fs.readFileSync(file, "utf8"); 58 | sourcesContent[file] = code; 59 | toplevel = UglifyJS.parse(code, { 60 | filename: options.fromString ? i : file, 61 | toplevel: toplevel 62 | }); 63 | }); 64 | } 65 | if (options.wrap) { 66 | toplevel = toplevel.wrap_commonjs(options.wrap, options.exportAll); 67 | } 68 | 69 | // 2. compress 70 | if (options.compress) { 71 | var compress = { warnings: options.warnings }; 72 | UglifyJS.merge(compress, options.compress); 73 | toplevel.figure_out_scope(); 74 | haveScope = true; 75 | var sq = UglifyJS.Compressor(compress); 76 | toplevel = toplevel.transform(sq); 77 | } 78 | 79 | // 3. mangle 80 | if (options.mangle) { 81 | toplevel.figure_out_scope(options.mangle); 82 | haveScope = true; 83 | toplevel.compute_char_frequency(options.mangle); 84 | toplevel.mangle_names(options.mangle); 85 | } 86 | 87 | // 4. scope (if needed) 88 | if (!haveScope) { 89 | toplevel.figure_out_scope(); 90 | } 91 | 92 | // 5. output 93 | var inMap = options.inSourceMap; 94 | var output = {}; 95 | if (typeof options.inSourceMap == "string") { 96 | inMap = fs.readFileSync(options.inSourceMap, "utf8"); 97 | } 98 | if (options.outSourceMap) { 99 | output.source_map = UglifyJS.SourceMap({ 100 | file: options.outSourceMap, 101 | orig: inMap, 102 | root: options.sourceRoot 103 | }); 104 | if (options.sourceMapIncludeSources) { 105 | for (var file in sourcesContent) { 106 | if (sourcesContent.hasOwnProperty(file)) { 107 | output.source_map.get().setSourceContent(file, sourcesContent[file]); 108 | } 109 | } 110 | } 111 | 112 | } 113 | if (options.output) { 114 | UglifyJS.merge(output, options.output); 115 | } 116 | var stream = UglifyJS.OutputStream(output); 117 | toplevel.print(stream); 118 | 119 | if (options.outSourceMap && "string" === typeof options.outSourceMap) { 120 | stream += "\n//# sourceMappingURL=" + options.outSourceMap; 121 | } 122 | 123 | var source_map = output.source_map; 124 | if (source_map) { 125 | source_map = source_map + ""; 126 | } 127 | 128 | return { 129 | code : stream + "", 130 | map : source_map 131 | }; 132 | }; 133 | 134 | // exports.describe_ast = function() { 135 | // function doitem(ctor) { 136 | // var sub = {}; 137 | // ctor.SUBCLASSES.forEach(function(ctor){ 138 | // sub[ctor.TYPE] = doitem(ctor); 139 | // }); 140 | // var ret = {}; 141 | // if (ctor.SELF_PROPS.length > 0) ret.props = ctor.SELF_PROPS; 142 | // if (ctor.SUBCLASSES.length > 0) ret.sub = sub; 143 | // return ret; 144 | // } 145 | // return doitem(UglifyJS.AST_Node).sub; 146 | // } 147 | 148 | exports.describe_ast = function() { 149 | var out = UglifyJS.OutputStream({ beautify: true }); 150 | function doitem(ctor) { 151 | out.print("AST_" + ctor.TYPE); 152 | var props = ctor.SELF_PROPS.filter(function(prop){ 153 | return !/^\$/.test(prop); 154 | }); 155 | if (props.length > 0) { 156 | out.space(); 157 | out.with_parens(function(){ 158 | props.forEach(function(prop, i){ 159 | if (i) out.space(); 160 | out.print(prop); 161 | }); 162 | }); 163 | } 164 | if (ctor.documentation) { 165 | out.space(); 166 | out.print_string(ctor.documentation); 167 | } 168 | if (ctor.SUBCLASSES.length > 0) { 169 | out.space(); 170 | out.with_block(function(){ 171 | ctor.SUBCLASSES.forEach(function(ctor, i){ 172 | out.indent(); 173 | doitem(ctor); 174 | out.newline(); 175 | }); 176 | }); 177 | } 178 | }; 179 | doitem(UglifyJS.AST_Node); 180 | return out + ""; 181 | }; 182 | 183 | function readReservedFile(filename, reserved) { 184 | if (!reserved) { 185 | reserved = { vars: [], props: [] }; 186 | } 187 | var data = fs.readFileSync(filename, "utf8"); 188 | data = JSON.parse(data); 189 | if (data.vars) { 190 | data.vars.forEach(function(name){ 191 | UglifyJS.push_uniq(reserved.vars, name); 192 | }); 193 | } 194 | if (data.props) { 195 | data.props.forEach(function(name){ 196 | UglifyJS.push_uniq(reserved.props, name); 197 | }); 198 | } 199 | return reserved; 200 | } 201 | 202 | exports.readReservedFile = readReservedFile; 203 | 204 | exports.readDefaultReservedFile = function(reserved) { 205 | return readReservedFile(path.join(__dirname, "domprops.json"), reserved); 206 | }; 207 | 208 | exports.readNameCache = function(filename, key) { 209 | var cache = null; 210 | if (filename) { 211 | try { 212 | var cache = fs.readFileSync(filename, "utf8"); 213 | cache = JSON.parse(cache)[key]; 214 | if (!cache) throw "init"; 215 | cache.props = UglifyJS.Dictionary.fromObject(cache.props); 216 | } catch(ex) { 217 | cache = { 218 | cname: -1, 219 | props: new UglifyJS.Dictionary() 220 | }; 221 | } 222 | } 223 | return cache; 224 | }; 225 | 226 | exports.writeNameCache = function(filename, key, cache) { 227 | if (filename) { 228 | var data; 229 | try { 230 | data = fs.readFileSync(filename, "utf8"); 231 | data = JSON.parse(data); 232 | } catch(ex) { 233 | data = {}; 234 | } 235 | data[key] = { 236 | cname: cache.cname, 237 | props: cache.props.toObject() 238 | }; 239 | fs.writeFileSync(filename, JSON.stringify(data, null, 2), "utf8"); 240 | } 241 | }; 242 | -------------------------------------------------------------------------------- /uglify/fromjsw/node.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var fs = require("fs"); 3 | 4 | var FILES = exports.FILES = [ 5 | "./utils.js", 6 | "./ast.js", 7 | "./parse.js", 8 | "./transform.js", 9 | "./scope.js", 10 | "./output.js", 11 | // "./compress.js", 12 | // "./sourcemap.js", 13 | // "./mozilla-ast.js", 14 | // "./propmangle.js", 15 | "./exports.js", 16 | ].map(function(file){ 17 | return fs.realpathSync(path.join(path.dirname(__filename), file)); 18 | }); 19 | 20 | var UglifyJS = exports; 21 | 22 | new Function("exports", FILES.map(function(file){ 23 | return fs.readFileSync(file, "utf8"); 24 | }).join("\n\n"))( UglifyJS ); 25 | 26 | UglifyJS.AST_Node.warn_function = function(txt) { 27 | console.error("WARN: %s", txt); 28 | }; 29 | 30 | exports.minify = function(files, options) { 31 | options = UglifyJS.defaults(options, { 32 | spidermonkey : false, 33 | outSourceMap : null, 34 | sourceRoot : null, 35 | inSourceMap : null, 36 | fromString : false, 37 | warnings : false, 38 | mangle : {}, 39 | output : null, 40 | compress : {} 41 | }); 42 | UglifyJS.base54.reset(); 43 | 44 | // 1. parse 45 | var haveScope = false; 46 | var toplevel = null, 47 | sourcesContent = {}; 48 | 49 | if (options.spidermonkey) { 50 | toplevel = UglifyJS.AST_Node.from_mozilla_ast(files); 51 | } else { 52 | if (typeof files == "string") 53 | files = [ files ]; 54 | files.forEach(function(file, i){ 55 | var code = options.fromString 56 | ? file 57 | : fs.readFileSync(file, "utf8"); 58 | sourcesContent[file] = code; 59 | toplevel = UglifyJS.parse(code, { 60 | filename: options.fromString ? i : file, 61 | toplevel: toplevel 62 | }); 63 | }); 64 | } 65 | if (options.wrap) { 66 | toplevel = toplevel.wrap_commonjs(options.wrap, options.exportAll); 67 | } 68 | 69 | // 2. compress 70 | if (options.compress) { 71 | var compress = { warnings: options.warnings }; 72 | UglifyJS.merge(compress, options.compress); 73 | toplevel.figure_out_scope(); 74 | haveScope = true; 75 | var sq = UglifyJS.Compressor(compress); 76 | toplevel = toplevel.transform(sq); 77 | } 78 | 79 | // 3. mangle 80 | if (options.mangle) { 81 | toplevel.figure_out_scope(options.mangle); 82 | haveScope = true; 83 | toplevel.compute_char_frequency(options.mangle); 84 | toplevel.mangle_names(options.mangle); 85 | } 86 | 87 | // 4. scope (if needed) 88 | if (!haveScope) { 89 | toplevel.figure_out_scope(); 90 | } 91 | 92 | // 5. output 93 | var inMap = options.inSourceMap; 94 | var output = {}; 95 | if (typeof options.inSourceMap == "string") { 96 | inMap = fs.readFileSync(options.inSourceMap, "utf8"); 97 | } 98 | if (options.outSourceMap) { 99 | output.source_map = UglifyJS.SourceMap({ 100 | file: options.outSourceMap, 101 | orig: inMap, 102 | root: options.sourceRoot 103 | }); 104 | if (options.sourceMapIncludeSources) { 105 | for (var file in sourcesContent) { 106 | if (sourcesContent.hasOwnProperty(file)) { 107 | output.source_map.get().setSourceContent(file, sourcesContent[file]); 108 | } 109 | } 110 | } 111 | 112 | } 113 | if (options.output) { 114 | UglifyJS.merge(output, options.output); 115 | } 116 | var stream = UglifyJS.OutputStream(output); 117 | toplevel.print(stream); 118 | 119 | if (options.outSourceMap && "string" === typeof options.outSourceMap) { 120 | stream += "\n//# sourceMappingURL=" + options.outSourceMap; 121 | } 122 | 123 | var source_map = output.source_map; 124 | if (source_map) { 125 | source_map = source_map + ""; 126 | } 127 | 128 | return { 129 | code : stream + "", 130 | map : source_map 131 | }; 132 | }; 133 | 134 | // exports.describe_ast = function() { 135 | // function doitem(ctor) { 136 | // var sub = {}; 137 | // ctor.SUBCLASSES.forEach(function(ctor){ 138 | // sub[ctor.TYPE] = doitem(ctor); 139 | // }); 140 | // var ret = {}; 141 | // if (ctor.SELF_PROPS.length > 0) ret.props = ctor.SELF_PROPS; 142 | // if (ctor.SUBCLASSES.length > 0) ret.sub = sub; 143 | // return ret; 144 | // } 145 | // return doitem(UglifyJS.AST_Node).sub; 146 | // } 147 | 148 | exports.describe_ast = function() { 149 | var out = UglifyJS.OutputStream({ beautify: true }); 150 | function doitem(ctor) { 151 | out.print("AST_" + ctor.TYPE); 152 | var props = ctor.SELF_PROPS.filter(function(prop){ 153 | return !/^\$/.test(prop); 154 | }); 155 | if (props.length > 0) { 156 | out.space(); 157 | out.with_parens(function(){ 158 | props.forEach(function(prop, i){ 159 | if (i) out.space(); 160 | out.print(prop); 161 | }); 162 | }); 163 | } 164 | if (ctor.documentation) { 165 | out.space(); 166 | out.print_string(ctor.documentation); 167 | } 168 | if (ctor.SUBCLASSES.length > 0) { 169 | out.space(); 170 | out.with_block(function(){ 171 | ctor.SUBCLASSES.forEach(function(ctor, i){ 172 | out.indent(); 173 | doitem(ctor); 174 | out.newline(); 175 | }); 176 | }); 177 | } 178 | }; 179 | doitem(UglifyJS.AST_Node); 180 | return out + ""; 181 | }; 182 | 183 | function readReservedFile(filename, reserved) { 184 | if (!reserved) { 185 | reserved = { vars: [], props: [] }; 186 | } 187 | var data = fs.readFileSync(filename, "utf8"); 188 | data = JSON.parse(data); 189 | if (data.vars) { 190 | data.vars.forEach(function(name){ 191 | UglifyJS.push_uniq(reserved.vars, name); 192 | }); 193 | } 194 | if (data.props) { 195 | data.props.forEach(function(name){ 196 | UglifyJS.push_uniq(reserved.props, name); 197 | }); 198 | } 199 | return reserved; 200 | } 201 | 202 | exports.readReservedFile = readReservedFile; 203 | 204 | exports.readDefaultReservedFile = function(reserved) { 205 | return readReservedFile(path.join(__dirname, "domprops.json"), reserved); 206 | }; 207 | 208 | exports.readNameCache = function(filename, key) { 209 | var cache = null; 210 | if (filename) { 211 | try { 212 | var cache = fs.readFileSync(filename, "utf8"); 213 | cache = JSON.parse(cache)[key]; 214 | if (!cache) throw "init"; 215 | cache.props = UglifyJS.Dictionary.fromObject(cache.props); 216 | } catch(ex) { 217 | cache = { 218 | cname: -1, 219 | props: new UglifyJS.Dictionary() 220 | }; 221 | } 222 | } 223 | return cache; 224 | }; 225 | 226 | exports.writeNameCache = function(filename, key, cache) { 227 | if (filename) { 228 | var data; 229 | try { 230 | data = fs.readFileSync(filename, "utf8"); 231 | data = JSON.parse(data); 232 | } catch(ex) { 233 | data = {}; 234 | } 235 | data[key] = { 236 | cname: cache.cname, 237 | props: cache.props.toObject() 238 | }; 239 | fs.writeFileSync(filename, JSON.stringify(data, null, 2), "utf8"); 240 | } 241 | }; 242 | -------------------------------------------------------------------------------- /uglify/tojsw/transform.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | // Tree transformer helpers. 47 | 48 | function TreeTransformer(before, after) { 49 | TreeWalker.call(this); 50 | this.before = before; 51 | this.after = after; 52 | } 53 | TreeTransformer.prototype = new TreeWalker; 54 | 55 | (function(undefined){ 56 | 57 | function _(node, descend) { 58 | node.DEFMETHOD("transform", function(tw, in_list){ 59 | var x, y; 60 | tw.push(this); 61 | if (tw.before) x = tw.before(this, descend, in_list); 62 | if (x === undefined) { 63 | if (!tw.after) { 64 | x = this; 65 | descend(x, tw); 66 | } else { 67 | tw.stack[tw.stack.length - 1] = x = this.clone(); 68 | descend(x, tw); 69 | y = tw.after(x, in_list); 70 | if (y !== undefined) x = y; 71 | } 72 | } 73 | tw.pop(); 74 | return x; 75 | }); 76 | }; 77 | 78 | function do_list(list, tw) { 79 | return MAP(list, function(node){ 80 | return node.transform(tw, true); 81 | }); 82 | }; 83 | 84 | _(AST_Node, noop); 85 | 86 | _(AST_LabeledStatement, function(self, tw){ 87 | self.label = self.label.transform(tw); 88 | self.body = self.body.transform(tw); 89 | }); 90 | 91 | _(AST_SimpleStatement, function(self, tw){ 92 | self.body = self.body.transform(tw); 93 | }); 94 | 95 | _(AST_Block, function(self, tw){ 96 | self.body = do_list(self.body, tw); 97 | }); 98 | 99 | _(AST_DWLoop, function(self, tw){ 100 | self.condition = self.condition.transform(tw); 101 | self.body = self.body.transform(tw); 102 | }); 103 | 104 | _(AST_For, function(self, tw){ 105 | if (self.init) self.init = self.init.transform(tw); 106 | if (self.condition) self.condition = self.condition.transform(tw); 107 | if (self.step) self.step = self.step.transform(tw); 108 | self.body = self.body.transform(tw); 109 | }); 110 | 111 | _(AST_ForIn, function(self, tw){ 112 | self.init = self.init.transform(tw); 113 | self.object = self.object.transform(tw); 114 | self.body = self.body.transform(tw); 115 | }); 116 | 117 | _(AST_With, function(self, tw){ 118 | self.expression = self.expression.transform(tw); 119 | self.body = self.body.transform(tw); 120 | }); 121 | 122 | _(AST_Exit, function(self, tw){ 123 | if (self.value) self.value = self.value.transform(tw); 124 | }); 125 | 126 | _(AST_LoopControl, function(self, tw){ 127 | if (self.label) self.label = self.label.transform(tw); 128 | }); 129 | 130 | _(AST_If, function(self, tw){ 131 | self.condition = self.condition.transform(tw); 132 | self.body = self.body.transform(tw); 133 | if (self.alternative) self.alternative = self.alternative.transform(tw); 134 | }); 135 | 136 | _(AST_Switch, function(self, tw){ 137 | self.expression = self.expression.transform(tw); 138 | self.body = do_list(self.body, tw); 139 | }); 140 | 141 | _(AST_Case, function(self, tw){ 142 | self.expression = self.expression.transform(tw); 143 | self.body = do_list(self.body, tw); 144 | }); 145 | 146 | _(AST_Try, function(self, tw){ 147 | self.body = do_list(self.body, tw); 148 | if (self.bcatch) self.bcatch = self.bcatch.transform(tw); 149 | if (self.bfinally) self.bfinally = self.bfinally.transform(tw); 150 | }); 151 | 152 | _(AST_Catch, function(self, tw){ 153 | self.argname = self.argname.transform(tw); 154 | self.body = do_list(self.body, tw); 155 | }); 156 | 157 | _(AST_Definitions, function(self, tw){ 158 | self.definitions = do_list(self.definitions, tw); 159 | }); 160 | 161 | _(AST_VarDef, function(self, tw){ 162 | self.name = self.name.transform(tw); 163 | if (self.value) self.value = self.value.transform(tw); 164 | }); 165 | 166 | _(AST_Destructuring, function(self, tw) { 167 | self.names = do_list(self.names, tw); 168 | }); 169 | 170 | _(AST_Lambda, function(self, tw){ 171 | if (self.name) self.name = self.name.transform(tw); 172 | self.argnames = do_list(self.argnames, tw); 173 | if (self.body instanceof AST_Node) { 174 | self.body = self.body.transform(tw); 175 | } else { 176 | self.body = do_list(self.body, tw); 177 | } 178 | }); 179 | 180 | _(AST_Call, function(self, tw){ 181 | self.expression = self.expression.transform(tw); 182 | self.args = do_list(self.args, tw); 183 | }); 184 | 185 | _(AST_Seq, function(self, tw){ 186 | self.car = self.car.transform(tw); 187 | self.cdr = self.cdr.transform(tw); 188 | }); 189 | 190 | _(AST_Dot, function(self, tw){ 191 | self.expression = self.expression.transform(tw); 192 | }); 193 | 194 | _(AST_Sub, function(self, tw){ 195 | self.expression = self.expression.transform(tw); 196 | self.property = self.property.transform(tw); 197 | }); 198 | 199 | _(AST_Unary, function(self, tw){ 200 | self.expression = self.expression.transform(tw); 201 | }); 202 | 203 | _(AST_Binary, function(self, tw){ 204 | self.left = self.left.transform(tw); 205 | self.right = self.right.transform(tw); 206 | }); 207 | 208 | _(AST_Conditional, function(self, tw){ 209 | self.condition = self.condition.transform(tw); 210 | self.consequent = self.consequent.transform(tw); 211 | self.alternative = self.alternative.transform(tw); 212 | }); 213 | 214 | _(AST_Array, function(self, tw){ 215 | self.elements = do_list(self.elements, tw); 216 | }); 217 | 218 | _(AST_Object, function(self, tw){ 219 | self.properties = do_list(self.properties, tw); 220 | }); 221 | 222 | _(AST_ObjectSymbol, function(self, tw){ 223 | self.symbol = self.symbol.transform(tw); 224 | }); 225 | 226 | _(AST_ObjectProperty, function(self, tw){ 227 | self.value = self.value.transform(tw); 228 | }); 229 | 230 | })(); 231 | -------------------------------------------------------------------------------- /uglify/fromjsw/transform.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | // Tree transformer helpers. 47 | 48 | function TreeTransformer(before, after) { 49 | TreeWalker.call(this); 50 | this.before = before; 51 | this.after = after; 52 | } 53 | TreeTransformer.prototype = new TreeWalker; 54 | 55 | (function(undefined){ 56 | 57 | function _(node, descend) { 58 | node.DEFMETHOD("transform", function(tw, in_list){ 59 | var x, y; 60 | tw.push(this); 61 | if (tw.before) x = tw.before(this, descend, in_list); 62 | if (x === undefined) { 63 | if (!tw.after) { 64 | x = this; 65 | descend(x, tw); 66 | } else { 67 | tw.stack[tw.stack.length - 1] = x = this.clone(); 68 | descend(x, tw); 69 | y = tw.after(x, in_list); 70 | if (y !== undefined) x = y; 71 | } 72 | } 73 | tw.pop(); 74 | return x; 75 | }); 76 | }; 77 | 78 | function do_list(list, tw) { 79 | return MAP(list, function(node){ 80 | return node.transform(tw, true); 81 | }); 82 | }; 83 | 84 | _(AST_Node, noop); 85 | 86 | _(AST_LabeledStatement, function(self, tw){ 87 | self.label = self.label.transform(tw); 88 | self.body = self.body.transform(tw); 89 | }); 90 | 91 | _(AST_SimpleStatement, function(self, tw){ 92 | self.body = self.body.transform(tw); 93 | }); 94 | 95 | _(AST_Block, function(self, tw){ 96 | self.body = do_list(self.body, tw); 97 | }); 98 | 99 | _(AST_DWLoop, function(self, tw){ 100 | self.condition = self.condition.transform(tw); 101 | self.body = self.body.transform(tw); 102 | }); 103 | 104 | _(AST_For, function(self, tw){ 105 | if (self.init) self.init = self.init.transform(tw); 106 | if (self.condition) self.condition = self.condition.transform(tw); 107 | if (self.step) self.step = self.step.transform(tw); 108 | self.body = self.body.transform(tw); 109 | }); 110 | 111 | _(AST_ForIn, function(self, tw){ 112 | self.init = self.init.transform(tw); 113 | self.object = self.object.transform(tw); 114 | self.body = self.body.transform(tw); 115 | }); 116 | 117 | _(AST_With, function(self, tw){ 118 | self.expression = self.expression.transform(tw); 119 | self.body = self.body.transform(tw); 120 | }); 121 | 122 | _(AST_Exit, function(self, tw){ 123 | if (self.value) self.value = self.value.transform(tw); 124 | }); 125 | 126 | _(AST_LoopControl, function(self, tw){ 127 | if (self.label) self.label = self.label.transform(tw); 128 | }); 129 | 130 | _(AST_If, function(self, tw){ 131 | self.condition = self.condition.transform(tw); 132 | self.body = self.body.transform(tw); 133 | if (self.alternative) self.alternative = self.alternative.transform(tw); 134 | }); 135 | 136 | _(AST_Switch, function(self, tw){ 137 | self.expression = self.expression.transform(tw); 138 | self.body = do_list(self.body, tw); 139 | }); 140 | 141 | _(AST_Case, function(self, tw){ 142 | self.expression = self.expression.transform(tw); 143 | self.body = do_list(self.body, tw); 144 | }); 145 | 146 | _(AST_Try, function(self, tw){ 147 | self.body = do_list(self.body, tw); 148 | if (self.bcatch) self.bcatch = self.bcatch.transform(tw); 149 | if (self.bfinally) self.bfinally = self.bfinally.transform(tw); 150 | }); 151 | 152 | _(AST_Catch, function(self, tw){ 153 | self.argname = self.argname.transform(tw); 154 | self.body = do_list(self.body, tw); 155 | }); 156 | 157 | _(AST_Definitions, function(self, tw){ 158 | self.definitions = do_list(self.definitions, tw); 159 | }); 160 | 161 | _(AST_VarDef, function(self, tw){ 162 | self.name = self.name.transform(tw); 163 | if (self.value) self.value = self.value.transform(tw); 164 | }); 165 | 166 | _(AST_Destructuring, function(self, tw) { 167 | self.names = do_list(self.names, tw); 168 | }); 169 | 170 | _(AST_Lambda, function(self, tw){ 171 | if (self.name) self.name = self.name.transform(tw); 172 | self.argnames = do_list(self.argnames, tw); 173 | if (self.body instanceof AST_Node) { 174 | self.body = self.body.transform(tw); 175 | } else { 176 | self.body = do_list(self.body, tw); 177 | } 178 | }); 179 | 180 | _(AST_Call, function(self, tw){ 181 | self.expression = self.expression.transform(tw); 182 | self.args = do_list(self.args, tw); 183 | }); 184 | 185 | _(AST_Seq, function(self, tw){ 186 | self.car = self.car.transform(tw); 187 | self.cdr = self.cdr.transform(tw); 188 | }); 189 | 190 | _(AST_Dot, function(self, tw){ 191 | self.expression = self.expression.transform(tw); 192 | }); 193 | 194 | _(AST_Sub, function(self, tw){ 195 | self.expression = self.expression.transform(tw); 196 | self.property = self.property.transform(tw); 197 | }); 198 | 199 | _(AST_Unary, function(self, tw){ 200 | self.expression = self.expression.transform(tw); 201 | }); 202 | 203 | _(AST_Binary, function(self, tw){ 204 | self.left = self.left.transform(tw); 205 | self.right = self.right.transform(tw); 206 | }); 207 | 208 | _(AST_Conditional, function(self, tw){ 209 | self.condition = self.condition.transform(tw); 210 | self.consequent = self.consequent.transform(tw); 211 | self.alternative = self.alternative.transform(tw); 212 | }); 213 | 214 | _(AST_Array, function(self, tw){ 215 | self.elements = do_list(self.elements, tw); 216 | }); 217 | 218 | _(AST_Object, function(self, tw){ 219 | self.properties = do_list(self.properties, tw); 220 | }); 221 | 222 | _(AST_ObjectSymbol, function(self, tw){ 223 | self.symbol = self.symbol.transform(tw); 224 | }); 225 | 226 | _(AST_ObjectProperty, function(self, tw){ 227 | self.value = self.value.transform(tw); 228 | }); 229 | 230 | })(); 231 | -------------------------------------------------------------------------------- /uglify/fromjsw/utils.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | function array_to_hash(a) { 47 | var ret = Object.create(null); 48 | for (var i = 0; i < a.length; ++i) 49 | ret[a[i]] = true; 50 | return ret; 51 | }; 52 | 53 | function slice(a, start) { 54 | return Array.prototype.slice.call(a, start || 0); 55 | }; 56 | 57 | function characters(str) { 58 | return str.split(""); 59 | }; 60 | 61 | function member(name, array) { 62 | for (var i = array.length; --i >= 0;) 63 | if (array[i] == name) 64 | return true; 65 | return false; 66 | }; 67 | 68 | function find_if(func, array) { 69 | for (var i = 0, n = array.length; i < n; ++i) { 70 | if (func(array[i])) 71 | return array[i]; 72 | } 73 | }; 74 | 75 | function repeat_string(str, i) { 76 | if (i <= 0) return ""; 77 | if (i == 1) return str; 78 | var d = repeat_string(str, i >> 1); 79 | d += d; 80 | if (i & 1) d += str; 81 | return d; 82 | }; 83 | 84 | function DefaultsError(msg, defs) { 85 | Error.call(this, msg); 86 | this.msg = msg; 87 | this.defs = defs; 88 | }; 89 | DefaultsError.prototype = Object.create(Error.prototype); 90 | DefaultsError.prototype.constructor = DefaultsError; 91 | 92 | DefaultsError.croak = function(msg, defs) { 93 | throw new DefaultsError(msg, defs); 94 | }; 95 | 96 | function defaults(args, defs, croak) { 97 | if (args === true) 98 | args = {}; 99 | var ret = args || {}; 100 | if (croak) for (var i in ret) if (ret.hasOwnProperty(i) && !defs.hasOwnProperty(i)) 101 | DefaultsError.croak("`" + i + "` is not a supported option", defs); 102 | for (var i in defs) if (defs.hasOwnProperty(i)) { 103 | ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i]; 104 | } 105 | return ret; 106 | }; 107 | 108 | function merge(obj, ext) { 109 | var count = 0; 110 | for (var i in ext) if (ext.hasOwnProperty(i)) { 111 | obj[i] = ext[i]; 112 | count++; 113 | } 114 | return count; 115 | }; 116 | 117 | function noop() {}; 118 | 119 | var MAP = (function(){ 120 | function MAP(a, f, backwards) { 121 | var ret = [], top = [], i; 122 | function doit() { 123 | var val = f(a[i], i); 124 | var is_last = val instanceof Last; 125 | if (is_last) val = val.v; 126 | if (val instanceof AtTop) { 127 | val = val.v; 128 | if (val instanceof Splice) { 129 | top.push.apply(top, backwards ? val.v.slice().reverse() : val.v); 130 | } else { 131 | top.push(val); 132 | } 133 | } 134 | else if (val !== skip) { 135 | if (val instanceof Splice) { 136 | ret.push.apply(ret, backwards ? val.v.slice().reverse() : val.v); 137 | } else { 138 | ret.push(val); 139 | } 140 | } 141 | return is_last; 142 | }; 143 | if (a instanceof Array) { 144 | if (backwards) { 145 | for (i = a.length; --i >= 0;) if (doit()) break; 146 | ret.reverse(); 147 | top.reverse(); 148 | } else { 149 | for (i = 0; i < a.length; ++i) if (doit()) break; 150 | } 151 | } 152 | else { 153 | for (i in a) if (a.hasOwnProperty(i)) if (doit()) break; 154 | } 155 | return top.concat(ret); 156 | }; 157 | MAP.at_top = function(val) { return new AtTop(val) }; 158 | MAP.splice = function(val) { return new Splice(val) }; 159 | MAP.last = function(val) { return new Last(val) }; 160 | var skip = MAP.skip = {}; 161 | function AtTop(val) { this.v = val }; 162 | function Splice(val) { this.v = val }; 163 | function Last(val) { this.v = val }; 164 | return MAP; 165 | })(); 166 | 167 | function push_uniq(array, el) { 168 | if (array.indexOf(el) < 0) 169 | array.push(el); 170 | }; 171 | 172 | function string_template(text, props) { 173 | return text.replace(/\{(.+?)\}/g, function(str, p){ 174 | return props[p]; 175 | }); 176 | }; 177 | 178 | function remove(array, el) { 179 | for (var i = array.length; --i >= 0;) { 180 | if (array[i] === el) array.splice(i, 1); 181 | } 182 | }; 183 | 184 | function mergeSort(array, cmp) { 185 | if (array.length < 2) return array.slice(); 186 | function merge(a, b) { 187 | var r = [], ai = 0, bi = 0, i = 0; 188 | while (ai < a.length && bi < b.length) { 189 | cmp(a[ai], b[bi]) <= 0 190 | ? r[i++] = a[ai++] 191 | : r[i++] = b[bi++]; 192 | } 193 | if (ai < a.length) r.push.apply(r, a.slice(ai)); 194 | if (bi < b.length) r.push.apply(r, b.slice(bi)); 195 | return r; 196 | }; 197 | function _ms(a) { 198 | if (a.length <= 1) 199 | return a; 200 | var m = Math.floor(a.length / 2), left = a.slice(0, m), right = a.slice(m); 201 | left = _ms(left); 202 | right = _ms(right); 203 | return merge(left, right); 204 | }; 205 | return _ms(array); 206 | }; 207 | 208 | function set_difference(a, b) { 209 | return a.filter(function(el){ 210 | return b.indexOf(el) < 0; 211 | }); 212 | }; 213 | 214 | function set_intersection(a, b) { 215 | return a.filter(function(el){ 216 | return b.indexOf(el) >= 0; 217 | }); 218 | }; 219 | 220 | // this function is taken from Acorn [1], written by Marijn Haverbeke 221 | // [1] https://github.com/marijnh/acorn 222 | function makePredicate(words) { 223 | if (!(words instanceof Array)) words = words.split(" "); 224 | var f = "", cats = []; 225 | out: for (var i = 0; i < words.length; ++i) { 226 | for (var j = 0; j < cats.length; ++j) 227 | if (cats[j][0].length == words[i].length) { 228 | cats[j].push(words[i]); 229 | continue out; 230 | } 231 | cats.push([words[i]]); 232 | } 233 | function compareTo(arr) { 234 | if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; 235 | f += "switch(str){"; 236 | for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; 237 | f += "return true}return false;"; 238 | } 239 | // When there are more than three length categories, an outer 240 | // switch first dispatches on the lengths, to save on comparisons. 241 | if (cats.length > 3) { 242 | cats.sort(function(a, b) {return b.length - a.length;}); 243 | f += "switch(str.length){"; 244 | for (var i = 0; i < cats.length; ++i) { 245 | var cat = cats[i]; 246 | f += "case " + cat[0].length + ":"; 247 | compareTo(cat); 248 | } 249 | f += "}"; 250 | // Otherwise, simply generate a flat `switch` statement. 251 | } else { 252 | compareTo(words); 253 | } 254 | return new Function("str", f); 255 | }; 256 | 257 | function all(array, predicate) { 258 | for (var i = array.length; --i >= 0;) 259 | if (!predicate(array[i])) 260 | return false; 261 | return true; 262 | }; 263 | 264 | function Dictionary() { 265 | this._values = Object.create(null); 266 | this._size = 0; 267 | }; 268 | Dictionary.prototype = { 269 | set: function(key, val) { 270 | if (!this.has(key)) ++this._size; 271 | this._values["$" + key] = val; 272 | return this; 273 | }, 274 | add: function(key, val) { 275 | if (this.has(key)) { 276 | this.get(key).push(val); 277 | } else { 278 | this.set(key, [ val ]); 279 | } 280 | return this; 281 | }, 282 | get: function(key) { return this._values["$" + key] }, 283 | del: function(key) { 284 | if (this.has(key)) { 285 | --this._size; 286 | delete this._values["$" + key]; 287 | } 288 | return this; 289 | }, 290 | has: function(key) { return ("$" + key) in this._values }, 291 | each: function(f) { 292 | for (var i in this._values) 293 | f(this._values[i], i.substr(1)); 294 | }, 295 | size: function() { 296 | return this._size; 297 | }, 298 | map: function(f) { 299 | var ret = []; 300 | for (var i in this._values) 301 | ret.push(f(this._values[i], i.substr(1))); 302 | return ret; 303 | }, 304 | toObject: function() { return this._values } 305 | }; 306 | Dictionary.fromObject = function(obj) { 307 | var dict = new Dictionary(); 308 | dict._size = merge(dict._values, obj); 309 | return dict; 310 | }; 311 | -------------------------------------------------------------------------------- /uglify/tojsw/utils.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | function array_to_hash(a) { 47 | var ret = Object.create(null); 48 | for (var i = 0; i < a.length; ++i) 49 | ret[a[i]] = true; 50 | return ret; 51 | }; 52 | 53 | function slice(a, start) { 54 | return Array.prototype.slice.call(a, start || 0); 55 | }; 56 | 57 | function characters(str) { 58 | return str.split(""); 59 | }; 60 | 61 | function member(name, array) { 62 | for (var i = array.length; --i >= 0;) 63 | if (array[i] == name) 64 | return true; 65 | return false; 66 | }; 67 | 68 | function find_if(func, array) { 69 | for (var i = 0, n = array.length; i < n; ++i) { 70 | if (func(array[i])) 71 | return array[i]; 72 | } 73 | }; 74 | 75 | function repeat_string(str, i) { 76 | if (i <= 0) return ""; 77 | if (i == 1) return str; 78 | var d = repeat_string(str, i >> 1); 79 | d += d; 80 | if (i & 1) d += str; 81 | return d; 82 | }; 83 | 84 | function DefaultsError(msg, defs) { 85 | Error.call(this, msg); 86 | this.msg = msg; 87 | this.defs = defs; 88 | }; 89 | DefaultsError.prototype = Object.create(Error.prototype); 90 | DefaultsError.prototype.constructor = DefaultsError; 91 | 92 | DefaultsError.croak = function(msg, defs) { 93 | throw new DefaultsError(msg, defs); 94 | }; 95 | 96 | function defaults(args, defs, croak) { 97 | if (args === true) 98 | args = {}; 99 | var ret = args || {}; 100 | if (croak) for (var i in ret) if (ret.hasOwnProperty(i) && !defs.hasOwnProperty(i)) 101 | DefaultsError.croak("`" + i + "` is not a supported option", defs); 102 | for (var i in defs) if (defs.hasOwnProperty(i)) { 103 | ret[i] = (args && args.hasOwnProperty(i)) ? args[i] : defs[i]; 104 | } 105 | return ret; 106 | }; 107 | 108 | function merge(obj, ext) { 109 | var count = 0; 110 | for (var i in ext) if (ext.hasOwnProperty(i)) { 111 | obj[i] = ext[i]; 112 | count++; 113 | } 114 | return count; 115 | }; 116 | 117 | function noop() {}; 118 | 119 | var MAP = (function(){ 120 | function MAP(a, f, backwards) { 121 | var ret = [], top = [], i; 122 | function doit() { 123 | var val = f(a[i], i); 124 | var is_last = val instanceof Last; 125 | if (is_last) val = val.v; 126 | if (val instanceof AtTop) { 127 | val = val.v; 128 | if (val instanceof Splice) { 129 | top.push.apply(top, backwards ? val.v.slice().reverse() : val.v); 130 | } else { 131 | top.push(val); 132 | } 133 | } 134 | else if (val !== skip) { 135 | if (val instanceof Splice) { 136 | ret.push.apply(ret, backwards ? val.v.slice().reverse() : val.v); 137 | } else { 138 | ret.push(val); 139 | } 140 | } 141 | return is_last; 142 | }; 143 | if (a instanceof Array) { 144 | if (backwards) { 145 | for (i = a.length; --i >= 0;) if (doit()) break; 146 | ret.reverse(); 147 | top.reverse(); 148 | } else { 149 | for (i = 0; i < a.length; ++i) if (doit()) break; 150 | } 151 | } 152 | else { 153 | for (i in a) if (a.hasOwnProperty(i)) if (doit()) break; 154 | } 155 | return top.concat(ret); 156 | }; 157 | MAP.at_top = function(val) { return new AtTop(val) }; 158 | MAP.splice = function(val) { return new Splice(val) }; 159 | MAP.last = function(val) { return new Last(val) }; 160 | var skip = MAP.skip = {}; 161 | function AtTop(val) { this.v = val }; 162 | function Splice(val) { this.v = val }; 163 | function Last(val) { this.v = val }; 164 | return MAP; 165 | })(); 166 | 167 | function push_uniq(array, el) { 168 | if (array.indexOf(el) < 0) 169 | array.push(el); 170 | }; 171 | 172 | function string_template(text, props) { 173 | return text.replace(/\{(.+?)\}/g, function(str, p){ 174 | return props[p]; 175 | }); 176 | }; 177 | 178 | function remove(array, el) { 179 | for (var i = array.length; --i >= 0;) { 180 | if (array[i] === el) array.splice(i, 1); 181 | } 182 | }; 183 | 184 | function mergeSort(array, cmp) { 185 | if (array.length < 2) return array.slice(); 186 | function merge(a, b) { 187 | var r = [], ai = 0, bi = 0, i = 0; 188 | while (ai < a.length && bi < b.length) { 189 | cmp(a[ai], b[bi]) <= 0 190 | ? r[i++] = a[ai++] 191 | : r[i++] = b[bi++]; 192 | } 193 | if (ai < a.length) r.push.apply(r, a.slice(ai)); 194 | if (bi < b.length) r.push.apply(r, b.slice(bi)); 195 | return r; 196 | }; 197 | function _ms(a) { 198 | if (a.length <= 1) 199 | return a; 200 | var m = Math.floor(a.length / 2), left = a.slice(0, m), right = a.slice(m); 201 | left = _ms(left); 202 | right = _ms(right); 203 | return merge(left, right); 204 | }; 205 | return _ms(array); 206 | }; 207 | 208 | function set_difference(a, b) { 209 | return a.filter(function(el){ 210 | return b.indexOf(el) < 0; 211 | }); 212 | }; 213 | 214 | function set_intersection(a, b) { 215 | return a.filter(function(el){ 216 | return b.indexOf(el) >= 0; 217 | }); 218 | }; 219 | 220 | // this function is taken from Acorn [1], written by Marijn Haverbeke 221 | // [1] https://github.com/marijnh/acorn 222 | function makePredicate(words) { 223 | if (!(words instanceof Array)) words = words.split(" "); 224 | var f = "", cats = []; 225 | out: for (var i = 0; i < words.length; ++i) { 226 | for (var j = 0; j < cats.length; ++j) 227 | if (cats[j][0].length == words[i].length) { 228 | cats[j].push(words[i]); 229 | continue out; 230 | } 231 | cats.push([words[i]]); 232 | } 233 | function compareTo(arr) { 234 | if (arr.length == 1) return f += "return str === " + JSON.stringify(arr[0]) + ";"; 235 | f += "switch(str){"; 236 | for (var i = 0; i < arr.length; ++i) f += "case " + JSON.stringify(arr[i]) + ":"; 237 | f += "return true}return false;"; 238 | } 239 | // When there are more than three length categories, an outer 240 | // switch first dispatches on the lengths, to save on comparisons. 241 | if (cats.length > 3) { 242 | cats.sort(function(a, b) {return b.length - a.length;}); 243 | f += "switch(str.length){"; 244 | for (var i = 0; i < cats.length; ++i) { 245 | var cat = cats[i]; 246 | f += "case " + cat[0].length + ":"; 247 | compareTo(cat); 248 | } 249 | f += "}"; 250 | // Otherwise, simply generate a flat `switch` statement. 251 | } else { 252 | compareTo(words); 253 | } 254 | return new Function("str", f); 255 | }; 256 | 257 | function all(array, predicate) { 258 | for (var i = array.length; --i >= 0;) 259 | if (!predicate(array[i])) 260 | return false; 261 | return true; 262 | }; 263 | 264 | function Dictionary() { 265 | this._values = Object.create(null); 266 | this._size = 0; 267 | }; 268 | Dictionary.prototype = { 269 | set: function(key, val) { 270 | if (!this.has(key)) ++this._size; 271 | this._values["$" + key] = val; 272 | return this; 273 | }, 274 | add: function(key, val) { 275 | if (this.has(key)) { 276 | this.get(key).push(val); 277 | } else { 278 | this.set(key, [ val ]); 279 | } 280 | return this; 281 | }, 282 | get: function(key) { return this._values["$" + key] }, 283 | del: function(key) { 284 | if (this.has(key)) { 285 | --this._size; 286 | delete this._values["$" + key]; 287 | } 288 | return this; 289 | }, 290 | has: function(key) { return ("$" + key) in this._values }, 291 | each: function(f) { 292 | for (var i in this._values) 293 | f(this._values[i], i.substr(1)); 294 | }, 295 | size: function() { 296 | return this._size; 297 | }, 298 | map: function(f) { 299 | var ret = []; 300 | for (var i in this._values) 301 | ret.push(f(this._values[i], i.substr(1))); 302 | return ret; 303 | }, 304 | toObject: function() { return this._values } 305 | }; 306 | Dictionary.fromObject = function(obj) { 307 | var dict = new Dictionary(); 308 | dict._size = merge(dict._values, obj); 309 | return dict; 310 | }; 311 | -------------------------------------------------------------------------------- /uglify/fromjsw/scope.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | function SymbolDef(scope, index, orig) { 47 | this.name = orig.name; 48 | this.orig = [ orig ]; 49 | this.scope = scope; 50 | this.references = []; 51 | this.global = false; 52 | this.mangled_name = null; 53 | this.object_destructuring_arg = false; 54 | this.undeclared = false; 55 | this.constant = false; 56 | this.index = index; 57 | }; 58 | 59 | SymbolDef.prototype = { 60 | unmangleable: function(options) { 61 | if (!options) options = {}; 62 | 63 | return (this.global && !options.toplevel) 64 | || this.object_destructuring_arg 65 | || this.undeclared 66 | || (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) 67 | || (options.keep_fnames 68 | && (this.orig[0] instanceof AST_SymbolLambda 69 | || this.orig[0] instanceof AST_SymbolDefun)); 70 | }, 71 | mangle: function(options) { 72 | var cache = options.cache && options.cache.props; 73 | if (this.global && cache && cache.has(this.name)) { 74 | this.mangled_name = cache.get(this.name); 75 | } 76 | else if (!this.mangled_name && !this.unmangleable(options)) { 77 | var s = this.scope; 78 | if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda) 79 | s = s.parent_scope; 80 | this.mangled_name = s.next_mangled(options, this); 81 | if (this.global && cache) { 82 | cache.set(this.name, this.mangled_name); 83 | } 84 | } 85 | } 86 | }; 87 | 88 | AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ 89 | options = defaults(options, { 90 | screw_ie8: false, 91 | cache: null 92 | }); 93 | 94 | // pass 1: setup scope chaining and handle definitions 95 | var self = this; 96 | var scope = self.parent_scope = null; 97 | var defun = null; 98 | var nesting = 0; 99 | var in_destructuring = null; 100 | var tw = new TreeWalker(function(node, descend){ 101 | if (options.screw_ie8 && node instanceof AST_Catch) { 102 | var save_scope = scope; 103 | scope = new AST_Scope(node); 104 | scope.init_scope_vars(nesting); 105 | scope.parent_scope = save_scope; 106 | descend(); 107 | scope = save_scope; 108 | return true; 109 | } 110 | if (node instanceof AST_Destructuring && node.is_array === false) { 111 | in_destructuring = node; // These don't nest 112 | descend(); 113 | in_destructuring = null; 114 | return true; 115 | } 116 | if (node instanceof AST_Scope) { 117 | node.init_scope_vars(nesting); 118 | var save_scope = node.parent_scope = scope; 119 | var save_defun = defun; 120 | defun = scope = node; 121 | ++nesting; descend(); --nesting; 122 | scope = save_scope; 123 | defun = save_defun; 124 | return true; // don't descend again in TreeWalker 125 | } 126 | if (node instanceof AST_Directive) { 127 | node.scope = scope; 128 | push_uniq(scope.directives, node.value); 129 | return true; 130 | } 131 | if (node instanceof AST_Number) { 132 | node.scope = scope; 133 | return true; 134 | } 135 | if (node instanceof AST_With) { 136 | for (var s = scope; s; s = s.parent_scope) 137 | s.uses_with = true; 138 | return; 139 | } 140 | if (node instanceof AST_Symbol) { 141 | node.scope = scope; 142 | } 143 | if (node instanceof AST_SymbolFunarg) { 144 | node.object_destructuring_arg = !!in_destructuring; 145 | defun.def_variable(node); 146 | } 147 | if (node instanceof AST_SymbolLambda) { 148 | defun.def_function(node); 149 | } 150 | else if (node instanceof AST_SymbolDefun) { 151 | // Careful here, the scope where this should be defined is 152 | // the parent scope. The reason is that we enter a new 153 | // scope when we encounter the AST_Defun node (which is 154 | // instanceof AST_Scope) but we get to the symbol a bit 155 | // later. 156 | (node.scope = defun.parent_scope).def_function(node); 157 | } 158 | else if (node instanceof AST_SymbolVar 159 | || node instanceof AST_SymbolConst) { 160 | var def = defun.def_variable(node); 161 | def.constant = node instanceof AST_SymbolConst; 162 | def.destructuring = in_destructuring; 163 | def.init = tw.parent().value; 164 | } 165 | else if (node instanceof AST_SymbolCatch) { 166 | (options.screw_ie8 ? scope : defun) 167 | .def_variable(node); 168 | } 169 | }); 170 | self.walk(tw); 171 | 172 | // pass 2: find back references and eval 173 | var func = null; 174 | var globals = self.globals = new Dictionary(); 175 | var tw = new TreeWalker(function(node, descend){ 176 | if (node instanceof AST_Lambda) { 177 | var prev_func = func; 178 | func = node; 179 | descend(); 180 | func = prev_func; 181 | return true; 182 | } 183 | if (node instanceof AST_SymbolRef) { 184 | var name = node.name; 185 | var sym = node.scope.find_variable(name); 186 | if (!sym) { 187 | var g; 188 | if (globals.has(name)) { 189 | g = globals.get(name); 190 | } else { 191 | g = new SymbolDef(self, globals.size(), node); 192 | g.undeclared = true; 193 | g.global = true; 194 | globals.set(name, g); 195 | } 196 | node.thedef = g; 197 | if (name == "eval" && tw.parent() instanceof AST_Call) { 198 | for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) 199 | s.uses_eval = true; 200 | } 201 | if (func && name == "arguments") { 202 | func.uses_arguments = true; 203 | } 204 | } else { 205 | node.thedef = sym; 206 | } 207 | node.reference(); 208 | return true; 209 | } 210 | }); 211 | self.walk(tw); 212 | 213 | if (options.cache) { 214 | this.cname = options.cache.cname; 215 | } 216 | }); 217 | 218 | AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ 219 | this.directives = []; // contains the directives defined in this scope, i.e. "use strict" 220 | this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) 221 | this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) 222 | this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement 223 | this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval` 224 | this.parent_scope = null; // the parent scope 225 | this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes 226 | this.cname = -1; // the current index for mangling functions/variables 227 | this.nesting = nesting; // the nesting level of this scope (0 means toplevel) 228 | }); 229 | 230 | AST_Scope.DEFMETHOD("strict", function(){ 231 | return this.has_directive("use strict"); 232 | }); 233 | 234 | AST_Lambda.DEFMETHOD("init_scope_vars", function(){ 235 | AST_Scope.prototype.init_scope_vars.apply(this, arguments); 236 | this.uses_arguments = false; 237 | }); 238 | 239 | AST_SymbolRef.DEFMETHOD("reference", function() { 240 | var def = this.definition(); 241 | def.references.push(this); 242 | var s = this.scope; 243 | while (s) { 244 | push_uniq(s.enclosed, def); 245 | if (s === def.scope) break; 246 | s = s.parent_scope; 247 | } 248 | this.frame = this.scope.nesting - def.scope.nesting; 249 | }); 250 | 251 | AST_Scope.DEFMETHOD("find_variable", function(name){ 252 | if (name instanceof AST_Symbol) name = name.name; 253 | return this.variables.get(name) 254 | || (this.parent_scope && this.parent_scope.find_variable(name)); 255 | }); 256 | 257 | AST_Scope.DEFMETHOD("has_directive", function(value){ 258 | return this.parent_scope && this.parent_scope.has_directive(value) 259 | || (this.directives.indexOf(value) >= 0 ? this : null); 260 | }); 261 | 262 | AST_Scope.DEFMETHOD("def_function", function(symbol){ 263 | this.functions.set(symbol.name, this.def_variable(symbol)); 264 | }); 265 | 266 | AST_Scope.DEFMETHOD("def_variable", function(symbol){ 267 | var def; 268 | if (!this.variables.has(symbol.name)) { 269 | def = new SymbolDef(this, this.variables.size(), symbol); 270 | this.variables.set(symbol.name, def); 271 | def.object_destructuring_arg = symbol.object_destructuring_arg; 272 | def.global = !this.parent_scope; 273 | } else { 274 | def = this.variables.get(symbol.name); 275 | def.orig.push(symbol); 276 | } 277 | return symbol.thedef = def; 278 | }); 279 | 280 | AST_Scope.DEFMETHOD("next_mangled", function(options){ 281 | var ext = this.enclosed; 282 | out: while (true) { 283 | var m = base54(++this.cname); 284 | if (!is_identifier(m)) continue; // skip over "do" 285 | 286 | // https://github.com/mishoo/UglifyJS2/issues/242 -- do not 287 | // shadow a name excepted from mangling. 288 | if (options.except.indexOf(m) >= 0) continue; 289 | 290 | // we must ensure that the mangled name does not shadow a name 291 | // from some parent scope that is referenced in this or in 292 | // inner scopes. 293 | for (var i = ext.length; --i >= 0;) { 294 | var sym = ext[i]; 295 | var name = sym.mangled_name || (sym.unmangleable(options) && sym.name); 296 | if (m == name) continue out; 297 | } 298 | return m; 299 | } 300 | }); 301 | 302 | AST_Function.DEFMETHOD("next_mangled", function(options, def){ 303 | // #179, #326 304 | // in Safari strict mode, something like (function x(x){...}) is a syntax error; 305 | // a function expression's argument cannot shadow the function expression's name 306 | 307 | var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition(); 308 | while (true) { 309 | var name = AST_Lambda.prototype.next_mangled.call(this, options, def); 310 | if (!(tricky_def && tricky_def.mangled_name == name)) 311 | return name; 312 | } 313 | }); 314 | 315 | AST_Scope.DEFMETHOD("references", function(sym){ 316 | if (sym instanceof AST_Symbol) sym = sym.definition(); 317 | return this.enclosed.indexOf(sym) < 0 ? null : sym; 318 | }); 319 | 320 | AST_Symbol.DEFMETHOD("unmangleable", function(options){ 321 | return this.definition().unmangleable(options); 322 | }); 323 | 324 | // property accessors are not mangleable 325 | AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){ 326 | return true; 327 | }); 328 | 329 | // labels are always mangleable 330 | AST_Label.DEFMETHOD("unmangleable", function(){ 331 | return false; 332 | }); 333 | 334 | AST_Symbol.DEFMETHOD("unreferenced", function(){ 335 | return this.definition().references.length == 0 336 | && !(this.scope.uses_eval || this.scope.uses_with); 337 | }); 338 | 339 | AST_Symbol.DEFMETHOD("undeclared", function(){ 340 | return this.definition().undeclared; 341 | }); 342 | 343 | AST_LabelRef.DEFMETHOD("undeclared", function(){ 344 | return false; 345 | }); 346 | 347 | AST_Label.DEFMETHOD("undeclared", function(){ 348 | return false; 349 | }); 350 | 351 | AST_Symbol.DEFMETHOD("definition", function(){ 352 | return this.thedef; 353 | }); 354 | 355 | AST_Symbol.DEFMETHOD("global", function(){ 356 | return this.definition().global; 357 | }); 358 | 359 | AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ 360 | return defaults(options, { 361 | except : [], 362 | eval : false, 363 | sort : false, 364 | toplevel : false, 365 | screw_ie8 : false, 366 | keep_fnames : false 367 | }); 368 | }); 369 | 370 | AST_Toplevel.DEFMETHOD("mangle_names", function(options){ 371 | options = this._default_mangler_options(options); 372 | // We only need to mangle declaration nodes. Special logic wired 373 | // into the code generator will display the mangled name if it's 374 | // present (and for AST_SymbolRef-s it'll use the mangled name of 375 | // the AST_SymbolDeclaration that it points to). 376 | var lname = -1; 377 | var to_mangle = []; 378 | 379 | if (options.cache) { 380 | this.globals.each(function(symbol){ 381 | if (options.except.indexOf(symbol.name) < 0) { 382 | to_mangle.push(symbol); 383 | } 384 | }); 385 | } 386 | 387 | var tw = new TreeWalker(function(node, descend){ 388 | if (node instanceof AST_LabeledStatement) { 389 | // lname is incremented when we get to the AST_Label 390 | var save_nesting = lname; 391 | descend(); 392 | lname = save_nesting; 393 | return true; // don't descend again in TreeWalker 394 | } 395 | if (node instanceof AST_Scope) { 396 | var p = tw.parent(), a = []; 397 | node.variables.each(function(symbol){ 398 | if (options.except.indexOf(symbol.name) < 0) { 399 | a.push(symbol); 400 | } 401 | }); 402 | if (options.sort) a.sort(function(a, b){ 403 | return b.references.length - a.references.length; 404 | }); 405 | to_mangle.push.apply(to_mangle, a); 406 | return; 407 | } 408 | if (node instanceof AST_Label) { 409 | var name; 410 | do name = base54(++lname); while (!is_identifier(name)); 411 | node.mangled_name = name; 412 | return true; 413 | } 414 | if (options.screw_ie8 && node instanceof AST_SymbolCatch) { 415 | to_mangle.push(node.definition()); 416 | return; 417 | } 418 | }); 419 | this.walk(tw); 420 | to_mangle.forEach(function(def){ 421 | if (def.destructuring && !def.destructuring.is_array) return; 422 | def.mangle(options); 423 | }); 424 | 425 | if (options.cache) { 426 | options.cache.cname = this.cname; 427 | } 428 | }); 429 | 430 | AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ 431 | options = this._default_mangler_options(options); 432 | var tw = new TreeWalker(function(node){ 433 | if (node instanceof AST_Constant) 434 | base54.consider(node.print_to_string()); 435 | else if (node instanceof AST_Return) 436 | base54.consider("return"); 437 | else if (node instanceof AST_Throw) 438 | base54.consider("throw"); 439 | else if (node instanceof AST_Continue) 440 | base54.consider("continue"); 441 | else if (node instanceof AST_Break) 442 | base54.consider("break"); 443 | else if (node instanceof AST_Debugger) 444 | base54.consider("debugger"); 445 | else if (node instanceof AST_Directive) 446 | base54.consider(node.value); 447 | else if (node instanceof AST_While) 448 | base54.consider("while"); 449 | else if (node instanceof AST_Do) 450 | base54.consider("do while"); 451 | else if (node instanceof AST_If) { 452 | base54.consider("if"); 453 | if (node.alternative) base54.consider("else"); 454 | } 455 | else if (node instanceof AST_Var) 456 | base54.consider("var"); 457 | else if (node instanceof AST_Const) 458 | base54.consider("const"); 459 | else if (node instanceof AST_Lambda) 460 | base54.consider("function"); 461 | else if (node instanceof AST_For) 462 | base54.consider("for"); 463 | else if (node instanceof AST_ForIn) 464 | base54.consider("for in"); 465 | else if (node instanceof AST_Switch) 466 | base54.consider("switch"); 467 | else if (node instanceof AST_Case) 468 | base54.consider("case"); 469 | else if (node instanceof AST_Default) 470 | base54.consider("default"); 471 | else if (node instanceof AST_With) 472 | base54.consider("with"); 473 | else if (node instanceof AST_ObjectSetter) 474 | base54.consider("set" + node.key); 475 | else if (node instanceof AST_ObjectGetter) 476 | base54.consider("get" + node.key); 477 | else if (node instanceof AST_ObjectKeyVal) 478 | base54.consider(node.key); 479 | else if (node instanceof AST_New) 480 | base54.consider("new"); 481 | else if (node instanceof AST_This) 482 | base54.consider("this"); 483 | else if (node instanceof AST_Super) 484 | base54.consider("super"); 485 | else if (node instanceof AST_Try) 486 | base54.consider("try"); 487 | else if (node instanceof AST_Catch) 488 | base54.consider("catch"); 489 | else if (node instanceof AST_Finally) 490 | base54.consider("finally"); 491 | else if (node instanceof AST_Symbol && node.unmangleable(options)) 492 | base54.consider(node.name); 493 | else if (node instanceof AST_Unary || node instanceof AST_Binary) 494 | base54.consider(node.operator); 495 | else if (node instanceof AST_Dot) 496 | base54.consider(node.property); 497 | }); 498 | this.walk(tw); 499 | base54.sort(); 500 | }); 501 | 502 | var base54 = (function() { 503 | var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; 504 | var chars, frequency; 505 | function reset() { 506 | frequency = Object.create(null); 507 | chars = string.split("").map(function(ch){ return ch.charCodeAt(0) }); 508 | chars.forEach(function(ch){ frequency[ch] = 0 }); 509 | } 510 | base54.consider = function(str){ 511 | for (var i = str.length; --i >= 0;) { 512 | var code = str.charCodeAt(i); 513 | if (code in frequency) ++frequency[code]; 514 | } 515 | }; 516 | base54.sort = function() { 517 | chars = mergeSort(chars, function(a, b){ 518 | if (is_digit(a) && !is_digit(b)) return 1; 519 | if (is_digit(b) && !is_digit(a)) return -1; 520 | return frequency[b] - frequency[a]; 521 | }); 522 | }; 523 | base54.reset = reset; 524 | reset(); 525 | base54.get = function(){ return chars }; 526 | base54.freq = function(){ return frequency }; 527 | function base54(num) { 528 | var ret = "", base = 54; 529 | num++; 530 | do { 531 | num--; 532 | ret += String.fromCharCode(chars[num % base]); 533 | num = Math.floor(num / base); 534 | base = 64; 535 | } while (num > 0); 536 | return ret; 537 | }; 538 | return base54; 539 | })(); 540 | 541 | AST_Toplevel.DEFMETHOD("scope_warnings", function(options){ 542 | options = defaults(options, { 543 | undeclared : false, // this makes a lot of noise 544 | unreferenced : true, 545 | assign_to_global : true, 546 | func_arguments : true, 547 | nested_defuns : true, 548 | eval : true 549 | }); 550 | var tw = new TreeWalker(function(node){ 551 | if (options.undeclared 552 | && node instanceof AST_SymbolRef 553 | && node.undeclared()) 554 | { 555 | // XXX: this also warns about JS standard names, 556 | // i.e. Object, Array, parseInt etc. Should add a list of 557 | // exceptions. 558 | AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", { 559 | name: node.name, 560 | file: node.start.file, 561 | line: node.start.line, 562 | col: node.start.col 563 | }); 564 | } 565 | if (options.assign_to_global) 566 | { 567 | var sym = null; 568 | if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef) 569 | sym = node.left; 570 | else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef) 571 | sym = node.init; 572 | if (sym 573 | && (sym.undeclared() 574 | || (sym.global() && sym.scope !== sym.definition().scope))) { 575 | AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", { 576 | msg: sym.undeclared() ? "Accidental global?" : "Assignment to global", 577 | name: sym.name, 578 | file: sym.start.file, 579 | line: sym.start.line, 580 | col: sym.start.col 581 | }); 582 | } 583 | } 584 | if (options.eval 585 | && node instanceof AST_SymbolRef 586 | && node.undeclared() 587 | && node.name == "eval") { 588 | AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start); 589 | } 590 | if (options.unreferenced 591 | && (node instanceof AST_SymbolDeclaration || node instanceof AST_Label) 592 | && !(node instanceof AST_SymbolCatch) 593 | && node.unreferenced()) { 594 | AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", { 595 | type: node instanceof AST_Label ? "Label" : "Symbol", 596 | name: node.name, 597 | file: node.start.file, 598 | line: node.start.line, 599 | col: node.start.col 600 | }); 601 | } 602 | if (options.func_arguments 603 | && node instanceof AST_Lambda 604 | && node.uses_arguments) { 605 | AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", { 606 | name: node.name ? node.name.name : "anonymous", 607 | file: node.start.file, 608 | line: node.start.line, 609 | col: node.start.col 610 | }); 611 | } 612 | if (options.nested_defuns 613 | && node instanceof AST_Defun 614 | && !(tw.parent() instanceof AST_Scope)) { 615 | AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", { 616 | name: node.name.name, 617 | type: tw.parent().TYPE, 618 | file: node.start.file, 619 | line: node.start.line, 620 | col: node.start.col 621 | }); 622 | } 623 | }); 624 | this.walk(tw); 625 | }); 626 | -------------------------------------------------------------------------------- /uglify/tojsw/scope.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | function SymbolDef(scope, index, orig) { 47 | this.name = orig.name; 48 | this.orig = [ orig ]; 49 | this.scope = scope; 50 | this.references = []; 51 | this.global = false; 52 | this.mangled_name = null; 53 | this.object_destructuring_arg = false; 54 | this.undeclared = false; 55 | this.constant = false; 56 | this.index = index; 57 | }; 58 | 59 | SymbolDef.prototype = { 60 | unmangleable: function(options) { 61 | if (!options) options = {}; 62 | 63 | return (this.global && !options.toplevel) 64 | || this.object_destructuring_arg 65 | || this.undeclared 66 | || (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) 67 | || (options.keep_fnames 68 | && (this.orig[0] instanceof AST_SymbolLambda 69 | || this.orig[0] instanceof AST_SymbolDefun)); 70 | }, 71 | mangle: function(options) { 72 | var cache = options.cache && options.cache.props; 73 | if (this.global && cache && cache.has(this.name)) { 74 | this.mangled_name = cache.get(this.name); 75 | } 76 | else if (!this.mangled_name && !this.unmangleable(options)) { 77 | var s = this.scope; 78 | if (!options.screw_ie8 && this.orig[0] instanceof AST_SymbolLambda) 79 | s = s.parent_scope; 80 | this.mangled_name = s.next_mangled(options, this); 81 | if (this.global && cache) { 82 | cache.set(this.name, this.mangled_name); 83 | } 84 | } 85 | } 86 | }; 87 | 88 | AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ 89 | options = defaults(options, { 90 | screw_ie8: false, 91 | cache: null 92 | }); 93 | 94 | // pass 1: setup scope chaining and handle definitions 95 | var self = this; 96 | var scope = self.parent_scope = null; 97 | var defun = null; 98 | var nesting = 0; 99 | var in_destructuring = null; 100 | var tw = new TreeWalker(function(node, descend){ 101 | if (options.screw_ie8 && node instanceof AST_Catch) { 102 | var save_scope = scope; 103 | scope = new AST_Scope(node); 104 | scope.init_scope_vars(nesting); 105 | scope.parent_scope = save_scope; 106 | descend(); 107 | scope = save_scope; 108 | return true; 109 | } 110 | if (node instanceof AST_Destructuring && node.is_array === false) { 111 | in_destructuring = node; // These don't nest 112 | descend(); 113 | in_destructuring = null; 114 | return true; 115 | } 116 | if (node instanceof AST_Scope) { 117 | node.init_scope_vars(nesting); 118 | var save_scope = node.parent_scope = scope; 119 | var save_defun = defun; 120 | defun = scope = node; 121 | ++nesting; descend(); --nesting; 122 | scope = save_scope; 123 | defun = save_defun; 124 | return true; // don't descend again in TreeWalker 125 | } 126 | if (node instanceof AST_Directive) { 127 | node.scope = scope; 128 | push_uniq(scope.directives, node.value); 129 | return true; 130 | } 131 | if (node instanceof AST_Number) { 132 | node.scope = scope; 133 | return true; 134 | } 135 | if (node instanceof AST_With) { 136 | for (var s = scope; s; s = s.parent_scope) 137 | s.uses_with = true; 138 | return; 139 | } 140 | if (node instanceof AST_Symbol) { 141 | node.scope = scope; 142 | } 143 | if (node instanceof AST_SymbolFunarg) { 144 | node.object_destructuring_arg = !!in_destructuring; 145 | defun.def_variable(node); 146 | } 147 | if (node instanceof AST_SymbolLambda) { 148 | defun.def_function(node); 149 | } 150 | else if (node instanceof AST_SymbolDefun) { 151 | // Careful here, the scope where this should be defined is 152 | // the parent scope. The reason is that we enter a new 153 | // scope when we encounter the AST_Defun node (which is 154 | // instanceof AST_Scope) but we get to the symbol a bit 155 | // later. 156 | (node.scope = defun.parent_scope).def_function(node); 157 | } 158 | else if (node instanceof AST_SymbolVar 159 | || node instanceof AST_SymbolConst) { 160 | var def = defun.def_variable(node); 161 | def.constant = node instanceof AST_SymbolConst; 162 | def.destructuring = in_destructuring; 163 | def.init = tw.parent().value; 164 | } 165 | else if (node instanceof AST_SymbolCatch) { 166 | (options.screw_ie8 ? scope : defun) 167 | .def_variable(node); 168 | } 169 | }); 170 | self.walk(tw); 171 | 172 | // pass 2: find back references and eval 173 | var func = null; 174 | var globals = self.globals = new Dictionary(); 175 | var tw = new TreeWalker(function(node, descend){ 176 | if (node instanceof AST_Lambda) { 177 | var prev_func = func; 178 | func = node; 179 | descend(); 180 | func = prev_func; 181 | return true; 182 | } 183 | if (node instanceof AST_SymbolRef) { 184 | var name = node.name; 185 | var sym = node.scope.find_variable(name); 186 | if (!sym) { 187 | var g; 188 | if (globals.has(name)) { 189 | g = globals.get(name); 190 | } else { 191 | g = new SymbolDef(self, globals.size(), node); 192 | g.undeclared = true; 193 | g.global = true; 194 | globals.set(name, g); 195 | } 196 | node.thedef = g; 197 | if (name == "eval" && tw.parent() instanceof AST_Call) { 198 | for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) 199 | s.uses_eval = true; 200 | } 201 | if (func && name == "arguments") { 202 | func.uses_arguments = true; 203 | } 204 | } else { 205 | node.thedef = sym; 206 | } 207 | node.reference(); 208 | return true; 209 | } 210 | }); 211 | self.walk(tw); 212 | 213 | if (options.cache) { 214 | this.cname = options.cache.cname; 215 | } 216 | }); 217 | 218 | AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ 219 | this.directives = []; // contains the directives defined in this scope, i.e. "use strict" 220 | this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) 221 | this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) 222 | this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement 223 | this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval` 224 | this.parent_scope = null; // the parent scope 225 | this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes 226 | this.cname = -1; // the current index for mangling functions/variables 227 | this.nesting = nesting; // the nesting level of this scope (0 means toplevel) 228 | }); 229 | 230 | AST_Scope.DEFMETHOD("strict", function(){ 231 | return this.has_directive("use strict"); 232 | }); 233 | 234 | AST_Lambda.DEFMETHOD("init_scope_vars", function(){ 235 | AST_Scope.prototype.init_scope_vars.apply(this, arguments); 236 | this.uses_arguments = false; 237 | }); 238 | 239 | AST_SymbolRef.DEFMETHOD("reference", function() { 240 | var def = this.definition(); 241 | def.references.push(this); 242 | var s = this.scope; 243 | while (s) { 244 | push_uniq(s.enclosed, def); 245 | if (s === def.scope) break; 246 | s = s.parent_scope; 247 | } 248 | this.frame = this.scope.nesting - def.scope.nesting; 249 | }); 250 | 251 | AST_Scope.DEFMETHOD("find_variable", function(name){ 252 | if (name instanceof AST_Symbol) name = name.name; 253 | return this.variables.get(name) 254 | || (this.parent_scope && this.parent_scope.find_variable(name)); 255 | }); 256 | 257 | AST_Scope.DEFMETHOD("has_directive", function(value){ 258 | return this.parent_scope && this.parent_scope.has_directive(value) 259 | || (this.directives.indexOf(value) >= 0 ? this : null); 260 | }); 261 | 262 | AST_Scope.DEFMETHOD("def_function", function(symbol){ 263 | this.functions.set(symbol.name, this.def_variable(symbol)); 264 | }); 265 | 266 | AST_Scope.DEFMETHOD("def_variable", function(symbol){ 267 | var def; 268 | if (!this.variables.has(symbol.name)) { 269 | def = new SymbolDef(this, this.variables.size(), symbol); 270 | this.variables.set(symbol.name, def); 271 | def.object_destructuring_arg = symbol.object_destructuring_arg; 272 | def.global = !this.parent_scope; 273 | } else { 274 | def = this.variables.get(symbol.name); 275 | def.orig.push(symbol); 276 | } 277 | return symbol.thedef = def; 278 | }); 279 | 280 | AST_Scope.DEFMETHOD("next_mangled", function(options){ 281 | var ext = this.enclosed; 282 | out: while (true) { 283 | var m = base54(++this.cname); 284 | if (!is_identifier(m)) continue; // skip over "do" 285 | 286 | // https://github.com/mishoo/UglifyJS2/issues/242 -- do not 287 | // shadow a name excepted from mangling. 288 | if (options.except.indexOf(m) >= 0) continue; 289 | 290 | // we must ensure that the mangled name does not shadow a name 291 | // from some parent scope that is referenced in this or in 292 | // inner scopes. 293 | for (var i = ext.length; --i >= 0;) { 294 | var sym = ext[i]; 295 | var name = sym.mangled_name || (sym.unmangleable(options) && sym.name); 296 | if (m == name) continue out; 297 | } 298 | return m; 299 | } 300 | }); 301 | 302 | AST_Function.DEFMETHOD("next_mangled", function(options, def){ 303 | // #179, #326 304 | // in Safari strict mode, something like (function x(x){...}) is a syntax error; 305 | // a function expression's argument cannot shadow the function expression's name 306 | 307 | var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition(); 308 | while (true) { 309 | var name = AST_Lambda.prototype.next_mangled.call(this, options, def); 310 | if (!(tricky_def && tricky_def.mangled_name == name)) 311 | return name; 312 | } 313 | }); 314 | 315 | AST_Scope.DEFMETHOD("references", function(sym){ 316 | if (sym instanceof AST_Symbol) sym = sym.definition(); 317 | return this.enclosed.indexOf(sym) < 0 ? null : sym; 318 | }); 319 | 320 | AST_Symbol.DEFMETHOD("unmangleable", function(options){ 321 | return this.definition().unmangleable(options); 322 | }); 323 | 324 | // property accessors are not mangleable 325 | AST_SymbolAccessor.DEFMETHOD("unmangleable", function(){ 326 | return true; 327 | }); 328 | 329 | // labels are always mangleable 330 | AST_Label.DEFMETHOD("unmangleable", function(){ 331 | return false; 332 | }); 333 | 334 | AST_Symbol.DEFMETHOD("unreferenced", function(){ 335 | return this.definition().references.length == 0 336 | && !(this.scope.uses_eval || this.scope.uses_with); 337 | }); 338 | 339 | AST_Symbol.DEFMETHOD("undeclared", function(){ 340 | return this.definition().undeclared; 341 | }); 342 | 343 | AST_LabelRef.DEFMETHOD("undeclared", function(){ 344 | return false; 345 | }); 346 | 347 | AST_Label.DEFMETHOD("undeclared", function(){ 348 | return false; 349 | }); 350 | 351 | AST_Symbol.DEFMETHOD("definition", function(){ 352 | return this.thedef; 353 | }); 354 | 355 | AST_Symbol.DEFMETHOD("global", function(){ 356 | return this.definition().global; 357 | }); 358 | 359 | AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ 360 | return defaults(options, { 361 | except : [], 362 | eval : false, 363 | sort : false, 364 | toplevel : false, 365 | screw_ie8 : false, 366 | keep_fnames : false 367 | }); 368 | }); 369 | 370 | AST_Toplevel.DEFMETHOD("mangle_names", function(options){ 371 | options = this._default_mangler_options(options); 372 | // We only need to mangle declaration nodes. Special logic wired 373 | // into the code generator will display the mangled name if it's 374 | // present (and for AST_SymbolRef-s it'll use the mangled name of 375 | // the AST_SymbolDeclaration that it points to). 376 | var lname = -1; 377 | var to_mangle = []; 378 | 379 | if (options.cache) { 380 | this.globals.each(function(symbol){ 381 | if (options.except.indexOf(symbol.name) < 0) { 382 | to_mangle.push(symbol); 383 | } 384 | }); 385 | } 386 | 387 | var tw = new TreeWalker(function(node, descend){ 388 | if (node instanceof AST_LabeledStatement) { 389 | // lname is incremented when we get to the AST_Label 390 | var save_nesting = lname; 391 | descend(); 392 | lname = save_nesting; 393 | return true; // don't descend again in TreeWalker 394 | } 395 | if (node instanceof AST_Scope) { 396 | var p = tw.parent(), a = []; 397 | node.variables.each(function(symbol){ 398 | if (options.except.indexOf(symbol.name) < 0) { 399 | a.push(symbol); 400 | } 401 | }); 402 | if (options.sort) a.sort(function(a, b){ 403 | return b.references.length - a.references.length; 404 | }); 405 | to_mangle.push.apply(to_mangle, a); 406 | return; 407 | } 408 | if (node instanceof AST_Label) { 409 | var name; 410 | do name = base54(++lname); while (!is_identifier(name)); 411 | node.mangled_name = name; 412 | return true; 413 | } 414 | if (options.screw_ie8 && node instanceof AST_SymbolCatch) { 415 | to_mangle.push(node.definition()); 416 | return; 417 | } 418 | }); 419 | this.walk(tw); 420 | to_mangle.forEach(function(def){ 421 | if (def.destructuring && !def.destructuring.is_array) return; 422 | def.mangle(options); 423 | }); 424 | 425 | if (options.cache) { 426 | options.cache.cname = this.cname; 427 | } 428 | }); 429 | 430 | AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ 431 | options = this._default_mangler_options(options); 432 | var tw = new TreeWalker(function(node){ 433 | if (node instanceof AST_Constant) 434 | base54.consider(node.print_to_string()); 435 | else if (node instanceof AST_Return) 436 | base54.consider("return"); 437 | else if (node instanceof AST_Throw) 438 | base54.consider("throw"); 439 | else if (node instanceof AST_Continue) 440 | base54.consider("continue"); 441 | else if (node instanceof AST_Break) 442 | base54.consider("break"); 443 | else if (node instanceof AST_Debugger) 444 | base54.consider("debugger"); 445 | else if (node instanceof AST_Directive) 446 | base54.consider(node.value); 447 | else if (node instanceof AST_While) 448 | base54.consider("while"); 449 | else if (node instanceof AST_Do) 450 | base54.consider("do while"); 451 | else if (node instanceof AST_If) { 452 | base54.consider("if"); 453 | if (node.alternative) base54.consider("else"); 454 | } 455 | else if (node instanceof AST_Var) 456 | base54.consider("var"); 457 | else if (node instanceof AST_Const) 458 | base54.consider("const"); 459 | else if (node instanceof AST_Lambda) 460 | base54.consider("function"); 461 | else if (node instanceof AST_For) 462 | base54.consider("for"); 463 | else if (node instanceof AST_ForIn) 464 | base54.consider("for in"); 465 | else if (node instanceof AST_Switch) 466 | base54.consider("switch"); 467 | else if (node instanceof AST_Case) 468 | base54.consider("case"); 469 | else if (node instanceof AST_Default) 470 | base54.consider("default"); 471 | else if (node instanceof AST_With) 472 | base54.consider("with"); 473 | else if (node instanceof AST_ObjectSetter) 474 | base54.consider("set" + node.key); 475 | else if (node instanceof AST_ObjectGetter) 476 | base54.consider("get" + node.key); 477 | else if (node instanceof AST_ObjectKeyVal) 478 | base54.consider(node.key); 479 | else if (node instanceof AST_New) 480 | base54.consider("new"); 481 | else if (node instanceof AST_This) 482 | base54.consider("this"); 483 | else if (node instanceof AST_Super) 484 | base54.consider("super"); 485 | else if (node instanceof AST_Try) 486 | base54.consider("try"); 487 | else if (node instanceof AST_Catch) 488 | base54.consider("catch"); 489 | else if (node instanceof AST_Finally) 490 | base54.consider("finally"); 491 | else if (node instanceof AST_Symbol && node.unmangleable(options)) 492 | base54.consider(node.name); 493 | else if (node instanceof AST_Unary || node instanceof AST_Binary) 494 | base54.consider(node.operator); 495 | else if (node instanceof AST_Dot) 496 | base54.consider(node.property); 497 | }); 498 | this.walk(tw); 499 | base54.sort(); 500 | }); 501 | 502 | var base54 = (function() { 503 | var string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_0123456789"; 504 | var chars, frequency; 505 | function reset() { 506 | frequency = Object.create(null); 507 | chars = string.split("").map(function(ch){ return ch.charCodeAt(0) }); 508 | chars.forEach(function(ch){ frequency[ch] = 0 }); 509 | } 510 | base54.consider = function(str){ 511 | for (var i = str.length; --i >= 0;) { 512 | var code = str.charCodeAt(i); 513 | if (code in frequency) ++frequency[code]; 514 | } 515 | }; 516 | base54.sort = function() { 517 | chars = mergeSort(chars, function(a, b){ 518 | if (is_digit(a) && !is_digit(b)) return 1; 519 | if (is_digit(b) && !is_digit(a)) return -1; 520 | return frequency[b] - frequency[a]; 521 | }); 522 | }; 523 | base54.reset = reset; 524 | reset(); 525 | base54.get = function(){ return chars }; 526 | base54.freq = function(){ return frequency }; 527 | function base54(num) { 528 | var ret = "", base = 54; 529 | num++; 530 | do { 531 | num--; 532 | ret += String.fromCharCode(chars[num % base]); 533 | num = Math.floor(num / base); 534 | base = 64; 535 | } while (num > 0); 536 | return ret; 537 | }; 538 | return base54; 539 | })(); 540 | 541 | AST_Toplevel.DEFMETHOD("scope_warnings", function(options){ 542 | options = defaults(options, { 543 | undeclared : false, // this makes a lot of noise 544 | unreferenced : true, 545 | assign_to_global : true, 546 | func_arguments : true, 547 | nested_defuns : true, 548 | eval : true 549 | }); 550 | var tw = new TreeWalker(function(node){ 551 | if (options.undeclared 552 | && node instanceof AST_SymbolRef 553 | && node.undeclared()) 554 | { 555 | // XXX: this also warns about JS standard names, 556 | // i.e. Object, Array, parseInt etc. Should add a list of 557 | // exceptions. 558 | AST_Node.warn("Undeclared symbol: {name} [{file}:{line},{col}]", { 559 | name: node.name, 560 | file: node.start.file, 561 | line: node.start.line, 562 | col: node.start.col 563 | }); 564 | } 565 | if (options.assign_to_global) 566 | { 567 | var sym = null; 568 | if (node instanceof AST_Assign && node.left instanceof AST_SymbolRef) 569 | sym = node.left; 570 | else if (node instanceof AST_ForIn && node.init instanceof AST_SymbolRef) 571 | sym = node.init; 572 | if (sym 573 | && (sym.undeclared() 574 | || (sym.global() && sym.scope !== sym.definition().scope))) { 575 | AST_Node.warn("{msg}: {name} [{file}:{line},{col}]", { 576 | msg: sym.undeclared() ? "Accidental global?" : "Assignment to global", 577 | name: sym.name, 578 | file: sym.start.file, 579 | line: sym.start.line, 580 | col: sym.start.col 581 | }); 582 | } 583 | } 584 | if (options.eval 585 | && node instanceof AST_SymbolRef 586 | && node.undeclared() 587 | && node.name == "eval") { 588 | AST_Node.warn("Eval is used [{file}:{line},{col}]", node.start); 589 | } 590 | if (options.unreferenced 591 | && (node instanceof AST_SymbolDeclaration || node instanceof AST_Label) 592 | && !(node instanceof AST_SymbolCatch) 593 | && node.unreferenced()) { 594 | AST_Node.warn("{type} {name} is declared but not referenced [{file}:{line},{col}]", { 595 | type: node instanceof AST_Label ? "Label" : "Symbol", 596 | name: node.name, 597 | file: node.start.file, 598 | line: node.start.line, 599 | col: node.start.col 600 | }); 601 | } 602 | if (options.func_arguments 603 | && node instanceof AST_Lambda 604 | && node.uses_arguments) { 605 | AST_Node.warn("arguments used in function {name} [{file}:{line},{col}]", { 606 | name: node.name ? node.name.name : "anonymous", 607 | file: node.start.file, 608 | line: node.start.line, 609 | col: node.start.col 610 | }); 611 | } 612 | if (options.nested_defuns 613 | && node instanceof AST_Defun 614 | && !(tw.parent() instanceof AST_Scope)) { 615 | AST_Node.warn("Function {name} declared in nested statement \"{type}\" [{file}:{line},{col}]", { 616 | name: node.name.name, 617 | type: tw.parent().TYPE, 618 | file: node.start.file, 619 | line: node.start.line, 620 | col: node.start.col 621 | }); 622 | } 623 | }); 624 | this.walk(tw); 625 | }); 626 | -------------------------------------------------------------------------------- /uglify/tojsw/ast.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | function DEFNODE(type, props, methods, base) { 47 | if (arguments.length < 4) base = AST_Node; 48 | if (!props) props = []; 49 | else props = props.split(/\s+/); 50 | var self_props = props; 51 | if (base && base.PROPS) 52 | props = props.concat(base.PROPS); 53 | var code = "return function AST_" + type + "(props){ if (props) { "; 54 | for (var i = props.length; --i >= 0;) { 55 | code += "this." + props[i] + " = props." + props[i] + ";"; 56 | } 57 | var proto = base && new base; 58 | if (proto && proto.initialize || (methods && methods.initialize)) 59 | code += "this.initialize();"; 60 | code += "}}"; 61 | var ctor = new Function(code)(); 62 | if (proto) { 63 | ctor.prototype = proto; 64 | ctor.BASE = base; 65 | } 66 | if (base) base.SUBCLASSES.push(ctor); 67 | ctor.prototype.CTOR = ctor; 68 | ctor.PROPS = props || null; 69 | ctor.SELF_PROPS = self_props; 70 | ctor.SUBCLASSES = []; 71 | if (type) { 72 | ctor.prototype.TYPE = ctor.TYPE = type; 73 | } 74 | if (methods) for (i in methods) if (methods.hasOwnProperty(i)) { 75 | if (/^\$/.test(i)) { 76 | ctor[i.substr(1)] = methods[i]; 77 | } else { 78 | ctor.prototype[i] = methods[i]; 79 | } 80 | } 81 | ctor.DEFMETHOD = function(name, method) { 82 | this.prototype[name] = method; 83 | }; 84 | exports["AST_" + type] = ctor; 85 | return ctor; 86 | }; 87 | 88 | var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", { 89 | }, null); 90 | 91 | var AST_Node = DEFNODE("Node", "start end", { 92 | clone: function() { 93 | return new this.CTOR(this); 94 | }, 95 | $documentation: "Base class of all AST nodes", 96 | $propdoc: { 97 | start: "[AST_Token] The first token of this node", 98 | end: "[AST_Token] The last token of this node" 99 | }, 100 | _walk: function(visitor) { 101 | return visitor._visit(this); 102 | }, 103 | walk: function(visitor) { 104 | return this._walk(visitor); // not sure the indirection will be any help 105 | } 106 | }, null); 107 | 108 | AST_Node.warn_function = null; 109 | AST_Node.warn = function(txt, props) { 110 | if (AST_Node.warn_function) 111 | AST_Node.warn_function(string_template(txt, props)); 112 | }; 113 | 114 | /* -----[ statements ]----- */ 115 | 116 | var AST_Statement = DEFNODE("Statement", null, { 117 | $documentation: "Base class of all statements", 118 | }); 119 | 120 | var AST_Debugger = DEFNODE("Debugger", null, { 121 | $documentation: "Represents a debugger statement", 122 | }, AST_Statement); 123 | 124 | var AST_Directive = DEFNODE("Directive", "value scope quote", { 125 | $documentation: "Represents a directive, like \"use strict\";", 126 | $propdoc: { 127 | value: "[string] The value of this directive as a plain string (it's not an AST_String!)", 128 | scope: "[AST_Scope/S] The scope that this directive affects", 129 | quote: "[string] the original quote character" 130 | }, 131 | }, AST_Statement); 132 | 133 | var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { 134 | $documentation: "A statement consisting of an expression, i.e. a = 1 + 2", 135 | $propdoc: { 136 | body: "[AST_Node] an expression node (should not be instanceof AST_Statement)" 137 | }, 138 | _walk: function(visitor) { 139 | return visitor._visit(this, function(){ 140 | this.body._walk(visitor); 141 | }); 142 | } 143 | }, AST_Statement); 144 | 145 | function walk_body(node, visitor) { 146 | if (node.body instanceof AST_Node) { 147 | node.body._walk(visitor); 148 | } 149 | else node.body.forEach(function(stat){ 150 | stat._walk(visitor); 151 | }); 152 | }; 153 | 154 | var AST_Block = DEFNODE("Block", "body", { 155 | $documentation: "A body of statements (usually bracketed)", 156 | $propdoc: { 157 | body: "[AST_Statement*] an array of statements" 158 | }, 159 | _walk: function(visitor) { 160 | return visitor._visit(this, function(){ 161 | walk_body(this, visitor); 162 | }); 163 | } 164 | }, AST_Statement); 165 | 166 | var AST_BlockStatement = DEFNODE("BlockStatement", null, { 167 | $documentation: "A block statement", 168 | }, AST_Block); 169 | 170 | var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { 171 | $documentation: "The empty statement (empty block or simply a semicolon)", 172 | _walk: function(visitor) { 173 | return visitor._visit(this); 174 | } 175 | }, AST_Statement); 176 | 177 | var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { 178 | $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", 179 | $propdoc: { 180 | body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" 181 | }, 182 | _walk: function(visitor) { 183 | return visitor._visit(this, function(){ 184 | this.body._walk(visitor); 185 | }); 186 | } 187 | }, AST_Statement); 188 | 189 | var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { 190 | $documentation: "Statement with a label", 191 | $propdoc: { 192 | label: "[AST_Label] a label definition" 193 | }, 194 | _walk: function(visitor) { 195 | return visitor._visit(this, function(){ 196 | this.label._walk(visitor); 197 | this.body._walk(visitor); 198 | }); 199 | } 200 | }, AST_StatementWithBody); 201 | 202 | var AST_IterationStatement = DEFNODE("IterationStatement", null, { 203 | $documentation: "Internal class. All loops inherit from it." 204 | }, AST_StatementWithBody); 205 | 206 | var AST_DWLoop = DEFNODE("DWLoop", "condition", { 207 | $documentation: "Base class for do/while statements", 208 | $propdoc: { 209 | condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" 210 | } 211 | }, AST_IterationStatement); 212 | 213 | var AST_Do = DEFNODE("Do", null, { 214 | $documentation: "A `do` statement", 215 | _walk: function(visitor) { 216 | return visitor._visit(this, function(){ 217 | this.body._walk(visitor); 218 | this.condition._walk(visitor); 219 | }); 220 | } 221 | }, AST_DWLoop); 222 | 223 | var AST_While = DEFNODE("While", null, { 224 | $documentation: "A `while` statement", 225 | _walk: function(visitor) { 226 | return visitor._visit(this, function(){ 227 | this.condition._walk(visitor); 228 | this.body._walk(visitor); 229 | }); 230 | } 231 | }, AST_DWLoop); 232 | 233 | var AST_For = DEFNODE("For", "init condition step", { 234 | $documentation: "A `for` statement", 235 | $propdoc: { 236 | init: "[AST_Node?] the `for` initialization code, or null if empty", 237 | condition: "[AST_Node?] the `for` termination clause, or null if empty", 238 | step: "[AST_Node?] the `for` update clause, or null if empty" 239 | }, 240 | _walk: function(visitor) { 241 | return visitor._visit(this, function(){ 242 | if (this.init) this.init._walk(visitor); 243 | if (this.condition) this.condition._walk(visitor); 244 | if (this.step) this.step._walk(visitor); 245 | this.body._walk(visitor); 246 | }); 247 | } 248 | }, AST_IterationStatement); 249 | 250 | var AST_ForIn = DEFNODE("ForIn", "init name object", { 251 | $documentation: "A `for ... in` statement", 252 | $propdoc: { 253 | init: "[AST_Node] the `for/in` initialization code", 254 | name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", 255 | object: "[AST_Node] the object that we're looping through" 256 | }, 257 | _walk: function(visitor) { 258 | return visitor._visit(this, function(){ 259 | this.init._walk(visitor); 260 | this.object._walk(visitor); 261 | this.body._walk(visitor); 262 | }); 263 | } 264 | }, AST_IterationStatement); 265 | 266 | var AST_ForOf = DEFNODE("ForOf", null, { 267 | $documentation: "A `for ... of` statement", 268 | }, AST_ForIn); 269 | 270 | var AST_With = DEFNODE("With", "expression", { 271 | $documentation: "A `with` statement", 272 | $propdoc: { 273 | expression: "[AST_Node] the `with` expression" 274 | }, 275 | _walk: function(visitor) { 276 | return visitor._visit(this, function(){ 277 | this.expression._walk(visitor); 278 | this.body._walk(visitor); 279 | }); 280 | } 281 | }, AST_StatementWithBody); 282 | 283 | /* -----[ scope and functions ]----- */ 284 | 285 | var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", { 286 | $documentation: "Base class for all statements introducing a lexical scope", 287 | $propdoc: { 288 | directives: "[string*/S] an array of directives declared in this scope", 289 | variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope", 290 | functions: "[Object/S] like `variables`, but only lists function declarations", 291 | uses_with: "[boolean/S] tells whether this scope uses the `with` statement", 292 | uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`", 293 | parent_scope: "[AST_Scope?/S] link to the parent scope", 294 | enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", 295 | cname: "[integer/S] current index for mangling variables (used internally by the mangler)", 296 | }, 297 | }, AST_Block); 298 | 299 | var AST_Toplevel = DEFNODE("Toplevel", "globals", { 300 | $documentation: "The toplevel scope", 301 | $propdoc: { 302 | globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", 303 | }, 304 | wrap_enclose: function(arg_parameter_pairs) { 305 | var self = this; 306 | var args = []; 307 | var parameters = []; 308 | 309 | arg_parameter_pairs.forEach(function(pair) { 310 | var splitAt = pair.lastIndexOf(":"); 311 | 312 | args.push(pair.substr(0, splitAt)); 313 | parameters.push(pair.substr(splitAt + 1)); 314 | }); 315 | 316 | var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")"; 317 | wrapped_tl = parse(wrapped_tl); 318 | wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ 319 | if (node instanceof AST_Directive && node.value == "$ORIG") { 320 | return MAP.splice(self.body); 321 | } 322 | })); 323 | return wrapped_tl; 324 | }, 325 | wrap_commonjs: function(name, export_all) { 326 | var self = this; 327 | var to_export = []; 328 | if (export_all) { 329 | self.figure_out_scope(); 330 | self.walk(new TreeWalker(function(node){ 331 | if (node instanceof AST_SymbolDeclaration && node.definition().global) { 332 | if (!find_if(function(n){ return n.name == node.name }, to_export)) 333 | to_export.push(node); 334 | } 335 | })); 336 | } 337 | var wrapped_tl = "(function(exports, global){ '$ORIG'; '$EXPORTS'; global['" + name + "'] = exports; }({}, (function(){return this}())))"; 338 | wrapped_tl = parse(wrapped_tl); 339 | wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ 340 | if (node instanceof AST_Directive) { 341 | switch (node.value) { 342 | case "$ORIG": 343 | return MAP.splice(self.body); 344 | case "$EXPORTS": 345 | var body = []; 346 | to_export.forEach(function(sym){ 347 | body.push(new AST_SimpleStatement({ 348 | body: new AST_Assign({ 349 | left: new AST_Sub({ 350 | expression: new AST_SymbolRef({ name: "exports" }), 351 | property: new AST_String({ value: sym.name }), 352 | }), 353 | operator: "=", 354 | right: new AST_SymbolRef(sym), 355 | }), 356 | })); 357 | }); 358 | return MAP.splice(body); 359 | } 360 | } 361 | })); 362 | return wrapped_tl; 363 | } 364 | }, AST_Scope); 365 | 366 | var AST_Expansion = DEFNODE("Expansion", "symbol", { 367 | $documentation: "An expandible argument, such as ...rest, a splat, such as [1,2,...all], or an expansion in a variable declaration, such as var [first, ...rest] = list", 368 | $propdoc: { 369 | symbol: "AST_Symbol the thing to be expanded" 370 | }, 371 | _walk: function(visitor) { 372 | var self = this; 373 | return visitor._visit(this, function(){ 374 | self.symbol.walk(visitor); 375 | }); 376 | } 377 | }); 378 | 379 | var AST_ArrowParametersOrSeq = DEFNODE("ArrowParametersOrSeq", "expressions", { 380 | $documentation: "A set of arrow function parameters or a sequence expression. This is used because when the parser sees a \"(\" it could be the start of a seq, or the start of a parameter list of an arrow function.", 381 | $propdoc: { 382 | expressions: "[AST_Expression|AST_Destructuring|AST_Expansion*] array of expressions or argument names or destructurings." 383 | }, 384 | as_params: function (croak) { 385 | // We don't want anything which doesn't belong in a destructuring 386 | var root = this; 387 | return this.expressions.map(function to_fun_args(ex) { 388 | if (ex instanceof AST_Object) { 389 | if (ex.properties.length == 0) 390 | croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); 391 | return new AST_Destructuring({ 392 | start: ex.start, 393 | end: ex.end, 394 | is_array: false, 395 | names: ex.properties.map(to_fun_args) 396 | }); 397 | } else if (ex instanceof AST_ObjectSymbol) { 398 | return new AST_SymbolFunarg({ 399 | name: ex.symbol.name, 400 | start: ex.start, 401 | end: ex.end 402 | }); 403 | } else if (ex instanceof AST_Destructuring) { 404 | if (ex.names.length == 0) 405 | croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); 406 | ex.names = ex.names.map(to_fun_args); 407 | return ex; 408 | } else if (ex instanceof AST_SymbolRef) { 409 | return new AST_SymbolFunarg({ 410 | name: ex.name, 411 | start: ex.start, 412 | end: ex.end 413 | }); 414 | } else if (ex instanceof AST_Expansion) { 415 | return ex; 416 | } else if (ex instanceof AST_Array) { 417 | if (ex.elements.length === 0) 418 | croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); 419 | return new AST_Destructuring({ 420 | start: ex.start, 421 | end: ex.end, 422 | is_array: true, 423 | names: ex.elements.map(to_fun_args) 424 | }); 425 | } else { 426 | croak("Invalid function parameter", ex.start.line, ex.start.col); 427 | } 428 | }); 429 | }, 430 | as_expr: function (croak) { 431 | return AST_Seq.from_array(this.expressions); 432 | } 433 | }); 434 | 435 | var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { 436 | $documentation: "Base class for functions", 437 | $propdoc: { 438 | name: "[AST_SymbolDeclaration?] the name of this function", 439 | argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion*] array of function arguments, destructurings, or expanding arguments", 440 | uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" 441 | }, 442 | args_as_names: function () { 443 | var out = []; 444 | for (var i = 0; i < this.argnames.length; i++) { 445 | if (this.argnames[i] instanceof AST_Destructuring) { 446 | out = out.concat(this.argnames[i].all_symbols()); 447 | } else { 448 | out.push(this.argnames[i]); 449 | } 450 | } 451 | return out; 452 | }, 453 | _walk: function(visitor) { 454 | return visitor._visit(this, function(){ 455 | if (this.name) this.name._walk(visitor); 456 | this.argnames.forEach(function(arg){ 457 | arg._walk(visitor); 458 | }); 459 | walk_body(this, visitor); 460 | }); 461 | } 462 | }, AST_Scope); 463 | 464 | var AST_Accessor = DEFNODE("Accessor", null, { 465 | $documentation: "A setter/getter function. The `name` property is always null." 466 | }, AST_Lambda); 467 | 468 | var AST_Function = DEFNODE("Function", null, { 469 | $documentation: "A function expression" 470 | }, AST_Lambda); 471 | 472 | var AST_Arrow = DEFNODE("Arrow", null, { 473 | $documentation: "An ES6 Arrow function ((a) => b)" 474 | }, AST_Lambda); 475 | 476 | var AST_Defun = DEFNODE("Defun", null, { 477 | $documentation: "A function definition" 478 | }, AST_Lambda); 479 | 480 | /* -----[ DESTRUCTURING ]----- */ 481 | var AST_Destructuring = DEFNODE("Destructuring", "names is_array", { 482 | $documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names", 483 | _walk: function(visitor) { 484 | return visitor._visit(this, function(){ 485 | this.names.forEach(function(name){ 486 | name._walk(visitor); 487 | }); 488 | }); 489 | }, 490 | all_symbols: function() { 491 | var out = []; 492 | this.walk(new TreeWalker(function (node) { 493 | if (node instanceof AST_Symbol) { 494 | out.push(node); 495 | } 496 | if (node instanceof AST_Expansion) { 497 | out.push(node.symbol); 498 | } 499 | })); 500 | return out; 501 | } 502 | }); 503 | 504 | var AST_PrefixedTemplateString = DEFNODE("PrefixedTemplateString", "template_string prefix", { 505 | $documentation: "A templatestring with a prefix, such as String.raw`foobarbaz`", 506 | $propdoc: { 507 | template_string: "[AST_TemplateString] The template string", 508 | prefix: "[AST_SymbolRef|AST_PropAccess] The prefix, which can be a symbol such as `foo` or a dotted expression such as `String.raw`." 509 | }, 510 | _walk: function(visitor) { 511 | this.prefix._walk(visitor); 512 | this.template_string._walk(visitor); 513 | } 514 | }) 515 | 516 | var AST_TemplateString = DEFNODE("TemplateString", "segments", { 517 | $documentation: "A template string literal", 518 | $propdoc: { 519 | segments: "[string|AST_Expression]* One or more segments. They can be the parts that are evaluated, or the raw string parts." 520 | }, 521 | _walk: function(visitor) { 522 | return visitor._visit(this, function(){ 523 | this.segments.forEach(function(seg, i){ 524 | if (i % 2 !== 0) { 525 | seg._walk(visitor); 526 | } 527 | }); 528 | }); 529 | } 530 | }); 531 | 532 | /* -----[ JUMPS ]----- */ 533 | 534 | var AST_Jump = DEFNODE("Jump", null, { 535 | $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)" 536 | }, AST_Statement); 537 | 538 | var AST_Exit = DEFNODE("Exit", "value", { 539 | $documentation: "Base class for “exits” (`return` and `throw`)", 540 | $propdoc: { 541 | value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return" 542 | }, 543 | _walk: function(visitor) { 544 | return visitor._visit(this, this.value && function(){ 545 | this.value._walk(visitor); 546 | }); 547 | } 548 | }, AST_Jump); 549 | 550 | var AST_Return = DEFNODE("Return", null, { 551 | $documentation: "A `return` statement" 552 | }, AST_Exit); 553 | 554 | var AST_Throw = DEFNODE("Throw", null, { 555 | $documentation: "A `throw` statement" 556 | }, AST_Exit); 557 | 558 | var AST_LoopControl = DEFNODE("LoopControl", "label", { 559 | $documentation: "Base class for loop control statements (`break` and `continue`)", 560 | $propdoc: { 561 | label: "[AST_LabelRef?] the label, or null if none", 562 | }, 563 | _walk: function(visitor) { 564 | return visitor._visit(this, this.label && function(){ 565 | this.label._walk(visitor); 566 | }); 567 | } 568 | }, AST_Jump); 569 | 570 | var AST_Break = DEFNODE("Break", null, { 571 | $documentation: "A `break` statement" 572 | }, AST_LoopControl); 573 | 574 | var AST_Continue = DEFNODE("Continue", null, { 575 | $documentation: "A `continue` statement" 576 | }, AST_LoopControl); 577 | 578 | /* -----[ IF ]----- */ 579 | 580 | var AST_If = DEFNODE("If", "condition alternative", { 581 | $documentation: "A `if` statement", 582 | $propdoc: { 583 | condition: "[AST_Node] the `if` condition", 584 | alternative: "[AST_Statement?] the `else` part, or null if not present" 585 | }, 586 | _walk: function(visitor) { 587 | return visitor._visit(this, function(){ 588 | this.condition._walk(visitor); 589 | this.body._walk(visitor); 590 | if (this.alternative) this.alternative._walk(visitor); 591 | }); 592 | } 593 | }, AST_StatementWithBody); 594 | 595 | /* -----[ SWITCH ]----- */ 596 | 597 | var AST_Switch = DEFNODE("Switch", "expression", { 598 | $documentation: "A `switch` statement", 599 | $propdoc: { 600 | expression: "[AST_Node] the `switch` “discriminant”" 601 | }, 602 | _walk: function(visitor) { 603 | return visitor._visit(this, function(){ 604 | this.expression._walk(visitor); 605 | walk_body(this, visitor); 606 | }); 607 | } 608 | }, AST_Block); 609 | 610 | var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { 611 | $documentation: "Base class for `switch` branches", 612 | }, AST_Block); 613 | 614 | var AST_Default = DEFNODE("Default", null, { 615 | $documentation: "A `default` switch branch", 616 | }, AST_SwitchBranch); 617 | 618 | var AST_Case = DEFNODE("Case", "expression", { 619 | $documentation: "A `case` switch branch", 620 | $propdoc: { 621 | expression: "[AST_Node] the `case` expression" 622 | }, 623 | _walk: function(visitor) { 624 | return visitor._visit(this, function(){ 625 | this.expression._walk(visitor); 626 | walk_body(this, visitor); 627 | }); 628 | } 629 | }, AST_SwitchBranch); 630 | 631 | /* -----[ EXCEPTIONS ]----- */ 632 | 633 | var AST_Try = DEFNODE("Try", "bcatch bfinally", { 634 | $documentation: "A `try` statement", 635 | $propdoc: { 636 | bcatch: "[AST_Catch?] the catch block, or null if not present", 637 | bfinally: "[AST_Finally?] the finally block, or null if not present" 638 | }, 639 | _walk: function(visitor) { 640 | return visitor._visit(this, function(){ 641 | walk_body(this, visitor); 642 | if (this.bcatch) this.bcatch._walk(visitor); 643 | if (this.bfinally) this.bfinally._walk(visitor); 644 | }); 645 | } 646 | }, AST_Block); 647 | 648 | var AST_Catch = DEFNODE("Catch", "argname", { 649 | $documentation: "A `catch` node; only makes sense as part of a `try` statement", 650 | $propdoc: { 651 | argname: "[AST_SymbolCatch] symbol for the exception" 652 | }, 653 | _walk: function(visitor) { 654 | return visitor._visit(this, function(){ 655 | this.argname._walk(visitor); 656 | walk_body(this, visitor); 657 | }); 658 | } 659 | }, AST_Block); 660 | 661 | var AST_Finally = DEFNODE("Finally", null, { 662 | $documentation: "A `finally` node; only makes sense as part of a `try` statement" 663 | }, AST_Block); 664 | 665 | /* -----[ VAR/CONST ]----- */ 666 | 667 | var AST_Definitions = DEFNODE("Definitions", "definitions", { 668 | $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", 669 | $propdoc: { 670 | definitions: "[AST_VarDef*] array of variable definitions" 671 | }, 672 | _walk: function(visitor) { 673 | return visitor._visit(this, function(){ 674 | this.definitions.forEach(function(def){ 675 | def._walk(visitor); 676 | }); 677 | }); 678 | } 679 | }, AST_Statement); 680 | 681 | var AST_Var = DEFNODE("Var", null, { 682 | $documentation: "A `var` statement" 683 | }, AST_Definitions); 684 | 685 | var AST_Let = DEFNODE("Let", null, { 686 | $documentation: "A `let` statement" 687 | }, AST_Definitions); 688 | 689 | var AST_Const = DEFNODE("Const", null, { 690 | $documentation: "A `const` statement" 691 | }, AST_Definitions); 692 | 693 | var AST_VarDef = DEFNODE("VarDef", "name value", { 694 | $documentation: "A variable declaration; only appears in a AST_Definitions node", 695 | $propdoc: { 696 | name: "[AST_SymbolVar|AST_SymbolConst|AST_Destructuring] name of the variable", 697 | value: "[AST_Node?] initializer, or null of there's no initializer" 698 | }, 699 | is_destructuring: function() { 700 | return this.name instanceof AST_Destructuring; 701 | }, 702 | _walk: function(visitor) { 703 | return visitor._visit(this, function(){ 704 | this.name._walk(visitor); 705 | if (this.value) this.value._walk(visitor); 706 | }); 707 | } 708 | }); 709 | 710 | /* -----[ OTHER ]----- */ 711 | 712 | var AST_Call = DEFNODE("Call", "expression args", { 713 | $documentation: "A function call expression", 714 | $propdoc: { 715 | expression: "[AST_Node] expression to invoke as function", 716 | args: "[AST_Node*] array of arguments" 717 | }, 718 | _walk: function(visitor) { 719 | return visitor._visit(this, function(){ 720 | this.expression._walk(visitor); 721 | this.args.forEach(function(arg){ 722 | arg._walk(visitor); 723 | }); 724 | }); 725 | } 726 | }); 727 | 728 | var AST_New = DEFNODE("New", null, { 729 | $documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" 730 | }, AST_Call); 731 | 732 | var AST_Seq = DEFNODE("Seq", "car cdr", { 733 | $documentation: "A sequence expression (two comma-separated expressions)", 734 | $propdoc: { 735 | car: "[AST_Node] first element in sequence", 736 | cdr: "[AST_Node] second element in sequence" 737 | }, 738 | $cons: function(x, y) { 739 | var seq = new AST_Seq(x); 740 | seq.car = x; 741 | seq.cdr = y; 742 | return seq; 743 | }, 744 | $from_array: function(array) { 745 | if (array.length == 0) return null; 746 | if (array.length == 1) return array[0].clone(); 747 | var list = null; 748 | for (var i = array.length; --i >= 0;) { 749 | list = AST_Seq.cons(array[i], list); 750 | } 751 | var p = list; 752 | while (p) { 753 | if (p.cdr && !p.cdr.cdr) { 754 | p.cdr = p.cdr.car; 755 | break; 756 | } 757 | p = p.cdr; 758 | } 759 | return list; 760 | }, 761 | to_array: function() { 762 | var p = this, a = []; 763 | while (p) { 764 | a.push(p.car); 765 | if (p.cdr && !(p.cdr instanceof AST_Seq)) { 766 | a.push(p.cdr); 767 | break; 768 | } 769 | p = p.cdr; 770 | } 771 | return a; 772 | }, 773 | add: function(node) { 774 | var p = this; 775 | while (p) { 776 | if (!(p.cdr instanceof AST_Seq)) { 777 | var cell = AST_Seq.cons(p.cdr, node); 778 | return p.cdr = cell; 779 | } 780 | p = p.cdr; 781 | } 782 | }, 783 | _walk: function(visitor) { 784 | return visitor._visit(this, function(){ 785 | this.car._walk(visitor); 786 | if (this.cdr) this.cdr._walk(visitor); 787 | }); 788 | } 789 | }); 790 | 791 | var AST_PropAccess = DEFNODE("PropAccess", "expression property", { 792 | $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`", 793 | $propdoc: { 794 | expression: "[AST_Node] the “container” expression", 795 | property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node" 796 | } 797 | }); 798 | 799 | var AST_Dot = DEFNODE("Dot", null, { 800 | $documentation: "A dotted property access expression", 801 | _walk: function(visitor) { 802 | return visitor._visit(this, function(){ 803 | this.expression._walk(visitor); 804 | }); 805 | } 806 | }, AST_PropAccess); 807 | 808 | var AST_Sub = DEFNODE("Sub", null, { 809 | $documentation: "Index-style property access, i.e. `a[\"foo\"]`", 810 | _walk: function(visitor) { 811 | return visitor._visit(this, function(){ 812 | this.expression._walk(visitor); 813 | this.property._walk(visitor); 814 | }); 815 | } 816 | }, AST_PropAccess); 817 | 818 | var AST_Unary = DEFNODE("Unary", "operator expression", { 819 | $documentation: "Base class for unary expressions", 820 | $propdoc: { 821 | operator: "[string] the operator", 822 | expression: "[AST_Node] expression that this unary operator applies to" 823 | }, 824 | _walk: function(visitor) { 825 | return visitor._visit(this, function(){ 826 | this.expression._walk(visitor); 827 | }); 828 | } 829 | }); 830 | 831 | var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, { 832 | $documentation: "Unary prefix expression, i.e. `typeof i` or `++i`" 833 | }, AST_Unary); 834 | 835 | var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, { 836 | $documentation: "Unary postfix expression, i.e. `i++`" 837 | }, AST_Unary); 838 | 839 | var AST_Binary = DEFNODE("Binary", "left operator right", { 840 | $documentation: "Binary expression, i.e. `a + b`", 841 | $propdoc: { 842 | left: "[AST_Node] left-hand side expression", 843 | operator: "[string] the operator", 844 | right: "[AST_Node] right-hand side expression" 845 | }, 846 | _walk: function(visitor) { 847 | return visitor._visit(this, function(){ 848 | this.left._walk(visitor); 849 | this.right._walk(visitor); 850 | }); 851 | } 852 | }); 853 | 854 | var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", { 855 | $documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`", 856 | $propdoc: { 857 | condition: "[AST_Node]", 858 | consequent: "[AST_Node]", 859 | alternative: "[AST_Node]" 860 | }, 861 | _walk: function(visitor) { 862 | return visitor._visit(this, function(){ 863 | this.condition._walk(visitor); 864 | this.consequent._walk(visitor); 865 | this.alternative._walk(visitor); 866 | }); 867 | } 868 | }); 869 | 870 | var AST_Assign = DEFNODE("Assign", null, { 871 | $documentation: "An assignment expression — `a = b + 5`", 872 | }, AST_Binary); 873 | 874 | /* -----[ LITERALS ]----- */ 875 | 876 | var AST_Array = DEFNODE("Array", "elements", { 877 | $documentation: "An array literal", 878 | $propdoc: { 879 | elements: "[AST_Node*] array of elements" 880 | }, 881 | _walk: function(visitor) { 882 | return visitor._visit(this, function(){ 883 | this.elements.forEach(function(el){ 884 | el._walk(visitor); 885 | }); 886 | }); 887 | } 888 | }); 889 | 890 | var AST_Object = DEFNODE("Object", "properties", { 891 | $documentation: "An object literal", 892 | $propdoc: { 893 | properties: "[AST_ObjectProperty*] array of properties" 894 | }, 895 | _walk: function(visitor) { 896 | return visitor._visit(this, function(){ 897 | this.properties.forEach(function(prop){ 898 | prop._walk(visitor); 899 | }); 900 | }); 901 | } 902 | }); 903 | 904 | var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { 905 | $documentation: "Base class for literal object properties", 906 | $propdoc: { 907 | key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an arbitrary AST_Node.", 908 | value: "[AST_Node] property value. For setters and getters this is an AST_Function." 909 | }, 910 | _walk: function(visitor) { 911 | return visitor._visit(this, function(){ 912 | this.value._walk(visitor); 913 | }); 914 | } 915 | }); 916 | 917 | var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", { 918 | $documentation: "A key: value object property", 919 | $propdoc: { 920 | quote: "[string] the original quote character" 921 | } 922 | }, AST_ObjectProperty); 923 | 924 | var AST_ObjectComputedKeyVal = DEFNODE("ObjectComputedKeyVal", null, { 925 | $documentation: "An object property whose key is computed. Like `[Symbol.iterator]: function...` or `[routes.homepage]: renderHomepage`", 926 | _walk: function(visitor) { 927 | return visitor._visit(this, function(){ 928 | this.key._walk(visitor); 929 | this.value._walk(visitor); 930 | }); 931 | } 932 | }, AST_ObjectProperty); 933 | 934 | var AST_ObjectSymbol = DEFNODE("ObjectSymbol", "symbol", { 935 | $propdoc: { 936 | symbol: "[AST_SymbolRef] what symbol it is" 937 | }, 938 | $documentation: "A symbol in an object", 939 | _walk: function (visitor) { 940 | return visitor._visit(this, function(){ 941 | this.symbol._walk(visitor); 942 | }); 943 | } 944 | }, AST_ObjectProperty); 945 | 946 | var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { 947 | $documentation: "An object setter property", 948 | }, AST_ObjectProperty); 949 | 950 | var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { 951 | $documentation: "An object getter property", 952 | }, AST_ObjectProperty); 953 | 954 | var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { 955 | $propdoc: { 956 | name: "[string] name of this symbol", 957 | scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", 958 | thedef: "[SymbolDef/S] the definition of this symbol" 959 | }, 960 | $documentation: "Base class for all symbols", 961 | }); 962 | 963 | var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { 964 | $documentation: "The name of a property accessor (setter/getter function)" 965 | }, AST_Symbol); 966 | 967 | var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { 968 | $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", 969 | $propdoc: { 970 | init: "[AST_Node*/S] array of initializers for this declaration." 971 | } 972 | }, AST_Symbol); 973 | 974 | var AST_SymbolVar = DEFNODE("SymbolVar", null, { 975 | $documentation: "Symbol defining a variable", 976 | }, AST_SymbolDeclaration); 977 | 978 | var AST_SymbolConst = DEFNODE("SymbolConst", null, { 979 | $documentation: "A constant declaration" 980 | }, AST_SymbolDeclaration); 981 | 982 | var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { 983 | $documentation: "Symbol naming a function argument", 984 | }, AST_SymbolVar); 985 | 986 | var AST_SymbolDefun = DEFNODE("SymbolDefun", null, { 987 | $documentation: "Symbol defining a function", 988 | }, AST_SymbolDeclaration); 989 | 990 | var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { 991 | $documentation: "Symbol naming a function expression", 992 | }, AST_SymbolDeclaration); 993 | 994 | var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { 995 | $documentation: "Symbol naming the exception in catch", 996 | }, AST_SymbolDeclaration); 997 | 998 | var AST_Label = DEFNODE("Label", "references", { 999 | $documentation: "Symbol naming a label (declaration)", 1000 | $propdoc: { 1001 | references: "[AST_LoopControl*] a list of nodes referring to this label" 1002 | }, 1003 | initialize: function() { 1004 | this.references = []; 1005 | this.thedef = this; 1006 | } 1007 | }, AST_Symbol); 1008 | 1009 | var AST_SymbolRef = DEFNODE("SymbolRef", null, { 1010 | $documentation: "Reference to some symbol (not definition/declaration)", 1011 | }, AST_Symbol); 1012 | 1013 | var AST_LabelRef = DEFNODE("LabelRef", null, { 1014 | $documentation: "Reference to a label symbol", 1015 | }, AST_Symbol); 1016 | 1017 | var AST_This = DEFNODE("This", null, { 1018 | $documentation: "The `this` symbol", 1019 | }, AST_Symbol); 1020 | 1021 | var AST_Super = DEFNODE("Super", null, { 1022 | $documentation: "The `super` symbol", 1023 | }, AST_Symbol); 1024 | 1025 | var AST_Constant = DEFNODE("Constant", null, { 1026 | $documentation: "Base class for all constants", 1027 | getValue: function() { 1028 | return this.value; 1029 | } 1030 | }); 1031 | 1032 | var AST_String = DEFNODE("String", "value quote", { 1033 | $documentation: "A string literal", 1034 | $propdoc: { 1035 | value: "[string] the contents of this string", 1036 | quote: "[string] the original quote character" 1037 | } 1038 | }, AST_Constant); 1039 | 1040 | var AST_Number = DEFNODE("Number", "value literal", { 1041 | $documentation: "A number literal", 1042 | $propdoc: { 1043 | value: "[number] the numeric value", 1044 | literal: "[string] numeric value as string (optional)" 1045 | } 1046 | }, AST_Constant); 1047 | 1048 | var AST_RegExp = DEFNODE("RegExp", "value", { 1049 | $documentation: "A regexp literal", 1050 | $propdoc: { 1051 | value: "[RegExp] the actual regexp" 1052 | } 1053 | }, AST_Constant); 1054 | 1055 | var AST_Atom = DEFNODE("Atom", null, { 1056 | $documentation: "Base class for atoms", 1057 | }, AST_Constant); 1058 | 1059 | var AST_Null = DEFNODE("Null", null, { 1060 | $documentation: "The `null` atom", 1061 | value: null 1062 | }, AST_Atom); 1063 | 1064 | var AST_NaN = DEFNODE("NaN", null, { 1065 | $documentation: "The impossible value", 1066 | value: 0/0 1067 | }, AST_Atom); 1068 | 1069 | var AST_Undefined = DEFNODE("Undefined", null, { 1070 | $documentation: "The `undefined` value", 1071 | value: (function(){}()) 1072 | }, AST_Atom); 1073 | 1074 | var AST_Hole = DEFNODE("Hole", null, { 1075 | $documentation: "A hole in an array", 1076 | value: (function(){}()) 1077 | }, AST_Atom); 1078 | 1079 | var AST_Infinity = DEFNODE("Infinity", null, { 1080 | $documentation: "The `Infinity` value", 1081 | value: 1/0 1082 | }, AST_Atom); 1083 | 1084 | var AST_Boolean = DEFNODE("Boolean", null, { 1085 | $documentation: "Base class for booleans", 1086 | }, AST_Atom); 1087 | 1088 | var AST_False = DEFNODE("False", null, { 1089 | $documentation: "The `false` atom", 1090 | value: false 1091 | }, AST_Boolean); 1092 | 1093 | var AST_True = DEFNODE("True", null, { 1094 | $documentation: "The `true` atom", 1095 | value: true 1096 | }, AST_Boolean); 1097 | 1098 | /* -----[ TreeWalker ]----- */ 1099 | 1100 | function TreeWalker(callback) { 1101 | this.visit = callback; 1102 | this.stack = []; 1103 | }; 1104 | TreeWalker.prototype = { 1105 | _visit: function(node, descend) { 1106 | this.stack.push(node); 1107 | var ret = this.visit(node, descend ? function(){ 1108 | descend.call(node); 1109 | } : noop); 1110 | if (!ret && descend) { 1111 | descend.call(node); 1112 | } 1113 | this.stack.pop(); 1114 | return ret; 1115 | }, 1116 | parent: function(n) { 1117 | return this.stack[this.stack.length - 2 - (n || 0)]; 1118 | }, 1119 | push: function (node) { 1120 | this.stack.push(node); 1121 | }, 1122 | pop: function() { 1123 | return this.stack.pop(); 1124 | }, 1125 | self: function() { 1126 | return this.stack[this.stack.length - 1]; 1127 | }, 1128 | find_parent: function(type) { 1129 | var stack = this.stack; 1130 | for (var i = stack.length; --i >= 0;) { 1131 | var x = stack[i]; 1132 | if (x instanceof type) return x; 1133 | } 1134 | }, 1135 | has_directive: function(type) { 1136 | return this.find_parent(AST_Scope).has_directive(type); 1137 | }, 1138 | in_boolean_context: function() { 1139 | var stack = this.stack; 1140 | var i = stack.length, self = stack[--i]; 1141 | while (i > 0) { 1142 | var p = stack[--i]; 1143 | if ((p instanceof AST_If && p.condition === self) || 1144 | (p instanceof AST_Conditional && p.condition === self) || 1145 | (p instanceof AST_DWLoop && p.condition === self) || 1146 | (p instanceof AST_For && p.condition === self) || 1147 | (p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self)) 1148 | { 1149 | return true; 1150 | } 1151 | if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||"))) 1152 | return false; 1153 | self = p; 1154 | } 1155 | }, 1156 | loopcontrol_target: function(label) { 1157 | var stack = this.stack; 1158 | if (label) for (var i = stack.length; --i >= 0;) { 1159 | var x = stack[i]; 1160 | if (x instanceof AST_LabeledStatement && x.label.name == label.name) { 1161 | return x.body; 1162 | } 1163 | } else for (var i = stack.length; --i >= 0;) { 1164 | var x = stack[i]; 1165 | if (x instanceof AST_Switch || x instanceof AST_IterationStatement) 1166 | return x; 1167 | } 1168 | } 1169 | }; 1170 | -------------------------------------------------------------------------------- /uglify/fromjsw/ast.js: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | 3 | A JavaScript tokenizer / parser / beautifier / compressor. 4 | https://github.com/mishoo/UglifyJS2 5 | 6 | -------------------------------- (C) --------------------------------- 7 | 8 | Author: Mihai Bazon 9 | 10 | http://mihai.bazon.net/blog 11 | 12 | Distributed under the BSD license: 13 | 14 | Copyright 2012 (c) Mihai Bazon 15 | 16 | Redistribution and use in source and binary forms, with or without 17 | modification, are permitted provided that the following conditions 18 | are met: 19 | 20 | * Redistributions of source code must retain the above 21 | copyright notice, this list of conditions and the following 22 | disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials 27 | provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 40 | SUCH DAMAGE. 41 | 42 | ***********************************************************************/ 43 | 44 | "use strict"; 45 | 46 | function DEFNODE(type, props, methods, base) { 47 | if (arguments.length < 4) base = AST_Node; 48 | if (!props) props = []; 49 | else props = props.split(/\s+/); 50 | var self_props = props; 51 | if (base && base.PROPS) 52 | props = props.concat(base.PROPS); 53 | var code = "return function AST_" + type + "(props){ if (props) { "; 54 | for (var i = props.length; --i >= 0;) { 55 | code += "this." + props[i] + " = props." + props[i] + ";"; 56 | } 57 | var proto = base && new base; 58 | if (proto && proto.initialize || (methods && methods.initialize)) 59 | code += "this.initialize();"; 60 | code += "}}"; 61 | var ctor = new Function(code)(); 62 | if (proto) { 63 | ctor.prototype = proto; 64 | ctor.BASE = base; 65 | } 66 | if (base) base.SUBCLASSES.push(ctor); 67 | ctor.prototype.CTOR = ctor; 68 | ctor.PROPS = props || null; 69 | ctor.SELF_PROPS = self_props; 70 | ctor.SUBCLASSES = []; 71 | if (type) { 72 | ctor.prototype.TYPE = ctor.TYPE = type; 73 | } 74 | if (methods) for (i in methods) if (methods.hasOwnProperty(i)) { 75 | if (/^\$/.test(i)) { 76 | ctor[i.substr(1)] = methods[i]; 77 | } else { 78 | ctor.prototype[i] = methods[i]; 79 | } 80 | } 81 | ctor.DEFMETHOD = function(name, method) { 82 | this.prototype[name] = method; 83 | }; 84 | exports["AST_" + type] = ctor; 85 | return ctor; 86 | }; 87 | 88 | var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", { 89 | }, null); 90 | 91 | var AST_Node = DEFNODE("Node", "start end", { 92 | clone: function() { 93 | return new this.CTOR(this); 94 | }, 95 | $documentation: "Base class of all AST nodes", 96 | $propdoc: { 97 | start: "[AST_Token] The first token of this node", 98 | end: "[AST_Token] The last token of this node" 99 | }, 100 | _walk: function(visitor) { 101 | return visitor._visit(this); 102 | }, 103 | walk: function(visitor) { 104 | return this._walk(visitor); // not sure the indirection will be any help 105 | } 106 | }, null); 107 | 108 | AST_Node.warn_function = null; 109 | AST_Node.warn = function(txt, props) { 110 | if (AST_Node.warn_function) 111 | AST_Node.warn_function(string_template(txt, props)); 112 | }; 113 | 114 | /* -----[ statements ]----- */ 115 | 116 | var AST_Statement = DEFNODE("Statement", null, { 117 | $documentation: "Base class of all statements", 118 | }); 119 | 120 | var AST_Debugger = DEFNODE("Debugger", null, { 121 | $documentation: "Represents a debugger statement", 122 | }, AST_Statement); 123 | 124 | var AST_Directive = DEFNODE("Directive", "value scope quote", { 125 | $documentation: "Represents a directive, like \"use strict\";", 126 | $propdoc: { 127 | value: "[string] The value of this directive as a plain string (it's not an AST_String!)", 128 | scope: "[AST_Scope/S] The scope that this directive affects", 129 | quote: "[string] the original quote character" 130 | }, 131 | }, AST_Statement); 132 | 133 | var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { 134 | $documentation: "A statement consisting of an expression, i.e. a = 1 + 2", 135 | $propdoc: { 136 | body: "[AST_Node] an expression node (should not be instanceof AST_Statement)" 137 | }, 138 | _walk: function(visitor) { 139 | return visitor._visit(this, function(){ 140 | this.body._walk(visitor); 141 | }); 142 | } 143 | }, AST_Statement); 144 | 145 | function walk_body(node, visitor) { 146 | if (node.body instanceof AST_Node) { 147 | node.body._walk(visitor); 148 | } 149 | else node.body.forEach(function(stat){ 150 | stat._walk(visitor); 151 | }); 152 | }; 153 | 154 | var AST_Block = DEFNODE("Block", "body", { 155 | $documentation: "A body of statements (usually bracketed)", 156 | $propdoc: { 157 | body: "[AST_Statement*] an array of statements" 158 | }, 159 | _walk: function(visitor) { 160 | return visitor._visit(this, function(){ 161 | walk_body(this, visitor); 162 | }); 163 | } 164 | }, AST_Statement); 165 | 166 | var AST_BlockStatement = DEFNODE("BlockStatement", null, { 167 | $documentation: "A block statement", 168 | }, AST_Block); 169 | 170 | var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { 171 | $documentation: "The empty statement (empty block or simply a semicolon)", 172 | _walk: function(visitor) { 173 | return visitor._visit(this); 174 | } 175 | }, AST_Statement); 176 | 177 | var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { 178 | $documentation: "Base class for all statements that contain one nested body: `For`, `ForIn`, `Do`, `While`, `With`", 179 | $propdoc: { 180 | body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" 181 | }, 182 | _walk: function(visitor) { 183 | return visitor._visit(this, function(){ 184 | this.body._walk(visitor); 185 | }); 186 | } 187 | }, AST_Statement); 188 | 189 | var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { 190 | $documentation: "Statement with a label", 191 | $propdoc: { 192 | label: "[AST_Label] a label definition" 193 | }, 194 | _walk: function(visitor) { 195 | return visitor._visit(this, function(){ 196 | this.label._walk(visitor); 197 | this.body._walk(visitor); 198 | }); 199 | } 200 | }, AST_StatementWithBody); 201 | 202 | var AST_IterationStatement = DEFNODE("IterationStatement", null, { 203 | $documentation: "Internal class. All loops inherit from it." 204 | }, AST_StatementWithBody); 205 | 206 | var AST_DWLoop = DEFNODE("DWLoop", "condition", { 207 | $documentation: "Base class for do/while statements", 208 | $propdoc: { 209 | condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" 210 | } 211 | }, AST_IterationStatement); 212 | 213 | var AST_Do = DEFNODE("Do", null, { 214 | $documentation: "A `do` statement", 215 | _walk: function(visitor) { 216 | return visitor._visit(this, function(){ 217 | this.body._walk(visitor); 218 | this.condition._walk(visitor); 219 | }); 220 | } 221 | }, AST_DWLoop); 222 | 223 | var AST_While = DEFNODE("While", null, { 224 | $documentation: "A `while` statement", 225 | _walk: function(visitor) { 226 | return visitor._visit(this, function(){ 227 | this.condition._walk(visitor); 228 | this.body._walk(visitor); 229 | }); 230 | } 231 | }, AST_DWLoop); 232 | 233 | var AST_For = DEFNODE("For", "init condition step", { 234 | $documentation: "A `for` statement", 235 | $propdoc: { 236 | init: "[AST_Node?] the `for` initialization code, or null if empty", 237 | condition: "[AST_Node?] the `for` termination clause, or null if empty", 238 | step: "[AST_Node?] the `for` update clause, or null if empty" 239 | }, 240 | _walk: function(visitor) { 241 | return visitor._visit(this, function(){ 242 | if (this.init) this.init._walk(visitor); 243 | if (this.condition) this.condition._walk(visitor); 244 | if (this.step) this.step._walk(visitor); 245 | this.body._walk(visitor); 246 | }); 247 | } 248 | }, AST_IterationStatement); 249 | 250 | var AST_ForIn = DEFNODE("ForIn", "init name object", { 251 | $documentation: "A `for ... in` statement", 252 | $propdoc: { 253 | init: "[AST_Node] the `for/in` initialization code", 254 | name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", 255 | object: "[AST_Node] the object that we're looping through" 256 | }, 257 | _walk: function(visitor) { 258 | return visitor._visit(this, function(){ 259 | this.init._walk(visitor); 260 | this.object._walk(visitor); 261 | this.body._walk(visitor); 262 | }); 263 | } 264 | }, AST_IterationStatement); 265 | 266 | var AST_ForOf = DEFNODE("ForOf", null, { 267 | $documentation: "A `for ... of` statement", 268 | }, AST_ForIn); 269 | 270 | var AST_With = DEFNODE("With", "expression", { 271 | $documentation: "A `with` statement", 272 | $propdoc: { 273 | expression: "[AST_Node] the `with` expression" 274 | }, 275 | _walk: function(visitor) { 276 | return visitor._visit(this, function(){ 277 | this.expression._walk(visitor); 278 | this.body._walk(visitor); 279 | }); 280 | } 281 | }, AST_StatementWithBody); 282 | 283 | /* -----[ scope and functions ]----- */ 284 | 285 | var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", { 286 | $documentation: "Base class for all statements introducing a lexical scope", 287 | $propdoc: { 288 | directives: "[string*/S] an array of directives declared in this scope", 289 | variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope", 290 | functions: "[Object/S] like `variables`, but only lists function declarations", 291 | uses_with: "[boolean/S] tells whether this scope uses the `with` statement", 292 | uses_eval: "[boolean/S] tells whether this scope contains a direct call to the global `eval`", 293 | parent_scope: "[AST_Scope?/S] link to the parent scope", 294 | enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", 295 | cname: "[integer/S] current index for mangling variables (used internally by the mangler)", 296 | }, 297 | }, AST_Block); 298 | 299 | var AST_Toplevel = DEFNODE("Toplevel", "globals", { 300 | $documentation: "The toplevel scope", 301 | $propdoc: { 302 | globals: "[Object/S] a map of name -> SymbolDef for all undeclared names", 303 | }, 304 | wrap_enclose: function(arg_parameter_pairs) { 305 | var self = this; 306 | var args = []; 307 | var parameters = []; 308 | 309 | arg_parameter_pairs.forEach(function(pair) { 310 | var splitAt = pair.lastIndexOf(":"); 311 | 312 | args.push(pair.substr(0, splitAt)); 313 | parameters.push(pair.substr(splitAt + 1)); 314 | }); 315 | 316 | var wrapped_tl = "(function(" + parameters.join(",") + "){ '$ORIG'; })(" + args.join(",") + ")"; 317 | wrapped_tl = parse(wrapped_tl); 318 | wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ 319 | if (node instanceof AST_Directive && node.value == "$ORIG") { 320 | return MAP.splice(self.body); 321 | } 322 | })); 323 | return wrapped_tl; 324 | }, 325 | wrap_commonjs: function(name, export_all) { 326 | var self = this; 327 | var to_export = []; 328 | if (export_all) { 329 | self.figure_out_scope(); 330 | self.walk(new TreeWalker(function(node){ 331 | if (node instanceof AST_SymbolDeclaration && node.definition().global) { 332 | if (!find_if(function(n){ return n.name == node.name }, to_export)) 333 | to_export.push(node); 334 | } 335 | })); 336 | } 337 | var wrapped_tl = "(function(exports, global){ '$ORIG'; '$EXPORTS'; global['" + name + "'] = exports; }({}, (function(){return this}())))"; 338 | wrapped_tl = parse(wrapped_tl); 339 | wrapped_tl = wrapped_tl.transform(new TreeTransformer(function before(node){ 340 | if (node instanceof AST_Directive) { 341 | switch (node.value) { 342 | case "$ORIG": 343 | return MAP.splice(self.body); 344 | case "$EXPORTS": 345 | var body = []; 346 | to_export.forEach(function(sym){ 347 | body.push(new AST_SimpleStatement({ 348 | body: new AST_Assign({ 349 | left: new AST_Sub({ 350 | expression: new AST_SymbolRef({ name: "exports" }), 351 | property: new AST_String({ value: sym.name }), 352 | }), 353 | operator: "=", 354 | right: new AST_SymbolRef(sym), 355 | }), 356 | })); 357 | }); 358 | return MAP.splice(body); 359 | } 360 | } 361 | })); 362 | return wrapped_tl; 363 | } 364 | }, AST_Scope); 365 | 366 | var AST_Expansion = DEFNODE("Expansion", "symbol", { 367 | $documentation: "An expandible argument, such as ...rest, a splat, such as [1,2,...all], or an expansion in a variable declaration, such as var [first, ...rest] = list", 368 | $propdoc: { 369 | symbol: "AST_Symbol the thing to be expanded" 370 | }, 371 | _walk: function(visitor) { 372 | var self = this; 373 | return visitor._visit(this, function(){ 374 | self.symbol.walk(visitor); 375 | }); 376 | } 377 | }); 378 | 379 | var AST_ArrowParametersOrSeq = DEFNODE("ArrowParametersOrSeq", "expressions", { 380 | $documentation: "A set of arrow function parameters or a sequence expression. This is used because when the parser sees a \"(\" it could be the start of a seq, or the start of a parameter list of an arrow function.", 381 | $propdoc: { 382 | expressions: "[AST_Expression|AST_Destructuring|AST_Expansion*] array of expressions or argument names or destructurings." 383 | }, 384 | as_params: function (croak) { 385 | // We don't want anything which doesn't belong in a destructuring 386 | var root = this; 387 | return this.expressions.map(function to_fun_args(ex) { 388 | if (ex instanceof AST_Object) { 389 | if (ex.properties.length == 0) 390 | croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); 391 | return new AST_Destructuring({ 392 | start: ex.start, 393 | end: ex.end, 394 | is_array: false, 395 | names: ex.properties.map(to_fun_args) 396 | }); 397 | } else if (ex instanceof AST_ObjectSymbol) { 398 | return new AST_SymbolFunarg({ 399 | name: ex.symbol.name, 400 | start: ex.start, 401 | end: ex.end 402 | }); 403 | } else if (ex instanceof AST_Destructuring) { 404 | if (ex.names.length == 0) 405 | croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); 406 | ex.names = ex.names.map(to_fun_args); 407 | return ex; 408 | } else if (ex instanceof AST_SymbolRef) { 409 | return new AST_SymbolFunarg({ 410 | name: ex.name, 411 | start: ex.start, 412 | end: ex.end 413 | }); 414 | } else if (ex instanceof AST_Expansion) { 415 | return ex; 416 | } else if (ex instanceof AST_Array) { 417 | if (ex.elements.length === 0) 418 | croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); 419 | return new AST_Destructuring({ 420 | start: ex.start, 421 | end: ex.end, 422 | is_array: true, 423 | names: ex.elements.map(to_fun_args) 424 | }); 425 | } else { 426 | croak("Invalid function parameter", ex.start.line, ex.start.col); 427 | } 428 | }); 429 | }, 430 | as_expr: function (croak) { 431 | return AST_Seq.from_array(this.expressions); 432 | } 433 | }); 434 | 435 | var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { 436 | $documentation: "Base class for functions", 437 | $propdoc: { 438 | name: "[AST_SymbolDeclaration?] the name of this function", 439 | argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion*] array of function arguments, destructurings, or expanding arguments", 440 | uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" 441 | }, 442 | args_as_names: function () { 443 | var out = []; 444 | for (var i = 0; i < this.argnames.length; i++) { 445 | if (this.argnames[i] instanceof AST_Destructuring) { 446 | out = out.concat(this.argnames[i].all_symbols()); 447 | } else { 448 | out.push(this.argnames[i]); 449 | } 450 | } 451 | return out; 452 | }, 453 | _walk: function(visitor) { 454 | return visitor._visit(this, function(){ 455 | if (this.name) this.name._walk(visitor); 456 | this.argnames.forEach(function(arg){ 457 | arg._walk(visitor); 458 | }); 459 | walk_body(this, visitor); 460 | }); 461 | } 462 | }, AST_Scope); 463 | 464 | var AST_Accessor = DEFNODE("Accessor", null, { 465 | $documentation: "A setter/getter function. The `name` property is always null." 466 | }, AST_Lambda); 467 | 468 | var AST_Function = DEFNODE("Function", null, { 469 | $documentation: "A function expression" 470 | }, AST_Lambda); 471 | 472 | var AST_Arrow = DEFNODE("Arrow", null, { 473 | $documentation: "An ES6 Arrow function ((a) => b)" 474 | }, AST_Lambda); 475 | 476 | var AST_Defun = DEFNODE("Defun", null, { 477 | $documentation: "A function definition" 478 | }, AST_Lambda); 479 | 480 | /* -----[ DESTRUCTURING ]----- */ 481 | var AST_Destructuring = DEFNODE("Destructuring", "names is_array", { 482 | $documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names", 483 | _walk: function(visitor) { 484 | return visitor._visit(this, function(){ 485 | this.names.forEach(function(name){ 486 | name._walk(visitor); 487 | }); 488 | }); 489 | }, 490 | all_symbols: function() { 491 | var out = []; 492 | this.walk(new TreeWalker(function (node) { 493 | if (node instanceof AST_Symbol) { 494 | out.push(node); 495 | } 496 | if (node instanceof AST_Expansion) { 497 | out.push(node.symbol); 498 | } 499 | })); 500 | return out; 501 | } 502 | }); 503 | 504 | var AST_PrefixedTemplateString = DEFNODE("PrefixedTemplateString", "template_string prefix", { 505 | $documentation: "A templatestring with a prefix, such as String.raw`foobarbaz`", 506 | $propdoc: { 507 | template_string: "[AST_TemplateString] The template string", 508 | prefix: "[AST_SymbolRef|AST_PropAccess] The prefix, which can be a symbol such as `foo` or a dotted expression such as `String.raw`." 509 | }, 510 | _walk: function(visitor) { 511 | this.prefix._walk(visitor); 512 | this.template_string._walk(visitor); 513 | } 514 | }) 515 | 516 | var AST_TemplateString = DEFNODE("TemplateString", "segments", { 517 | $documentation: "A template string literal", 518 | $propdoc: { 519 | segments: "[string|AST_Expression]* One or more segments. They can be the parts that are evaluated, or the raw string parts." 520 | }, 521 | _walk: function(visitor) { 522 | return visitor._visit(this, function(){ 523 | this.segments.forEach(function(seg, i){ 524 | if (i % 2 !== 0) { 525 | seg._walk(visitor); 526 | } 527 | }); 528 | }); 529 | } 530 | }); 531 | 532 | /* -----[ JUMPS ]----- */ 533 | 534 | var AST_Jump = DEFNODE("Jump", null, { 535 | $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)" 536 | }, AST_Statement); 537 | 538 | var AST_Exit = DEFNODE("Exit", "value", { 539 | $documentation: "Base class for “exits” (`return` and `throw`)", 540 | $propdoc: { 541 | value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return" 542 | }, 543 | _walk: function(visitor) { 544 | return visitor._visit(this, this.value && function(){ 545 | this.value._walk(visitor); 546 | }); 547 | } 548 | }, AST_Jump); 549 | 550 | var AST_Return = DEFNODE("Return", null, { 551 | $documentation: "A `return` statement" 552 | }, AST_Exit); 553 | 554 | var AST_Throw = DEFNODE("Throw", null, { 555 | $documentation: "A `throw` statement" 556 | }, AST_Exit); 557 | 558 | var AST_LoopControl = DEFNODE("LoopControl", "label", { 559 | $documentation: "Base class for loop control statements (`break` and `continue`)", 560 | $propdoc: { 561 | label: "[AST_LabelRef?] the label, or null if none", 562 | }, 563 | _walk: function(visitor) { 564 | return visitor._visit(this, this.label && function(){ 565 | this.label._walk(visitor); 566 | }); 567 | } 568 | }, AST_Jump); 569 | 570 | var AST_Break = DEFNODE("Break", null, { 571 | $documentation: "A `break` statement" 572 | }, AST_LoopControl); 573 | 574 | var AST_Continue = DEFNODE("Continue", null, { 575 | $documentation: "A `continue` statement" 576 | }, AST_LoopControl); 577 | 578 | /* -----[ IF ]----- */ 579 | 580 | var AST_If = DEFNODE("If", "condition alternative", { 581 | $documentation: "A `if` statement", 582 | $propdoc: { 583 | condition: "[AST_Node] the `if` condition", 584 | alternative: "[AST_Statement?] the `else` part, or null if not present" 585 | }, 586 | _walk: function(visitor) { 587 | return visitor._visit(this, function(){ 588 | this.condition._walk(visitor); 589 | this.body._walk(visitor); 590 | if (this.alternative) this.alternative._walk(visitor); 591 | }); 592 | } 593 | }, AST_StatementWithBody); 594 | 595 | /* -----[ SWITCH ]----- */ 596 | 597 | var AST_Switch = DEFNODE("Switch", "expression", { 598 | $documentation: "A `switch` statement", 599 | $propdoc: { 600 | expression: "[AST_Node] the `switch` “discriminant”" 601 | }, 602 | _walk: function(visitor) { 603 | return visitor._visit(this, function(){ 604 | this.expression._walk(visitor); 605 | walk_body(this, visitor); 606 | }); 607 | } 608 | }, AST_Block); 609 | 610 | var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { 611 | $documentation: "Base class for `switch` branches", 612 | }, AST_Block); 613 | 614 | var AST_Default = DEFNODE("Default", null, { 615 | $documentation: "A `default` switch branch", 616 | }, AST_SwitchBranch); 617 | 618 | var AST_Case = DEFNODE("Case", "expression", { 619 | $documentation: "A `case` switch branch", 620 | $propdoc: { 621 | expression: "[AST_Node] the `case` expression" 622 | }, 623 | _walk: function(visitor) { 624 | return visitor._visit(this, function(){ 625 | this.expression._walk(visitor); 626 | walk_body(this, visitor); 627 | }); 628 | } 629 | }, AST_SwitchBranch); 630 | 631 | /* -----[ EXCEPTIONS ]----- */ 632 | 633 | var AST_Try = DEFNODE("Try", "bcatch bfinally", { 634 | $documentation: "A `try` statement", 635 | $propdoc: { 636 | bcatch: "[AST_Catch?] the catch block, or null if not present", 637 | bfinally: "[AST_Finally?] the finally block, or null if not present" 638 | }, 639 | _walk: function(visitor) { 640 | return visitor._visit(this, function(){ 641 | walk_body(this, visitor); 642 | if (this.bcatch) this.bcatch._walk(visitor); 643 | if (this.bfinally) this.bfinally._walk(visitor); 644 | }); 645 | } 646 | }, AST_Block); 647 | 648 | var AST_Catch = DEFNODE("Catch", "argname", { 649 | $documentation: "A `catch` node; only makes sense as part of a `try` statement", 650 | $propdoc: { 651 | argname: "[AST_SymbolCatch] symbol for the exception" 652 | }, 653 | _walk: function(visitor) { 654 | return visitor._visit(this, function(){ 655 | this.argname._walk(visitor); 656 | walk_body(this, visitor); 657 | }); 658 | } 659 | }, AST_Block); 660 | 661 | var AST_Finally = DEFNODE("Finally", null, { 662 | $documentation: "A `finally` node; only makes sense as part of a `try` statement" 663 | }, AST_Block); 664 | 665 | /* -----[ VAR/CONST ]----- */ 666 | 667 | var AST_Definitions = DEFNODE("Definitions", "definitions", { 668 | $documentation: "Base class for `var` or `const` nodes (variable declarations/initializations)", 669 | $propdoc: { 670 | definitions: "[AST_VarDef*] array of variable definitions" 671 | }, 672 | _walk: function(visitor) { 673 | return visitor._visit(this, function(){ 674 | this.definitions.forEach(function(def){ 675 | def._walk(visitor); 676 | }); 677 | }); 678 | } 679 | }, AST_Statement); 680 | 681 | var AST_Var = DEFNODE("Var", null, { 682 | $documentation: "A `var` statement" 683 | }, AST_Definitions); 684 | 685 | var AST_Let = DEFNODE("Let", null, { 686 | $documentation: "A `let` statement" 687 | }, AST_Definitions); 688 | 689 | var AST_Const = DEFNODE("Const", null, { 690 | $documentation: "A `const` statement" 691 | }, AST_Definitions); 692 | 693 | var AST_VarDef = DEFNODE("VarDef", "name value", { 694 | $documentation: "A variable declaration; only appears in a AST_Definitions node", 695 | $propdoc: { 696 | name: "[AST_SymbolVar|AST_SymbolConst|AST_Destructuring] name of the variable", 697 | value: "[AST_Node?] initializer, or null of there's no initializer" 698 | }, 699 | is_destructuring: function() { 700 | return this.name instanceof AST_Destructuring; 701 | }, 702 | _walk: function(visitor) { 703 | return visitor._visit(this, function(){ 704 | this.name._walk(visitor); 705 | if (this.value) this.value._walk(visitor); 706 | }); 707 | } 708 | }); 709 | 710 | /* -----[ OTHER ]----- */ 711 | 712 | var AST_Call = DEFNODE("Call", "expression args", { 713 | $documentation: "A function call expression", 714 | $propdoc: { 715 | expression: "[AST_Node] expression to invoke as function", 716 | args: "[AST_Node*] array of arguments" 717 | }, 718 | _walk: function(visitor) { 719 | return visitor._visit(this, function(){ 720 | this.expression._walk(visitor); 721 | this.args.forEach(function(arg){ 722 | arg._walk(visitor); 723 | }); 724 | }); 725 | } 726 | }); 727 | 728 | var AST_New = DEFNODE("New", null, { 729 | $documentation: "An object instantiation. Derives from a function call since it has exactly the same properties" 730 | }, AST_Call); 731 | 732 | var AST_Seq = DEFNODE("Seq", "car cdr", { 733 | $documentation: "A sequence expression (two comma-separated expressions)", 734 | $propdoc: { 735 | car: "[AST_Node] first element in sequence", 736 | cdr: "[AST_Node] second element in sequence" 737 | }, 738 | $cons: function(x, y) { 739 | var seq = new AST_Seq(x); 740 | seq.car = x; 741 | seq.cdr = y; 742 | return seq; 743 | }, 744 | $from_array: function(array) { 745 | if (array.length == 0) return null; 746 | if (array.length == 1) return array[0].clone(); 747 | var list = null; 748 | for (var i = array.length; --i >= 0;) { 749 | list = AST_Seq.cons(array[i], list); 750 | } 751 | var p = list; 752 | while (p) { 753 | if (p.cdr && !p.cdr.cdr) { 754 | p.cdr = p.cdr.car; 755 | break; 756 | } 757 | p = p.cdr; 758 | } 759 | return list; 760 | }, 761 | to_array: function() { 762 | var p = this, a = []; 763 | while (p) { 764 | a.push(p.car); 765 | if (p.cdr && !(p.cdr instanceof AST_Seq)) { 766 | a.push(p.cdr); 767 | break; 768 | } 769 | p = p.cdr; 770 | } 771 | return a; 772 | }, 773 | add: function(node) { 774 | var p = this; 775 | while (p) { 776 | if (!(p.cdr instanceof AST_Seq)) { 777 | var cell = AST_Seq.cons(p.cdr, node); 778 | return p.cdr = cell; 779 | } 780 | p = p.cdr; 781 | } 782 | }, 783 | _walk: function(visitor) { 784 | return visitor._visit(this, function(){ 785 | this.car._walk(visitor); 786 | if (this.cdr) this.cdr._walk(visitor); 787 | }); 788 | } 789 | }); 790 | 791 | var AST_PropAccess = DEFNODE("PropAccess", "expression property", { 792 | $documentation: "Base class for property access expressions, i.e. `a.foo` or `a[\"foo\"]`", 793 | $propdoc: { 794 | expression: "[AST_Node] the “container” expression", 795 | property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node" 796 | } 797 | }); 798 | 799 | var AST_Dot = DEFNODE("Dot", null, { 800 | $documentation: "A dotted property access expression", 801 | _walk: function(visitor) { 802 | return visitor._visit(this, function(){ 803 | this.expression._walk(visitor); 804 | }); 805 | } 806 | }, AST_PropAccess); 807 | 808 | var AST_Sub = DEFNODE("Sub", null, { 809 | $documentation: "Index-style property access, i.e. `a[\"foo\"]`", 810 | _walk: function(visitor) { 811 | return visitor._visit(this, function(){ 812 | this.expression._walk(visitor); 813 | this.property._walk(visitor); 814 | }); 815 | } 816 | }, AST_PropAccess); 817 | 818 | var AST_Unary = DEFNODE("Unary", "operator expression", { 819 | $documentation: "Base class for unary expressions", 820 | $propdoc: { 821 | operator: "[string] the operator", 822 | expression: "[AST_Node] expression that this unary operator applies to" 823 | }, 824 | _walk: function(visitor) { 825 | return visitor._visit(this, function(){ 826 | this.expression._walk(visitor); 827 | }); 828 | } 829 | }); 830 | 831 | var AST_UnaryPrefix = DEFNODE("UnaryPrefix", null, { 832 | $documentation: "Unary prefix expression, i.e. `typeof i` or `++i`" 833 | }, AST_Unary); 834 | 835 | var AST_UnaryPostfix = DEFNODE("UnaryPostfix", null, { 836 | $documentation: "Unary postfix expression, i.e. `i++`" 837 | }, AST_Unary); 838 | 839 | var AST_Binary = DEFNODE("Binary", "left operator right", { 840 | $documentation: "Binary expression, i.e. `a + b`", 841 | $propdoc: { 842 | left: "[AST_Node] left-hand side expression", 843 | operator: "[string] the operator", 844 | right: "[AST_Node] right-hand side expression" 845 | }, 846 | _walk: function(visitor) { 847 | return visitor._visit(this, function(){ 848 | this.left._walk(visitor); 849 | this.right._walk(visitor); 850 | }); 851 | } 852 | }); 853 | 854 | var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative", { 855 | $documentation: "Conditional expression using the ternary operator, i.e. `a ? b : c`", 856 | $propdoc: { 857 | condition: "[AST_Node]", 858 | consequent: "[AST_Node]", 859 | alternative: "[AST_Node]" 860 | }, 861 | _walk: function(visitor) { 862 | return visitor._visit(this, function(){ 863 | this.condition._walk(visitor); 864 | this.consequent._walk(visitor); 865 | this.alternative._walk(visitor); 866 | }); 867 | } 868 | }); 869 | 870 | var AST_Assign = DEFNODE("Assign", null, { 871 | $documentation: "An assignment expression — `a = b + 5`", 872 | }, AST_Binary); 873 | 874 | /* -----[ LITERALS ]----- */ 875 | 876 | var AST_Array = DEFNODE("Array", "elements", { 877 | $documentation: "An array literal", 878 | $propdoc: { 879 | elements: "[AST_Node*] array of elements" 880 | }, 881 | _walk: function(visitor) { 882 | return visitor._visit(this, function(){ 883 | this.elements.forEach(function(el){ 884 | el._walk(visitor); 885 | }); 886 | }); 887 | } 888 | }); 889 | 890 | var AST_Object = DEFNODE("Object", "properties", { 891 | $documentation: "An object literal", 892 | $propdoc: { 893 | properties: "[AST_ObjectProperty*] array of properties" 894 | }, 895 | _walk: function(visitor) { 896 | return visitor._visit(this, function(){ 897 | this.properties.forEach(function(prop){ 898 | prop._walk(visitor); 899 | }); 900 | }); 901 | } 902 | }); 903 | 904 | var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { 905 | $documentation: "Base class for literal object properties", 906 | $propdoc: { 907 | key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an arbitrary AST_Node.", 908 | value: "[AST_Node] property value. For setters and getters this is an AST_Function." 909 | }, 910 | _walk: function(visitor) { 911 | return visitor._visit(this, function(){ 912 | this.value._walk(visitor); 913 | }); 914 | } 915 | }); 916 | 917 | var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", { 918 | $documentation: "A key: value object property", 919 | $propdoc: { 920 | quote: "[string] the original quote character" 921 | } 922 | }, AST_ObjectProperty); 923 | 924 | var AST_ObjectComputedKeyVal = DEFNODE("ObjectComputedKeyVal", null, { 925 | $documentation: "An object property whose key is computed. Like `[Symbol.iterator]: function...` or `[routes.homepage]: renderHomepage`", 926 | _walk: function(visitor) { 927 | return visitor._visit(this, function(){ 928 | this.key._walk(visitor); 929 | this.value._walk(visitor); 930 | }); 931 | } 932 | }, AST_ObjectProperty); 933 | 934 | var AST_ObjectSymbol = DEFNODE("ObjectSymbol", "symbol", { 935 | $propdoc: { 936 | symbol: "[AST_SymbolRef] what symbol it is" 937 | }, 938 | $documentation: "A symbol in an object", 939 | _walk: function (visitor) { 940 | return visitor._visit(this, function(){ 941 | this.symbol._walk(visitor); 942 | }); 943 | } 944 | }, AST_ObjectProperty); 945 | 946 | var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { 947 | $documentation: "An object setter property", 948 | }, AST_ObjectProperty); 949 | 950 | var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { 951 | $documentation: "An object getter property", 952 | }, AST_ObjectProperty); 953 | 954 | var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { 955 | $propdoc: { 956 | name: "[string] name of this symbol", 957 | scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", 958 | thedef: "[SymbolDef/S] the definition of this symbol" 959 | }, 960 | $documentation: "Base class for all symbols", 961 | }); 962 | 963 | var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { 964 | $documentation: "The name of a property accessor (setter/getter function)" 965 | }, AST_Symbol); 966 | 967 | var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { 968 | $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", 969 | $propdoc: { 970 | init: "[AST_Node*/S] array of initializers for this declaration." 971 | } 972 | }, AST_Symbol); 973 | 974 | var AST_SymbolVar = DEFNODE("SymbolVar", null, { 975 | $documentation: "Symbol defining a variable", 976 | }, AST_SymbolDeclaration); 977 | 978 | var AST_SymbolConst = DEFNODE("SymbolConst", null, { 979 | $documentation: "A constant declaration" 980 | }, AST_SymbolDeclaration); 981 | 982 | var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { 983 | $documentation: "Symbol naming a function argument", 984 | }, AST_SymbolVar); 985 | 986 | var AST_SymbolDefun = DEFNODE("SymbolDefun", null, { 987 | $documentation: "Symbol defining a function", 988 | }, AST_SymbolDeclaration); 989 | 990 | var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { 991 | $documentation: "Symbol naming a function expression", 992 | }, AST_SymbolDeclaration); 993 | 994 | var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { 995 | $documentation: "Symbol naming the exception in catch", 996 | }, AST_SymbolDeclaration); 997 | 998 | var AST_Label = DEFNODE("Label", "references", { 999 | $documentation: "Symbol naming a label (declaration)", 1000 | $propdoc: { 1001 | references: "[AST_LoopControl*] a list of nodes referring to this label" 1002 | }, 1003 | initialize: function() { 1004 | this.references = []; 1005 | this.thedef = this; 1006 | } 1007 | }, AST_Symbol); 1008 | 1009 | var AST_SymbolRef = DEFNODE("SymbolRef", null, { 1010 | $documentation: "Reference to some symbol (not definition/declaration)", 1011 | }, AST_Symbol); 1012 | 1013 | var AST_LabelRef = DEFNODE("LabelRef", null, { 1014 | $documentation: "Reference to a label symbol", 1015 | }, AST_Symbol); 1016 | 1017 | var AST_This = DEFNODE("This", null, { 1018 | $documentation: "The `this` symbol", 1019 | }, AST_Symbol); 1020 | 1021 | var AST_Super = DEFNODE("Super", null, { 1022 | $documentation: "The `super` symbol", 1023 | }, AST_Symbol); 1024 | 1025 | var AST_Constant = DEFNODE("Constant", null, { 1026 | $documentation: "Base class for all constants", 1027 | getValue: function() { 1028 | return this.value; 1029 | } 1030 | }); 1031 | 1032 | var AST_String = DEFNODE("String", "value quote", { 1033 | $documentation: "A string literal", 1034 | $propdoc: { 1035 | value: "[string] the contents of this string", 1036 | quote: "[string] the original quote character" 1037 | } 1038 | }, AST_Constant); 1039 | 1040 | var AST_Number = DEFNODE("Number", "value literal", { 1041 | $documentation: "A number literal", 1042 | $propdoc: { 1043 | value: "[number] the numeric value", 1044 | literal: "[string] numeric value as string (optional)" 1045 | } 1046 | }, AST_Constant); 1047 | 1048 | var AST_RegExp = DEFNODE("RegExp", "value", { 1049 | $documentation: "A regexp literal", 1050 | $propdoc: { 1051 | value: "[RegExp] the actual regexp" 1052 | } 1053 | }, AST_Constant); 1054 | 1055 | var AST_Atom = DEFNODE("Atom", null, { 1056 | $documentation: "Base class for atoms", 1057 | }, AST_Constant); 1058 | 1059 | var AST_Null = DEFNODE("Null", null, { 1060 | $documentation: "The `null` atom", 1061 | value: null 1062 | }, AST_Atom); 1063 | 1064 | var AST_NaN = DEFNODE("NaN", null, { 1065 | $documentation: "The impossible value", 1066 | value: 0/0 1067 | }, AST_Atom); 1068 | 1069 | var AST_Undefined = DEFNODE("Undefined", null, { 1070 | $documentation: "The `undefined` value", 1071 | value: (function(){}()) 1072 | }, AST_Atom); 1073 | 1074 | var AST_Hole = DEFNODE("Hole", null, { 1075 | $documentation: "A hole in an array", 1076 | value: (function(){}()) 1077 | }, AST_Atom); 1078 | 1079 | var AST_Infinity = DEFNODE("Infinity", null, { 1080 | $documentation: "The `Infinity` value", 1081 | value: 1/0 1082 | }, AST_Atom); 1083 | 1084 | var AST_Boolean = DEFNODE("Boolean", null, { 1085 | $documentation: "Base class for booleans", 1086 | }, AST_Atom); 1087 | 1088 | var AST_False = DEFNODE("False", null, { 1089 | $documentation: "The `false` atom", 1090 | value: false 1091 | }, AST_Boolean); 1092 | 1093 | var AST_True = DEFNODE("True", null, { 1094 | $documentation: "The `true` atom", 1095 | value: true 1096 | }, AST_Boolean); 1097 | 1098 | /* -----[ TreeWalker ]----- */ 1099 | 1100 | function TreeWalker(callback) { 1101 | this.visit = callback; 1102 | this.stack = []; 1103 | }; 1104 | TreeWalker.prototype = { 1105 | _visit: function(node, descend) { 1106 | this.stack.push(node); 1107 | var ret = this.visit(node, descend ? function(){ 1108 | descend.call(node); 1109 | } : noop); 1110 | if (!ret && descend) { 1111 | descend.call(node); 1112 | } 1113 | this.stack.pop(); 1114 | return ret; 1115 | }, 1116 | parent: function(n) { 1117 | return this.stack[this.stack.length - 2 - (n || 0)]; 1118 | }, 1119 | push: function (node) { 1120 | this.stack.push(node); 1121 | }, 1122 | pop: function() { 1123 | return this.stack.pop(); 1124 | }, 1125 | self: function() { 1126 | return this.stack[this.stack.length - 1]; 1127 | }, 1128 | find_parent: function(type) { 1129 | var stack = this.stack; 1130 | for (var i = stack.length; --i >= 0;) { 1131 | var x = stack[i]; 1132 | if (x instanceof type) return x; 1133 | } 1134 | }, 1135 | has_directive: function(type) { 1136 | return this.find_parent(AST_Scope).has_directive(type); 1137 | }, 1138 | in_boolean_context: function() { 1139 | var stack = this.stack; 1140 | var i = stack.length, self = stack[--i]; 1141 | while (i > 0) { 1142 | var p = stack[--i]; 1143 | if ((p instanceof AST_If && p.condition === self) || 1144 | (p instanceof AST_Conditional && p.condition === self) || 1145 | (p instanceof AST_DWLoop && p.condition === self) || 1146 | (p instanceof AST_For && p.condition === self) || 1147 | (p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self)) 1148 | { 1149 | return true; 1150 | } 1151 | if (!(p instanceof AST_Binary && (p.operator == "&&" || p.operator == "||"))) 1152 | return false; 1153 | self = p; 1154 | } 1155 | }, 1156 | loopcontrol_target: function(label) { 1157 | var stack = this.stack; 1158 | if (label) for (var i = stack.length; --i >= 0;) { 1159 | var x = stack[i]; 1160 | if (x instanceof AST_LabeledStatement && x.label.name == label.name) { 1161 | return x.body; 1162 | } 1163 | } else for (var i = stack.length; --i >= 0;) { 1164 | var x = stack[i]; 1165 | if (x instanceof AST_Switch || x instanceof AST_IterationStatement) 1166 | return x; 1167 | } 1168 | } 1169 | }; 1170 | --------------------------------------------------------------------------------