includes 'parser/walker' 'parser/sexp' 'parser/env' :set

a = walker.new

a.init = { ast, interactive = false |
  my.ast = ast
  my.functions = []
  my.inner_functions = [[]]
  my.env = env.new
  my.interactive? = interactive
}

a.prototype.assign = {
  my.walk_sexps(my.ast.nodes)
  my.ast
}

a.prototype.set_upvar_assign = {
  my.functions.each { f | f.upvar_assign? = true }
}

a.prototype.set_upvar_access = { access |
  my.functions.each { f | f.upvar_access? = true }
  f = my.functions.last
  env_index = -1
  var = true? (access.name == :invoke_up || { access.name == :meth_access_up })
          { access.method }
          { access.value }
  not_found_local = true
  c = my
  c.set = set

  my.functions.reverse_each_while { func |
    false? func == f # skip current function
      {
        env_index = env_index - 1
        false? c.env.variables[env_index][var]
          {
            null? func.upvars, { func.upvars = c.set.new }
            func.upvars << var
          }
          {
            not_found_local = false
          }
      }

    not_found_local
  }

  null? f.upvars
    { f.upvars = c.set.new }

  f.upvars << var
}

a.prototype.var_type = { var |
  true? my.env.local_var?(var)
    { :local }
    {
      true? my.env[var]
        { :up }
        { :new }
    }
}

a.unhandled = { node |
  walk_sexps node.nodes
}

a.walk :var_assign { node |
  var = node.lhs
  t = var_type var
  true? t == :local
    { node.name = "local_var_reassign" }
    {
      true? t == :up
        {
          node.name = "upvar_assign"
          set_upvar_assign
        }
        {
          true? (my.functions.empty? && my.interactive?)
            { node.name = "local_var_assign_interactive" }
            { node.name = "local_var_assign" }
            my.env[var] = true
        }
    }

  process node.rhs
}

a.prototype.check_potentials = {
  false? my.interactive?
    {
      ps = my.inner_functions.pop
      false? my.functions.empty?
        {
          true? ps.any?({ f | f.upvar_assign? })
            { ps.each { f | false? (f.upvar_access? || { f.upvar_assign? }) { f.name = :liftable_function }}}
            { ps.each { f | false? f.upvar_assign?, { f.name = :liftable_function }}}
        }
    }
}

a.walk :function { node |
  my.env.new_scope
  my.functions << node
  my.inner_functions << []
  node.upvar_access? = false
  node.upvar_assign? = false
  node.upvars = null
  walk_sexps node.args
  walk_sexps node.body

  check_potentials

  my.functions.pop
  my.env.pop_scope
  my.inner_functions.last << node
}

a.walk :call { node |
  target = node.target
  true? target
    { process node.target }
  true? node.args
    { walk_sexps node.args }
  meth = node.method

  null? target
    {
      t = var_type meth
      true? t == :local
        { node.name = :invoke_local }
        { true? t == :up
            { node.name = :invoke_up; set_upvar_access node }
            { node.name = :invoke_self }
        }
    }
}

a.walk :get_value { node |
  t = var_type(node.value)
  true? t == :local
    { node.name = :get_local_value }
    { true? t == :up
        { node.name = :get_up_value; set_upvar_access node }
    }
}

a.walk :simple_index_get { node |
  my.proc_call node
}

a.walk :simple_index_set { node |
  my.proc_call node
}


a.walk :arg { node |
  my.env[node.id] = true
}

a.walk :def_arg { node |
  my.env[node.id] = true
  process node.value
}

a.walk :var_arg { node |
  my.env[node.id] = true
}

a.walk :meth_access { node |
  null? node.target
    {
      t = var_type(node.method)
      true? t == :local
        { node.name = :meth_access_local }
        { true? t == :up
            { node.name = :meth_access_up; set_upvar_access node }
            { node.name = :meth_access_self }
        }
    }
    {
      process node.target
    }
}

export a, :var_assigner