-
Notifications
You must be signed in to change notification settings - Fork 7
/
scope.wren
123 lines (109 loc) · 3.11 KB
/
scope.wren
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
import "./chars" for Chars
class Scope {
construct new(reporter) {
_reporter = reporter
// TODO: Hard-coding these is a hack (as is using "true" for their value
// instead of a token). Should load the core library and implicitly import
// it.
var moduleScope = {
"Bool": true,
"Class": true,
"Fiber": true,
"Fn": true,
"List": true,
"Map": true,
"MapKeySequence": true,
"MapSequence": true,
"MapValueSequence": true,
"Null": true,
"Num": true,
"Object": true,
"Range": true,
"Sequence": true,
"String": true,
"StringByteSequence": true,
"StringCodePointSequence": true,
"System": true,
"WhereSequence": true
}
_scopes = [moduleScope]
_forwardReferences = []
}
/// Declares a variable with [name] in the current scope.
declare(name) {
var scope = _scopes[-1]
if (scope.containsKey(name.text)) {
_reporter.error(
"A variable named '%(name.text)' is already defined in " +
"this scope, on line %(scope[name.text].lineStart).",
[scope[name.text], name])
return
}
scope[name.text] = name
}
/// Looks for a previously declared variable with [name].
///
/// Reports an error if not found.
resolve(name) {
var reachedClass = false
for (i in (_scopes.count - 1)..0) {
// Don't walk past a class definition.
if (_scopes[i] == null) {
reachedClass = true
break
}
if (_scopes[i].containsKey(name.text)) {
// Found it in a containing lexical scope.
return _scopes[i][name.text]
}
}
if (reachedClass) {
if (Chars.isLowerAlpha(name.text.bytes[0])) {
// A lowercase name inside a class is treated like a self-send so do
// nothing.
return null
} else {
// A capitalized name is resolved at the module level.
if (_scopes[0].containsKey(name.text)) {
// Found it in a containing lexical scope.
return _scopes[0][name.text]
} else {
// Assume it's a forward reference for now.
_forwardReferences.add(name)
return
}
}
}
// If we got here, it's not defined.
_reporter.error("Variable '%(name.text)' is not defined.", [name])
}
// Begins a new lexical block scope.
begin() {
_scopes.add({})
}
// Ends the innermost scope.
end() {
_scopes.removeAt(-1)
}
/// Marks that we are inside a class definition.
///
/// When walking up lexical scopes for a name, we stop at a class definition
/// so we can detect implicit self sends.
beginClass() {
// TODO: Using a null scope as a sentinel is kind of hacky.
_scopes.add(null)
}
/// Ends the current class definition.
endClass() {
_scopes.removeAt(-1)
}
/// Report errors for any module-level variables that were referenced but
/// never defined.
checkForwardReferences() {
for (use in _forwardReferences) {
if (!_scopes[0].containsKey(use.text)) {
_reporter.error("Variable '%(use.text)' is not defined.", [use])
}
}
}
}