From a96f087ac33b6d18d5ffa3cacd39dd693defb7cf Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 17 Dec 2020 10:23:41 +0000 Subject: support arrow function (#4385) --- lib/parse.js | 156 +++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 114 insertions(+), 42 deletions(-) (limited to 'lib/parse.js') diff --git a/lib/parse.js b/lib/parse.js index c65b2f42..f760d0de 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -569,6 +569,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } if (is_digit(code)) return read_num(); if (PUNC_CHARS[ch]) return token("punc", next()); + if (looking_at("=>")) return token("punc", next() + next()); if (OPERATOR_CHARS[ch]) return read_operator(); if (code == 92 || !NON_IDENTIFIER_CHARS[ch]) return read_word(); break; @@ -634,7 +635,7 @@ var PRECEDENCE = function(a, ret) { ["*", "/", "%"] ], {}); -var ATOMIC_START_TOKEN = makePredicate("atom num string regexp name"); +var ATOMIC_START_TOKEN = makePredicate("atom num regexp string"); /* -----[ Parser ]----- */ @@ -741,7 +742,7 @@ function parse($TEXT, options) { function parenthesised() { expect("("); - var exp = expression(true); + var exp = expression(); expect(")"); return exp; } @@ -769,7 +770,7 @@ function parse($TEXT, options) { switch (S.token.type) { case "string": var dir = S.in_directives; - var body = expression(true); + var body = expression(); if (dir) { if (body instanceof AST_String) { var value = body.start.raw.slice(1, -1); @@ -887,7 +888,7 @@ function parse($TEXT, options) { if (is("punc", ";")) { next(); } else if (!can_insert_semicolon()) { - value = expression(true); + value = expression(); semicolon(); } return new AST_Return({ @@ -905,7 +906,7 @@ function parse($TEXT, options) { next(); if (has_newline_before(S.token)) croak("Illegal newline after 'throw'"); - var value = expression(true); + var value = expression(); semicolon(); return new AST_Throw({ value: value @@ -956,9 +957,7 @@ function parse($TEXT, options) { // https://github.com/mishoo/UglifyJS/issues/287 label.references.forEach(function(ref) { if (ref instanceof AST_Continue) { - ref = ref.label.start; - croak("Continue label `" + label.name + "` refers to non-IterationStatement.", - ref.line, ref.col, ref.pos); + token_error(ref.label.start, "Continue label `" + label.name + "` must refer to IterationStatement"); } }); } @@ -966,7 +965,7 @@ function parse($TEXT, options) { } function simple_statement() { - var body = expression(true); + var body = expression(); semicolon(); return new AST_SimpleStatement({ body: body }); } @@ -980,7 +979,7 @@ function parse($TEXT, options) { ldef = find_if(function(l) { return l.name == label.name; }, S.labels); - if (!ldef) croak("Undefined label " + label.name); + if (!ldef) token_error(label.start, "Undefined label " + label.name); label.thedef = ldef; } else if (S.in_loop == 0) croak(type.TYPE + " not inside a loop or switch"); semicolon(); @@ -999,13 +998,14 @@ function parse($TEXT, options) { ? (next(), let_(true)) : is("keyword", "var") ? (next(), var_(true)) - : expression(true, true); + : expression(true); if (is("operator", "in")) { if (init instanceof AST_Definitions) { - if (init.definitions.length > 1) - croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos); + if (init.definitions.length > 1) { + token_error(init.start, "Only one variable declaration allowed in for..in loop"); + } } else if (!(is_assignable(init) || (init = to_destructured(init)) instanceof AST_Destructured)) { - croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos); + token_error(init.start, "Invalid left-hand side in for..in loop"); } next(); return for_in(init); @@ -1016,9 +1016,9 @@ function parse($TEXT, options) { function regular_for(init) { expect(";"); - var test = is("punc", ";") ? null : expression(true); + var test = is("punc", ";") ? null : expression(); expect(";"); - var step = is("punc", ")") ? null : expression(true); + var step = is("punc", ")") ? null : expression(); expect(")"); return new AST_For({ init : init, @@ -1029,7 +1029,7 @@ function parse($TEXT, options) { } function for_in(init) { - var obj = expression(true); + var obj = expression(); expect(")"); return new AST_ForIn({ init : init, @@ -1038,6 +1038,71 @@ function parse($TEXT, options) { }); } + function to_funarg(node) { + if (node instanceof AST_Array) return new AST_DestructuredArray({ + start: node.start, + elements: node.elements.map(function(node) { + return node instanceof AST_Hole ? node : to_funarg(node); + }), + end: node.end, + }); + if (node instanceof AST_Object) return new AST_DestructuredObject({ + start: node.start, + properties: node.properties.map(function(prop) { + if (!(prop instanceof AST_ObjectKeyVal)) token_error(prop.start, "Invalid destructuring assignment"); + return new AST_DestructuredKeyVal({ + start: prop.start, + key: prop.key, + value: to_funarg(prop.value), + end: prop.end, + }); + }), + end: node.end, + }); + if (node instanceof AST_SymbolRef) return new AST_SymbolFunarg(node); + token_error(node.start, "Invalid arrow parameter"); + } + + function arrow(exprs, start) { + var was_async = S.in_async; + S.in_async = false; + var was_funarg = S.in_funarg; + S.in_funarg = S.in_function; + var argnames = exprs.map(to_funarg); + S.in_funarg = was_funarg; + expect("=>"); + var body, value; + var loop = S.in_loop; + var labels = S.labels; + ++S.in_function; + S.in_directives = true; + S.input.push_directives_stack(); + S.in_loop = 0; + S.labels = []; + if (is("punc", "{")) { + body = block_(); + value = null; + if (S.input.has_directive("use strict")) { + argnames.forEach(strict_verify_symbol); + } + } else { + body = []; + value = maybe_assign(); + } + S.input.pop_directives_stack(); + --S.in_function; + S.in_loop = loop; + S.labels = labels; + S.in_async = was_async; + return new AST_Arrow({ + start: start, + argnames: argnames, + body: body, + value: value, + end: prev(), + }); + } + var function_ = function(ctor) { var was_async = S.in_async; var name; @@ -1118,7 +1183,7 @@ function parse($TEXT, options) { cur = []; branch = new AST_Case({ start : (tmp = S.token, next(), tmp), - expression : expression(true), + expression : expression(), body : cur }); a.push(branch); @@ -1187,7 +1252,7 @@ function parse($TEXT, options) { var value = null; if (is("operator", "=")) { next(); - value = expression(false, no_in); + value = maybe_assign(no_in); } else if (!no_in && (type === AST_SymbolConst || name instanceof AST_Destructured)) { croak("Missing initializer in declaration"); } @@ -1251,9 +1316,6 @@ function parse($TEXT, options) { function as_atom_node() { var tok = S.token, ret; switch (tok.type) { - case "name": - ret = _make_symbol(AST_SymbolRef, tok); - break; case "num": ret = new AST_Number({ start: tok, end: tok, value: tok.value }); break; @@ -1295,7 +1357,11 @@ function parse($TEXT, options) { switch (start.value) { case "(": next(); - var ex = expression(true); + if (is("punc", ")")) { + next(); + return arrow([], start); + } + var ex = expression(false, true); var len = start.comments_before.length; [].unshift.apply(ex.start.comments_before, start.comments_before); start.comments_before.length = 0; @@ -1318,6 +1384,7 @@ function parse($TEXT, options) { end.comments_after = ex.end.comments_after; ex.end = end; if (ex instanceof AST_Call) mark_pure(ex); + if (is("punc", "=>")) return arrow(ex instanceof AST_Sequence ? ex.expressions : [ ex ], start); return subscripts(ex, allow_calls); case "[": return subscripts(array_(), allow_calls); @@ -1340,6 +1407,11 @@ function parse($TEXT, options) { func.end = prev(); return subscripts(func, allow_calls); } + if (is("name")) { + var sym = _make_symbol(AST_SymbolRef, start); + next(); + return is("punc", "=>") ? arrow([ sym ], start) : subscripts(sym, allow_calls); + } if (ATOMIC_START_TOKEN[S.token.type]) { return subscripts(as_atom_node(), allow_calls); } @@ -1347,14 +1419,14 @@ function parse($TEXT, options) { }; function expr_list(closing, allow_trailing_comma, allow_empty, parser) { - if (!parser) parser = expression; + if (!parser) parser = maybe_assign; var first = true, a = []; while (!is("punc", closing)) { if (first) first = false; else expect(","); if (allow_trailing_comma && is("punc", closing)) break; if (allow_empty && is("punc", ",")) { a.push(new AST_Hole({ start: S.token, end: S.token })); - } else if (parser === expression && is("operator", "...")) { + } else if (parser === maybe_assign && is("operator", "...")) { a.push(new AST_Spread({ start: S.token, expression: (next(), parser()), @@ -1391,7 +1463,7 @@ function parse($TEXT, options) { next(); a.push(new AST_Spread({ start: start, - expression: expression(false), + expression: maybe_assign(), end: prev(), })); continue; @@ -1440,7 +1512,7 @@ function parse($TEXT, options) { a.push(new AST_ObjectKeyVal({ start: start, key: key, - value: expression(false), + value: maybe_assign(), end: prev(), })); } @@ -1463,7 +1535,7 @@ function parse($TEXT, options) { case "punc": if (tmp.value != "[") unexpected(); next(); - var key = expression(false); + var key = maybe_assign(); expect("]"); return key; default: @@ -1490,7 +1562,7 @@ function parse($TEXT, options) { function strict_verify_symbol(sym) { if (sym.name == "arguments" || sym.name == "eval") - croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos); + token_error(sym.start, "Unexpected " + sym.name + " in strict mode"); } function as_symbol(type, noerror) { @@ -1580,7 +1652,7 @@ function parse($TEXT, options) { } if (is("punc", "[")) { next(); - var prop = expression(true); + var prop = expression(); expect("]"); return subscripts(new AST_Sub({ start : start, @@ -1629,11 +1701,11 @@ function parse($TEXT, options) { case "++": case "--": if (!is_assignable(expr)) - croak("Invalid use of " + op + " operator", token.line, token.col, token.pos); + token_error(token, "Invalid use of " + op + " operator"); break; case "delete": if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict")) - croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos); + token_error(expr.start, "Calling delete on expression not allowed in strict mode"); break; } return new ctor({ operator: op, expression: expr }); @@ -1679,13 +1751,13 @@ function parse($TEXT, options) { var expr = expr_ops(no_in); if (is("operator", "?")) { next(); - var yes = expression(false); + var yes = maybe_assign(); expect(":"); return new AST_Conditional({ start : start, condition : expr, consequent : yes, - alternative : expression(false, no_in), + alternative : maybe_assign(no_in), end : prev() }); } @@ -1728,7 +1800,7 @@ function parse($TEXT, options) { }); } - var maybe_assign = function(no_in) { + function maybe_assign(no_in) { var start = S.token; var left = maybe_conditional(no_in), val = S.token.value; if (is("operator") && ASSIGNMENT[val]) { @@ -1745,23 +1817,23 @@ function parse($TEXT, options) { croak("Invalid assignment"); } return left; - }; + } - var expression = function(commas, no_in) { + function expression(no_in, maybe_arrow) { var start = S.token; var exprs = []; while (true) { exprs.push(maybe_assign(no_in)); - if (!commas || !is("punc", ",")) break; + if (!is("punc", ",")) break; next(); - commas = true; + if (maybe_arrow && is("punc", ")") && is_token(peek(), "punc", "=>")) break; } return exprs.length == 1 ? exprs[0] : new AST_Sequence({ start : start, expressions : exprs, - end : peek() + end : prev() }); - }; + } function in_loop(cont) { ++S.in_loop; @@ -1772,7 +1844,7 @@ function parse($TEXT, options) { if (options.expression) { handle_regexp(); - return expression(true); + return expression(); } return function() { -- cgit v1.2.3