lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


> -- Syntax is a bit more complicated that what you exacly want
> local Foo = in_context_of(my_dsl)
> {
>        ["Alpha"] = { 'op1', "a", 'op2', "b" };
>        ["Beta"] = { 'op2', "a", 'op1', "b", 'op3', "c" };
> };

Hey, this is an interesting idea for implementation of some kind of
virtual machine-interpreted DSL:

Silly example:

-- Should print 3.
interprete(make_vm()) 'add' '1 '2' 'prn'

Code follows.

Alexander.

=======

assert_is_function = function(val, msg)
  assert(type(val) == "function", msg)
  return val
end

assert_is_table = function(val, msg)
  assert(type(val) == "table", msg)
  return val
end

assert_is_string = function(val, msg)
  assert(type(val) == "string", msg)
  return val
end

in_context_of = function(t)
  assert_is_table(t)
  return function(func)
    assert_is_function(func)
    --  TODO: Get the original environment of the function!
    setfenv(func, t)
    return func(), setfenv(func, _G) -- Assuming setfenv returns nil.
  end
end

local make_nary = function(num)
  assert(num >= 0)

  local wrapper = function(func)
    assert_is_function(func)

    --  Instruction parameter is for error reporting only.
    --  TODO: These should be implemented more effectively,
    --        removing table creation and function creation
    --        on each operand call.
    local brancher = function(callback, instruction_name)
      assert_is_function(callback)
      assert_is_string(instruction_name)

      if num == 0 then
        func()
        return callback
      else
        local n = num
        local operands = { }
        local operand_collector
        operand_collector = function(operand)
          assert(operand, instruction_name .. " : " .. num .. "
operands required.")

          table.insert(operands, operand)
          n = n - 1

          if n > 0 then
            return operand_collector
          else
            func(unpack(operands))
            return callback
          end
        end

        return operand_collector
      end
    end

    return brancher
  end

  return wrapper
end

interprete = function(vm_instructions)
  assert(vm_instructions)
  local interpreter
  interpreter = function(op)
    assert(op)
    local handler = vm_instructions[op]
    assert_is_function(handler, "Unknown instruction, " .. op)
    return handler(interpreter, op)
  end
  return interpreter
end

interpreter_util =
{
  --  TODO: Would benefit from optimized version of each!
  nullary = make_nary(0);
  unary   = make_nary(1);
  binary  = make_nary(2);
  nary    = make_nary;
}

--  All VM API should be mentioned here due to in_context_of's setfenv call.
--  If this is not what you want, you can move implementation of instruction
--  handling functions outside of the instruction table declaration.
--  Or just remove that in_context_of call.
local my_vm_api =
{
  print = print;
}

local make_vm_1 = function()
  local vm = my_vm_api
  return in_context_of(interpreter_util) (function()
    return
    {
      op0 = nullary (function() vm.print("op0") end);
      op1 = unary   (function(var) vm.print("op1", var) end);
      op2 = binary  (function(var1, var2) vm.print("op2", var1, var2) end);
      op3 = nary(3) (function(var1, var2, var3) vm.print("op3", var1,
var2, var3) end);
    }
  end)
end

-- Silly example for VM with state.
local make_vm_2 = function()
  local op1 = 0
  local op2 = 0
  local result = 0
  local vm = my_vm_api
  return in_context_of(interpreter_util) (function()
    return
    {
      add = binary  (function(var1, var2) result = var1 + var2 end);
      prn = nullary (function() vm.print(result) end);
    }
  end)
end

interprete(make_vm_1()) 'op0' 'op1' 'a' 'op0' 'op3' 'b' 'c' 'd' 'op2' 'e' 'f'
--  TODO: Handle "hanging" instructions with not enough parameters at
the end of the stream.
--        like "interprete(make_my_vm()) 'inc'". For example, it can
be done by passing
--        special 'end' opcode at the end of the stream.
interprete(make_vm_2()) 'prn' 'add' '1' '2' 'prn'

--  Note states of VMs are separate
interprete(make_vm_2()) 'prn'