From cbe3070ea8d3ebed2038097efb750ee11df86e66 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 10 Jul 2022 03:23:37 +0900 Subject: [PATCH] JavaScript: extract variables and constants in destructuring assignment Close #1112. Signed-off-by: Masatake YAMATO --- .../js-destructural-binding.d/args.ctags | 1 + .../js-destructural-binding.d/expected.tags | 59 +++++++ .../js-destructural-binding.d/input.js | 56 ++++++ .../js-destructural-binding.d/validator | 1 + .../js-empty-class-name.d/expected.tags | 1 + parsers/jscript.c | 159 +++++++++++++++++- 6 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/args.ctags create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/expected.tags create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/input.js create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/validator diff --git a/Units/parser-javascript.r/js-destructural-binding.d/args.ctags b/Units/parser-javascript.r/js-destructural-binding.d/args.ctags new file mode 100644 index 0000000000..5ee5f79f70 --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/args.ctags @@ -0,0 +1 @@ +--sort=no diff --git a/Units/parser-javascript.r/js-destructural-binding.d/expected.tags b/Units/parser-javascript.r/js-destructural-binding.d/expected.tags new file mode 100644 index 0000000000..b77bd2e284 --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/expected.tags @@ -0,0 +1,59 @@ +x input.js /^const [x] = [1];$/;" C +y input.js /^const [y, z] = [1, 2, 3, 4, 5];$/;" C +z input.js /^const [y, z] = [1, 2, 3, 4, 5];$/;" C +a input.js /^let [a=5, b=7] = [1];$/;" v +b input.js /^let [a=5, b=7] = [1];$/;" v +c input.js /^let [c, , d] = [1, 2, 3];$/;" v +d input.js /^let [c, , d] = [1, 2, 3];$/;" v +e input.js /^let [e, f = 0, , g] = [1, 2, 3, 4];$/;" v +f input.js /^let [e, f = 0, , g] = [1, 2, 3, 4];$/;" v +g input.js /^let [e, f = 0, , g] = [1, 2, 3, 4];$/;" v +h input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +i input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +j input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +k input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +l input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +m input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +n input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +o input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +p input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +q input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +A input.js /^const [A, B, ...{ C, D }] = []$/;" C +B input.js /^const [A, B, ...{ C, D }] = []$/;" C +C input.js /^const [A, B, ...{ C, D }] = []$/;" C +D input.js /^const [A, B, ...{ C, D }] = []$/;" C +E input.js /^ E: 42,$/;" p variable:user +F input.js /^ F: true$/;" p variable:user +user input.js /^const user = {$/;" v +E input.js /^const {E, F} = user;$/;" C +F input.js /^const {E, F} = user;$/;" C +G input.js /^const {E: G, F: H} = user;$/;" C +H input.js /^const {E: G, F: H} = user;$/;" C +I input.js /^const {I = 10, J = 5} = {I: 3};$/;" C +J input.js /^const {I = 10, J = 5} = {I: 3};$/;" C +I input.js /^const {I = 10, J = 5} = {I: 3};$/;" p variable:anonymousObject785a93f40105 +anonymousObject785a93f40105 input.js /^const {I = 10, J = 5} = {I: 3};$/;" v +K input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" v +L input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" v +a input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" p variable:anonymousObject785a93f40205 +anonymousObject785a93f40205 input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" v +M input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +N input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +O input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +M input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +N input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +c input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +d input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +anonymousObject785a93f40305 input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +title input.js /^ title: 'Scratchpad',$/;" p variable:metadata +anonymousObject785a93f40405 input.js /^ {$/;" v variable:metadata +locale input.js /^ locale: 'de',$/;" p variable:metadata.anonymousObject785a93f40405 +localization_tags input.js /^ localization_tags: [],$/;" p variable:metadata.anonymousObject785a93f40405 +last_edit input.js /^ last_edit: '2014-04-14T08:43:37',$/;" p variable:metadata.anonymousObject785a93f40405 +url input.js /^ url: '\/de\/docs\/Tools\/Scratchpad',$/;" p variable:metadata.anonymousObject785a93f40405 +title input.js /^ title: 'JavaScript-Umgebung'$/;" p variable:metadata.anonymousObject785a93f40405 +translations input.js /^ translations: [$/;" p variable:metadata +url input.js /^ url: '\/en-US\/docs\/Tools\/Scratchpad'$/;" p variable:metadata +metadata input.js /^const metadata = {$/;" v +englishTitle input.js /^ title: englishTitle, \/\/ rename$/;" v +localeTitle input.js /^ title: localeTitle, \/\/ rename$/;" v diff --git a/Units/parser-javascript.r/js-destructural-binding.d/input.js b/Units/parser-javascript.r/js-destructural-binding.d/input.js new file mode 100644 index 0000000000..5fc18b740f --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/input.js @@ -0,0 +1,56 @@ +// Derrived from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +const [x] = [1]; +const [y, z] = [1, 2, 3, 4, 5]; +let [a=5, b=7] = [1]; + +let [c, , d] = [1, 2, 3]; +let [e, f = 0, , g] = [1, 2, 3, 4]; + +let [,,] = [1, 2, 3]; +let [,] = [1, 2, 3]; +let [] = [1, 2, 3]; + +const [h, i, ...[j, k]] = [1, 2, 3, 4]; +const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6]; + +const [A, B, ...{ C, D }] = [] + +const user = { + E: 42, + F: true +}; + +const {E, F} = user; + +const {E: G, F: H} = user; + + +const {I = 10, J = 5} = {I: 3}; + +let {a: K = 10, b: L = 5} = {a: 3}; + +let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40} + + +const metadata = { + title: 'Scratchpad', + translations: [ + { + locale: 'de', + localization_tags: [], + last_edit: '2014-04-14T08:43:37', + url: '/de/docs/Tools/Scratchpad', + title: 'JavaScript-Umgebung' + } + ], + url: '/en-US/docs/Tools/Scratchpad' +}; + +let { + title: englishTitle, // rename + translations: [ + { + title: localeTitle, // rename + }, + ], +} = metadata; diff --git a/Units/parser-javascript.r/js-destructural-binding.d/validator b/Units/parser-javascript.r/js-destructural-binding.d/validator new file mode 100644 index 0000000000..64f5a0a681 --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/validator @@ -0,0 +1 @@ +node diff --git a/Units/parser-javascript.r/js-empty-class-name.d/expected.tags b/Units/parser-javascript.r/js-empty-class-name.d/expected.tags index 438218215f..3a51634fb7 100644 --- a/Units/parser-javascript.r/js-empty-class-name.d/expected.tags +++ b/Units/parser-javascript.r/js-empty-class-name.d/expected.tags @@ -1,2 +1,3 @@ +prop input.js /^var {prop} = { prop: "value" };$/;" v prop input.js /^var {prop} = { prop: "value" };$/;" p variable:anonymousObject4ca5b60a0105 anonymousObject4ca5b60a0105 input.js /^var {prop} = { prop: "value" };$/;" v diff --git a/parsers/jscript.c b/parsers/jscript.c index 90401cddcd..95da024b53 100644 --- a/parsers/jscript.c +++ b/parsers/jscript.c @@ -2637,7 +2637,8 @@ static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, st if (state->indexForName == CORK_NIL) { - state->indexForName = makeJsTag (name, state->isConst ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + if (!vStringIsEmpty (name->string)) + state->indexForName = makeJsTag (name, state->isConst ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); if (isType (token, TOKEN_IDENTIFIER)) canbe_arrowfun = true; } @@ -2706,6 +2707,143 @@ static bool parseStatementRHS (tokenInfo *const name, tokenInfo *const token, st return true; } +static bool parseObjectDestructuring (tokenInfo *const token, bool is_const); +static bool parseArrayDestructuring (tokenInfo *const token, bool is_const) +{ + int nest_level = 1; + bool in_left_side = true; + bool found = false; + + while (nest_level > 0 && ! isType (token, TOKEN_EOF)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_SQUARE)) + { + in_left_side = true; + nest_level++; + } + else if (isType (token, TOKEN_CLOSE_SQUARE)) + { + in_left_side = false; + nest_level--; + } + else if (isType (token, TOKEN_OPEN_CURLY)) + { + in_left_side = false; + if (parseObjectDestructuring (token, is_const)) + found = true; + } + else if (isType (token, TOKEN_COMMA) + || isType (token, TOKEN_DOTS)) + in_left_side = true; + else if (in_left_side && isType (token, TOKEN_IDENTIFIER)) + { + in_left_side = false; + makeJsTag (token, + is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, + NULL, NULL); + found = true; + } + else if (isType (token, TOKEN_EQUAL_SIGN)) + { + in_left_side = false; + /* TODO: SKIP */ + } + else + in_left_side = false; + } + + return found; +} + +static bool parseObjectDestructuring (tokenInfo *const token, bool is_const) +{ + tokenInfo *const name = newToken (); + bool found = false; + + /* + * let { k0: v0, k1: v1 = 0, v3 }; + * | | || | | | + * ^...|..|^...|..|....^.....: start + * ....^..|....^..|..........: colon + * .......^.......^..........: emitted (made a tag for an id after colon) + */ + enum objDestructuringState { + OBJ_DESTRUCTURING_START, + OBJ_DESTRUCTURING_COLON, + OBJ_DESTRUCTURING_EMITTED, + } state = OBJ_DESTRUCTURING_START; + + while (! isType (token, TOKEN_EOF)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_CURLY)) + { + if (parseObjectDestructuring (token, is_const)) + found = true; + if (state == OBJ_DESTRUCTURING_COLON) + state = OBJ_DESTRUCTURING_EMITTED; + } + else if (isType (token, TOKEN_CLOSE_CURLY)) + { + if (!vStringIsEmpty(name->string)) + { + makeJsTag (name, + is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, + NULL, NULL); + found = true; + } + break; + } + else if (isType (token, TOKEN_OPEN_SQUARE)) + { + if (parseArrayDestructuring (token, is_const)) + found = true; + if (state == OBJ_DESTRUCTURING_COLON) + state = OBJ_DESTRUCTURING_EMITTED; + } + else if (isType (token, TOKEN_IDENTIFIER)) + { + if (state == OBJ_DESTRUCTURING_COLON) + { + makeJsTag (token, + is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, + NULL, NULL); + found = true; + state = OBJ_DESTRUCTURING_EMITTED; + } + else if (state == OBJ_DESTRUCTURING_START + && vStringIsEmpty(name->string)) + copyToken(name, token, true); + } + else if (isType (token, TOKEN_COMMA)) + { + if (!vStringIsEmpty(name->string)) + { + makeJsTag (name, + is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, + NULL, NULL); + found = true; + vStringClear (name->string); + } + state = OBJ_DESTRUCTURING_START; + } + else if (isType (token, TOKEN_COLON)) + { + vStringClear (name->string); + state = OBJ_DESTRUCTURING_COLON; + } + else + { + if (state == OBJ_DESTRUCTURING_COLON) + state = OBJ_DESTRUCTURING_EMITTED; + } + } + + deleteToken (name); + return found; +} + static bool parseStatement (tokenInfo *const token, bool is_inside_class) { TRACE_ENTER_TEXT("is_inside_class: %s", is_inside_class? "yes": "no"); @@ -2773,6 +2911,22 @@ static bool parseStatement (tokenInfo *const token, bool is_inside_class) state.isGlobal = true; } readToken(token); + + if (state.isGlobal) + { + bool found = false; + if (isType (token, TOKEN_OPEN_CURLY)) + found = parseObjectDestructuring (token, state.isConst); + else if (isType (token, TOKEN_OPEN_SQUARE)) + found = parseArrayDestructuring (token, state.isConst); + + if (found) + { + /* Adjust the context to the code for non-destructing. */ + found_lhs = true; + readToken(token); + } + } } nextVar: @@ -2860,7 +3014,8 @@ static bool parseStatement (tokenInfo *const token, bool is_inside_class) * Handles this syntax: * var g_var2; */ - state.indexForName = makeJsTag (name, state.isConst ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + if (!vStringIsEmpty (name->string)) + state.indexForName = makeJsTag (name, state.isConst ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); } /* * Statement has ended.