forked from Hyperion/HyperionOS
326 lines
9.3 KiB
Plaintext
326 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 == "\19" then
|
|
if cursor > 1 then cursor = cursor - 1; dirty = true end
|
|
elseif key == "\20" then
|
|
if cursor <= #input then cursor = cursor + 1; dirty = true end
|
|
elseif key == "\17" then
|
|
if history and histIdx < #history then
|
|
histIdx = histIdx + 1
|
|
input = history[#history - histIdx + 1]
|
|
cursor = #input + 1; dirty = true
|
|
end
|
|
elseif key == "\18" 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
|
|
|
|
syscall.devctl(1, "clear")
|
|
syscall.devctl(1, "spos", 1, 1)
|
|
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
|