scope.coffee | |
---|---|
Evaluates expr in a given lexical scope Todo | exports.COFFEESCRIPT_HELPERS =
__slice: Array::slice
__bind: (fn, me) -> -> fn.apply me, arguments
__indexOf: Array::indexOf or (item) ->
return i if x is item for x, i in @
__hasProp: Object::hasOwnProperty
__extends: (child, parent) ->
for key of parent
child[key] = parent[key] if eval('__hasProp').call parent, key
ctor = -> @constructor = child
ctor:: = parent.prototype
child.prototype = new ctor
child.__super__ = parent::
child
exports.OPTIMIZE_INCLUDES = true
exports.globalEval = globalEval = (expr) ->
`(1, eval)(expr)`
matches = (str, word) ->
if word.match /\$/ then str.match /\$/
else str.match ///\b#{word}\b///
exports.scope = (lex, f) ->
throw "Reserved" if '__scoped' of lex or '__sc' of lex
fstring = f.toString()
vars =
(k for k of lex when !exports.OPTIMIZE_INCLUDES \
or matches fstring, k)
declarations =
if vars.length
"var #{(k for k in vars).join ', '};"
else ""
end = "\n "
setScope = ("#{k} = __sc.#{k};#{end}" for k in vars).join ''
getScope = ("__sc.#{k} = #{k};#{end}" for k in vars).join ''
globalEval("""
(function() {
#{declarations}
var __scoped = #{fstring};
__scoped.scope = function (__sc) {
if (__sc) {
#{setScope}return __scoped;
} else {
__sc = {};
#{getScope}return __sc;
}
};
return __scoped;
})();
""")
.scope lex
exports.scope2 = (f, scope, capture) ->
|
import scope into lexical scope | head = []; head.push "#{k} = __scope__.#{k}" for k of scope
head = "var #{head.join ', '};\n"
lines = f.toString().split('\n')
|
code to call the function | lines[0] = "(#{lines[0]}"
tail = ").apply(this, __args__)"
|
Capture new or overriden scope |
if capture
|
CoffeeScript compiler puts top-level vars nicely on first line after our wrapped function | if declns = lines[1]?.match /^\s*var (.*);/
|
capture the result of the wrapped function | lines[0] = "var __result__ = #{lines[0]}"
|
delete local declarations line | lines.splice(1,1)
|
Newly declared vars that are not in scope | newVars = (v for v in declns[1].split ', ' when not scope[v]?)
|
Build declaration of new vars. | declstr = 'var '; declstr += "#{v}, " for v in newVars
declstr = declstr.substr(0, declstr.length - 2) + ';'
|
Insert new declarations at the top, outside the function. | lines.splice(0,0, declstr) #insert
|
capture local vars | scope[v] = null for v in newVars
tail += "\n__scope__.#{k} = #{k};" for k of scope
|
state the result | tail += '\n__result__'
lines = lines.join '\n'
code = head + lines + tail
|
console.log 'code ---------------------\n', code |
->
((__scope__, __args__) -> eval code).call this, scope, arguments
class exports.Scope
constructor: (@scope, @_this_) ->
eval: (expr) ->
exports.scope2 @scope, expr
exports.test = ->
f = (q) ->
a = y
@y = y
@foo = 74
@q = q
self =
foo: 42
bar: 22
scope =
x: 15
y: 34
capture = false
result = exports.scope2(f, scope, capture).call self, q = 3.14
throw 'bad' if self.q isnt q
throw 'bad' if self.foo isnt 74
throw 'bad' if result isnt q
if capture |
Only works with capture enabled | throw 'bad' if scope.a isnt 34
throw 'bad' if scope.y isnt 43
result = exports.scope(scope, f).call self, q = 3.14
throw 'bad' if self.q isnt q
throw 'bad' if self.foo isnt 74
throw 'bad' if result isnt q
throw 'bad' if self.y isnt 34
result
exports.test()
|