• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { PandaGen } from "src/pandagen";
17import * as ts from "typescript";
18import { LReference } from "./base/lreference";
19import {
20    isArrayBindingOrAssignmentPattern,
21    isObjectBindingOrAssignmentPattern
22} from "./base/util";
23import {
24    CacheList,
25    getVregisterCache
26} from "./base/vregisterCache";
27import { Compiler } from "./compiler";
28import {
29    Label,
30    VReg
31} from "./irnodes";
32import * as jshelpers from "./jshelpers";
33import {
34    CatchTable,
35    LabelPair
36} from "./statement/tryStatement";
37import { Iterator } from "./base/iterator";
38
39export function compileDestructuring(pattern: ts.BindingOrAssignmentPattern, pandaGen: PandaGen, compiler: Compiler): void {
40    let rhs = pandaGen.getTemp();
41    pandaGen.storeAccumulator(pattern, rhs);
42
43    if (isArrayBindingOrAssignmentPattern(pattern)) {
44        compileArrayDestructuring(<ts.ArrayBindingOrAssignmentPattern>pattern, pandaGen, compiler);
45    }
46
47    if (isObjectBindingOrAssignmentPattern(pattern)) {
48        compileObjectDestructuring(<ts.ObjectBindingOrAssignmentPattern>pattern, pandaGen, compiler);
49    }
50
51    pandaGen.loadAccumulator(pattern, rhs);
52    pandaGen.freeTemps(rhs);
53}
54
55function compileArrayDestructuring(arr: ts.ArrayBindingOrAssignmentPattern, pandaGen: PandaGen, compiler: Compiler): void {
56    let iter = pandaGen.getTemp();
57    let nextMethod = pandaGen.getTemp();
58    let iterDone = pandaGen.getTemp();
59    let iterValue = pandaGen.getTemp();
60    let nextResult = pandaGen.getTemp();
61    let exception = pandaGen.getTemp();
62
63    let isDeclaration = ts.isArrayBindingPattern(arr) ? true : false;
64
65    // get iterator
66    let iterator = new Iterator({iterator: iter, nextMethod: nextMethod}, iterDone, iterValue, pandaGen, arr);
67    iterator.getIterator();
68
69    if (arr.elements.length === 0) {
70        iterator.close();
71        pandaGen.freeTemps(iter, nextMethod, iterDone, iterValue, nextResult, exception);
72        return;
73    }
74
75    // prepare try-catch for iterate over all the elements
76    let tryBeginLabel = new Label();
77    let tryEndLabel = new Label();
78    let catchBeginLabel = new Label();
79    let catchEndLabel = new Label();
80    let normalClose = new Label();
81    let endLabel = new Label();
82    new CatchTable(
83        pandaGen,
84        catchBeginLabel,
85        new LabelPair(tryBeginLabel, tryEndLabel)
86    );
87
88    // try start
89    pandaGen.label(arr, tryBeginLabel);
90
91    for (let i = 0; i < arr.elements.length; i++) {
92        let element = arr.elements[i];
93        iterator.callNext(nextResult);
94
95        // if a hole exist, just let the iterator step ahead
96        if (ts.isOmittedExpression(element)) {
97            iterator.iteratorComplete(nextResult);
98            continue;
99        }
100
101        // if its spread element
102        if ((!isDeclaration && ts.isSpreadElement(element)) ||
103            (isDeclaration && (<ts.BindingElement>element).dotDotDotToken)) {
104            emitRestElement(isDeclaration ? (<ts.BindingElement>element).name : (<ts.SpreadElement>element).expression,
105                            iterator, nextResult, pandaGen, compiler, isDeclaration);
106            pandaGen.branch(element, endLabel);
107            break;
108        }
109
110        let hasInit = false;
111        let target: ts.Node = isDeclaration ? (<ts.BindingElement>element).name : <ts.Expression>element;
112        let init: ts.Expression | undefined = undefined;
113        // in case init is present
114        if (!isDeclaration && ts.isBinaryExpression(element)) {
115            if (element.operatorToken.kind != ts.SyntaxKind.EqualsToken) {
116                throw new Error("Invalid destructuring assignment target");
117            }
118
119            target = element.left;
120            init = element.right;
121            hasInit = true;
122        } else if (isDeclaration && (<ts.BindingElement>element).initializer) {
123            init = (<ts.BindingElement>element).initializer;
124            hasInit = true;
125        }
126
127        let lRef = LReference.generateLReference(compiler, target, isDeclaration ? true : false);
128
129        let getDefaultLabel = new Label();
130        let getUndefinedLabel = new Label();
131        let storeLabel = new Label();
132
133        iterator.iteratorComplete(nextResult);
134        pandaGen.condition(
135            element,
136            ts.SyntaxKind.ExclamationEqualsEqualsToken,
137            getVregisterCache(pandaGen, CacheList.TRUE),
138            hasInit ? getDefaultLabel : getUndefinedLabel
139        );
140
141        // iterdone === false, get current itervalue
142        iterator.iteratorValue(nextResult);
143
144        if (hasInit) {
145            pandaGen.condition(
146                element,
147                ts.SyntaxKind.ExclamationEqualsEqualsToken,
148                getVregisterCache(pandaGen, CacheList.UNDEFINED),
149                getDefaultLabel
150            )
151
152            pandaGen.loadAccumulator(element, iterator.getCurrentValue());
153            pandaGen.branch(element, storeLabel);
154
155            pandaGen.label(element, getDefaultLabel);
156            compiler.compileExpression(<ts.Expression>init);
157
158            pandaGen.branch(element, storeLabel);
159        } else {
160            pandaGen.branch(element, storeLabel);
161        }
162
163        pandaGen.label(element, getUndefinedLabel);
164        pandaGen.loadAccumulator(element, getVregisterCache(pandaGen, CacheList.UNDEFINED));
165
166        pandaGen.label(element, storeLabel);
167        lRef.setValue();
168    }
169    // end of try
170    pandaGen.label(arr, tryEndLabel);
171
172    pandaGen.loadAccumulator(arr, iterator.getCurrrentDone());
173    pandaGen.condition(
174        arr,
175        ts.SyntaxKind.EqualsEqualsEqualsToken,
176        getVregisterCache(pandaGen, CacheList.TRUE),
177        normalClose
178    );
179
180    // nothing need to be done
181    pandaGen.branch(arr, endLabel);
182
183    // if any exception ocurrs, store it, close iterator and rethrow exception
184    pandaGen.label(arr, catchBeginLabel);
185    pandaGen.storeAccumulator(arr, exception);
186    iterator.close();
187    pandaGen.loadAccumulator(arr, exception);
188    pandaGen.throw(arr);
189    pandaGen.label(arr, catchEndLabel);
190
191    // if iterDone is not true after normal completion, close iterator
192    pandaGen.label(arr, normalClose);
193    iterator.close();
194
195    pandaGen.label(arr, endLabel);
196    pandaGen.freeTemps(iter, nextMethod, iterDone, iterValue, nextResult, exception);
197}
198
199function emitRestElement(restElement: ts.BindingName | ts.Expression, iterator: Iterator, iterResult: VReg,
200                         pandaGen: PandaGen, compiler: Compiler, isDeclaration: boolean): void {
201    let arrayObj = pandaGen.getTemp();
202    let index = pandaGen.getTemp();
203
204    let nextLabel = new Label();
205    let doneLabel = new Label();
206
207    // create left reference for rest element
208    let target = restElement;
209    let lRef = LReference.generateLReference(compiler, target, isDeclaration);
210
211    // create an empty array first
212    pandaGen.createEmptyArray(restElement);
213    pandaGen.storeAccumulator(restElement, arrayObj);
214
215    // index = 0
216    pandaGen.loadAccumulatorInt(restElement, 0);
217    pandaGen.storeAccumulator(restElement, index);
218
219    pandaGen.label(restElement, nextLabel);
220
221    // if iterDone === true, done with the process of building array
222    iterator.iteratorComplete(iterResult);
223    pandaGen.condition(
224        restElement,
225        ts.SyntaxKind.ExclamationEqualsEqualsToken,
226        getVregisterCache(pandaGen, CacheList.TRUE),
227        doneLabel
228    );
229
230    // get value from iter and store it to arrayObj
231    iterator.iteratorValue(iterResult);
232    pandaGen.storeObjProperty(restElement, arrayObj, index);
233
234    // index++
235    pandaGen.loadAccumulatorInt(restElement, 1);
236    pandaGen.binary(restElement, ts.SyntaxKind.PlusToken, index);
237    pandaGen.storeAccumulator(restElement, index);
238
239    iterator.callNext(iterResult);
240    pandaGen.branch(restElement, nextLabel);
241
242    pandaGen.label(restElement, doneLabel);
243    pandaGen.loadAccumulator(restElement, arrayObj);
244
245    lRef.setValue();
246
247    pandaGen.freeTemps(arrayObj, index);
248}
249
250function compileObjectDestructuring(obj: ts.ObjectBindingOrAssignmentPattern, pandaGen: PandaGen, compiler: Compiler): void {
251    let value = pandaGen.getTemp();
252    pandaGen.storeAccumulator(obj, value);
253
254    let isDeclaration: boolean = ts.isObjectLiteralExpression(obj) ? false : true;
255    let elements = isDeclaration ? (<ts.ObjectBindingPattern>obj).elements : (<ts.ObjectLiteralExpression>obj).properties;
256    let elementsLength = elements.length;
257
258    // check if value is coercible
259    if (elementsLength === 0 ||
260        (isDeclaration && isRestElement(<ts.BindingElement>elements[0])) ||
261        (!isDeclaration && ts.isSpreadAssignment(elements[0]))) {
262        let notNullish: Label = new Label();
263        let nullLish: Label = new Label();
264
265        pandaGen.loadAccumulator(obj, value);
266        pandaGen.condition(obj, ts.SyntaxKind.ExclamationEqualsEqualsToken, getVregisterCache(pandaGen, CacheList.NULL), nullLish);
267        pandaGen.loadAccumulator(obj, value);
268        pandaGen.condition(obj, ts.SyntaxKind.ExclamationEqualsEqualsToken, getVregisterCache(pandaGen, CacheList.UNDEFINED), nullLish);
269        pandaGen.branch(obj, notNullish);
270
271        // value == null or undefined, throw error
272        pandaGen.label(obj, nullLish);
273        pandaGen.throwObjectNonCoercible(obj);
274
275        pandaGen.label(obj, notNullish);
276    }
277
278    // create before to store the properties
279    let propertiesReg: Array<VReg> = new Array<VReg>();
280    let properties: Array<VReg | string> = new Array<VReg | string>();
281    let excludedProp: Array<VReg | string> = new Array<VReg | string>();
282
283    for (let i = 0; i < elementsLength; i++) {
284        let tmp = pandaGen.getTemp();
285        properties.push(tmp);
286        propertiesReg.push(tmp);
287    }
288
289    for (let i = 0; i < elementsLength; i++) {
290        let element = elements[i];
291
292        // emit rest property
293        if ((isDeclaration && isRestElement(<ts.BindingElement>element)) ||
294            (!isDeclaration && ts.isSpreadAssignment(element))) {
295            emitRestProperty(<ts.BindingElement | ts.SpreadAssignment>element, excludedProp, value, pandaGen, compiler);
296            break;
297        }
298
299        let loadedValue: VReg = pandaGen.getTemp();
300        let key: ts.Expression | ts.ComputedPropertyName;
301        let target: ts.Node = element;
302        let init: ts.Expression | undefined = undefined;
303        let hasInit: boolean = false;
304
305        if (isDeclaration) {
306            let bindingElement = <ts.BindingElement>element;
307            target = bindingElement.name;
308
309            if (bindingElement.propertyName) {
310                key = <ts.Expression>bindingElement.propertyName;
311            } else {
312                key = <ts.Identifier>bindingElement.name;
313            }
314
315            // obtain init if exists
316            if (bindingElement.initializer) {
317                hasInit = true;
318                init = bindingElement.initializer;
319            }
320        } else {
321            if (ts.isPropertyAssignment(element)) {
322                key = <ts.Expression>element.name;
323
324                let targetExpr = element.initializer;
325                if (ts.isBinaryExpression(targetExpr)) {
326                    if (targetExpr.operatorToken.kind != ts.SyntaxKind.EqualsToken) {
327                        throw new Error("Invalid destructuring target");
328                    }
329
330                    target = targetExpr.left;
331                    init = targetExpr.right;
332                } else {
333                    target = targetExpr;
334                }
335            } else if (ts.isShorthandPropertyAssignment(element)) {
336                key = element.name;
337                target = element.name;
338                init = element.objectAssignmentInitializer ? element.objectAssignmentInitializer : undefined;
339            } else {
340                throw new Error("Invalid destructuring target");
341            }
342        }
343
344        // compile key
345        if (ts.isIdentifier(key)) {
346            let keyName: string = jshelpers.getTextOfIdentifierOrLiteral(key);
347            properties[i] = keyName;
348        } else {
349            ts.isComputedPropertyName(key) ? compiler.compileExpression(key.expression) :
350                                             compiler.compileExpression(key);
351            pandaGen.storeAccumulator(key, <VReg>properties[i]);
352        }
353
354        excludedProp.push(properties[i]);
355
356        // create left reference
357        let lRef = LReference.generateLReference(compiler, target, isDeclaration);
358
359        // load obj property from rhs, return undefined if no corresponding property exists
360        pandaGen.loadObjProperty(element, value, properties[i]);
361
362        let getDefaultLabel = new Label();
363        let storeLabel = new Label();
364
365        if (hasInit) {
366            pandaGen.storeAccumulator(element, loadedValue);
367            pandaGen.condition(
368                element,
369                ts.SyntaxKind.ExclamationEqualsEqualsToken,
370                getVregisterCache(pandaGen, CacheList.UNDEFINED),
371                getDefaultLabel
372            );
373
374            // load the new value
375            pandaGen.loadAccumulator(element, loadedValue);
376            pandaGen.branch(element, storeLabel);
377
378            // use default value
379            pandaGen.label(element, getDefaultLabel);
380            compiler.compileExpression(<ts.Expression>init);
381
382            pandaGen.label(element, storeLabel);
383        }
384
385        lRef.setValue();
386        pandaGen.freeTemps(loadedValue);
387    }
388
389    pandaGen.freeTemps(value, ...propertiesReg);
390}
391
392function emitRestProperty(restProperty: ts.BindingElement | ts.SpreadAssignment, excludedProp: Array<VReg | string>,
393                          obj: VReg, pandaGen: PandaGen, compiler: Compiler): void {
394    let isDeclaration = ts.isBindingElement(restProperty) ? true : false;
395    let target = isDeclaration ? (<ts.BindingElement>restProperty).name : (<ts.SpreadAssignment>restProperty).expression;
396    let lRef = LReference.generateLReference(compiler, target, true);
397
398    if (excludedProp.length === 0) {
399        excludedProp = [getVregisterCache(pandaGen, CacheList.UNDEFINED)];
400    }
401
402    // Create a Object with the information of excluded properties
403    let namedPropRegs: Array<VReg> = new Array<VReg>();
404    for (let i = 0; i < excludedProp.length; i++) {
405        let prop: VReg | string = excludedProp[i];
406        if (prop instanceof VReg) {
407            continue;
408        }
409
410        let propReg: VReg = pandaGen.getTemp();
411        namedPropRegs.push(propReg);
412        pandaGen.loadAccumulatorString(restProperty, prop);
413        pandaGen.storeAccumulator(restProperty, propReg);
414        excludedProp[i] = propReg;
415    }
416    pandaGen.createObjectWithExcludedKeys(restProperty, obj, <Array<VReg>>excludedProp);
417
418    lRef.setValue();
419    pandaGen.freeTemps(...namedPropRegs);
420}
421
422function isRestElement(node: ts.BindingElement): boolean {
423    if (node.dotDotDotToken) {
424        return true;
425    }
426
427    return false;
428}