Last active
November 25, 2025 01:05
-
-
Save FelixWolf/d28995f9c9bf944631be6e2954d6807c to your computer and use it in GitHub Desktop.
A VM for Second Life
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- 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