scope.coffee | |
---|---|
The Scope class regulates lexical scoping within CoffeeScript. As you
generate code, you create a tree of scopes in the same shape as the nested
function bodies. Each scope knows about the variables declared within it,
and has a reference to its parent enclosing scope. In this way, we know which
variables are new and need to be declared with | |
Import the helpers we plan to use. | {extend, last} = require './helpers'
exports.Scope = class Scope |
The top-level Scope object. | @root: null |
Initialize a scope with its parent, for lookups up the chain, as well as a reference to the Expressions node is belongs to, which is where it should declare its variables, and a reference to the function that it wraps. | constructor:(@parent, @expressions, @method) ->
@variables = [{name: 'arguments', type: 'arguments'}]
@positions = {}
if @parent
@garbage = @parent.garbage
else
@garbage = []
Scope.root = this |
Adds a new variable or overrides an existing one. | add: (name, type) ->
if typeof (pos = @positions[name]) is 'number'
@variables[pos].type = type
else
@positions[name] = @variables.push({name, type}) - 1 |
Create a new garbage level | startLevel: ->
@garbage.push []
this |
Return to the previous garbage level and erase referenced temporary variables in current level from scope. | endLevel: ->
@add name, 'reuse' for name in @garbage.pop() when @type(name) is 'var'
this |
Look up a variable name in lexical scope, and declare it if it does not already exist. | find: (name, options) ->
return true if @check name, options
@add name, 'var'
false |
Test variables and return | any: (fn) ->
return yes for v in @variables when fn v
no |
Reserve a variable name as originating from a function parameter for this
scope. No | parameter: (name) ->
@add name, 'param' |
Just check to see if a variable has already been declared, without reserving, walks up to the root scope. | check: (name, immediate) ->
found = !!@type(name)
return found if found or immediate
!!@parent?.check name |
Generate a temporary variable name at the given index. | temporary: (name, index) ->
if name.length > 1
'_' + name + if index > 1 then index else ''
else
'_' + (index + parseInt name, 36).toString(36).replace /\d/g, 'a' |
Gets the type of a variable. | type: (name) ->
for v in @variables when v.name is name then return v.type
null |
If we need to store an intermediate result, find an available name for a
compiler-generated variable. | freeVariable: (type) ->
index = 0
index++ while @check((temp = @temporary type, index), true) and @type(temp) isnt 'reuse'
@add temp, 'var'
last(@garbage)?.push temp
temp |
Ensure that an assignment is made at the top of this scope (or at the top-level scope, if requested). | assign: (name, value) ->
@add name, value: value, assigned: true |
Does this scope reference any variables that need to be declared in the given function body? | hasDeclarations: (body) ->
body is @expressions and @any (v) -> v.type in ['var', 'reuse'] |
Does this scope reference any assignments that need to be declared at the top of the given function body? | hasAssignments: (body) ->
body is @expressions and @any (v) -> v.type.assigned |
Return the list of variables first declared in this scope. | declaredVariables: ->
usr = []
tmp = []
for v in @variables when v.type in ['var', 'reuse']
(if v.name.charAt(0) is '_' then tmp else usr).push v.name
usr.sort().concat tmp.sort() |
Return the list of assignments that are supposed to be made at the top of this scope. | assignedVariables: ->
("#{v.name} = #{v.type.value}" for v in @variables when v.type.assigned) |
Compile the JavaScript for all of the variable declarations in this scope. | compiledDeclarations: ->
@declaredVariables().join ', ' |
Compile the JavaScript for all of the variable assignments in this scope. | compiledAssignments: ->
@assignedVariables().join ', '
|