Skip to content

Instantly share code, notes, and snippets.

@FelixWolf
Last active November 25, 2025 01:05
Show Gist options
  • Select an option

  • Save FelixWolf/d28995f9c9bf944631be6e2954d6807c to your computer and use it in GitHub Desktop.

Select an option

Save FelixWolf/d28995f9c9bf944631be6e2954d6807c to your computer and use it in GitHub Desktop.
A VM for Second Life
-- URL handling
local gURL = ""
function requestURL()
ll.ReleaseURL(gURL)
ll.RequestURL()
end
-- Async routines
local asyncRoutines = {}
local TIMEOUT = 5
local gcHandler = LLTimers:every(1, function(scheduled_time: number, interval: number)
for handle, data in pairs(asyncRoutines) do
if scheduled_time - data.time >= TIMEOUT then
coroutine.resume(data.routine, nil)
end
end
end)
-- Handle asm
function handleRequest(req: string, body: string)
ll.SetText("BEGIN", vector(1,1,1), 1)
local ok, asm = pcall(lljson.decode, body)
if not ok then
ll.HTTPResponse(req, 400, "JSON decode error: " .. tostring(asm))
return
end
local stack, flag = {}, false
local inst = 1
function fault(reason: string)
ll.HTTPResponse(req, 400, "Line " .. inst .. ": " .. reason .. "\n\n" .. lljson.encode(stack))
end
while inst <= #asm do
local line = asm[inst]
-- local old = ll.List2String(ll.GetLinkPrimitiveParams(LINK_THIS, {PRIM_TEXT}),1)
-- ll.SetText(ll.DumpList2String(line, ", ") .. "\n" .. old, vector(1,1,1), 1)
-- ll.SetText(ll.DumpList2String(stack, ", "), vector(1,1,1), 1)
-- ll.OwnerSay(inst .. ": " .. ll.DumpList2String(line, ", "))
local op = line[1]
if op == "nop" then
-- Nothing
elseif op == "push" then
stack[#stack + 1] = line[2]
elseif op == "pop" then
stack[#stack] = nil
elseif op == "copy" then
local where = line[2]
if #stack < where then
return fault("Buffer underflow")
end
stack[#stack + 1] = stack[#stack - where]
elseif op == "cast" then
local t = line[2]
if t == 0 then
stack[#stack] = tonumber(stack[#stack])
elseif t == 1 then
stack[#stack] = tostring(stack[#stack])
elseif t == 2 then
stack[#stack] = uuid(stack[#stack])
elseif t == 3 then
stack[#stack] = vector(stack[#stack])
else
return fault("Invalid cast " .. tostring(t))
end
elseif op == "jmp" then
inst = line[2] - 1
elseif op == "jnz" then
local location = line[2]
if flag ~= 0 then
inst = location - 1
end
elseif op == "jz" then
local location = line[2]
if flag == 0 then
inst = location - 1
end
elseif op == "jg" then
local location = line[2]
if flag > 0 then
inst = location - 1
end
elseif op == "jl" then
local location = line[2]
if flag < 0 then
inst = location - 1
end
elseif op == "cmp" then
if #stack < 2 then
return fault("cmp expects two values")
end
local a = stack[#stack - 1]
local b = stack[#stack]
if type(a) ~= type(b) and not (type(a) == "number" and type(b) == "number") then
return fault("Type mismatch")
end
if a == b then
flag = 0
elseif a < b then
flag = -1
else
flag = 1
end
elseif op == "add" then
local b = table.remove(stack)
local a = table.remove(stack)
if type(a) ~= "number" or type(b) ~= "number" then
return fault("Add expects two numbers")
end
stack[#stack + 1] = a + b
elseif op == "sub" then
local b = table.remove(stack)
local a = table.remove(stack)
if type(a) ~= "number" or type(b) ~= "number" then
return fault("Sub expects two numbers")
end
stack[#stack + 1] = a - b
elseif op == "mul" then
local b = table.remove(stack)
local a = table.remove(stack)
if type(a) ~= "number" or type(b) ~= "number" then
return fault("Mul expects two numbers")
end
stack[#stack + 1] = a * b
elseif op == "div" then
local b = table.remove(stack)
local a = table.remove(stack)
if type(a) ~= "number" or type(b) ~= "number" then
return fault("Div expects two numbers")
end
stack[#stack + 1] = a / b
elseif op == "mod" then
local b = table.remove(stack)
local a = table.remove(stack)
if type(a) ~= "number" or type(b) ~= "number" then
return fault("Mod expects two numbers")
end
stack[#stack + 1] = a % b
elseif op == "call" then
local funcName = line[2]
if type(funcName) ~= "string" or funcName:sub(1, 2) ~= "ll" then
return fault("Invalid function name: " .. tostring(funcName))
end
local key = funcName:sub(3)
local fn = llcompat[key]
if type(fn) ~= "function" then
return fault("Unknown ll function on line: " .. funcName)
end
local argCount = line[3]
if #stack < argCount then
return fault("Stack underflow")
end
local params = {}
for i = 1, argCount do
params[argCount - i + 1] = stack[#stack]
stack[#stack] = nil
end
local ok, result = pcall(fn, table.unpack(params))
if not ok then
return fault("Function error: " .. tostring(result))
end
if result ~= nil then
stack[#stack + 1] = result
end
elseif op == "acall" then
local funcName = line[2]
if type(funcName) ~= "string" or funcName:sub(1, 2) ~= "ll" then
return fault("Invalid function name: " .. tostring(funcName))
end
local key = funcName:sub(3)
local fn = llcompat[key]
if type(fn) ~= "function" then
return fault("Unknown ll function on line: " .. funcName)
end
local argCount = line[3]
if #stack < argCount then
return fault("Stack underflow")
end
local params = {}
for i = 1, argCount do
params[argCount - i + 1] = stack[#stack]
stack[#stack] = nil
end
local ok, handle = pcall(fn, table.unpack(params))
if not ok then
return fault("Function error: " .. tostring(result))
end
asyncRoutines[handle] = {
routine = coroutine.running(),
time = os.clock()
}
result = coroutine.yield()
asyncRoutines[handle] = nil
if result ~= nil then
stack[#stack + 1] = result
end
else
return fault("Invalid operation " .. op)
end
inst += 1
end
ll.SetText("", vector(1,1,1), 1)
if #stack == 0 then
ll.HTTPResponse(req, 200, "[]")
else
ll.HTTPResponse(req, 200, lljson.encode(stack))
end
end
LLEvents:on("dataserver", function(req: string, data: string)
if asyncRoutines[req] ~= nil then
coroutine.resume(asyncRoutines[req].routine, data)
end
end)
LLEvents:on("http_request", function(req: string, method: string, body: string)
if method == URL_REQUEST_GRANTED then
ll.Say(1, body)
elseif method == URL_REQUEST_DENIED then
requestURL()
elseif method == "POST" then
local r = coroutine.create(handleRequest)
coroutine.resume(r, req, body)
else
ll.HTTPResponse(req, 501, "Not Implemented")
end
end)
requestURL()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment