--[[ Environment Module
     KILT -- Kamin Interpreters in Lua Toolset
     H. Conrad Cunningham, Professor
     Computer and Information Science
     University of Mississippi

Developed for CSci 658, Software Language Engineering, Fall 2013

2013-08-12: Module built from Core interpreter Environment
2013-08-14: Function dumpEnv added to aid in debugging
2013-08-22: Added require of Utilities module
2013-08-24: Cleaned dependencies and comments
2013-11-13: Corrected setVal ("senv" in "assign" call)

1234567890123456789012345678901234567890123456789012345678901234567890

Table globalEnv stores the definitions for variables at the outermost
level.  Other environment frames are linked to this global environment
using their ENCLOSING keys.  The Kamin Core and Lisp interpreters
only use environment frames for one level of function call
parameter/argument bindings.

--]]

-- KILT tree utilities library
local util = require "utilities"
local treeConcat = util.treeConcat

-- Global Environment table
local globalEnv = {} 

local ENCLOSING = -1  -- key for enclosing environment pointer

-- Function "newEnv" constructs a new empty environment frame enclosed
-- within "env". If "env" is nil or unspecified, it defaults the
-- enclosing environment to "globalEnv".

local function newEnv(env)
  env = env or globalEnv  
  local localEnv      = {}   -- new empty environment frame
  localEnv[ENCLOSING] = env  --   that is enclosed within "env"
  return localEnv
end

-- Function "findBinding" returns the environment frame in which "s"
-- is defined; else it returns nil.  The returned value is "env" or an
-- environment frame that encloses it.

local function findBinding(s,env)
  if not env then
    return nil
  elseif env[s] then
    return env
  else 
    return findBinding(s,env[ENCLOSING])
  end
end

-- Function "getVal" returns the value of name "n" in the environment
-- frame "env" or some enclosing environment frame; it returns nil if
-- not found.

local function getVal(n,env)
  local boundin = findBinding(n,env)
  if boundin then 
    return boundin[n] 
  else
    return nil
  end
end

-- Side-effecting function "assign" sets name "n" to have value "v" in
-- environment frame "env".

local function assign(n,v,env)
  env[n] = v
end

-- Side-effecting function "setVal" searches for a binding of name "n"
-- in environment frame "env" or any enclosing environment frame.  If
-- "n" is bound, then "setVal" sets the value to "v"; otherwise,it
-- binds "n" to "v" in environment frame "defenv" or in "globalenv" if
-- "defenv" is nil or unspecified.

local function setVal(n,v,env,defenv)
  defenv = defenv or globalEnv
  senv = findBinding(n,env) or defenv
  assign(n,v,senv)
  return v
end

-- Procedure "dumpEnv" prints all bindings in the environment.  It is
-- intended for debugging.

local function dumpEnv(env)
  print("Environment frame dump")
  if env then
    for k,v in pairs(env) do
      print(k,treeConcat(v))
    end
    dumpEnv(env[ENCLOSING])
  else
    print("End dump")
  end
end


-- MODULE EXPORT LIST

-- ENCLOSING and findBinding are not exported 

-- Caution: An environment frame's table can be directly accessed by
-- clients. This should not be necessary because the "getVal",
-- "assign", and "setVal" functions should give the neeed access.

return {
  globalEnv   = globalEnv,
  newEnv      = newEnv,
  getVal      = getVal,
  assign      = assign, 
  setVal      = setVal,
  dumpEnv     = dumpEnv -- for debugging
}
