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}