From: Mihai Bazon Date: Wed, 15 Aug 2012 10:32:37 +0000 (+0300) Subject: WIP X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=c0ba9e29861ab08ad8a5131705f421e96d634f4b;p=UglifyJS.git WIP --- diff --git a/lib/ast.js b/lib/ast.js index 683f602f..b8b6b072 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -8,6 +8,8 @@ function DEFNODE(type, props, methods, base) { for (var i = props.length; --i >= 0;) { code += "this." + props[i] + " = props." + props[i] + ";"; } + if (methods && methods.initialize) + code += "this.initialize();" code += " } }"; var ctor = new Function(code)(); if (base) { @@ -29,11 +31,15 @@ var AST_Token = DEFNODE("Token", "type value line col pos endpos nlb comments_be }, null); var AST_Node = DEFNODE("Node", "start end", { - renew: function(args) { - var ctor = this.CTOR, props = ctor.props; - for (var i in props) if (!HOP(args, i)) args[i] = this[i]; - return new ctor(args); + clone: function() { + return new this.CTOR(this); }, + // XXX: what was this for? + // renew: function(args) { + // var ctor = this.CTOR, props = ctor.props; + // for (var i in props) if (!HOP(args, i)) args[i] = this[i]; + // return new ctor(args); + // }, walk: function(w) { w._visit(this); } @@ -75,41 +81,50 @@ Used for bodies of FUNCTION/TRY/CATCH/THROW/SWITCH.", /* -----[ loops ]----- */ -var AST_LabeledStatement = DEFNODE("LabeledStatement", "label body", { +var AST_Statement = DEFNODE("Statement", "label body", { walk: function(w) { w._visit(this, function(){ if (this.label) this.label.walk(w); if (this.body) { - if (this.body instanceof Array) - AST_Bracketed.prototype.walk.call(this, w); - else + if (this.body instanceof AST_Node) this.body.walk(w); + else + this.walk_array(w); } }); - } + }, + walk_array: AST_Bracketed.prototype.walk }); -var AST_Statement = DEFNODE("Statement", null, { +var AST_SimpleStatement = DEFNODE("SimpleStatement", null, { + +}, AST_Statement); -}, AST_LabeledStatement); +var AST_BlockStatement = DEFNODE("BlockStatement", null, { + +}, AST_Statement); + +var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { + +}, AST_Statement); var AST_Do = DEFNODE("Do", "condition", { walk: function(w) { w._visit(this, function(){ this.condition.walk(w); - AST_LabeledStatement.prototype.walk.call(this, w); + AST_Statement.prototype.walk.call(this, w); }); } -}, AST_LabeledStatement); +}, AST_Statement); var AST_While = DEFNODE("While", "condition", { walk: function(w) { w._visit(this, function(){ this.condition.walk(w); - AST_LabeledStatement.prototype.walk.call(this, w); + AST_Statement.prototype.walk.call(this, w); }); } -}, AST_LabeledStatement); +}, AST_Statement); var AST_For = DEFNODE("For", "init condition step", { walk: function(w) { @@ -117,42 +132,42 @@ var AST_For = DEFNODE("For", "init condition step", { if (this.init) this.init.walk(w); if (this.condition) this.condition.walk(w); if (this.step) this.step.walk(w); - AST_LabeledStatement.prototype.walk.call(this, w); + AST_Statement.prototype.walk.call(this, w); }); } -}, AST_LabeledStatement); +}, AST_Statement); var AST_ForIn = DEFNODE("ForIn", "init name object", { walk: function(w) { w._visit(this, function(){ if (this.init) this.init.walk(w); this.object.walk(w); - AST_LabeledStatement.prototype.walk.call(this, w); + AST_Statement.prototype.walk.call(this, w); }); } -}, AST_LabeledStatement); +}, AST_Statement); -var AST_With = DEFNODE("With", "expression body", { +var AST_With = DEFNODE("With", "expression", { walk: function(w) { w._visit(this, function(){ this.expression.walk(w); - AST_LabeledStatement.prototype.walk.call(this, w); + AST_Statement.prototype.walk.call(this, w); }); } -}); +}, AST_Statement); /* -----[ functions ]----- */ -var AST_Scope = DEFNODE("Scope", "identifiers body", { +var AST_Scope = DEFNODE("Scope", "identifiers", { walk: function(w) { w._visit(this, function(){ if (this.identifiers) this.identifiers.forEach(function(el){ el.walk(w); }); - AST_LabeledStatement.prototype.walk.call(this, w); + AST_Statement.prototype.walk.call(this, w); }); } -}); +}, AST_Statement); var AST_Toplevel = DEFNODE("Toplevel", null, { @@ -234,25 +249,23 @@ var AST_Switch = DEFNODE("Switch", "expression", { walk: function(w) { w._visit(this, function(){ this.expression.walk(w); - AST_LabeledStatement.prototype.walk.call(this, w); + AST_Statement.prototype.walk.call(this, w); }); } -}, AST_LabeledStatement); +}, AST_Statement); var AST_SwitchBlock = DEFNODE("SwitchBlock", null, { - + walk : AST_Statement.prototype.walk, + walk_array : AST_Bracketed.prototype.walk }, AST_Bracketed); var AST_SwitchBranch = DEFNODE("SwitchBranch", "body", { - + walk : AST_Statement.prototype.walk, + walk_array : AST_Bracketed.prototype.walk }); var AST_Default = DEFNODE("Default", null, { - walk: function(w) { - w._visit(this, function(){ - AST_Statement.prototype.walk.call(this, w); - }); - } + }, AST_SwitchBranch); var AST_Case = DEFNODE("Case", "expression", { @@ -482,10 +495,8 @@ var AST_Number = DEFNODE("Number", "value", { }, AST_Constant); var AST_RegExp = DEFNODE("Regexp", "pattern mods", { - getValue: function() { - return this._regexp || ( - this._regexp = new RegExp(this.pattern, this.mods) - ); + initialize: function() { + this.value = new RegExp(this.pattern, this.mods); } }, AST_Constant); @@ -494,17 +505,45 @@ var AST_Atom = DEFNODE("Atom", null, { }, AST_Constant); var AST_Null = DEFNODE("Null", null, { - getValue: function() { return null } + value: null }, AST_Atom); var AST_Undefined = DEFNODE("Undefined", null, { - getValue: function() { return (function(){}()) } + value: (function(){}()) }, AST_Atom); var AST_False = DEFNODE("False", null, { - getValue: function() { return false } + value: false }, AST_Atom); var AST_True = DEFNODE("True", null, { - getValue: function() { return true } + value: true }, AST_Atom); + +/* -----[ Walker ]----- */ + +function TreeWalker(visitor) { + this.stack = []; + if (visitor) this.visit = visitor; +}; + +TreeWalker.prototype = { + visit: function(node){}, + parent: function(n) { + if (n == null) n = 1; + return this.stack[this.stack.length - n]; + }, + find_parent: function(type) { + for (var a = this.stack, i = a.length; --i >= 0;) + if (a[i] instanceof type) return a[i]; + return null; + }, + _visit: function(node, descend) { + this.visit(node); + if (descend) { + this.stack.push(node); + descend.call(node); + this.stack.pop(); + } + } +}; diff --git a/lib/node.js b/lib/node.js index 9089a5fe..571cdd80 100755 --- a/lib/node.js +++ b/lib/node.js @@ -24,12 +24,10 @@ console.timeEnd("parse"); console.time("walk"); - ast.walk({ - _visit: function(node, descend) { - //console.log(node); - if (descend) descend.call(node); - } + var w = new TreeWalker(function(node){ + console.log(node.TYPE + " [ start: " + node.start.line + ":" + node.start.col + ", end: " + node.end.line + ":" + node.end.col + "]"); }); + ast.walk(w); console.timeEnd("walk"); })(); diff --git a/lib/output.js b/lib/output.js index 2c4c6fdc..cf17c3e6 100644 --- a/lib/output.js +++ b/lib/output.js @@ -4,7 +4,6 @@ function OutputStream(options) { indent_level : 4, quote_keys : false, space_colon : false, - beautify : true, ascii_only : false, inline_script : false, width : 80 @@ -12,6 +11,7 @@ function OutputStream(options) { var indentation = 0; var current_col = 0; + var current_line = 0; var OUTPUT = ""; function to_ascii(str) { @@ -45,12 +45,9 @@ function OutputStream(options) { }; function print(str) { - var nl = str.lastIndexOf("\n"); - if (nl >= 0) { - current_col = nl; - } else { - current_col += str.length; - } + var a = str.split(/\r?\n/), n = a.length; + current_line += n; + current_col += a[n - 1].length; OUTPUT += str; }; @@ -71,12 +68,12 @@ function OutputStream(options) { function make_indent(line) { if (line == null) line = ""; - if (beautify) - line = repeat_string(" ", options.indent_start + indentation) + line; + line = repeat_string(" ", options.indent_start + indentation) + line; return line; }; function with_indent(col, cont) { + if (col === true) col = next_indent(); var save_indentation = indentation; indentation = col; var ret = cont(); @@ -85,34 +82,31 @@ function OutputStream(options) { }; function indent() { - if (options.beautify) print(make_indent()); + print(make_indent()); }; function newline() { - if (options.beautify) { - print("\n"); - print(make_indent()); - } + print("\n"); }; function next_indent() { return indentation + options.indent_level; }; - function with_block(cont) { + function with_block(cont, beautify) { var ret; print("{"); with_indent(next_indent(), function(){ - newline(); + if (beautify) newline(); ret = cont(); - newline(); + if (beautify) newline(); }); - indent(); + if (beautify) indent(); print("}"); return ret; }; - function with_parens(cont) { + function with_parens(cont, beautify) { print("("); var ret = with_indent(current_col, cont); print(")"); @@ -128,7 +122,10 @@ function OutputStream(options) { with_indent : with_indent, with_block : with_block, with_parens : with_parens, - options : function() { return options } + options : function() { return options }, + line : function() { return current_line }, + col : function() { return current_col }, + pos : function() { return OUTPUT.length } }; }; diff --git a/lib/parse.js b/lib/parse.js index 9dcfa635..2cd160fa 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -739,13 +739,13 @@ function parse($TEXT, exigent_mode) { case "punc": switch (S.token.value) { case "{": - return new AST_Statement({ body: block_() }); + return new AST_BlockStatement({ body: block_() }); case "[": case "(": return simple_statement(); case ";": next(); - return new AST_Statement(); + return new AST_EmptyStatement(); default: unexpected(); } diff --git a/lib/test.js b/lib/test.js index b765132f..7972d346 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,346 +1,5 @@ -var func = function tokenizer($TEXT) { - - var S = { - text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), - pos : 0, - tokpos : 0, - line : 0, - tokline : 0, - col : 0, - tokcol : 0, - newline_before : false, - regex_allowed : false, - comments_before : [] - }; - - function peek() { return S.text.charAt(S.pos); }; - - function next(signal_eof, in_string) { - var ch = S.text.charAt(S.pos++); - if (signal_eof && !ch) - throw EX_EOF; - if (ch == "\n") { - S.newline_before = S.newline_before || !in_string; - ++S.line; - S.col = 0; - } else { - ++S.col; - } - return ch; - }; - - function eof() { - return !S.peek(); - }; - - function find(what, signal_eof) { - var pos = S.text.indexOf(what, S.pos); - if (signal_eof && pos == -1) throw EX_EOF; - return pos; - }; - - function start_token() { - S.tokline = S.line; - S.tokcol = S.col; - S.tokpos = S.pos; - }; - - function token(type, value, is_comment) { - S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) || - (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) || - (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value))); - var ret = { - type : type, - value : value, - line : S.tokline, - col : S.tokcol, - pos : S.tokpos, - endpos : S.pos, - nlb : S.newline_before - }; - if (!is_comment) { - ret.comments_before = S.comments_before; - S.comments_before = []; - // make note of any newlines in the comments that came before - for (var i = 0, len = ret.comments_before.length; i < len; i++) { - ret.nlb = ret.nlb || ret.comments_before[i].nlb; - } - } - S.newline_before = false; - return new AST_Token(ret); - }; - - function skip_whitespace() { - while (HOP(WHITESPACE_CHARS, peek())) - next(); - }; - - function read_while(pred) { - var ret = "", ch = peek(), i = 0; - while (ch && pred(ch, i++)) { - ret += next(); - ch = peek(); - } - return ret; - }; - - function parse_error(err) { - js_error(err, S.tokline, S.tokcol, S.tokpos); - }; - - function read_num(prefix) { - var has_e = false, after_e = false, has_x = false, has_dot = prefix == "."; - var num = read_while(function(ch, i){ - if (ch == "x" || ch == "X") { - if (has_x) return false; - return has_x = true; - } - if (!has_x && (ch == "E" || ch == "e")) { - if (has_e) return false; - return has_e = after_e = true; - } - if (ch == "-") { - if (after_e || (i == 0 && !prefix)) return true; - return false; - } - if (ch == "+") return after_e; - after_e = false; - if (ch == ".") { - if (!has_dot && !has_x && !has_e) - return has_dot = true; - return false; - } - return is_alphanumeric_char(ch); - }); - if (prefix) - num = prefix + num; - var valid = parse_js_number(num); - if (!isNaN(valid)) { - return token("num", valid); - } else { - parse_error("Invalid syntax: " + num); - } - }; - - function read_escaped_char(in_string) { - var ch = next(true, in_string); - switch (ch) { - case "n" : return "\n"; - case "r" : return "\r"; - case "t" : return "\t"; - case "b" : return "\b"; - case "v" : return "\u000b"; - case "f" : return "\f"; - case "0" : return "\0"; - case "x" : return String.fromCharCode(hex_bytes(2)); - case "u" : return String.fromCharCode(hex_bytes(4)); - case "\n": return ""; - default : return ch; - } - }; - - function hex_bytes(n) { - var num = 0; - for (; n > 0; --n) { - var digit = parseInt(next(true), 16); - if (isNaN(digit)) - parse_error("Invalid hex-character pattern in string"); - num = (num << 4) | digit; - } - return num; - }; - - function read_string() { - return with_eof_error("Unterminated string constant", function(){ - var quote = next(), ret = ""; - for (;;) { - var ch = next(true); - if (ch == "\\") { - // read OctalEscapeSequence (XXX: deprecated if "strict mode") - // https://github.com/mishoo/UglifyJS/issues/178 - var octal_len = 0, first = null; - ch = read_while(function(ch){ - if (ch >= "0" && ch <= "7") { - if (!first) { - first = ch; - return ++octal_len; - } - else if (first <= "3" && octal_len <= 2) return ++octal_len; - else if (first >= "4" && octal_len <= 1) return ++octal_len; - } - return false; - }); - if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); - else ch = read_escaped_char(true); - } - else if (ch == quote) break; - ret += ch; - } - return token("string", ret); - }); - }; - - function read_line_comment() { - next(); - var i = find("\n"), ret; - if (i == -1) { - ret = S.text.substr(S.pos); - S.pos = S.text.length; - } else { - ret = S.text.substring(S.pos, i); - S.pos = i; - } - return token("comment1", ret, true); - }; - - function read_multiline_comment() { - next(); - return with_eof_error("Unterminated multiline comment", function(){ - var i = find("*/", true), - text = S.text.substring(S.pos, i); - S.pos = i + 2; - S.line += text.split("\n").length - 1; - S.newline_before = S.newline_before || text.indexOf("\n") >= 0; - - // https://github.com/mishoo/UglifyJS/issues/#issue/100 - if (/^@cc_on/i.test(text)) { - warn("WARNING: at line " + S.line); - warn("*** Found \"conditional comment\": " + text); - warn("*** UglifyJS DISCARDS ALL COMMENTS. This means your code might no longer work properly in Internet Explorer."); - } - - return token("comment2", text, true); - }); - }; - - function read_name() { - var backslash = false, name = "", ch, escaped = false, hex; - while ((ch = peek()) != null) { - if (!backslash) { - if (ch == "\\") escaped = backslash = true, next(); - else if (is_identifier_char(ch)) name += next(); - else break; - } - else { - if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); - ch = read_escaped_char(); - if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); - name += ch; - backslash = false; - } - } - if (HOP(KEYWORDS, name) && escaped) { - hex = name.charCodeAt(0).toString(16).toUpperCase(); - name = "\\u" + "0000".substr(hex.length) + hex + name.slice(1); - } - return name; - }; - - function read_regexp(regexp) { - return with_eof_error("Unterminated regular expression", function(){ - var prev_backslash = false, ch, in_class = false; - while ((ch = next(true))) if (prev_backslash) { - regexp += "\\" + ch; - prev_backslash = false; - } else if (ch == "[") { - in_class = true; - regexp += ch; - } else if (ch == "]" && in_class) { - in_class = false; - regexp += ch; - } else if (ch == "/" && !in_class) { - break; - } else if (ch == "\\") { - prev_backslash = true; - } else { - regexp += ch; - } - var mods = read_name(); - return token("regexp", [ regexp, mods ]); - }); - }; - - function read_operator(prefix) { - function grow(op) { - if (!peek()) return op; - var bigger = op + peek(); - if (HOP(OPERATORS, bigger)) { - next(); - return grow(bigger); - } else { - return op; - } - }; - return token("operator", grow(prefix || next())); - }; - - function handle_slash() { - next(); - var regex_allowed = S.regex_allowed; - switch (peek()) { - case "/": - S.comments_before.push(read_line_comment()); - S.regex_allowed = regex_allowed; - return next_token(); - case "*": - S.comments_before.push(read_multiline_comment()); - S.regex_allowed = regex_allowed; - return next_token(); - } - return S.regex_allowed ? read_regexp("") : read_operator("/"); - }; - - function handle_dot() { - next(); - return is_digit(peek()) - ? read_num(".") - : token("punc", "."); - }; - - function read_word() { - var word = read_name(); - return HOP(KEYWORDS_ATOM, word) - ? token("atom", word) - : !HOP(KEYWORDS, word) - ? token("name", word) - : HOP(OPERATORS, word) - ? token("operator", word) - : token("keyword", word); - }; - - function with_eof_error(eof_error, cont) { - try { - return cont(); - } catch(ex) { - if (ex === EX_EOF) parse_error(eof_error); - else throw ex; - } - }; - - function next_token(force_regexp) { - if (force_regexp != null) - return read_regexp(force_regexp); - skip_whitespace(); - start_token(); - var ch = peek(); - if (!ch) return token("eof"); - if (is_digit(ch)) return read_num(); - if (ch == '"' || ch == "'") return read_string(); - if (HOP(PUNC_CHARS, ch)) return token("punc", next()); - if (ch == ".") return handle_dot(); - if (ch == "/") return handle_slash(); - if (HOP(OPERATOR_CHARS, ch)) return read_operator(); - if (ch == "\\" || is_identifier_start(ch)) return read_word(); - parse_error("Unexpected character '" + ch + "'"); - }; - - next_token.context = function(nc) { - if (nc) S = nc; - return S; - }; - - return next_token; - +var func = function TEST () { + }; console.time("parse");