• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2022 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 * as ts from "typescript";
17import { SourceTextModuleRecord } from "./ecmaModule";
18import { LOGD } from "./log";
19import {
20    GlobalVariable,
21    LocalVariable,
22    MandatoryFuncObj,
23    ModuleVariable,
24    VarDeclarationKind,
25    Variable
26} from "./variable";
27
28
29export enum InitStatus {
30    INITIALIZED, UNINITIALIZED
31}
32
33export enum ModuleVarKind {
34    IMPORTED, EXPORTED, NOT
35}
36
37export abstract class Decl {
38    name: string;
39    node: ts.Node;
40    isModule: ModuleVarKind;
41
42    constructor(name: string, node: ts.Node, isModule: ModuleVarKind) {
43        this.name = name;
44        this.node = node;
45        this.isModule = isModule;
46    }
47}
48
49export class VarDecl extends Decl {
50    constructor(varName: string, node: ts.Node, isModule: ModuleVarKind) {
51        super(varName, node, isModule);
52    }
53}
54
55export class LetDecl extends Decl {
56    constructor(letName: string, node: ts.Node, isModule: ModuleVarKind) {
57        super(letName, node, isModule);
58    }
59}
60
61export class ConstDecl extends Decl {
62    constructor(constName: string, node: ts.Node, isModule: ModuleVarKind) {
63        super(constName, node, isModule);
64    }
65}
66
67export class FuncDecl extends Decl {
68    constructor(funcName: string, node: ts.Node, isModule: ModuleVarKind) {
69        super(funcName, node, isModule);
70    }
71}
72
73export class ClassDecl extends Decl {
74    constructor(className: string, node: ts.Node, isModule: ModuleVarKind) {
75        super(className, node, isModule);
76    }
77}
78
79export class CatchParameter extends Decl {
80    constructor(cpName: string, node: ts.Node, isModule: ModuleVarKind = ModuleVarKind.NOT) {
81        super(cpName, node, isModule);
82    }
83}
84
85export class FunctionParameter extends Decl {
86    constructor(fpName: string, node: ts.Node, isModule: ModuleVarKind = ModuleVarKind.NOT) {
87        super(fpName, node, isModule);
88    }
89}
90
91export abstract class Scope {
92    protected debugTag = "scope";
93    protected name2variable: Map<string, Variable> = new Map<string, Variable>();
94    protected decls: Decl[] = [];
95    protected parent: Scope | undefined = undefined;
96    // for debuginfo
97    protected startInsIdx: number | undefined;
98    protected endInsIdx: number | undefined;
99    private isArgumentsOrRestargs: boolean = false;
100
101    constructor() { }
102
103    abstract add(decl: Decl | string, declKind: VarDeclarationKind, status?: InitStatus): Variable | undefined;
104
105    getName2variable(): Map<string, Variable> {
106        return this.name2variable;
107    }
108
109    getScopeStartInsIdx(): number {
110        return this.startInsIdx;
111    }
112
113    setScopeStartInsIdx(startInsIdx: number) {
114        this.startInsIdx = startInsIdx;
115    }
116
117    setScopeEndInsIdx(endInsIdx: number) {
118        this.endInsIdx = endInsIdx;
119    }
120
121    getScopeEndInsIdx(): number {
122        return this.endInsIdx;
123    }
124
125    setParent(parentScope: Scope | undefined): void {
126        this.parent = parentScope;
127    }
128
129    getParent(): Scope | undefined {
130        return this.parent;
131    }
132
133    getRootScope(): Scope {
134        let sp: Scope | undefined = this;
135        let pp = this.getParent();
136        while (pp != undefined) {
137            sp = pp;
138            pp = pp.getParent();
139        }
140
141        return sp;
142    }
143
144    getNearestVariableScope(): VariableScope | undefined {
145        let sp: Scope | undefined = this;
146
147        while (sp) {
148            if (sp instanceof VariableScope) {
149                return <VariableScope>sp;
150            }
151            sp = sp.parent;
152        }
153
154        return undefined;
155    }
156
157    getNearestLexicalScope(): VariableScope | LoopScope | undefined {
158        let curScope: Scope | undefined = this;
159
160        while (curScope) {
161            if (curScope instanceof VariableScope || curScope instanceof LoopScope) {
162                return <VariableScope | LoopScope>curScope;
163            }
164            curScope = curScope.parent;
165        }
166
167        return undefined;
168    }
169
170    getNthVariableScope(level: number): VariableScope | undefined {
171        let sp: Scope | undefined = this;
172        let tempLevel = level;
173
174        while (sp) {
175            if (sp instanceof VariableScope) {
176                if (tempLevel === 0) {
177                    return <VariableScope>sp;
178                } else {
179                    tempLevel--;
180                }
181            }
182            sp = sp.parent;
183        }
184
185        return undefined;
186    }
187
188    findLocal(name: string): Variable | undefined {
189        return this.name2variable.get(name);
190    }
191
192    find(name: string): { scope: Scope | undefined, level: number, v: Variable | undefined } {
193        let curLevel = 0;
194        let curScope: Scope | undefined = this;
195
196        while (curScope) {
197            let resolve = null;
198            let tmpLevel = curLevel; // to store current level, not impact by ++
199            if (curScope.isLexicalScope() && (<VariableScope | LoopScope>curScope).need2CreateLexEnv()) {
200                curLevel++;
201            }
202            resolve = curScope.findLocal(name);
203            if (resolve) {
204                LOGD(this.debugTag, "scope.find (" + name + ") :");
205                LOGD(undefined, resolve);
206                return { scope: curScope, level: tmpLevel, v: resolve };
207            }
208
209            curScope = curScope.getParent();
210        }
211
212        LOGD(this.debugTag, "scope.find (" + name + ") : undefined");
213        return { scope: undefined, level: 0, v: undefined };
214    }
215
216    findDeclPos(name: string): Scope | undefined {
217        let declPos: Scope | undefined = undefined;
218        let curScope: Scope | undefined = this;
219        while (curScope) {
220            if (curScope.hasDecl(name)) {
221                declPos = curScope;
222                break;
223            }
224
225            curScope = curScope.getParent();
226        }
227
228        return declPos;
229    }
230
231    resolveDeclPos(name: string) {
232        let crossFunc: boolean = false;
233        let curScope: Scope | undefined = this;
234        let enclosingVariableScope: VariableScope = this.getNearestVariableScope();
235
236        while (curScope) {
237            if (curScope === enclosingVariableScope.parent) {
238                crossFunc = true;
239            }
240
241            let result = curScope.findLocal(name);
242            if (result) {
243                if (!crossFunc) {
244                    return {isLexical: false, scope: curScope, defLexicalScope: undefined, v: result};
245                }
246
247                let enclosingDefLexicalScope = curScope.getNearestLexicalScope();
248                return {isLexical: true, scope: curScope, defLexicalScope: enclosingDefLexicalScope, v: result};
249            }
250
251            curScope = curScope.parent;
252        }
253
254        return {isLexical: false, scope: undefined, defLexicalScope: undefined, v: undefined};
255    }
256
257    setDecls(decl: Decl): void {
258        this.decls.push(decl);
259    }
260
261    hasDecl(name: string): boolean {
262        let decls = this.decls;
263        for (let i = 0; i < decls.length; i++) {
264            if (decls[i].name === name) {
265                return true;
266            }
267        }
268
269        return false;
270    }
271
272    getDecl(name: string): Decl | undefined {
273        let decls = this.decls;
274        for (let i = 0; i < decls.length; i++) {
275            if (decls[i].name === name) {
276                return decls[i];
277            }
278        }
279
280        return undefined;
281    }
282
283    getDecls(): Decl[] {
284        return this.decls;
285    }
286
287    public setArgumentsOrRestargs(): void {
288        this.isArgumentsOrRestargs = true;
289    }
290
291    public getArgumentsOrRestargs() {
292        return this.isArgumentsOrRestargs;
293    }
294
295    isLexicalScope(): boolean {
296        let scope = this;
297        return ((scope instanceof VariableScope) || (scope instanceof LoopScope));
298    }
299}
300
301export abstract class VariableScope extends Scope {
302    protected startLexIdx: number = 0;
303    protected needCreateLexEnv: boolean = false;
304    protected parameters: LocalVariable[] = [];
305    protected useArgs = false;
306    protected node: ts.Node | undefined = undefined;
307    protected parentVariableScope: VariableScope | null = null;
308    protected childVariableScope: VariableScope[] = [];
309    protected lexVarInfo: Map<string, number> = new Map<string, number>();
310
311    getLexVarInfo(): Map<string, number> {
312        return this.lexVarInfo;
313    }
314
315    addLexVarInfo(name: string, slot: number): void {
316        this.lexVarInfo.set(name, slot);
317    }
318
319    getBindingNode(): ts.Node {
320        return this.node;
321    }
322
323    setParentVariableScope(scope: VariableScope): void {
324        this.parentVariableScope = scope;
325    }
326
327    getParentVariableScope(): VariableScope {
328        return this.parentVariableScope;
329    }
330
331    getChildVariableScope(): VariableScope[] {
332        return this.childVariableScope;
333    }
334
335    addChildVariableScope(scope: VariableScope): void {
336        this.childVariableScope.push(scope);
337    }
338
339    addParameter(name: string, declKind: VarDeclarationKind, argIdx: number): Variable | undefined {
340        LOGD(this.debugTag, "VariableScope.addArg(" + name + "), kind(" + declKind + ")", "argIdx(" + argIdx + ")");
341        let v = this.add(name, declKind, InitStatus.INITIALIZED);
342        if (!(v instanceof LocalVariable)) {
343            throw new Error("Error: argument must be local variable!");
344        }
345        this.parameters.push(v);
346        return v;
347    }
348
349    addFuncName(funcName: string): void {
350        let funcObj = this.name2variable.get(MandatoryFuncObj);
351        this.name2variable.set(funcName, funcObj!);
352    }
353
354    need2CreateLexEnv(): boolean {
355        return this.needCreateLexEnv;
356    }
357
358    pendingCreateEnv(): void {
359        this.needCreateLexEnv = true;
360    }
361
362    getNumLexEnv(): number {
363        return this.startLexIdx;
364    }
365
366    getParametersCount(): number {
367        return this.parameters.length;
368    }
369
370    getParameters(): LocalVariable[] {
371        return this.parameters;
372    }
373
374    getLexVarIdx(): number {
375        this.needCreateLexEnv = true;
376        return this.startLexIdx++;
377    }
378
379    setUseArgs(value: boolean) {
380        this.useArgs = value;
381    }
382
383    getUseArgs(): boolean {
384        return this.useArgs;
385    }
386
387    hasAfChild(): boolean {
388        let childVariableScopes = this.getChildVariableScope();
389        for (let child of childVariableScopes) {
390            if (ts.isArrowFunction(child.getBindingNode())) {
391                return true;
392            }
393        }
394
395        return false;
396    }
397}
398
399export class GlobalScope extends VariableScope {
400    constructor(node?: ts.SourceFile) {
401        super();
402        this.node = node ? node : undefined;
403    }
404
405    add(decl: Decl | string, declKind: VarDeclarationKind, status?: InitStatus): Variable | undefined {
406        let name = decl instanceof Decl ? decl.name : decl;
407        LOGD(this.debugTag, "globalscope.add (" + name + "), kind:" + declKind);
408        let v: Variable | undefined;
409        if (declKind === VarDeclarationKind.NONE || declKind === VarDeclarationKind.VAR || declKind === VarDeclarationKind.FUNCTION) {
410            v = new GlobalVariable(declKind, name);
411        } else {
412            v = new LocalVariable(declKind, name, status);
413        }
414        this.name2variable.set(name, v);
415        return v;
416    }
417}
418
419export class ModuleScope extends VariableScope {
420    private moduleRecord: SourceTextModuleRecord;
421
422    constructor(node: ts.SourceFile) {
423        super();
424        this.node = node;
425        this.moduleRecord = new SourceTextModuleRecord(node.fileName);
426    }
427
428    setExportDecl(exportedLocalName: string): void {
429        let decl = this.getDecl(exportedLocalName);
430        if (decl && decl.isModule != ModuleVarKind.IMPORTED) {
431            decl.isModule = ModuleVarKind.EXPORTED;
432        }
433    }
434
435    module(): SourceTextModuleRecord {
436        return this.moduleRecord;
437    }
438
439    add(decl: Decl | string, declKind: VarDeclarationKind, status?: InitStatus): Variable | undefined {
440        let [name, isModule] = decl instanceof Decl ? [decl.name, decl.isModule] : [decl, ModuleVarKind.NOT];
441        LOGD(this.debugTag, "modulescope.add (" + name + "), kind:" + declKind);
442        let v: Variable | undefined;
443
444        if (isModule !== ModuleVarKind.NOT) {
445            v = new ModuleVariable(declKind, name, InitStatus.UNINITIALIZED);
446            if (isModule === ModuleVarKind.EXPORTED) {
447                (<ModuleVariable>v).setExport();
448            }
449        } else {
450            if (declKind === VarDeclarationKind.NONE) {
451                v = new GlobalVariable(declKind, name);
452            } else if (declKind === VarDeclarationKind.VAR || declKind === VarDeclarationKind.FUNCTION) {
453                v = new LocalVariable(declKind, name);
454            } else {
455                v = new LocalVariable(declKind, name, status);
456            }
457        }
458
459        this.name2variable.set(name, v);
460        return v;
461    }
462}
463
464export class FunctionScope extends VariableScope {
465    private parameterLength: number = 0;
466    private funcName: string = "";
467    constructor(parent?: Scope, node?: ts.FunctionLikeDeclaration) {
468        super();
469        this.parent = parent ? parent : undefined;
470        this.node = node ? node : undefined;
471    }
472
473    setParameterLength(length: number): void {
474        this.parameterLength = length;
475    }
476
477    getParameterLength(): number {
478        return this.parameterLength;
479    }
480
481    setFuncName(name: string): void {
482        this.funcName = name;
483    }
484
485    getFuncName(): string {
486        return this.funcName;
487    }
488
489    getParent(): Scope | undefined {
490        return this.parent;
491    }
492
493    add(decl: Decl | string, declKind: VarDeclarationKind, status?: InitStatus): Variable | undefined {
494        let name = decl instanceof Decl ? decl.name : decl;
495        let v: Variable | undefined;
496        LOGD(this.debugTag, "functionscope.add (" + name + "), kind:" + declKind);
497
498        if (declKind === VarDeclarationKind.NONE) {
499            // the variable declared without anything should be global
500            // See EcmaStandard: 13.3.2 Variable Statement
501            let topLevelScope = this.getRootScope();
502            v = topLevelScope.add(name, declKind);
503        } else if (declKind === VarDeclarationKind.VAR || declKind === VarDeclarationKind.FUNCTION) {
504            v = new LocalVariable(declKind, name);
505            this.name2variable.set(name, v);
506        } else {
507            v = new LocalVariable(declKind, name, status);
508            this.name2variable.set(name, v);
509        }
510        return v;
511    }
512}
513
514export class LocalScope extends Scope {
515    constructor(parent: Scope) {
516        super();
517        this.parent = parent;
518    }
519
520    add(decl: Decl | string, declKind: VarDeclarationKind, status?: InitStatus): Variable | undefined {
521        let name = decl instanceof Decl ? decl.name : decl;
522        let v: Variable | undefined;
523
524        LOGD(this.debugTag, "localscope.add (" + name + "), kind:" + declKind);
525        if (declKind === VarDeclarationKind.NONE) {
526            let topLevelScope = this.getRootScope();
527            v = topLevelScope.add(name, declKind);
528        } else if (declKind === VarDeclarationKind.VAR) {
529            /**
530             * the variable declared without anything should be accessible
531             * in all parent scopes so delegate creation to the parent
532             * See EcmaStandard: 13.3.2 Variable Statement
533             */
534            let functionScope = this.getNearestVariableScope();
535            v = functionScope!.add(name, declKind);
536        } else {
537            v = new LocalVariable(declKind, name, status);
538            this.name2variable.set(name, v);
539        }
540
541        return v;
542    }
543}
544
545export class LoopScope extends LocalScope {
546    protected startLexIdx: number = 0;
547    protected needCreateLexEnv: boolean = false;
548    protected lexVarInfo: Map<string, number> = new Map<string, number>();
549    constructor(parent: Scope) {
550        super(parent);
551    }
552
553    getLexVarInfo(): Map<string, number> {
554        return this.lexVarInfo;
555    }
556
557    addLexVarInfo(name: string, slot: number): void {
558        this.lexVarInfo.set(name, slot);
559    }
560
561    need2CreateLexEnv(): boolean {
562        return this.needCreateLexEnv;
563    }
564
565    pendingCreateEnv(): void {
566        this.needCreateLexEnv = true;
567    }
568
569    getLexVarIdx(): number {
570        this.needCreateLexEnv = true;
571        return this.startLexIdx++;
572    }
573
574    getNumLexEnv(): number {
575        return this.startLexIdx;
576    }
577}
578
579