-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
308 lines (219 loc) · 8.94 KB
/
index.html
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>ScopeJS: Pimp your lexical scopes.</title>
<link rel="stylesheet" type="text/css" href="documentation/css/doc.css" />
<link rel="shortcut icon" href="documentation/images/favicon.ico" />
</head>
<body>
<h1>ScopeJS</h1>
<p>A JavaScript library for defining and working with lexical scopes.</p>
<p><a href="http://github.com/dbrans/scope">ScopeJS is hosted on github</a>.</p>
<p>Latest version: <a href="http://github.com/dbrans/scope">0.10.1</a></p>
<h2>Table of Contents</h2>
<ul>
<li><a href="#usage">Installation and Usage</a></li>
<li><a href="#theory">Theory</a></li>
<li><a href="#api">API</a></li>
<li><a href="documentation/docs/scope.html">Annotated Source</a></li>
<li><a href="#coffee">CoffeeScript Support</a></li>
</ul>
<h2>Overview</h2>
<pre><code>// Create a new lexical scope
var scope = Scope.create({
locals: {foo: 3},
literals: {
multiplyFoo: function(val) {foo *= val;}
}
});
// Call a local function.
scope.multiplyFoo(4);
// Evaluate an expression locally.
scope.eval('foo++');
// Get a local variable.
log(scope.foo); // 13
// Extend a scope.
var inner = scope.extend({
locals: {bar: 'bar'}
});
// Inner scope has access to the outer scope:
inner.foo = 6;
log(scope.foo)); // 6
// But outer scope does not have access to the inner scope:
log(scope.eval('bar')); // Error: bar is not defined.
</code></pre>
<p><div id="usage"></div></p>
<h2>Installation</h2>
<p>There are two ways to install ScopeJS: via npm or via git.</p>
<h3>NPM</h3>
<ol>
<li>If you haven't already, <a href="http://howtonode.org/introduction-to-npm">install npm</a>. </li>
<li><code>npm install -g scope</code>
(Leave off the -g if you don't wish to install globally.)</li>
</ol>
<h3>Git</h3>
<p>To install from source, you should have <a href="http://coffeescript.org">CoffeeScript</a> installed.</p>
<p>Then run:</p>
<pre><code>git clone git://github.com/dbrans/scope.git
cd scope
cake install
</code></pre>
<h2>Including ScopeJS in your project.</h2>
<h3>CommonJS Environment (e.g., Nodejs)</h3>
<pre><code>var Scope = require('scope').Scope;
var scope = Scope.create(...);
</code></pre>
<h3>Browser Environment</h3>
<p>Include <code>browser/scope.js</code> in your html page:</p>
<pre><code><script type="text/javascript" src="PATH_TO_SCOPE/browser/scope.js"></script>
</code></pre>
<p><code>browser/scope.js</code> defines <code>Scope</code> as a global variable. So you can just go
ahead and use it:</p>
<pre><code>var scope = Scope.create(...);
</code></pre>
<h2>Community</h2>
<p>Please use the <a href="http://github.com/dbrans/scope">github page for ScopeJS</a>
to discuss and raise issues about ScopeJS.</p>
<p><div id="theory"></div></p>
<h2>Theory</h2>
<h3>A 'scoped eval' function</h3>
<p>Consider this function literal as a string:</p>
<pre><code>var EVAL_LITERAL = "(function (expr) {return eval(expr)})";
</code></pre>
<p>Eval-ing this string in some lexical scope creates a 'scoped eval':
a function that, if passed outside of the current scope, can evaluate
expressions inside that scope. Here's a demonstration:</p>
<pre><code>var scopeEval = (function() {
var x = 3;
return eval(EVAL_LITERAL);
})();
log(scopeEval('x')); // 3
log(eval('x')); // Error: x is not defined
</code></pre>
<p>In general, given a list of variable names <code>var1</code>, <code>var2</code>, ..., you can dynamically
create a lexical scope where those variables are defined like this:</p>
<pre><code>var vars = ['var1', 'var2', ..., 'varN'];
var scopeEval = (function() {
eval("var " + vars.join(',') + ';' + EVAL_LITERAL);
})();
</code></pre>
<p>This idea of a scoped eval function is at the heart of ScopeJS. </p>
<p>Let's develop this idea a little bit.</p>
<h3><em>Current</em> vs <em>Target</em> scopes</h3>
<p>Let's call the scope in which our examples are imagined to run
the <em>current scope</em>
and the scope in which a scopeEval expression runs, the <em>target scope</em>.</p>
<p>For simplicity, in our examples the current scope is the outer scope of
the target scope. In other words, </p>
<pre><code>// For simplicity:
// CURRENT SCOPE
var scopeEval = (function() {
// TARGET SCOPE
eval("var " + vars.join(',') + ';' + EVAL_LITERAL);
})();
</code></pre>
<p>In ScopeJS, however, the target scope lives somewhere else and cannot see the
current scope (see the <code>Scope.eval</code> class method in the
<a href="documentation/docs/scope.html">Annotated Source</a>)</p>
<h3>Undeclared Variables</h3>
<p>In these examples, the variables var1, var2, etc. refer to variables that
are already declared in the target scope. Any assignment to undeclared
variables is a typo and would create global variables.</p>
<h3>Literal Expressions</h3>
<p>We can evaluate array, object and function literals inside the target scope.
Those literals have access to local variables. Consider:</p>
<pre><code>scopeEval('var1 = 3');
var obj = scopeEval('[var1]');
log(obj[0]); // 3
</code></pre>
<h4>Function Literals and Closures</h4>
<p>Function literals that are eval'd in a scope close over local variables in that
scope and can manipulate them directly:</p>
<pre><code>var addToVar = scopeEval('(function(x){var1 += x})');
addToVar(2);
log(scopeEval('var1')); // 5
</code></pre>
<h3>Extending the Target Scope</h3>
<p>As you may have noticed, it's impossible to
declare a new variable in the target scope, once it is created.
This is because the eval happens in the function local scope described
by EVAL_LITERAL.
This means that newly declared variables only exist for the duration of that
function's invocation:</p>
<pre><code>// Here 'newvar' exists:
log(scopeEval('var newvar = 3; newvar')); // 3
// ...but here it doesn't:
scopeEval('newvar'); // Error: newvar is not defined
</code></pre>
<p>Instead, we create an inner scope where new variables are defined. </p>
<pre><code>var newvars = ['newvar1', 'newvar2', ...];
var innerEval = scopeEval('var ' + newvars.join(',') + ';' + EVAL_LITERAL);
</code></pre>
<p>Because we used <code>scopeEval</code> to create our lexical scope, innerEval and scopeEval
share variables var1, var2, etc. In addition, innerEval has it's own variables
newvar1, newvar2, ...</p>
<pre><code>// var1 is visible in both scopes
innerEval('var1 = 6');
log(scopeEval('var1')); // 6
// newvar1 is only visible in the inner scope
innerEval('newvar1 = 'A');
log(scopeEval('newvar1')); // Error: newvar1 is not defined.
</code></pre>
<p>We might say that <code>innerEval</code> <em>extends</em> <code>scopeEval</code> because <code>innerEval</code> includes
<code>scopeEval</code>'s variables and declares new ones. </p>
<p><code>scopeEval</code> is actually the <em>outer</em> lexical scope of <code>innerEval</code>, or rather the target
scopes of those two functions. A static view of the nested
scopes we've created so far might look like this:</p>
<pre><code>// GLOBAL SCOPE
(function() {
// scopeEval's target scope
var var1, var2, ...;
(function () {
// innerEval's target scope
var newvar1, newvar2, ...;
})()
})()
</code></pre>
<p><div id="api"></div></p>
<h2>API</h2>
<p>For API documentation, please refer to the <a href="documentation/docs/scope.html">annotated source</a></p>
<p><div id="coffee"></div></p>
<h2>CoffeeScript support</h2>
<p>Every root scope defines all the coffeescript helpers that are currently generated by the CoffeeScript
compiler. This means that recompiling functions that were once compiled from CoffeeScript will
work fine:</p>
<pre><code># This is coffeescript
scope.eval ->
# invoke CoffeeScript's __bind runtime helper.
=>
</code></pre>
<p>If you have a string of CoffeeScript code that you want to compile or run inside a target scope,
use </p>
<pre><code>CoffeeScript.compile code, bare:true
</code></pre>
<p>to compile <code>code</code> first. 'Native' CoffeeScript eval/compile
support could be envisioned for the future.</p>
<p>One important caveat when <strong>setting a variable in the target scope</strong> inside a CoffeeScript function:
The CoffeeScript compiler does lexical analysis around a function to decide if a variable has been seen
before or if it needs to be declared inside the function.
So, unless you have a local variable with the same name in the current scope, the CoffeeScript compiler will
declare a variable with that name inside your function, even if that variable exists in the target scope.
This function-local variable will mask the one in the target scope:</p>
<pre><code>scope = Scope.create
locals:
foo: 3
literals:
# Won't have the desired effect.
setFoo: (val) -> foo = val
</code></pre>
<p>In the above code, calling <code>scope.setFoo 4</code> will have no effect since, in the current scope,
CoffeeScript compiles <code>(val) -> foo = val</code> to </p>
<pre><code>function(val) {
var foo;
return foo = val;
}
</code></pre>
<p>A fix which analyzes decompiled CoffeeScript functions is possible.</p>
</body>
</html>