 
                 b.expressionStatement(b.callExpression(b.memberExpression(b.identifier("Object"), b.identifier("defineProperties")), [target, propdescs_literal]))
 
-#
-# each element in env is an object of the form:
-#   { exp: ...     // the AST node corresponding to this environment.  will be a function or a BlockStatement. (right now just functions)
-#     id:  ...     // the number/id of this environment
-#     decls: ..    // a set of the variable names that are declared in this environment
-#     closed: ...  // a set of the variable names that are used from this environment (i.e. the ones that need to move to the environment)
-#     parent: ...  // the parent environment object
-#   }
-# 
-LocateEnv = class LocateEnv extends TransformPass
-        constructor: ->
-                super
-                @envs = new Stack
-                @env_id = 0
-
-        visitFunction: (n) ->
-                current_env = @envs.top if @envs.depth > 0
-                new_env = { exp: n, id: @env_id, decls: (if n.ejs_decls? then n.ejs_decls else new Set), closed: new Set, parent: current_env }
-                @env_id += 1
-                n.ejs_env = new_env
-                @envs.push new_env
-                # don't visit the parameters for the same reason we don't visit the id's of variable declarators below:
-                # we don't want a function to look like:  function foo (%env_1.x) { }
-                # instead of                              function foo (x) { }
-                n.body = @visit n.body
-                @envs.pop()
-                n
-
-        visitBlock: (n) ->
-                return super(n) if n.no_ejs_env
-                        
-                current_env = @envs.top if @envs.depth > 0
-                new_env = { exp: n, id: @env_id, decls: (if n.ejs_decls? then n.ejs_decls else new Set), closed: new Set, parent: current_env }
-                @env_id += 1
-                n.ejs_env = new_env
-                @envs.push new_env
-                super(n)
-                @envs.pop()
-                n
-
-        visitVariableDeclarator: (n) ->
-                # don't visit the id, as that will cause us to mark it ejs_substitute = true, and we'll end up with things like:
-                #   var %env_1.x = blah
-                # instead of what we want:
-                #   var x = blah
-                n.init = @visit n.init
-                n
-
-        visitProperty: (n) ->
-                if n.key.type is ComputedPropertyKey
-                        n.key = @visit n.key
-                n.value = @visit n.value
-                n
-
-        visitIdentifier: (n) ->
-                # find the environment in the env-stack that includes this variable's decl.  add it to the env's .closed set.
-                current_env = @envs.top
-                env = current_env
-
-                # if the current environment declares that identifier, nothing to do.
-                return n if env.decls.has n.name
-
-                closed_over = false
-                # look up our environment stack for the decl for it.
-                env = env.parent
-                intervening_function = false
-                while env?
-                        if env.decls?.has n.name
-                                closed_over = intervening_function
-                                env = null
-                        else
-                                if env.exp.type isnt BlockStatement # if it's a function
-                                        intervening_function = true
-                                env = env.parent
-                                
-
-                # if we found it higher on the function stack, we need to walk back up the stack forcing environments along the way, and make
-                # sure the frame that declares it knows that something down the stack closes over it.
-                if closed_over
-                        env = current_env.parent
-                        while env?
-                                if env.decls?.has n.name
-                                        env.closed.add n.name
-                                        env = null
-                                else
-                                        env.nested_requires_env = true
-                                        env = env.parent
-                
-                        n.ejs_substitute = true
-                n
-
 # this should move to echo-desugar.coffee
 
 class HoistFuncDecls extends TransformPass

                        n.body.body.unshift m.prepend
                n
                

