-
Notifications
You must be signed in to change notification settings - Fork 150
/
scope.js
158 lines (132 loc) · 4.82 KB
/
scope.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// scope.js
// MIT licensed, see LICENSE file
// Copyright (c) 2013-2016 Olov Lassus <[email protected]>
"use strict";
const assert = require("assert");
const stringmap = require("stringmap");
const stringset = require("stringset");
const is = require("simple-is");
const fmt = require("simple-fmt");
function Scope(args) {
assert(is.someof(args.kind, ["hoist", "block", "catch-block"]));
assert(is.object(args.node));
assert(args.parent === null || is.object(args.parent));
// kind === "hoist": function scopes, program scope, injected globals
// kind === "block": ES6 block scopes
// kind === "catch-block": catch block scopes
this.kind = args.kind;
// the AST node the block corresponds to
this.node = args.node;
// parent scope
this.parent = args.parent;
// children scopes for easier traversal (populated internally)
this.children = [];
// scope declarations. decls[variable_name] = {
// kind: "fun" for functions,
// "param" for function parameters,
// "caught" for catch parameter
// "var",
// "const",
// "let"
// node: the AST node the declaration corresponds to
// from: source code index from which it is visible at earliest
// (only stored for "const", "let" [and "var"] nodes)
// }
this.decls = stringmap();
// names of all variables declared outside this hoist scope but
// referenced in this scope (immediately or in child).
// only stored on hoist scopes for efficiency
// (because we currently generate lots of empty block scopes)
this.propagates = (this.kind === "hoist" ? stringset() : null);
// scopes register themselves with their parents for easier traversal
if (this.parent) {
this.parent.children.push(this);
}
}
Scope.prototype.print = function(indent) {
indent = indent || 0;
const scope = this;
const names = this.decls.keys().map(function(name) {
return fmt("{0} [{1}]", name, scope.decls.get(name).kind);
}).join(", ");
const propagates = this.propagates ? this.propagates.items().join(", ") : "";
console.log(fmt("{0}{1}: {2}. propagates: {3}", fmt.repeat(" ", indent), this.node.type, names, propagates));
this.children.forEach(function(c) {
c.print(indent + 2);
});
};
Scope.prototype.add = function(name, kind, node, referableFromPos) {
assert(is.someof(kind, ["fun", "param", "var", "caught", "const", "let"]));
function isConstLet(kind) {
return is.someof(kind, ["const", "let"]);
}
let scope = this;
// search nearest hoist-scope for fun, param and var's
// const, let and caught variables go directly in the scope (which may be hoist, block or catch-block)
if (is.someof(kind, ["fun", "param", "var"])) {
while (scope.kind !== "hoist") {
// if (scope.decls.has(name) && isConstLet(scope.decls.get(name).kind)) { // could be caught
// return error(getline(node), "{0} is already declared", name);
// }
scope = scope.parent;
}
}
// name exists in scope and either new or existing kind is const|let => error
// if (scope.decls.has(name) && (isConstLet(scope.decls.get(name).kind) || isConstLet(kind))) {
// return error(getline(node), "{0} is already declared", name);
// }
const declaration = {
kind: kind,
node: node,
};
if (referableFromPos) {
assert(is.someof(kind, ["var", "const", "let"]));
declaration.from = referableFromPos;
}
scope.decls.set(name, declaration);
};
Scope.prototype.getKind = function(name) {
assert(is.string(name));
const decl = this.decls.get(name);
return decl ? decl.kind : null;
};
Scope.prototype.getNode = function(name) {
assert(is.string(name));
const decl = this.decls.get(name);
return decl ? decl.node : null;
};
Scope.prototype.getFromPos = function(name) {
assert(is.string(name));
const decl = this.decls.get(name);
return decl ? decl.from : null;
};
Scope.prototype.hasOwn = function(name) {
return this.decls.has(name);
};
Scope.prototype.remove = function(name) {
return this.decls.remove(name);
};
Scope.prototype.doesPropagate = function(name) {
return this.propagates.has(name);
};
Scope.prototype.markPropagates = function(name) {
this.propagates.add(name);
};
Scope.prototype.closestHoistScope = function() {
let scope = this;
while (scope.kind !== "hoist") {
scope = scope.parent;
}
return scope;
};
Scope.prototype.lookup = function(name) {
for (let scope = this; scope; scope = scope.parent) {
if (scope.decls.has(name)) {
return scope;
} else if (scope.kind === "hoist") {
scope.propagates.add(name);
}
}
return null;
};
module.exports = Scope;