UNPKG

coffee-script

Version:

Unfancy JavaScript

127 lines (105 loc) 4.34 kB
# 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 `var`, and which are shared # with the outside. # 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 `true` the first time `fn(v)` returns `true` 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 `var` required for internal references. 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. `_var`, `_var2`, and so on... 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 ', '