# this class really doesn't behave like a normal TreeVisitor, as it modifies the tree in-place.
# XXX reformulate this as a TreeVisitor subclass.
class ComputeFree extends TransformPass
        constructor: ->
                super
                @call_free = @free.bind @
                @setUnion = Set.union
                
        visit: (n) ->
                @free n
                n

        decl_names: (arr) ->
                result = []
                for n in arr
                        if n.declarations?
                                result = result.concat (decl.id.name for decl in n.declarations)
                        else if n.id?
                                result.push n.id.name
                new Set result

        id_names: (arr) ->
                new Set arr.map ((id) -> id.name)

        collect_decls: (body) ->
                statement for statement in body when statement.type is VariableDeclaration or statement.type is FunctionDeclaration

        free_blocklike: (exp,body) ->
                if body?
                        decls = @decl_names @collect_decls body
                        uses = @setUnion.apply null, body.map @call_free
                else
                        decls = []
                        uses = new Set()
                exp.ejs_decls = decls
                exp.ejs_free_vars = uses.subtract decls
                exp.ejs_free_vars

        get_decls = (exp) -> if not exp? then null else exp.ejs_decls
        
        # TODO: move this into the @visit method
        free: (exp) ->
                return new Set if not exp?

                # define the properties we'll be filling in below, so that we can make them non-enumerable
                Object.defineProperty exp, "ejs_decls",     { value: undefined, writable: true, configurable: true }
                Object.defineProperty exp, "ejs_free_vars", { value: undefined, writable: true, configurable: true }
                
                switch exp.type
                        when Program
                                decls = @decl_names @collect_decls exp.body
                                uses = @setUnion.apply null, exp.body.map @call_free
                                exp.ejs_decls = decls
                                exp.ejs_free_vars = uses.subtract decls
                        when FunctionDeclaration
                                # this should only happen for the toplevel function we create to wrap the source file
                                param_names = @id_names exp.params
                                param_names.add exp.rest.name if exp.rest?
                                exp.ejs_free_vars = @free(exp.body).subtract param_names
                                exp.ejs_decls = exp.body.ejs_decls.union param_names
                        when FunctionExpression
                                param_names = @id_names exp.params
                                param_names.add exp.rest.name if exp.rest?
                                exp.ejs_free_vars = @free(exp.body).subtract param_names
                                exp.ejs_decls = param_names.union exp.body.ejs_decls
                        when ArrowFunctionExpression
                                param_names = @id_names exp.params
                                param_names.add exp.rest.name if exp.rest?
                                exp.ejs_free_vars = @free(exp.body).subtract param_names
                                exp.ejs_decls = param_names.union exp.body.ejs_decls
                        when LabeledStatement      then exp.ejs_free_vars = @free exp.body
                        when BlockStatement        then exp.ejs_free_vars = @free_blocklike exp, exp.body
                        when TryStatement          then exp.ejs_free_vars = @setUnion.apply null, [@free(exp.block)].concat exp.handlers.map @call_free
                        when CatchClause
                                param_set = if exp.param?.name? then new Set [exp.param.name] else new Set
                                exp.ejs_free_vars = @free(exp.body).subtract param_set
                                exp.ejs_decls = exp.body.ejs_decls.union param_set
                        when VariableDeclaration   then exp.ejs_free_vars = @setUnion.apply null, exp.declarations.map @call_free
                        when VariableDeclarator    then exp.ejs_free_vars = @free exp.init
                        when ExpressionStatement   then exp.ejs_free_vars = @free exp.expression
                        when Identifier            then exp.ejs_free_vars = new Set [exp.name]
                        when ThrowStatement        then exp.ejs_free_vars = @free exp.argument
                        when ForStatement
                                decls = null
                                if exp.init?.type is VariableDeclaration
                                        decls = @decl_names [exp.init]

                                uses = @setUnion.call null, @free(exp.init), @free(exp.test), @free(exp.update), @free(exp.body)
                                exp.ejs_decls = decls
                                exp.ejs_free_vars = uses.subtract decls
                        when ForInStatement        then exp.ejs_free_vars = @setUnion.call null, @free(exp.left), @free(exp.right), @free(exp.body)
                        when ForOfStatement        then exp.ejs_free_vars = @setUnion.call null, @free(exp.left), @free(exp.right), @free(exp.body)
                        when WhileStatement        then exp.ejs_free_vars = @setUnion.call null, @free(exp.test), @free(exp.body)
                        when DoWhileStatement      then exp.ejs_free_vars = @setUnion.call null, @free(exp.test), @free(exp.body)
                        when SwitchStatement       then exp.ejs_free_vars = @setUnion.apply null, [@free exp.discriminant].concat exp.cases.map @call_free
                        when SwitchCase            then exp.ejs_free_vars = @free_blocklike exp, exp.consequent
                        when EmptyStatement        then exp.ejs_free_vars = new Set
                        when BreakStatement        then exp.ejs_free_vars = new Set
                        when ContinueStatement     then exp.ejs_free_vars = new Set
                        when SpreadElement         then exp.ejs_free_vars = @free exp.argument
                        when UpdateExpression      then exp.ejs_free_vars = @free exp.argument
                        when ReturnStatement       then exp.ejs_free_vars = @free exp.argument
                        when UnaryExpression       then exp.ejs_free_vars = @free exp.argument
                        when BinaryExpression      then exp.ejs_free_vars = @free(exp.left).union @free exp.right
                        when LogicalExpression     then exp.ejs_free_vars = @free(exp.left).union @free exp.right
                        when MemberExpression      then exp.ejs_free_vars = @free(exp.object) # we don't traverse into the property
                        when CallExpression        then exp.ejs_free_vars = @setUnion.apply null, [@free exp.callee].concat exp.arguments.map @call_free
                        when NewExpression         then exp.ejs_free_vars = @setUnion.apply null, [@free exp.callee].concat exp.arguments.map @call_free
                        when SequenceExpression    then exp.ejs_free_vars = @setUnion.apply null, exp.expressions.map @call_free
                        when ConditionalExpression then exp.ejs_free_vars = @setUnion.call null, @free(exp.test), @free(exp.consequent), @free(exp.alternate)
                        when TaggedTemplateExpression then exp.ejs_free_vars = @free exp.quasi
                        when TemplateLiteral       then exp.ejs_free_vars = @setUnion.apply null, exp.expressions.map @call_free
                        when Literal               then exp.ejs_free_vars = new Set
                        when ThisExpression        then exp.ejs_free_vars = new Set
                        when ComputedPropertyKey   then exp.ejs_free_vars = @free exp.expression
                        when Property
                                exp.ejs_free_vars = @free exp.value
                                if exp.key.type is ComputedPropertyKey
                                        # we only do this when the key is computed, or else identifiers show up as free
                                        exp.ejs_free_vars = exp.ejs_free_vars.union @free exp.key
                                exp.ejs_free_vars
                        when ObjectExpression
                                exp.ejs_free_vars = if exp.properties.length is 0 then (new Set) else @setUnion.apply null, (@free p for p in exp.properties)
                        when ArrayExpression
                                exp.ejs_free_vars = if exp.elements.length is 0 then (new Set) else @setUnion.apply null, exp.elements.map @call_free
                        when IfStatement           then exp.ejs_free_vars = @setUnion.call null, @free(exp.test), @free(exp.consequent), @free(exp.alternate)
                        when AssignmentExpression  then exp.ejs_free_vars = @free(exp.left).union @free exp.right
                        when ModuleDeclaration     then exp.ejs_free_vars = @free exp.body
                        when ExportDeclaration
                                exp.ejs_free_vars = @free exp.declaration
                                exp.ejs_decls = exp.declaration.ejs_decls
                        when ImportDeclaration
                                exp.ejs_decls = new Set exp.specifiers.map ((specifier) -> specifier.id.name)
                                # no free vars in an ImportDeclaration
                                exp.ejs_free_vars = new Set
                                
                        else throw "Internal error: unhandled node '#{JSON.stringify exp}' in free()"
                exp.ejs_free_vars

                
                
