Jump To …

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()