forked from Hyperion/HyperionOS
324 lines
9.3 KiB
Plaintext
324 lines
9.3 KiB
Plaintext
--:Minify:--
|
||
local C_PROMPT = 7
|
||
local C_CONT = 13
|
||
local C_OUT = 5
|
||
local C_ERR = 2
|
||
local C_KEY = 3
|
||
local C_STR = 9
|
||
local C_NUM = 10
|
||
local C_BOOL = 8
|
||
local C_NIL = 12
|
||
local C_TABLE = 13
|
||
|
||
local function c(col) syscall.devctl(1, "sfgc", col) end
|
||
local function w(s) syscall.write(1, tostring(s)) end
|
||
|
||
local MAX_DEPTH = 6
|
||
local MAX_ENTRIES = 64
|
||
|
||
local function prettyVal(val, indent, seen)
|
||
indent = indent or 0
|
||
seen = seen or {}
|
||
local t = type(val)
|
||
|
||
if t == "nil" then
|
||
c(C_NIL); w("nil")
|
||
elseif t == "boolean" then
|
||
c(C_BOOL); w(tostring(val))
|
||
elseif t == "number" then
|
||
c(C_NUM)
|
||
if val ~= val then
|
||
w("nan")
|
||
elseif val == math.huge then
|
||
w("inf")
|
||
elseif val == -math.huge then
|
||
w("-inf")
|
||
elseif val == math.floor(val) and math.abs(val) < 1e15 then
|
||
w(tostring(math.floor(val)))
|
||
else
|
||
w(tostring(val))
|
||
end
|
||
elseif t == "string" then
|
||
c(C_STR)
|
||
local s = string.format("%q", val)
|
||
w(s)
|
||
elseif t == "function" then
|
||
c(C_TABLE); w(tostring(val))
|
||
elseif t == "table" then
|
||
if seen[val] then
|
||
c(C_TABLE); w("<circular " .. tostring(val) .. ">")
|
||
return
|
||
end
|
||
if indent >= MAX_DEPTH then
|
||
c(C_TABLE); w("<table " .. tostring(val) .. ">")
|
||
return
|
||
end
|
||
seen[val] = true
|
||
|
||
local pad = string.rep(" ", indent)
|
||
local padIn = string.rep(" ", indent + 1)
|
||
|
||
local arrKeys = {}
|
||
local hashKeys = {}
|
||
local arrMax = #val
|
||
|
||
for k in pairs(val) do
|
||
if type(k) == "number" and k >= 1 and k <= arrMax and k == math.floor(k) then
|
||
arrKeys[#arrKeys+1] = k
|
||
else
|
||
hashKeys[#hashKeys+1] = k
|
||
end
|
||
end
|
||
table.sort(arrKeys)
|
||
|
||
table.sort(hashKeys, function(a, b)
|
||
local ta, tb = type(a), type(b)
|
||
if ta == tb then
|
||
if ta == "string" then return a < b end
|
||
if ta == "number" then return a < b end
|
||
return tostring(a) < tostring(b)
|
||
end
|
||
return ta < tb
|
||
end)
|
||
|
||
local total = #arrKeys + #hashKeys
|
||
if total == 0 then
|
||
c(C_TABLE); w("{}")
|
||
seen[val] = nil
|
||
return
|
||
end
|
||
|
||
c(C_TABLE); w("{\n")
|
||
|
||
local shown = 0
|
||
local function printEntry(k, v, isLast)
|
||
shown = shown + 1
|
||
if shown > MAX_ENTRIES then return true end
|
||
w(padIn)
|
||
if type(k) == "number" and arrKeys[k] then
|
||
else
|
||
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||
c(C_KEY); w(k)
|
||
else
|
||
c(C_TABLE); w("[")
|
||
prettyVal(k, indent+1, seen)
|
||
c(C_TABLE); w("]")
|
||
end
|
||
c(C_TABLE); w(" = ")
|
||
end
|
||
prettyVal(v, indent+1, seen)
|
||
c(C_TABLE)
|
||
if not isLast then w(",") end
|
||
w("\n")
|
||
end
|
||
|
||
for i, k in ipairs(arrKeys) do
|
||
w(padIn)
|
||
prettyVal(val[k], indent+1, seen)
|
||
c(C_TABLE)
|
||
if i < total then w(",") end
|
||
w("\n")
|
||
shown = shown + 1
|
||
if shown >= MAX_ENTRIES then
|
||
c(C_NIL); w(padIn .. "-- ..." .. (total - shown) .. " more entries\n")
|
||
break
|
||
end
|
||
end
|
||
|
||
if shown < MAX_ENTRIES then
|
||
for i, k in ipairs(hashKeys) do
|
||
local isLast = (shown + 1 >= total)
|
||
w(padIn)
|
||
if type(k) == "string" and k:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then
|
||
c(C_KEY); w(k)
|
||
else
|
||
c(C_TABLE); w("[")
|
||
prettyVal(k, indent+1, seen)
|
||
c(C_TABLE); w("]")
|
||
end
|
||
c(C_TABLE); w(" = ")
|
||
prettyVal(val[k], indent+1, seen)
|
||
c(C_TABLE)
|
||
shown = shown + 1
|
||
if shown < total then w(",") end
|
||
w("\n")
|
||
if shown >= MAX_ENTRIES then
|
||
local rem = total - shown
|
||
if rem > 0 then
|
||
c(C_NIL); w(padIn .. "-- ..." .. rem .. " more entries\n")
|
||
end
|
||
break
|
||
end
|
||
end
|
||
end
|
||
|
||
c(C_TABLE); w(pad .. "}")
|
||
seen[val] = nil
|
||
else
|
||
c(C_TABLE); w(tostring(val))
|
||
end
|
||
c(1)
|
||
end
|
||
|
||
local function printResults(...)
|
||
local n = select("#", ...)
|
||
if n == 0 then return end
|
||
for i = 1, n do
|
||
if i > 1 then c(C_TABLE); w("\t") end
|
||
prettyVal(select(i, ...), 0, {})
|
||
end
|
||
w("\n")
|
||
c(1)
|
||
end
|
||
|
||
local luaEnv = setmetatable({}, {__index = _ENV})
|
||
luaEnv._G = luaEnv
|
||
|
||
luaEnv.print = function(...)
|
||
local n = select("#", ...)
|
||
for i = 1, n do
|
||
if i > 1 then w("\t") end
|
||
prettyVal(select(i, ...), 0, {})
|
||
end
|
||
w("\n")
|
||
c(1)
|
||
end
|
||
|
||
luaEnv.pp = function(val)
|
||
prettyVal(val, 0, {})
|
||
w("\n")
|
||
c(1)
|
||
end
|
||
|
||
luaEnv.exit = setmetatable({}, {
|
||
__tostring = function() return "function: exit()" end,
|
||
__call = function() syscall.exit() end,
|
||
})
|
||
|
||
local function compile(code)
|
||
local exprFn = load("return " .. code, "@lua", "t", luaEnv)
|
||
if exprFn then return exprFn, true end
|
||
local stmtFn, err = load(code, "@lua", "t", luaEnv)
|
||
return stmtFn, false, err
|
||
end
|
||
|
||
local function isIncomplete(code)
|
||
local _, err = load(code, "@lua", "t", luaEnv)
|
||
return err and (err:find("<eof>") ~= nil or err:find("'end'") ~= nil
|
||
or err:find("'then'") ~= nil or err:find("'until'") ~= nil)
|
||
end
|
||
|
||
local function cleanErr(msg)
|
||
return tostring(msg)
|
||
:gsub("^%[string .-%]:", "")
|
||
:gsub("^@lua:", "")
|
||
:gsub("stack traceback:.*", "")
|
||
:match("^%s*(.-)%s*$")
|
||
end
|
||
|
||
local function runCode(code)
|
||
local fn, isExpr, err = compile(code)
|
||
if not fn then
|
||
c(C_ERR); w("[error] "); c(1); w(cleanErr(err) .. "\n")
|
||
return
|
||
end
|
||
|
||
local results = table.pack(xpcall(fn, debug.traceback))
|
||
local ok = table.remove(results, 1)
|
||
results.n = results.n - 1
|
||
|
||
if not ok then
|
||
c(C_ERR); w("[error] "); c(1); w(cleanErr(results[1]) .. "\n")
|
||
elseif isExpr and results.n > 0 then
|
||
c(C_OUT); w("= ")
|
||
printResults(table.unpack(results, 1, results.n))
|
||
end
|
||
end
|
||
|
||
local function getUserInput(prompt, history)
|
||
c(C_PROMPT); w(prompt); c(1)
|
||
local pos = syscall.devctl(1, "gpos")
|
||
local ox = tonumber(pos:sub(1, pos:find(";")-1))
|
||
local oy = tonumber(pos:sub(pos:find(";")+1))
|
||
|
||
local input = ""
|
||
local cursor = 1
|
||
local histIdx = 0
|
||
local blink = false
|
||
local dirty = true
|
||
|
||
local function redraw()
|
||
syscall.devctl(1, "spos", ox, oy)
|
||
w(input:sub(1, cursor-1))
|
||
if blink then syscall.devctl(1,"sfgc",16); syscall.devctl(1,"sbgc",1) end
|
||
w(cursor > #input and " " or input:sub(cursor, cursor))
|
||
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||
w(input:sub(cursor+1) .. " ")
|
||
dirty = false
|
||
end
|
||
|
||
while true do
|
||
local key = syscall.read(0)
|
||
if key and key ~= "" then
|
||
if key == "[C" then
|
||
if cursor > 1 then cursor = cursor - 1; dirty = true end
|
||
elseif key == "[D" then
|
||
if cursor <= #input then cursor = cursor + 1; dirty = true end
|
||
elseif key == "[A" then
|
||
if history and histIdx < #history then
|
||
histIdx = histIdx + 1
|
||
input = history[#history - histIdx + 1]
|
||
cursor = #input + 1; dirty = true
|
||
end
|
||
elseif key == "[B" then
|
||
if histIdx > 1 then
|
||
histIdx = histIdx - 1
|
||
input = history[#history - histIdx + 1]
|
||
cursor = #input + 1; dirty = true
|
||
elseif histIdx == 1 then
|
||
histIdx = 0; input = ""; cursor = 1; dirty = true
|
||
end
|
||
elseif key == "\b" then
|
||
if cursor > 1 then
|
||
input = input:sub(1, cursor-2) .. input:sub(cursor)
|
||
cursor = cursor - 1; dirty = true
|
||
end
|
||
elseif key == "\n" then
|
||
syscall.devctl(1,"sfgc",1); syscall.devctl(1,"sbgc",16)
|
||
syscall.devctl(1,"spos",ox,oy)
|
||
w(input .. " \n")
|
||
return input
|
||
else
|
||
input = input:sub(1, cursor-1) .. key .. input:sub(cursor)
|
||
cursor = cursor + 1; dirty = true
|
||
end
|
||
end
|
||
local nb = (math.floor(syscall.getUptime() / 500) % 2) == 0
|
||
if nb ~= blink then blink = nb; dirty = true end
|
||
if dirty then redraw() end
|
||
end
|
||
end
|
||
|
||
c(C_PROMPT); w("HyperionOS " .. _VERSION .. "\n")
|
||
c(C_NIL)
|
||
w("Interactive Lua REPL. exit() to quit.\n\n")
|
||
c(1)
|
||
|
||
local history = {}
|
||
|
||
while true do
|
||
local code = getUserInput("lua> ", history)
|
||
if code == "" then goto continue end
|
||
|
||
while isIncomplete(code) do
|
||
code = code .. "\n" .. getUserInput("... ", nil)
|
||
end
|
||
|
||
if code ~= history[#history] then
|
||
history[#history+1] = code
|
||
end
|
||
|
||
runCode(code)
|
||
::continue::
|
||
end
|