#! /usr/local/bin/lua
-- Helper functions to encode VM operations
local function int(value) -- convert value to a 32-bit unsigned little-endian integer
local b1, b2, b3, b4
b1 = value % 0x100
value = (value - b1) / 0x100
b2 = value % 0x100
value = (value - b2) / 0x100
b3 = value % 0x100
value = (value - b3) / 0x100
b4 = value % 0x100
return string.char(b1, b2, b3, b4)
end
local function ABC(op, a, b, c) -- create an ABC VM instruction
local value = op + a * 2^6 + c * 2^14 + b * 2^23
return int(value)
end
-- factory for count_raw
local function make_count_raw()
local sentinel = {}
local nxt
-- raw: function(count, test, ...)
-- if type(test) ~= "none", returns raw(count + 1, ...)
-- otherwise returns count
local raw = function(...)
local null, count, sent
sent = sentinel
null = {...} -- this line gets modified
-- after modification, ... is loaded into count, sent, etc. and then setlisted into
-- a nil value to correct L->top. If we had a vararg, then sent will no longer be
-- sentinel. (also note that after modification, no tables are created)
if sent == sentinel then
return count
else
-- we cannot trim a value off the front of the varargs ourselves, so proxy it
return nxt(...)
end
end
-- nxt: helper for raw
nxt = function(count, a, ...)
return raw(count + 1, ...)
end
return raw
end
-- tweak the factory to work as intended
do
make_count_raw = string.dump(make_count_raw)
local to_find = ABC(4 , 3, 0, 0) -- GETUPVAL sentinel
.. ABC(10, 4, 0, 0) -- NEWTABLE
.. ABC(37, 5, 0, 0) -- VARARG
.. ABC(34, 4, 0, 1) -- SETLIST
.. ABC(0 , 1, 4, 0) -- MOVE 1, 4
local to_rep = ABC(4 , 3, 0, 0) -- GETUPVAL sentinel
.. ABC(0 , 0, 0, 0) -- NOP
.. ABC(37, 2, 0, 0) -- VARARG (sets count, *may* overwrite sent)
.. ABC(34, 1, 0, 1) -- SETLIST (into a nil, so it does nothing)
.. ABC(0 , 0, 0, 0) -- NOP
local first, last = make_count_raw:find(to_find, 1, true)
make_count_raw = make_count_raw:sub(1, first - 1) .. to_rep .. make_count_raw:sub(last + 1)
make_count_raw = assert(loadstring(make_count_raw))
end
-- create count_raw and define count
local count_raw = make_count_raw()
function count(...)
return count_raw(0, ...)
end
-- show it working
print(count("a", "b", "c")) --> 3
print(count(nil, "d", nil, nil)) --> 4
print(count("e", nil, nil, "f", nil, nil, nil)) --> 7
print(count()) --> 0
print(count(nil)) --> 1