# 1) allocates the environment at the start of the n
# 2) adds mappings for all .closed variables
class SubstituteVariables extends TransformPass
        constructor: ->
                super
                @function_stack = new Stack
                @mappings = new Stack

        currentMapping: -> if @mappings.depth > 0 then @mappings.top else Object.create null

        visitIdentifier: (n) ->
                if hasOwnProperty.call @currentMapping(), n.name
                        return @currentMapping()[n.name]
                n

        visitFor: (n) ->
                # for loops complicate things.
                # if any of the variables declared in n.init are closed over
                # we promote all of them outside of n.init.

                @skipExpressionStatement = true
                init = @visit n.init
                @skipExpressionStatement = false
                n.test = @visit n.test
                n.update = @visit n.update
                n.body = @visit n.body
                if Array.isArray init
                        n.init = null
                        return b.blockStatement(init.concat([n]))

                n.init = init
                n

        visitForIn: (n) ->
                # for-in loops complicate things.

                left = @visit n.left
                n.right = @visit n.right
                n.body = @visit n.body
                if Array.isArray left
                        console.log "whu?"
                        n.left = b.identifier(left[0].declarations[0].id.name)
                        return b.blockStatement(left.concat([n]))

                n.left = left
                n

        visitVariableDeclaration: (n) ->
                # here we do some magic depending on whether or not
                # variables are closed over (i.e. pushed into the
                # environment).
                # 
                # 1. for variables that are closed over that aren't
                #    initialized (that is, they're implicitly
                #    'undefined'), we just remove their declaration
                #    entirely.  it's already been converted to a slot
                #    everywhere else, and env slots are explicitly
                #    initialized to undefined by the runtime.
                #
                # 2. for variables that are closed over that *are*
                #    initialized, we splice them into the list and
                #    split the VariableDeclaration node into two, so
                #    if 'y' is closed over in the following input:
                #
                #    let x = 2, y = x * 2, z = 10;
                #
                #    we'll end up with this in the output:
                #
                #    let x = 2;
                #    %slot(%env, 1, 'y') = x * 2;
                #    let z = 10;
                #
                decls = n.declarations

                rv = []
                
                new_declarations = []

                # we loop until we find a variable that's closed over *and* has an initializer.
                for decl in decls
                        decl.init = @visit decl.init

                        closed_over = hasOwnProperty.call @currentMapping(), decl.id.name
                        if closed_over
                                # for variables that are closed over but undefined, we skip them (thereby removing them from the list of decls)

                                if decl.init? # FIXME: we should also check for an explicit 'undefined' here

                                        # push the current set of new_declarations if there are any
                                        if new_declarations.length > 0
                                                rv.push b.variableDeclaration(n.kind, new_declarations)

                                        # splice in this assignment
                                        rv.push b.expressionStatement(b.assignmentExpression(@currentMapping()[decl.id.name], "=", decl.init))

                                        # then re-init the new_declarations array
                                        new_declarations = []
                        
                        else
                                # for variables that aren't closed over, we just add them to the currect decl list.
                                new_declarations.push decl
                        

                # push the last set of new_declarations if there were any
                if new_declarations.length > 0
                        rv.push b.variableDeclaration(n.kind, new_declarations)

                if rv.length is 0
                        rv = b.emptyStatement()
                rv

        visitProperty: (n) ->
                if n.key.type is ComputedPropertyKey
                        n.key = @visit n.key
                n.value = @visit n.value
                n

        visitBlock: (n) ->
                return super(n) if not n.ejs_env
                
                this_env_id = b.identifier("%env_#{n.ejs_env.id}")
                if n.ejs_env.parent?
                        parent_env_name = "%env_#{n.ejs_env.parent.id}"
                        
                env_prepends = []
                new_mapping = shallow_copy_object @currentMapping()
                
                if n.ejs_env.closed.empty() and not n.ejs_env.nested_requires_env
                        env_prepends.push b.letDeclaration(this_env_id, b.null())
                else
                        # insert environment creation (at the start of the block)
                        env_prepends.push b.letDeclaration(this_env_id, intrinsic makeClosureEnv_id, [b.literal n.ejs_env.closed.size() + if n.ejs_env.parent? then 1 else 0])
                
                        n.ejs_env.slot_mapping = Object.create null
                        i = 0
                        if n.ejs_env.parent?
                                n.ejs_env.slot_mapping[parent_env_name] = i
                                i += 1
                        n.ejs_env.closed.map (el) ->
                                n.ejs_env.slot_mapping[el] = i
                                i += 1
                                
                        
                        if n.ejs_env.parent?
                                parent_env_slot = n.ejs_env.slot_mapping[parent_env_name]
                                env_prepends.push b.expressionStatement(intrinsic setSlot_id, [this_env_id, b.literal(parent_env_slot), b.literal(parent_env_name), b.identifier(parent_env_name) ]);

                        # XXX here's where function handling pushes closed over parameters.  i'm guessing we need special logic for incoming environment slots for loop variables?

                        new_mapping["%slot_mapping"] = n.ejs_env.slot_mapping

                        flatten_memberexp = (exp, mapping) ->
                                if exp.type isnt CallExpression
                                        [b.literal(mapping[exp.name])]
                                else
                                        flatten_memberexp(exp.arguments[0], mapping).concat [exp.arguments[1]]

                        prepend_environment = (exps) ->
                                obj = this_env_id
                                for prop in exps
                                        obj = intrinsic(slot_id, [obj, prop])
                                obj

                        # if there are existing mappings prepend "%env." (a MemberExpression) to them
                        for mapped of new_mapping
                                val = new_mapping[mapped]
                                if mapped isnt "%slot_mapping"
                                        new_mapping[mapped] = prepend_environment(flatten_memberexp(val, n.ejs_env.slot_mapping))
                        
                        # and add mappings for all variables in .closed from "x" to "%env.x"

                        new_mapping["%env"] = this_env_id
                        n.ejs_env.closed.map (sym) ->
                                new_mapping[sym] = intrinsic slot_id, [this_env_id, b.literal(n.ejs_env.slot_mapping[sym]), b.literal(sym)]

                # remove all mappings for variables declared in this block
                if n.ejs_decls?
                        new_mapping = reject new_mapping, (sym) -> (n.ejs_decls.has sym) and not (n.ejs_env.closed.has sym)

                @mappings.push new_mapping
                super(n)
                if env_prepends.length > 0
                        n.body = env_prepends.concat n.body
                @mappings.pop()
                n

        visitFunctionBody: (n) ->
                n.scratch_size = 0
                
                # we use this instead of calling super in visitFunction because we don't want to visit parameters
                # during this pass, or they'll be substituted with an %env.

                @function_stack.push n
                n.body = @visit n.body
                @function_stack.pop()
                
                n

        visitFunction: (n) ->
            try
                this_env_id = b.identifier("%env_#{n.ejs_env.id}")
                if n.ejs_env.parent?
                        parent_env_name = "%env_#{n.ejs_env.parent.id}"

                env_prepends = []
                new_mapping = shallow_copy_object @currentMapping()
                if n.ejs_env.closed.empty() and not n.ejs_env.nested_requires_env
                        env_prepends.push b.letDeclaration(this_env_id, b.null())
                else
                        # insert environment creation (at the start of the function body)
                        env_prepends.push b.letDeclaration(this_env_id, intrinsic makeClosureEnv_id, [b.literal n.ejs_env.closed.size() + if n.ejs_env.parent? then 1 else 0])

                        n.ejs_env.slot_mapping = Object.create null
                        i = 0
                        if n.ejs_env.parent?
                                n.ejs_env.slot_mapping[parent_env_name] = i
                                i += 1
                        n.ejs_env.closed.map (el) ->
                                n.ejs_env.slot_mapping[el] = i
                                i += 1
                                
                        
                        if n.ejs_env.parent?
                                parent_env_slot = n.ejs_env.slot_mapping[parent_env_name]
                                env_prepends.push b.expressionStatement(intrinsic setSlot_id, [this_env_id, b.literal(parent_env_slot), b.literal(parent_env_name), b.identifier(parent_env_name) ]);
                                

                        # we need to push assignments of any closed over parameters into the environment at this point
                        for param in n.params
                                if n.ejs_env.closed.has param.name
                                        env_prepends.push b.expressionStatement(intrinsic setSlot_id, [ this_env_id, b.literal(n.ejs_env.slot_mapping[param.name]), b.literal(param.name), b.identifier(param.name) ])

                        new_mapping["%slot_mapping"] = n.ejs_env.slot_mapping

                        flatten_memberexp = (exp, mapping) ->
                                if exp.type isnt CallExpression
                                        [b.literal(mapping[exp.name])]
                                else
                                        flatten_memberexp(exp.arguments[0], mapping).concat [exp.arguments[1]]

                        prepend_environment = (exps) ->
                                obj = this_env_id
                                for prop in exps
                                        obj = intrinsic(slot_id, [obj, prop])
                                obj

                        # if there are existing mappings prepend "%env." (a MemberExpression) to them
                        for mapped of new_mapping
                                val = new_mapping[mapped]
                                if mapped isnt "%slot_mapping"
                                        new_mapping[mapped] = prepend_environment(flatten_memberexp(val, n.ejs_env.slot_mapping))
                        
                        # and add mappings for all variables in .closed from "x" to "%env.x"

                        new_mapping["%env"] = this_env_id
                        n.ejs_env.closed.map (sym) ->
                                new_mapping[sym] = intrinsic slot_id, [this_env_id, b.literal(n.ejs_env.slot_mapping[sym]), b.literal(sym)]

                # remove all mappings for variables declared in this function
                if n.ejs_decls?
                        new_mapping = reject new_mapping, (sym) -> (n.ejs_decls.has sym) and not (n.ejs_env.closed.has sym)

                @mappings.push new_mapping
                @visitFunctionBody n
                if env_prepends.length > 0
                        n.body.body = env_prepends.concat n.body.body
                @mappings.pop()

                # convert function expressions to an explicit closure creation, so:
                # 
                #    function X () { ...body... }
                #
                # replace inline with:
                #
                #    makeClosure(%current_env, "X", function X () { ...body... })

                if not n.toplevel
                        if n.type is FunctionDeclaration
                                throw "there should be no FunctionDeclarations at this point"
                        else # n.type is FunctionExpression
                                intrinsic_args = []
                                intrinsic_args.push(if n.ejs_env.parent? then b.identifier(parent_env_name) else b.null())
                                
                                if n.id?
                                        intrinsic_id = makeClosure_id
                                        if n.id.type is Identifier
                                                intrinsic_args.push(b.literal(n.id.name))
                                        else
                                                intrinsic_args.push(b.literal(escodegen.generate n.id))
                                else
                                        intrinsic_id = makeAnonClosure_id

                                intrinsic_args.push(n)
                                
                                return intrinsic intrinsic_id, intrinsic_args
                n
            catch e
                console.warn "exception: " + e
                #console.warn "compiling the following code:"
                #console.warn escodegen.generate n
                throw e
                
        visitCallExpression: (n) ->
                n = super

                # replace calls of the form:
                #   X (arg1, arg2, ...)
                # 
                # with
                #   invokeClosure(X, %this, %argCount, arg1, arg2, ...);

                return n if is_intrinsic(n)
                @function_stack.top.scratch_size = Math.max @function_stack.top.scratch_size, n.arguments.length
                intrinsic invokeClosure_id, [n.callee].concat n.arguments

        visitNewExpression: (n) ->
                super

                # replace calls of the form:
                #   new X (arg1, arg2, ...)
                # 
                # with
                #   invokeClosure(X, %this, %argCount, arg1, arg2, ...);

                @function_stack.top.scratch_size = Math.max @function_stack.top.scratch_size, n.arguments.length

                rv = intrinsic invokeClosure_id, [n.callee].concat n.arguments
                rv.type = NewExpression
                rv

#
#   This pass walks the tree and moves all function expressions to the toplevel.
#

                if rhs.type is FunctionExpression and not rhs.id?.name
                        rhs.displayName = escodegen.generate lhs
                n

#
# special pass to inline some common idioms dealing with IIFEs
