-- Hack `_G` so that undefined variables spit out binding objects for us. -- The only job of a binding objects is to look up a vlaue in the current -- scope when it is eval or ran. -- -- This could be made less wasteful by caching, but this is a -- poof-of-concenpt :3 setmetatable (_G, { __index = function (t, k) local value = rawget (t, k) if value then return value end return { name = k ; function (scope) return scope [k] end ; } end}) -- Handles executing a program (a list of function / thunks) or executing -- a thunk. If the value is anything else, just return it. local eval = function (program, scope) if type (program) == "function" then return program (scope) elseif type (program) == "table" then local value = nil for _, action in ipairs (program) do value = action (scope) end return value else return program end end -- Run exist to provide the creation of new scopes over eval local run = function (program, previous_scope) local scope = setmetatable ({ getters = {} }, {__index = previous_scope}) return eval (program, scope) end -- Each one of these operations eventually produces a thunk that -- gets placed into the program and evulated later. Think of these -- as little compilers. local set = function (var) return { to = function (val) return function (scope) scope[var.name] = eval (val, scope) end end } end local show = function (expression) return function (scope) print (run (expression, scope)) end end -- Expressions need to always unwrap the value given -- in the current scope. There inputs could be a raw -- value or a thunk that needs to be evaluated. local multiply = function (a) return { by = function (b) return function (scope) return eval (a, scope) * eval (b, scope) end end } end local add = function (a) return { with = function (b) return function (scope) return eval (a, scope) + eval (b, scope) end end } end local sqrt = function (x) return function (scope) return math.sqrt (eval (x, scope)) end end run { set (x) .to (3) ; set (y) .to (4) ; -- There could be a better means of handling scoping -- show current makes a scope implicitly when it could -- be an explicit operation. -- -- Other operations don't do this. show { set (x_sq) .to (multiply (x) .by (x)) ; set (y_sq) .to (multiply (y) .by (y)) ; sqrt (add (x_sq) .with (y_sq)) ; } ; }