actual initial commit smh

This commit is contained in:
2026-06-20 21:21:19 +03:00
parent fff0e5ada5
commit 85978ee33d
219 changed files with 12350 additions and 1 deletions
+120
View File
@@ -0,0 +1,120 @@
local cliParse = {}
local cliParseConfig
function cliParse.config(config)
-- Check if config is valid
checkArg(1, config, "table")
for flagName, argCount in pairs(config) do
if type(flagName) ~= "string" then
error("Flag name " .. tostring(flagName) .. " must be a string")
end
if type(argCount) == "table" then -- Min and max arg count specified
if type(argCount[1]) ~= "number" then
error("Min args for flag " .. flagName .. " must be a number")
end
if argCount[1] < 0 then
error("Min args for flag " .. flagName .. " must not be lower than 0")
end
if type(argCount[2]) ~= "number" then
error("Max args for flag " .. flagName .. " must be a number")
end
if argCount[2] < 0 then
error("Max args for flag " .. flagName .. " must not be lower than 0")
end
if argCount[2] < argCount[1] then
error("Max args for flag " .. flagName .. " must be more than or equal to min args")
end
elseif type(argCount) == "number" then -- Required arg count specified
if argCount < 0 then
error("Required args for flag " .. flagName .. " must not be lower than 0")
end
else
error("Arg count for flag " .. flagName .. " must be either a table of 2 numbers or a number")
end
-- Config is all good, set it
cliParseConfig = config
end
end
function cliParse.parse(...)
local args = { ... }
local returnTable = { ["flags"] = {}, ["args"] = {} } -- This will be filled out and returned in the end
for i = 1, #args do -- This is used instead of pairs() so that the code can skip ahead when it finds arguments instead of flags by just incrementing i
local flagList = {}
if args[i]:sub(1, 2) == "--" then -- Long flag
flagList = { args[i]:sub(3) }
elseif args[i]:sub(1, 1) == "-" then -- Short flag(s)
for i2 = 2, #args[i] do -- i is 2 to account for the - character at the start
table.insert(flagList, args[i]:sub(i2, i2))
end
end
for flagIndex, flag in ipairs(flagList) do -- Yes, this has to be in the argument loop for the skipahead to work.
local flagConfig = cliParseConfig[flag]
if flagConfig then -- This is a real flag
returnTable.flags[flag] = {}
if type(flagConfig) == "table" then
if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one
if flagConfig[1] ~= 0 then
return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got 0"
end
else
for i2 = 1, flagConfig[1] do -- Iterate through the items AFTER the flags to find the minimum arguments
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
else
return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got " .. i2
end
end
i = i + flagConfig[1]
local i2IncrementAmount -- See line 71 and 76
for i2 = 1, flagConfig[2] - flagConfig[1] do -- Now search for the max args
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
else
i2IncrementAmount = i2 - 1
-- Since the current argument is NOT a valid one, decrement by 1 to return to the last valid one (this is important when adding i2 to i)
break
end
end
i = i + (i2IncrementAmount or flagConfig[2])
end
else -- Flag required args are a single number
if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one
if flagConfig ~= 0 then
return false, "Flag " .. flag .. " expects " .. flagConfig .. " arguments, got 0"
end
else
for i2 = 1, flagConfig do -- Now search for the max args
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
else
return false, "Flag " .. flag .. " expects " .. flagConfig[1] .. " arguments, got " .. i2
end
end
i = i + flagConfig
end
end
else
return false, "Unexpected flag: " .. flag
end
end
end
for _, arg in pairs(args) do
local foundArg = false
for _, flag in pairs(returnTable.flags) do -- A loop in a loop... Peak efficiency
if table.find(flag, arg) or arg:sub(1, 1) == "-" then -- AAAND ANOTHER LOOP?!
foundArg = true
break
end
end
if not foundArg then
table.insert(returnTable.args, arg)
end
end
return returnTable
end
return cliParse
+140
View File
@@ -0,0 +1,140 @@
local computer
if require then
-- libcomponent can get loaded early enough in the boot process where require is not present
computer = require("computer")
else
computer = _G.computer
end
local compLib
local LLcomponent
if table.copy then
compLib = table.copy(component)
LLcomponent = table.copy(component)
else
compLib = {}
LLcomponent = component
end
-- local ocelot = LLcomponent.proxy(LLcomponent.list("ocelot")())
-- ocelot.log("loaded")
_G.componentlib = { ["additions"] = {}, ["removals"] = {} }
compLib.virtual = {}
function compLib.virtual.add(address, componentType, proxy)
checkArg(1, address, "string")
checkArg(2, componentType, "string")
checkArg(3, proxy, "table")
proxy["address"] = address
local proc
if _PUBLIC.tsched then
proc = _PUBLIC.tsched.getCurrentTask()
end
componentlib.additions[address] = { ["componentType"] = componentType, ["proxy"] = proxy, ["proc"] = proc }
if componentlib.removals[address] then
componentlib.removals[address] = nil
end
computer.pushSignal("component_added", address, componentType)
end
function compLib.virtual.remove(address)
checkArg(1, address, "string")
if componentlib.additions[address] then
computer.pushSignal("component_removed", address, componentlib.additions[address].componentType)
componentlib.additions[address] = nil
else
local componentTypeOutput = compLib.type(address)
computer.pushSignal("component_removed", address, componentTypeOutput or "unknown")
table.insert(componentlib.removals, address)
end
end
function compLib.virtual.check(address)
checkArg(1, address, "string")
if _G.componentlib.additions[address] then
return true, _G.componentlib.additions[address].proc
else
return false
end
end
function compLib.list(componentType)
checkArg(1, componentType, "string", "nil")
local componentList = LLcomponent.list(componentType)
for address, dataTable in pairs(componentlib.additions) do
if dataTable.componentType == componentType or not componentType then
componentList[address] = dataTable.componentType
end
end
for _, address in pairs(componentlib.removals) do
componentList[address] = nil
end
local i, value
setmetatable(componentList, {
__call = function(self)
i, value = next(self, i)
return i, value
end,
})
return componentList
end
function compLib.proxy(address)
if componentlib.additions[address] then
--ocelot.log("vcomponent")
return componentlib.additions[address].proxy
else
return LLcomponent.proxy(address)
end
end
function compLib.invoke(address, funcName, ...)
--ocelot.log("Invoking " .. funcName .. " from " .. address)
if componentlib.additions[address] then
--ocelot.log("vcomponent")
if not componentlib.additions[address].proxy[funcName] then
error("no such method")
end
return componentlib.additions[address].proxy[funcName](...)
else
return LLcomponent.invoke(address, funcName, ...)
end
end
function compLib.get(address)
checkArg(1, address, "string")
if #address < 3 then
return nil, "abbreviated address must be at least 3 characters long"
end
for currentAddress, name in compLib.list() do
if currentAddress:find("^" .. address) then
return currentAddress
end
end
return nil, "full address not found"
end
function compLib.isAvailable(componentType)
checkArg(1, componentType, "string")
if LLcomponent.list(componentType)() then
return true
else
return false
end
end
-- Add main component proxies to the library
setmetatable(compLib, {
["__index"] = function(_, item)
if LLcomponent.list(item)() then
return compLib.proxy(compLib.list(item)())
else
-- Why did I ever fucking write this??
-- return compLib[item]
return nil
end
end,
})
return compLib
+14
View File
@@ -0,0 +1,14 @@
local computerlib = table.copy(computer)
local LLcomputer = table.copy(computer)
function computerlib.pullSignal(timeout)
local startTime = LLcomputer.uptime()
local result
repeat
result = {LLcomputer.pullSignal(0)}
coroutine.yield()
until result or timeout and LLcomputer.uptime() >= startTime + timeout
return table.unpack(result)
end
return computerlib
+62
View File
@@ -0,0 +1,62 @@
local computer = require("computer")
local event = {}
local bufferTime = 0.1 -- A little bit of buffer time so events won't be skipped by accident.
--local ocelot = component.proxy(component.list("ocelot")())
function event.pull(...)
local pid = _PUBLIC.tsched and _PUBLIC.tsched.getCurrentTask() and _PUBLIC.tsched.getCurrentTask().id or "kernel"
if not evmgr.eventQueue[pid] then
evmgr.eventQueue[pid] = {}
end
local eventQueue = evmgr.eventQueue[pid]
local args = { ... }
local evtypes, timeout = {}, nil
for _, arg in pairs(args) do
if type(arg) == "number" and not timeout then -- It's a timeout
timeout = arg
else -- It's an event type
table.insert(evtypes, tostring(arg))
end
end
local startTime = computer.uptime()
while true do
-- Check event queue for matching event
for i = 1, #eventQueue do
local foundevent = false
if evtypes[1] then -- event type(s) specified
for _, evtype in pairs(evtypes) do
if eventQueue[i][2] == evtype and eventQueue[i][1] >= startTime - bufferTime then
foundevent = true
end
end
else
if eventQueue[i][1] >= startTime - bufferTime then
foundevent = true
end
end
if foundevent then
-- Found matching event (or any event if no type specified)
local result = table.copy(eventQueue[i])
table.remove(eventQueue, i)
table.remove(result, 1) -- remove the time of event argument
return table.unpack(result)
end
end
if timeout ~= nil and computer.uptime() >= startTime + timeout then
return nil
end
if timeout == nil then
coroutine.yield()
elseif timeout > 0 then
coroutine.yield()
end
end
end
return event
+630
View File
@@ -0,0 +1,630 @@
local loadfile = ... -- raw loadfile from boot.lua
local unicode, component, computer
local bufferSize = math.huge or math.maxinteger
if loadfile then
unicode = loadfile("/lib/unicode.lua")(loadfile)
component = loadfile("/lib/component.lua")(loadfile)
computer = _G.computer
elseif require then
unicode = require("unicode")
component = require("component")
computer = require("computer")
end
local filesystem = {}
function filesystem.canonical(path)
checkArg(1, path, "string")
local segList = {}
if path:sub(1, 1) ~= "/" then
path = "/" .. path
end
path = path:gsub("/+", "/")
for segment in path:gmatch("[^/]+") do
if segment == ".." and segList[1] then
table.remove(segList, #segList)
elseif segment ~= "." then
table.insert(segList, segment)
end
end
return "/" .. table.concat(segList, "/")
end
function filesystem.basename(path)
checkArg(1, path, "string")
return path:match("/([^/]+)/?$") or ""
end
function filesystem.concat(path1, path2)
checkArg(1, path1, "string")
checkArg(2, path2, "string")
if path1:sub(-1, -1) == "/" then
path1 = path1:sub(1, -2)
end
if path2:sub(1, 1) ~= "/" then
path2 = "/" .. path2
end
return path1 .. path2
end
function filesystem.absolutePath(path) -- returns the address and absolute path of an object
checkArg(1, path, "string")
path = filesystem.canonical(path)
local address = nil
if path:find("^/tmp") then
address = computer.tmpAddress()
path = path:sub(5)
elseif path:find("^/mnt/...") then
address = component.get(path:sub(6, 8))
if not address then
address = computer.getBootAddress()
else
path = path:sub(9)
end
else
address = computer.getBootAddress()
end
if not address then
return nil, "no such component"
end
return address, path
end
function filesystem.parent(path)
checkArg(1, path, "string")
local p = filesystem.canonical(path)
-- return "/" on "/"
return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/")
end
function filesystem.exists(path) -- check if path exists
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if absPath:find("^/special/drive/...") then
return not not (computer.getBootAddress() and component.get(absPath:sub(16, 18)))
end
if absPath:find("^/special/eeprom/") then
return table.find({ "init.lua", "data.bin", "label.txt" }, absPath:sub(17))
end
return component.invoke(address, "exists", absPath)
end
local function readBytes(self, n)
n = n or 1
if n == 1 then
local byte = self:read(1)
if byte == nil then
return nil
end
return string.byte(byte)
end
local bytes, res = { string.byte(self:read(n), 1, n) }, 0
if self.littleEndian then
for i = #bytes, 1, -1 do
res = (res << 8) & 0xFFFFFFFF | bytes[i]
end
else
for i = 1, #bytes do
res = (res << 8) & 0xFFFFFFFF | bytes[i]
end
end
return res
end
local function readUnicodeChar(self)
return unicode.readChar(function()
return self:readBytes(1)
end)
end
local function iterateBytes(self)
return function()
local byte = readBytes(self, 1)
if byte == nil then
self:close()
end
return byte
end
end
local function iterateUnicodeChars(self)
return unicode.iterate(iterateBytes(self))
end
function filesystem.makeReadStream(content)
local properHandle = {}
local readCursor = 1
function properHandle.read(self, amount)
checkArg(2, amount, "number")
local limit = string.len(content) + 1
local out = nil
if readCursor < limit then
if amount == math.huge then
out = string.sub(content, math.min(readCursor, limit))
else
out = string.sub(content, math.min(readCursor, limit), math.min(readCursor + amount - 1, limit))
end
end
readCursor = readCursor + amount
if out == "" then
return nil
end
return out
end
properHandle.readBytes = readBytes
properHandle.readUnicodeChar = readUnicodeChar
properHandle.iterateBytes = iterateBytes
properHandle.iterateUnicodeChars = iterateUnicodeChars
function properHandle.write()
return nil
end
function properHandle.close()
content = nil
end
return properHandle
end
function filesystem.open(path, mode, buffered) -- opens a file and returns its handle
checkArg(1, path, "string")
checkArg(2, mode, "string", "nil")
checkArg(3, buffered, "boolean", "nil")
if not mode then
mode = "r"
end
if buffered == nil then
buffered = true
end
if not (mode == "r" or mode == "w" or mode == "rb" or mode == "wb" or mode == "a" or mode == "ab") then
return nil, "invalid handle type"
end
if path:find("^/special") and not filesystem.exists(path) then
return nil, "/special does not allow creating files"
end
local address, absPath = filesystem.absolutePath(path)
local unmanagedDrive = address == computer.getBootAddress() and absPath:find("^/special/drive")
local unmanagedProxy, sectorSize, sectorCount, handle
if unmanagedDrive then
unmanagedProxy = component.proxy(component.get(absPath:sub(16, 18)))
sectorSize = unmanagedProxy.getSectorSize()
sectorCount = math.ceil(unmanagedProxy.getCapacity() / sectorSize)
elseif not (address == computer.getBootAddress() and absPath:find("^/special/")) then
local handleArgs = { component.invoke(address, "open", absPath, mode) }
handle = handleArgs[1]
if not handle then
return table.unpack(handleArgs)
end
handleArgs = nil
end
local properHandle = {}
properHandle.handle = handle
properHandle.address = address
local content = nil
local bufferOffset = 0 -- Position in file where buffer starts
local readCursor = 1 -- Position within buffer (1-based)
if buffered and mode == "r" then
content = component.invoke(address, "read", handle, bufferSize) or ""
bufferOffset = 0
readCursor = 1
end
function properHandle.read(self, amount)
checkArg(2, amount, "number")
if unmanagedDrive then
-- TODO: Test if this still works
local sectorIdx = ((readCursor - 1) // sectorSize) + 1
if sectorIdx > sectorCount then
return nil
end
local sector = unmanagedProxy.readSector(sectorIdx)
local data = sector:sub(
((readCursor - 1) % sectorSize) + 1,
((readCursor + math.min(amount, sectorSize) - 2) % sectorSize) + 1
)
readCursor = readCursor + #data
if data == "" then
return nil
end
return data
else
if buffered then
if amount == math.huge or amount == math.maxinteger then
-- Read everything remaining
local result = ""
-- First, get what's left in current buffer
if content and readCursor <= #content then
result = content:sub(readCursor)
readCursor = #content + 1
end
-- Then read all remaining data from file
while true do
local newData = component.invoke(address, "read", handle, bufferSize)
if not newData or newData == "" then
break
end
result = result .. newData
end
-- Update buffer state
content = nil
bufferOffset = bufferOffset + #(content or "")
return result ~= "" and result or nil
else
local result = ""
local remaining = amount
while remaining > 0 do
-- If we need more data or buffer is empty
if not content or readCursor > #content then
content = component.invoke(address, "read", handle, bufferSize)
if not content or content == "" then
break
end
bufferOffset = bufferOffset + (readCursor - 1)
readCursor = 1
end
-- Extract data from current buffer
local available = #content - readCursor + 1
local toRead = math.min(remaining, available)
local chunk = content:sub(readCursor, readCursor + toRead - 1)
result = result .. chunk
readCursor = readCursor + toRead
remaining = remaining - toRead
end
return result ~= "" and result or nil
end
else
return component.invoke(self.address, "read", self.handle, amount)
end
end
end
properHandle.readBytes = readBytes
properHandle.readUnicodeChar = readUnicodeChar
properHandle.iterateBytes = iterateBytes
properHandle.iterateUnicodeChars = iterateUnicodeChars
function properHandle.write(self, data)
checkArg(2, data, "string")
if unmanagedDrive then
local startSector = ((readCursor - 1) // sectorSize) + 1
if startSector > sectorCount then
return nil, "not enough space"
end
local startSByte = ((readCursor - 1) % sectorSize) + 1
local sect = unmanagedProxy.readSector(startSector)
unmanagedProxy.writeSector(startSector, sect:sub(1, startSByte - 1) .. data:sub(1, sectorSize - startSByte + 1))
for i = 2, (#data + startSByte) // sectorSize do
if startSector + i - 1 > sectorCount then
return nil, "not enough space"
end
unmanagedProxy.writeSector(
startSector + i - 1,
data:sub(startSByte + sectorSize * (i - 1), startSByte + sectorSize * i - 1)
)
end
readCursor = readCursor + #data
return true
else
return component.invoke(self.address, "write", self.handle, data)
end
end
function properHandle.close(self)
if buffered then
content = nil
end
return component.invoke(self.address, "close", self.handle)
end
if address == computer.getBootAddress() then
local eeprom
pcall(function()
eeprom = component.eeprom
end)
if eeprom then
local getFunc, setFunc
if absPath == "/special/eeprom/init.lua" then
getFunc, setFunc = "get", "set"
elseif absPath == "/special/eeprom/data.bin" then
getFunc, setFunc = "getData", "setData"
elseif absPath == "/special/eeprom/label.txt" then
getFunc, setFunc = "getLabel", "setLabel"
end
if mode:sub(1, 1) == "r" and getFunc then
local stream = filesystem.makeReadStream(eeprom[getFunc]() or "")
properHandle.read = stream.read
properHandle.close = stream.close
elseif mode:sub(1, 1) == "w" and setFunc then
local content = ""
function properHandle.write(self, data)
checkArg(2, data, "string")
content = content .. data
end
function properHandle.close(self)
return eeprom[setFunc](content)
end
end
end
end
function properHandle.seek(self, whence, offset)
checkArg(2, whence, "string", "number", "nil")
checkArg(3, offset, "number", "nil")
if not offset then
offset = 0
end
if type(whence) == "number" then
offset = whence
end
if not whence or type(whence) == "number" then
whence = "cur"
end
if buffered then
local currentAbsolutePos = bufferOffset + readCursor - 1
-- Seek real handle position to buffer handle position
component.invoke(self.address, "seek", self.handle, "set", currentAbsolutePos)
local newPos = component.invoke(self.address, "seek", self.handle, whence, math.max(offset, -currentAbsolutePos))
content = nil
bufferOffset = newPos or 0
readCursor = 1
return newPos
else
return component.invoke(self.address, "seek", self.handle, whence, offset)
end
end
return properHandle
end
function filesystem.list(path)
checkArg(1, path, "string")
path = filesystem.canonical(path)
if path == "/mnt" then
-- list drives
local returnTable = {}
local tmpAddress = computer.tmpAddress()
for address, _ in component.list("filesystem") do
if address ~= tmpAddress then
table.insert(returnTable, address:sub(1, 3) .. "/")
end
end
return returnTable
elseif path == "/special/drive" then
local returnTable = {}
local tmpAddress = computer.tmpAddress()
for address, type in component.list("drive") do
if address ~= tmpAddress and type == "drive" then
table.insert(returnTable, address:sub(1, 3))
end
end
return returnTable
elseif path == "/special/eeprom" then
return { "init.lua", "data.bin", "label.txt" }
else
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "list", absPath)
end
end
function filesystem.size(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if address == computer.getBootAddress() then
if absPath:find("^/special/drive") then
local drive = component.get(absPath:sub(16, 18))
if not drive then
return false
end
return component.invoke(drive, "getCapacity")
elseif absPath:find("^/special/eeprom") then
local eeprom
pcall(function()
eeprom = component.eeprom
end)
if eeprom then
local getFunc
if absPath == "/special/eeprom/init.lua" then
getFunc = "get"
elseif absPath == "/special/eeprom/data.bin" then
getFunc = "getData"
elseif absPath == "/special/eeprom/label.txt" then
getFunc = "getLabel"
end
return #(eeprom[getFunc]())
end
end
end
return component.invoke(address, "size", absPath)
end
local function getRecursiveList(address, absPath)
local list = component.invoke(address, "list", absPath)
local dirList = {}
local listChanged = true
while listChanged do
listChanged = false
for i = 1, #list do
if component.invoke(address, "isDirectory", absPath .. "/" .. list[i]) then
listChanged = true
local dir = list[i]
if dir:sub(-1) == "/" then
dir = dir:sub(1, -2)
end
table.insert(dirList, dir)
table.remove(list, i)
local subDir = component.invoke(address, "list", absPath .. "/" .. dir)
for j = 1, #subDir do
table.insert(list, dir .. "/" .. subDir[j])
end
end
end
end
return list, dirList
end
local function copyContent(fromHandle, toHandle)
if not (fromHandle and toHandle) then
return
end
while true do
local tmpdata = fromHandle.read(2048)
if not tmpdata then
break
end
local status, reason = toHandle.write(tmpdata)
if status ~= true then
break
end
end
fromHandle:close()
toHandle:close()
end
local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
if fromAbsPath:sub(-1) == "/" then
fromAbsPath = fromAbsPath:sub(1, -2)
end
if toAbsPath:sub(-1) == "/" then
toAbsPath = toAbsPath:sub(1, -2)
end
component.invoke(toAddress, "makeDirectory", toAbsPath)
local fileList, dirList = getRecursiveList(fromAddress, fromAbsPath)
for i = 1, #dirList do
component.invoke(toAddress, "makeDirectory", toAbsPath .. "/" .. dirList[i])
end
for i = 1, #fileList do
local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i]
local fromHandle = component.invoke(fromAddress, "open", fromFile, "r")
local toHandle = component.invoke(toAddress, "open", toFile, "w")
copyContent({
["read"] = function(...)
return component.invoke(fromAddress, "read", fromHandle, ...)
end,
["close"] = function(...)
return component.invoke(fromAddress, "close", fromHandle, ...)
end,
}, {
["write"] = function(...)
return component.invoke(toAddress, "write", toHandle, ...)
end,
["close"] = function(...)
return component.invoke(toAddress, "close", toHandle, ...)
end,
})
end
end
function filesystem.isDirectory(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "isDirectory", absPath)
end
function filesystem.isFile(path)
return not filesystem.isDirectory(path) and filesystem.exists(path)
end
function filesystem.rename(fromPath, toPath)
checkArg(1, fromPath, "string")
checkArg(2, toPath, "string")
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
if not fromAddress or not toAddress then
return false
end
if fromAddress == toAddress then
return component.invoke(fromAddress, "rename", fromAbsPath, toAbsPath)
elseif filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath) then
copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
filesystem.remove(fromPath) -- component.invoke(fromAddress,"remove", fromAbsPath)
else
local handle, data, tmpdata = filesystem.open(fromPath), "", nil -- component.invoke(fromAddress, "open", fromAbsPath, "r"), "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger) -- component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = handle:close() -- component.invoke(fromAddress, "close", handle)
local handle = filesystem.open(toPath) -- component.invoke(toAddress, "open", toAbsPath, "w")
handle:write(data) -- component.invoke(toAddress, "write", handle, data)
handle:close() -- component.invoke(toAddress, "close", handle)
filesystem.remove(fromPath) -- component.invoke(fromAddress, "remove", fromAbsPath)
end
end
function filesystem.copy(fromPath, toPath)
checkArg(1, fromPath, "string")
checkArg(2, toPath, "string")
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
if not fromAddress or not toAddress then
return false
end
if filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath)
copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
else
--[[ local handle = filesystem.open(fromPath,"r")
local data, tmpdata = "", nil
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = handle:close()
local handle = filesystem.open(toPath,"w")
handle:write(data)
handle:close() ]]
copyContent(filesystem.open(fromPath, "r"), filesystem.open(toPath, "w"))
end
end
function filesystem.remove(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
if absPath:find("^/special") then
return false
end
if absPath:find("^/tmp") then
return false
end
if absPath:find("^/mnt") then
return false
end
return component.invoke(address, "remove", absPath)
end
function filesystem.makeDirectory(path)
checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path)
if not address then
return false
end
return component.invoke(address, "makeDirectory", absPath)
end
return filesystem
+4
View File
@@ -0,0 +1,4 @@
-- json.lua by rxi
-- Minified with luamin
-- Original: https://github.com/rxi/json.lua
local a={_version="0.1.2"}local b;local c={["\\"]="\\",["\""]="\"",["\b"]="b",["\f"]="f",["\n"]="n",["\r"]="r",["\t"]="t"}local d={["/"]="/"}for e,f in pairs(c)do d[f]=e end;local function g(h)return"\\"..(c[h]or string.format("u%04x",h:byte()))end;local function i(j)return"null"end;local function k(j,l)local m={}l=l or{}if l[j]then error("circular reference")end;l[j]=true;if rawget(j,1)~=nil or next(j)==nil then local n=0;for e in pairs(j)do if type(e)~="number"then error("invalid table: mixed or invalid key types")end;n=n+1 end;if n~=#j then error("invalid table: sparse array")end;for o,f in ipairs(j)do table.insert(m,b(f,l))end;l[j]=nil;return"["..table.concat(m,",").."]"else for e,f in pairs(j)do if type(e)~="string"then error("invalid table: mixed or invalid key types")end;table.insert(m,b(e,l)..":"..b(f,l))end;l[j]=nil;return"{"..table.concat(m,",").."}"end end;local function p(j)return'"'..j:gsub('[%z\1-\31\\"]',g)..'"'end;local function q(j)if j~=j or j<=-math.huge or j>=math.huge then error("unexpected number value '"..tostring(j).."'")end;return string.format("%.14g",j)end;local r={["nil"]=i,["table"]=k,["string"]=p,["number"]=q,["boolean"]=tostring}b=function(j,l)local s=type(j)local t=r[s]if t then return t(j,l)end;error("unexpected type '"..s.."'")end;function a.encode(j)return b(j)end;local u;local function v(...)local m={}for o=1,select("#",...)do m[select(o,...)]=true end;return m end;local w=v(" ","\t","\r","\n")local x=v(" ","\t","\r","\n","]","}",",")local y=v("\\","/",'"',"b","f","n","r","t","u")local z=v("true","false","null")local A={["true"]=true,["false"]=false,["null"]=nil}local function B(C,D,E,F)for o=D,#C do if E[C:sub(o,o)]~=F then return o end end;return#C+1 end;local function G(C,D,H)local I=1;local J=1;for o=1,D-1 do J=J+1;if C:sub(o,o)=="\n"then I=I+1;J=1 end end;error(string.format("%s at line %d col %d",H,I,J))end;local function K(n)local t=math.floor;if n<=0x7f then return string.char(n)elseif n<=0x7ff then return string.char(t(n/64)+192,n%64+128)elseif n<=0xffff then return string.char(t(n/4096)+224,t(n%4096/64)+128,n%64+128)elseif n<=0x10ffff then return string.char(t(n/262144)+240,t(n%262144/4096)+128,t(n%4096/64)+128,n%64+128)end;error(string.format("invalid unicode codepoint '%x'",n))end;local function L(M)local N=tonumber(M:sub(1,4),16)local O=tonumber(M:sub(7,10),16)if O then return K((N-0xd800)*0x400+O-0xdc00+0x10000)else return K(N)end end;local function P(C,o)local m=""local Q=o+1;local e=Q;while Q<=#C do local R=C:byte(Q)if R<32 then G(C,Q,"control character in string")elseif R==92 then m=m..C:sub(e,Q-1)Q=Q+1;local h=C:sub(Q,Q)if h=="u"then local S=C:match("^[dD][89aAbB]%x%x\\u%x%x%x%x",Q+1)or C:match("^%x%x%x%x",Q+1)or G(C,Q-1,"invalid unicode escape in string")m=m..L(S)Q=Q+#S else if not y[h]then G(C,Q-1,"invalid escape char '"..h.."' in string")end;m=m..d[h]end;e=Q+1 elseif R==34 then m=m..C:sub(e,Q-1)return m,Q+1 end;Q=Q+1 end;G(C,o,"expected closing quote for string")end;local function T(C,o)local R=B(C,o,x)local M=C:sub(o,R-1)local n=tonumber(M)if not n then G(C,o,"invalid number '"..M.."'")end;return n,R end;local function U(C,o)local R=B(C,o,x)local V=C:sub(o,R-1)if not z[V]then G(C,o,"invalid literal '"..V.."'")end;return A[V],R end;local function W(C,o)local m={}local n=1;o=o+1;while 1 do local R;o=B(C,o,w,true)if C:sub(o,o)=="]"then o=o+1;break end;R,o=u(C,o)m[n]=R;n=n+1;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="]"then break end;if X~=","then G(C,o,"expected ']' or ','")end end;return m,o end;local function Y(C,o)local m={}o=o+1;while 1 do local Z,j;o=B(C,o,w,true)if C:sub(o,o)=="}"then o=o+1;break end;if C:sub(o,o)~='"'then G(C,o,"expected string for key")end;Z,o=u(C,o)o=B(C,o,w,true)if C:sub(o,o)~=":"then G(C,o,"expected ':' after key")end;o=B(C,o+1,w,true)j,o=u(C,o)m[Z]=j;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="}"then break end;if X~=","then G(C,o,"expected '}' or ','")end end;return m,o end;local _={['"']=P,["0"]=T,["1"]=T,["2"]=T,["3"]=T,["4"]=T,["5"]=T,["6"]=T,["7"]=T,["8"]=T,["9"]=T,["-"]=T,["t"]=U,["f"]=U,["n"]=U,["["]=W,["{"]=Y}u=function(C,D)local X=C:sub(D,D)local t=_[X]if t then return t(C,D)end;G(C,D,"unexpected character '"..X.."'")end;function a.decode(C)if type(C)~="string"then error("expected argument of type string, got "..type(C))end;local m,D=u(C,B(C,1,w,true))D=B(C,D,w,true)if D<=#C then G(C,D,"trailing garbage")end;return m end;return a
+143
View File
@@ -0,0 +1,143 @@
local fs, computer, gpu
local chunkSize = 1024
if require then
fs = require("filesystem")
computer = require("computer")
gpu = require("component").gpu
else
local loadfile = ...
fs = loadfile("/lib/filesystem.lua")(loadfile)
computer = _G.computer
gpu = loadfile("/lib/component.lua")(loadfile).gpu
end
local resX, resY = gpu.getResolution()
local log = {}
if not _G.logSettings then
_G.logSettings = { -- We have to preload the library just for this :P
["printLogs"] = true, -- FIXME: Or do we?
["printerY"] = 1,
}
end
function getlines(s)
if s:sub(-1) ~= "\n" then
s = s .. "\n"
end
return s:gmatch("(.-)\n")
end
local function writeToScreen(text)
-- Print onscreen
if text:sub(1, 4) == "INFO" then -- Set color
gpu.setForeground(0xFFFFFF)
elseif text:sub(1, 4) == "WARN" then
gpu.setForeground(0xFFFF00)
elseif text:sub(1, 5) == "ERROR" then
gpu.setForeground(0xFF0000)
end
local i = 0
for line in getlines(text) do
line = line:gsub("\t", " ")
repeat -- Line wrapping
if _G.logSettings.printerY > resY then
gpu.copy(1, 2, resX, resY - 1, 0, -1)
_G.logSettings.printerY = resY
end
gpu.set(1, _G.logSettings.printerY, line .. string.rep(" ", resX - #line))
line = line:sub(resX + 1)
_G.logSettings.printerY = _G.logSettings.printerY + 1
until line == ""
end
end
local logFileSizeLimit = 16384
local function writeToLog(path, text)
fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories
local handle = fs.open(path, "a")
if handle then
handle:write(text .. "\n")
handle:close()
end
-- Log trimming if it gets too long
if fs.size(path) > logFileSizeLimit then
local sizeCounter = 0
local readHandle = fs.open(path, "r", false) -- Making sure buffering is disabled, otherwise this whole thing is pointless
local currentChunk = ""
readHandle:seek("end", -chunkSize)
repeat
currentChunk = readHandle:read(chunkSize)
readHandle:seek(-chunkSize * 2)
sizeCounter = sizeCounter + chunkSize
until sizeCounter >= logFileSizeLimit * 0.75
while true do
local infoEntry = currentChunk:find("INFO [", 1, true)
local warnEntry = currentChunk:find("WARN [", 1, true)
local errorEntry = currentChunk:find("ERROR [", 1, true)
if not infoEntry and not warnEntry and not errorEntry then
readHandle:seek(-chunkSize)
else
readHandle:seek(
math.min(
infoEntry or math.huge or math.maxinteger,
warnEntry or math.huge or math.maxinteger,
errorEntry or math.huge or math.maxinteger
) - 1
)
break
end
if readHandle:seek("cur") == 0 then -- Failsafe to prevent infinite loops
break
end
end
local writeHandle = fs.open(path, "w")
while true do
local tmpdata = readHandle:read(math.huge or math.maxinteger)
if not tmpdata then
break
end
writeHandle:write(tmpdata)
end
readHandle:close()
writeHandle:close()
end
if _G.logSettings.printLogs then
writeToScreen(text)
end
end
setmetatable(log, {
["__index"] = function(_, index)
return {
["logpath"] = fs.concat("/halyde/logs/", index .. ".log"),
["info"] = function(text)
writeToLog(
fs.concat("/halyde/logs/", index .. ".log"),
"INFO [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
)
end,
["warn"] = function(text)
writeToLog(
fs.concat("/halyde/logs/", index .. ".log"),
"WARN [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
)
end,
["error"] = function(text)
writeToLog(
fs.concat("/halyde/logs/", index .. ".log"),
"ERROR [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
)
end,
}
end,
})
function log.setPrintLogs(setting) -- Yes, this works with the metatable.
checkArg(1, setting, "boolean")
_G.logSettings.printLogs = setting
end
return log
+27
View File
File diff suppressed because one or more lines are too long
+35
View File
@@ -0,0 +1,35 @@
local thing = _G._PUBLIC or _G
thing.__PROFILER_INSTANCE = thing.__PROFILER_INSTANCE or { timers = {} }
local timers = thing.__PROFILER_INSTANCE.timers
local profiler = {}
function profiler.start(label, overwrite)
thing.__PROFILER_INSTANCE.lastadded = label
timers[label] = timers[label] or {}
if not timers[label].start or overwrite then
timers[label].start = os.clock()
end
return function() timers[label].time = timers[label].time or os.clock() - timers[label].start end
end
function profiler.results()
local _now = nil
local function now()
_now = _now or os.clock()
return _now
end
local out = {}
for label, t in pairs(timers) do
table.insert(out, { label = label, time = t.time or now() - t.start })
end
table.sort(out, function(a, b) return a.time > b.time end)
return out
end
function profiler.profile(label, func, ...)
local stop = profiler.start(label)
func(...)
stop()
end
return profiler
+432
View File
@@ -0,0 +1,432 @@
local raster = {
["units"]={},
["defaultBackgroundColor"]=0x000000,
["defaultForegroundColor"]=0xFFFFFF,
["displayWidth"]=0,
["displayHeight"]=0,
["charWidth"]=0,
["charHeight"]=0,
["backgroundColor"]=0xFFFFFF
}
local component = require("component")
-- local ocelot = component.proxy(component.list("ocelot")())
local gpu = component.gpu
local display = {}
local chunksAffected = {}
local renderBuffer = nil
-- braille rendering
function raster.units.charToBraille(x,y)
return x*2,y*4
end
function raster.units.brailleToChar(x,y)
return math.ceil(x/2),math.ceil(y/4)
end
function raster.init(width, height, bgcolor)
-- NOTE: Width and height are in characters, not pixels in braille.
-- If the width and height are nil, the entire screen will be used.
if width==nil and height==nil then
width, height = gpu.getResolution()
end
raster.charWidth = width
raster.charHeight = height
width, height = raster.units.charToBraille(width, height)
bgcolor = bgcolor or raster.defaultBackgroundColor
if bgcolor~=0 then
for i=1,width*height do
display[i]=bgcolor
end
end
raster.displayWidth = width
raster.displayHeight = height
raster.backgroundColor = bgcolor
pcall(function()
renderBuffer = gpu.allocateBuffer()
end)
raster.clear()
end
function raster.set(x, y, color)
if x<1 or x>raster.displayWidth or y<1 or y>raster.displayHeight then
return false
end
color = color or raster.defaultForegroundColor
local i = x+y*raster.displayWidth
display[i] = color
local ci = math.floor((x-1)/2)+math.floor((y-1)/4)*raster.charWidth+1
-- ocelot.log(x..","..y..":"..ci)
chunksAffected[ci] = true
return true
end
function raster.get(x, y)
local i = x+y*raster.displayWidth
return display[i] or raster.backgroundColor
end
local function stats(arr)
local out = {}
for i=1,#arr do
local v = arr[i]
if out[v]==nil then
out[v]=1
else
out[v] = out[v] + 1
end
end
return out
end
local function getKeys(t)
local keys = {}
for k,v in pairs(t) do
table.insert(keys,{k,v})
end
table.sort(keys,function(a,b)
return a[2]>b[2]
end)
for i=1,#keys do
keys[i] = keys[i][1]
end
return keys
end
local function colorDifference(a,b)
return ((a>>16)&255)-((b>>16)&255)+((a>>8)&255)-((b>>8)&255)+(a&255)-(b&255)
end
local function limitTwoColors(arr)
local colors = getKeys(stats(arr))
for i=1,#arr do
local v=arr[i]
if v==colors[1] then
arr[i]=0
goto continue
elseif v==colors[2] then
arr[i]=1
goto continue
else
--error("Pixel is not in the two colors (raster.lua:90)")
-- get closest color so atleast it kinda shows
if colorDifference(v,colors[1])<colorDifference(v,colors[2]) then
arr[i]=0
else
arr[i]=1
end
end
::continue::
end
return arr,colors[1] or 0,colors[2] or 0
end
local function arrayToBraille(arr)
local codePoint = 0x2800
for i=1,8 do
codePoint = codePoint | arr[i]<<(i-1)
end
if codePoint==0x2800 then return " " end
return utf8.char(codePoint)
end
function raster.update()
if renderBuffer~=nil then
gpu.setActiveBuffer(renderBuffer)
end
for y=1,raster.displayHeight,4 do
-- gpu.set(0,0,tostring(y))
for x=1,raster.displayWidth,2 do
local ci = math.floor(x/2)+math.floor(y/4)*raster.charWidth+1
if chunksAffected[ci] then
local chunk = {
raster.get(x,y),
raster.get(x,y+1),
raster.get(x,y+2),
raster.get(x+1,y),
raster.get(x+1,y+1),
raster.get(x+1,y+2),
raster.get(x,y+3),
raster.get(x+1,y+3)
}
local colorA = nil
local colorB = nil
chunk,colorA,colorB = limitTwoColors(chunk)
-- print(tostring(colorA)..","..tostring(colorB))
cx,cy=raster.units.brailleToChar(x,y)
gpu.setBackground(colorA)
gpu.setForeground(colorB)
-- gpu.set(cx,cy,tostring(colorB/0xFFFFFF))
gpu.set(cx,cy,arrayToBraille(chunk))
chunksAffected[ci] = false
end
end
end
if renderBuffer~=nil then
gpu.bitblt()
gpu.setActiveBuffer(0)
end
end
function raster.clear()
if renderBuffer~=nil then
gpu.setActiveBuffer(renderBuffer)
end
-- clear()
local bgcolor = raster.backgroundColor
gpu.setBackground(bgcolor)
gpu.fill(1,1,raster.displayWidth,raster.displayHeight," ")
display = {}
end
function raster.free()
if renderBuffer==nil then
return true
else
return gpu.freeBuffer(renderBuffer)
end
end
-- advanced rendering
function raster.drawLine(x1, y1, x2, y2, color)
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
local dx = math.abs(x2 - x1)
local dy = math.abs(y2 - y1)
local sx = x1 < x2 and 1 or -1
local sy = y1 < y2 and 1 or -1
local err = dx - dy
while true do
raster.set(x1, y1, color)
if x1 == x2 and y1 == y2 then
break
end
local e2 = 2 * err
if e2 > -dy then
err = err - dy
x1 = x1 + sx
end
if e2 < dx then
err = err + dx
y1 = y1 + sy
end
end
end
function raster.drawRect(x1,y1,x2,y2,col)
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
for x=x1,x2 do
raster.set(x,y1,col)
raster.set(x,y2,col)
end
for y=y1+1,y2-1 do
raster.set(x1,y,col)
raster.set(x2,y,col)
end
end
function raster.fillRect(x1,y1,x2,y2,col)
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
for x=x1,x2 do
for y=y1,y2 do
raster.set(x,y,col)
end
end
end
function raster.drawCircle(xc, yc, radius, color)
xc=math.floor(xc)
yc=math.floor(yc)
radius=math.floor(radius)
local x = 0
local y = radius
local d = 3 - 2 * radius
while y >= x do
-- Draw 8 symmetric points
raster.set(xc + x, yc + y, color)
raster.set(xc - x, yc + y, color)
raster.set(xc + x, yc - y, color)
raster.set(xc - x, yc - y, color)
raster.set(xc + y, yc + x, color)
raster.set(xc - y, yc + x, color)
raster.set(xc + y, yc - x, color)
raster.set(xc - y, yc - x, color)
if d < 0 then
d = d + 4 * x + 6
else
d = d + 4 * (x - y) + 10
y = y - 1
end
x = x + 1
end
end
function raster.drawEllipse(x1, y1, x2, y2, color)
if x1 > x2 then x1, x2 = x2, x1 end
if y1 > y2 then y1, y2 = y2, y1 end
local xc = math.floor((x1 + x2) / 2)
local yc = math.floor((y1 + y2) / 2)
local a = math.floor((x2 - x1) / 2)
local b = math.floor((y2 - y1) / 2)
if a <= 0 or b <= 0 then
return
end
if a == b then
raster.drawCircle(xc, yc, a, color)
return
end
if a <= 1 and b <= 1 then
raster.set(xc, yc, color)
return
elseif a <= 1 then
for y = yc - b, yc + b do
raster.set(xc, y, color)
end
return
elseif b <= 1 then
for x = xc - a, xc + a do
raster.set(x, yc, color)
end
return
end
local x = 0
local y = b
local a2 = a * a
local b2 = b * b
local d1 = b2 - (a2 * b) + (0.25 * a2)
local dx = 2 * b2 * x
local dy = 2 * a2 * y
while dx < dy do
raster.set(xc + x, yc + y, color)
raster.set(xc - x, yc + y, color)
raster.set(xc + x, yc - y, color)
if d1 < 0 then
x = x + 1
dx = dx + (2 * b2)
d1 = d1 + dx + b2
else
x = x + 1
y = y - 1
dx = dx + (2 * b2)
dy = dy - (2 * a2)
d1 = d1 + dx - dy + b2
end
end
local d2 = b2 * (x + 0.5) * (x + 0.5) + a2 * (y - 1) * (y - 1) - a2 * b2
while y >= 0 do
raster.set(xc + x, yc + y, color)
raster.set(xc - x, yc + y, color)
raster.set(xc + x, yc - y, color)
raster.set(xc - x, yc - y, color)
if d2 > 0 then
y = y - 1
dy = dy - (2 * a2)
d2 = d2 - dy + a2
else
y = y - 1
x = x + 1
dx = dx + (2 * b2)
dy = dy - (2 * a2)
d2 = d2 + dx - dy + a2
end
end
end
function raster.fillCircle(x, y, r, color)
x, y = math.floor(x + 0.5), math.floor(y + 0.5)
r = math.floor(r + 0.5)
if r <= 0 then return end
local minX, maxX = x - r, x + r
local minY, maxY = y - r, y + r
for py = minY, maxY do
for px = minX, maxX do
local dx, dy = px - x, py - y
local distSquared = dx*dx + dy*dy
if distSquared <= r*r then
raster.set(px, py, color)
end
end
end
end
function raster.fillEllipse(x1, y1, x2, y2, color)
local centerX = (x1 + x2) / 2
local centerY = (y1 + y2) / 2
local a = math.abs(x2 - x1) / 2
local b = math.abs(y2 - y1) / 2
centerX = math.floor(centerX + 0.5)
centerY = math.floor(centerY + 0.5)
a = math.floor(a + 0.5)
b = math.floor(b + 0.5)
if a <= 0 or b <= 0 then return end
if a == b then
raster.fillCircle(centerX, centerY, a, color)
return
end
local minX = centerX - a
local maxX = centerX + a
local minY = centerY - b
local maxY = centerY + b
for y = minY, maxY do
for x = minX, maxX do
local dx = x - centerX
local dy = y - centerY
local value = (dx*dx)/(a*a) + (dy*dy)/(b*b)
if value <= 1 then
raster.set(x, y, color)
end
end
end
end
return raster
+48
View File
@@ -0,0 +1,48 @@
local function _serialize(value, indent, level, visited)
local currentIndent = indent and string.rep(indent, level) or ""
local nextIndent = indent and string.rep(indent, level + 1) or ""
local sep = indent and "\n" or " "
local t = type(value)
if t == "nil" then return "nil" end
if t == "string" then return string.format("%q", value) end
if t == "number" then
if value ~= value then return "0/0" end
if value == math.huge then return "math.huge" end
if value == -math.huge then return "-math.huge" end
return tostring(value)
end
if t == "boolean" then return tostring(value) end
if t == "table" then
if visited[value] then return "..." end
visited[value] = true
local items = {}
local arrayCount = 0
for i = 1, #value do
if value[i] ~= nil then arrayCount = i else break end
end
for i = 1, arrayCount do
table.insert(items, nextIndent .. _serialize(value[i], indent, level + 1, visited))
end
for k, v in pairs(value) do
if type(k) ~= "number" or k < 1 or k > arrayCount then
local keyStr
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
keyStr = k
else
keyStr = "[" .. _serialize(k, indent, level + 1, visited) .. "]"
end
table.insert(items, nextIndent .. keyStr .. " = " .. _serialize(v, indent, level + 1, visited))
end
end
visited[value] = nil
if #items == 0 then return "{}" end
return "{" .. sep .. table.concat(items, "," .. sep) .. sep .. currentIndent .. "}"
end
return tostring(value)
end
function serialize(value, indent)
return _serialize(value, indent, 0, {})
end
return serialize
+42
View File
@@ -0,0 +1,42 @@
local filesystem = require("filesystem")
-- get a list of installed shells
local shellDir = filesystem.list("/halyde/scripts/") -- HACK: /halyde/scripts features more than just shells!
local shells = {}
for i=1,#shellDir do
table.insert(shells,string.match(shellDir[i],"([^/]+)%.lua$"))
end
-- locate the shell
local tasks = tsched.getTasks()
-- print(tasks)
local pid = tsched.getCurrentTask().id
local function taskFromPID(pid)
checkArg(1,pid,"number")
for i=1,#tasks do
if tasks[i] and tasks[i].id==pid then
return tasks[i]
end
end
end
local shellProcess
while true do
local task = taskFromPID(pid)
if not task then
error("parent shell task doesn't exist (ID="..pid..")")
end
if table.find(shells,task.name) then
shellProcess = task
break
end
pid = task.parent
if not pid then
error("could not find parent shell task")
end
end
if not shellProcess then error("could not locate shell task") end
-- get the shell object from the process
-- print("Process ID: "..shellProcess.id)
-- print(ipc.shared[shellProcess.id].shell)
return ipc.shared[shellProcess.id].shell
+501
View File
@@ -0,0 +1,501 @@
local defaultDBPath = "/ag2/testdb.json"
local computer = require("computer")
local fs = require("filesystem")
-- local db = require("solvitdb")
local db = {}
local json = require("json")
function db.create(dbpath)
local handle = fs.open(dbpath,"w")
handle:write("{}")
handle:close()
end
function db.readJSON(dbpath)
local handle = fs.open(dbpath,"r")
local content = ""
while true do
local s = handle:read(math.huge or math.maxinteger)
if not s then break end
content=content..s
end
handle:close()
return content
end
function db.get(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
return dbc[pack]
end
function db.set(dbpath,pack,info)
local dbc = json.decode(db.readJSON(dbpath))
dbc[pack]=info
local handle = fs.open(dbpath,"w")
handle:write(json.encode(dbc))
handle:close()
end
function db.remove(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
dbc[pack]=nil
local handle = fs.open(dbpath,"w")
handle:write(json.encode(dbc))
handle:close()
end
function db.list(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
local keys = {}
for i,_ in pairs(dbc) do
table.insert(keys,i)
end
return ipairs(keys)
end
local avs = {}
function avs.splitSingular(s)
local result = {}
for str in string.gmatch(s, "([^.]+)") do
table.insert(result,tonumber(str) or -1)
end
return result
end
function avs.parse(pack)
if not string.find(pack,"=") then
return {pack}
end
local idx=pack:find("=")
local name=pack:sub(1,idx-1)
local verstr=pack:sub(idx+1)
if string.find(verstr,"-") then
idx=verstr:find("-")
verstr={verstr:sub(1,idx-1),verstr:sub(idx+1)}
else
verstr={verstr}
end
for i=1,#verstr do
verstr[i]=avs.splitSingular(verstr[i])
end
if #verstr>1 then
for i=1,3 do
verstr[1][i]=math.max(verstr[1][i],0)
end
end
return {name,verstr}
end
function avs.serializeSingle(ver)
local ver2 = table.copy(ver)
for i=1,3 do
if ver2[i]==-1 then
ver2[i]="*"
else
ver2[i]=tostring(ver2[i])
end
end
return ver2[1].."."..ver2[2].."."..ver2[3]
end
function avs.serializeVersion(ver)
local singles = {}
for i=1,#ver do
table.insert(singles,avs.serializeSingle(ver[i]))
end
if singles[1]==singles[2] then
singles={singles[1]}
end
local out=""
for i=1,#singles do
out=out..singles[i]
if i~=#singles then
out=out.."-"
end
end
return out
end
function avs.serializePack(pack)
if #pack==1 then
return pack[1]
end
return pack[1].."="..avs.serializeVersion(pack[2])
end
function avs.singleGreater(ver1,ver2)
for i=1,3 do
if ver1[i]~=ver2[i] then
if ver1[i]==-1 or ver1[i]>ver2[i] then
return true
end
if ver2[i]==-1 or ver2[i]>ver1[i] then
return false
end
end
end
return false
end
function avs.singleLesser(ver1,ver2)
return avs.singleGreater(ver2,ver1)
end
function avs.singleMin(ver1,ver2)
return avs.singleLesser(ver1,ver2) and ver1 or ver2
end
function avs.singleMax(ver1,ver2)
return avs.singleGreater(ver1,ver2) and ver1 or ver2
end
function avs.compatibleRange(vers)
for i=1,#vers do
if type(vers[i])=="string" then vers[i]=avs.parse(vers[i])[2] end
if #vers[i]==1 then vers[i]={vers[i][1],vers[i][1]} end
end
local range = vers[1]
for i=2,#vers do
range[1]=avs.singleMax(range[1],vers[i][1])
range[2]=avs.singleMin(range[2],vers[i][2])
end
if avs.singleGreater(range[1],range[2]) then
return nil
end
return range
end
function avs.matching(pack1,pack2)
if pack1[1]~=pack2[1] then return false end
local ver1 = pack1[2] or {{-1,-1,-1}}
local ver2 = pack2[2] or {{-1,-1,-1}}
if #ver1==1 then ver1={ver1[1],ver1[1]} end
if #ver2==1 then ver2={ver2[1],ver2[1]} end
return avs.compatibleRange({ver1,ver2})~=nil
end
local function packageInArray(pack,arr)
for i=1,#arr do
if avs.matching(avs.parse(arr[i]),pack) then
return true,arr[i]
end
end
return false
end
local function packageNameInArray(pack,arr)
for i=1,#arr do
if arr[i][1]==pack[1] then
return true,arr[i]
end
end
return false
end
local function removeFromArray(el,arr)
for i=1,#arr do
if arr[i]==el then
table.remove(arr,i)
return i
end
end
end
local function startTransaction(dbpath)
dbpath = dbpath or defaultDBPath
if not fs.exists(dbpath) then
db.create(dbpath)
end
local yieldClock = computer.uptime()
local function yieldIfNecessary()
if computer.uptime()-yieldClock>=0.1 then
coroutine.yield()
yieldClock = computer.uptime()
end
end
local installIncomplete = false
local removeIncomplete = false
local packInfo = {}
local ins = {}
local rem = {}
local transaction = {}
function transaction.install(name)
table.insert(ins,avs.parse(name))
installIncomplete = true
end
function transaction.remove(name)
table.insert(rem,avs.parse(name))
removeIncomplete = true
end
function transaction.autoRemove()
end
function transaction.update(name)
end
function transaction.updateAll(name)
end
function transaction.addInfo(name,info)
if not info.type then info.type="package" end
packInfo[name]=info
-- print(require("serialize")(packInfo))
end
local function getPackInfo(pack)
return packInfo[avs.serializePack(pack)]
end
local function findReverseDependencies(pack)
local out={}
for i,info in pairs(packInfo) do
if info.dependencies and packageInArray(pack,info.dependencies) then
table.insert(out,avs.parse(i))
end
end
return out
end
local function finalizeInstall(settings)
-- find missing package information
local missing = {}
for i=1,#ins do
if getPackInfo(ins[i])==nil then
table.insert(missing,avs.serializePack(ins[i]))
end
end
if #missing>0 then
return false,missing
end
-- find dependencies
installIncomplete=false
local i=1
while i<=#ins do
local deps = getPackInfo(ins[i]).dependencies
if deps and #deps>=1 then
for j=1,#deps do
local dep = avs.parse(deps[j])
local inArr,arrPack = packageNameInArray(dep,ins)
if inArr then
if not avs.matching(dep,arrPack) then
local msg = ""
if settings.resolveConflict then
msg="Cannot resolve conflict: "..msg
end
local rev = findReverseDependencies(arrPack)
if #rev==0 then
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack)
return false, msg
elseif #rev==1 then
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack)
return false, msg
else
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages "
for i=1,#rev do
if i>1 and i~=#rev then
msg=msg..", "
elseif i==#rev then
msg=msg.." and "
end
msg=msg..avs.serializePack(rev[i])
end
msg=msg.." depend on "..avs.serializePack(arrPack)
return false, msg
end
end
elseif type(db.get(dbpath,dep[1]))=="nil" then
installIncomplete=true
table.insert(ins,j,dep)
else
local dbinfo = db.get(dbpath,dep[1])
local dbpack = {dep[1]}
if dbinfo.version then
dbpack = avs.parse(dep[1].."="..dbinfo.version)
end
if not avs.matching(dep,dbpack) then
if settings.resolveConflict then
removeIncomplete=true
table.insert(rem,1,dbpack)
installIncomplete=true
table.insert(ins,j,dep)
else
local msg = "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but another version ("..avs.serializePack(dbpack)..") is installed"
return false, msg
end
end
end
end
i=i+#deps
end
i=i+1
end
end
local function finalizeRemove(settings)
removeIncomplete=false
-- filter to only have packages in the database
local i=1
while i<=#rem do
local dat = db.get(dbpath,rem[i][1])
if not dat then
table.remove(rem,i)
else
packInfo[avs.serializePack(rem[i])]=dat
i=i+1
end
end
-- get package info from database
for i=1,#rem do
if not getPackInfo(rem[i]) then
packInfo[avs.serializePack(rem[i])]=db.get(rem[i][1])
end
end
-- find if the main package is reverse dependant to another
i=1
while i<=#rem do
local dat = getPackInfo(rem[i])
if dat.reverseDependencies and #dat.reverseDependencies>0 then
for _,dep in ipairs(dat.reverseDependencies) do
if not packageNameInArray({dep},rem) then
if settings.cascade then
table.insert(rem,1,{dep})
i=i+1
else
return false, "Package "..rem[i][1].." is a dependency of "..dep
end
end
end
end
i=i+1
end
-- look for dependencies if settings.autoremove is on
if settings.autoremove then
i=1
while i<=#rem do
local deps = getPackInfo(rem[i]).dependencies
if deps and #deps>=1 then
for j=1,#deps do
local dep = avs.parse(deps[j])
local depdat = packInfo[deps[j]]
if not depdat then
depdat = db.get(dbpath,dep[1])
packInfo[deps[j]]=depdat
end
if (not packageNameInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==rem[i][1]) then
removeIncomplete=true
table.insert(rem,j,dep)
end
end
i=i+#deps
end
i=i+1
end
end
end
function transaction.finalize(settings)
settings = settings or {}
while installIncomplete or removeIncomplete do
while installIncomplete do
local out = {finalizeInstall(settings)}
if out[1]==false then return table.unpack(out) end
yieldIfNecessary()
end
while removeIncomplete do
local out = {finalizeRemove(settings)}
if out[1]==false then return table.unpack(out) end
yieldIfNecessary()
end
end
local install = {}
local remove = {}
for i=1,#ins do
table.insert(install,avs.serializePack(ins[i]))
end
for i=1,#rem do
table.insert(remove,avs.serializePack(rem[i]))
end
return true, {install=install,remove=remove}
-- return "true, {["install"] = {"dep1", "package1", "package2"}, ["remove"] = {"package3"}}" on success
-- return "false, {"dep1"}" when not enough data
-- return "false, "[verbose string]"" when conflict found
-- TODO: handle same range AVS
-- TODO: handle different intercompatible 1.*.* range AVS
-- TODO: handle different incompatible 1.*.* range AVS
-- TODO: handle different intercompatible 1.*.*-2.*.* range AVS
-- TODO: handle different incompatible 1.*.*-2.*.* range AVS
-- TODO: handle conflicts from package info
-- TODO: handle reverse conflicts from another package's info
-- TODO: handle automatic conflict resolving
-- TODO: handle update of a single package with no dependencies
-- TODO: handle update of a single package with dependencies that don't need updating
-- TODO: handle update of a single package with dependencies that need updating
-- TODO: handle update of a single package that has a set dependency version changed
-- TODO: handle updating all packages in the database
-- TODO: handle installing optional packages
-- TODO: handle installing virtual packages and store this vpack info to database
-- TODO: handle removing virtual packages from database info
-- TODO: handle installing groups and store this group info to database
-- TODO: handle removing groups and store this group info to database
end
local function storeInstall()
-- directly set
for _,pack in ipairs(ins) do
if getPackInfo(pack) then
local info = table.copy(getPackInfo(pack))
if pack[2] then
info.version=avs.serializeVersion(pack[2])
else
info.version=info.latestVersion or info.version
end
db.set(dbpath,pack[1],info)
end
end
-- set reverse dependencies
for _,pack in pairs(ins) do
local i = avs.serializePack(pack)
local v = packInfo[i]
if v and v.dependencies then
for _,dep in ipairs(v.dependencies) do
local depname = avs.parse(dep)[1]
local dat = db.get(dbpath,depname)
if not dat then goto continue end
if type(dat.reverseDependencies)~="table" then
dat.reverseDependencies={}
end
table.insert(dat.reverseDependencies,pack[1])
db.set(dbpath,depname,dat)
::continue::
end
end
end
end
local function storeRemove()
-- directly remove
for _,pack in ipairs(rem) do
db.remove(dbpath,pack[1])
end
-- remove reverse dependencies
for _,pack in ipairs(rem) do
local pdat = getPackInfo(pack)
if not pdat.dependencies then goto continue end
for _,dep in ipairs(pdat.dependencies) do
local depname = avs.parse(dep)[1]
local dat = db.get(dbpath,depname)
if dat.reverseDependencies then
removeFromArray(pack[1],dat.reverseDependencies)
end
db.set(dbpath,depname,dat)
end
::continue::
end
end
function transaction.store()
if #ins>0 then
storeInstall()
end
if #rem>0 then
storeRemove()
end
end
return transaction
end
return { avs=avs,startTransaction=startTransaction }
+312
View File
@@ -0,0 +1,312 @@
local solvitdb = {}
local fs = require("filesystem")
local function checkValidityAndOpen(path)
local handle = assert(fs.open(path))
local data = assert(handle:read(8))
if data:sub(5, 8) == "RTFM" then
local patLength = string.unpack("<I4", data:sub(1, 4))
return handle, patLength
else
error("missing magic")
end
end
local function readPat(handle, patLength)
-- This needs the handle to be at byte 4
local data, tmpdata = ""
repeat
tmpdata = handle:read(patLength - #data)
data = data .. (tmpdata or "")
until not tmpdata
local packages = {}
for packageData in data:gmatch("(.-%.....);") do
packages[packageData:sub(1, -6)] = string.unpack("<I4", packageData:sub(-4, -1))
end
return packages
end
local function insert(filePath, location, bytes)
local chunkLength = 512
local readHandle = assert(fs.open(filePath, "r"))
local tmpFilePath = filePath .. ".tmp"
local writeHandle = assert(fs.open(tmpFilePath, "w"))
local i = 0
while true do
local readAmount = chunkLength
if readAmount > location - i then
readAmount = location - i
end
if readAmount == 0 then
break
end
local data = readHandle:read(readAmount)
i = i + readAmount
assert(writeHandle:write(data))
end
assert(writeHandle:write(bytes))
while true do
local data = readHandle:read(chunkLength)
if not data then
break
end
assert(writeHandle:write(data))
end
readHandle:close()
writeHandle:close()
fs.rename(tmpFilePath, filePath)
end
local function remove(filePath, location, length)
local chunkLength = 512
if length > 512 then
chunkLength = length
end
-- The file has to get shortened, so I have no choice but to do these shenanigans
local readHandle = assert(fs.open(filePath, "r"))
local tmpFilePath = filePath .. ".tmp"
local writeHandle = assert(fs.open(tmpFilePath, "w"))
local i = 0
while true do
local readAmount = chunkLength
if readAmount > location - i then
readAmount = location - i
end
if readAmount == 0 then
break
end
local data = readHandle:read(readAmount)
i = i + readAmount
assert(writeHandle:write(data))
end
readHandle:seek(length)
while true do
local data = readHandle:read(chunkLength)
if not data then
break
end
assert(writeHandle:write(data))
end
readHandle:close()
writeHandle:close()
fs.rename(tmpFilePath, filePath)
end
local function adjustPatLocationsAfterPackage(filePath, packageName, offset)
local readHandle, patLength = checkValidityAndOpen(filePath)
local pat = readPat(readHandle, patLength)
readHandle:close()
local modifiedLocation = pat[packageName]
local toAdjust = {}
for name, location in pairs(pat) do
if location > modifiedLocation then
table.insert(toAdjust, { name = name, location = location })
end
end
if #toAdjust == 0 then
return
end
local readHandle = assert(fs.open(filePath, "r"))
readHandle:seek(8) -- Skip header
local patBytes = assert(readHandle:read(patLength))
readHandle:close()
local patFieldPositions = {}
for _, entry in ipairs(toAdjust) do
local needle = entry.name .. "."
local startPos = patBytes:find(needle, 1, true)
patFieldPositions[entry.name] = 8 + startPos + #needle - 1
end
local writeHandle = assert(fs.open(filePath, "a"))
for _, entry in ipairs(toAdjust) do
local newLocation = entry.location + offset
writeHandle:seek("set", patFieldPositions[entry.name])
writeHandle:write(string.pack("<I4", newLocation))
end
writeHandle:close()
end
function solvitdb.create(path)
checkArg(1, path, "string")
local handle = assert(fs.open(path, "w"))
assert(handle:write("\0\0\0\0RTFM"))
handle:close()
end
function solvitdb.set(path, name, data)
checkArg(1, path, "string")
checkArg(2, name, "string")
checkArg(3, data, "table")
local function sanitize(tab)
for _, item in pairs(tab) do
if type(item) == "string" then
item = item:lower():gsub("[;|]", "-")
elseif type(item) == "table" then
sanitize(item)
end
end
end
sanitize(data)
local readHandle, patLength = checkValidityAndOpen(path)
local pat = readPat(readHandle, patLength)
local writeHandle = assert(fs.open(path, "a"))
local encodedString = ""
if data.type == "package" then
encodedString = "P"
elseif data.type == "group" then
encodedString = "G"
elseif data.type == "virtual-package" then
encodedString = "V"
end
if data.dependencies then
encodedString = encodedString .. "d" .. table.concat(data.dependencies, ";") .. "|"
end
if data.reverseDependencies then
encodedString = encodedString .. "D" .. table.concat(data.reverseDependencies, ";") .. "|"
end
if data.conflicts then
encodedString = encodedString .. "c" .. table.concat(data.conflicts, ";") .. "|"
end
if data.packages then
encodedString = encodedString .. "p" .. table.concat(data.packages, ";") .. "|"
end
if data.version then
encodedString = encodedString .. "v" .. data.version .. "|"
end
if pat[name] then
readHandle:seek(pat[name])
local oldData, tmpdata = ""
repeat
tmpdata = readHandle:read(math.huge)
oldData = oldData .. (tmpdata or "")
until oldData:find("\n", 1, true) or not tmpdata
readHandle:close()
if not oldData:find("\n", 1, true) and not tmpdata then
error("hit unexpected EOF")
end
oldData = oldData:match("^[^\n]+")
local difference = #encodedString - #oldData
writeHandle:seek("set", pat[name] + patLength + 8)
if difference == 0 then
readHandle:close()
writeHandle:write(encodedString)
writeHandle:close()
elseif difference < 0 then
writeHandle:write(encodedString)
local currentSeek = writeHandle:seek()
writeHandle:close()
remove(path, currentSeek, -difference)
adjustPatLocationsAfterPackage(path, name, difference)
elseif difference > 0 then
writeHandle:write(encodedString:sub(1, #encodedString - difference))
local currentSeek = writeHandle:seek()
writeHandle:close()
insert(path, currentSeek + 1, encodedString:sub(#data - difference, -1))
adjustPatLocationsAfterPackage(path, name, difference)
end
else
readHandle:close()
writeHandle:seek("end")
local newPackageLocation = writeHandle:seek() - patLength - 8
writeHandle:write(encodedString .. "\n")
writeHandle:close()
local patData = ("%s.%s;"):format(name, string.pack("<I4", newPackageLocation))
insert(path, patLength + 8, patData)
local newPatLength = patLength + #patData
local writeHandle = assert(fs.open(path, "a")) -- If this works, that means handles must be reopened after insert() or remove()
writeHandle:seek("set", 0)
assert(writeHandle:write(string.pack("<I4", newPatLength)))
writeHandle:close()
end
end
function solvitdb.get(path, name)
checkArg(1, path, "string")
checkArg(2, name, "string")
local handle, patLength = checkValidityAndOpen(path)
local pat = readPat(handle, patLength)
if not pat[name] then
return nil
end
handle:seek(pat[name])
local data, tmpdata = ""
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until data:find("\n", 1, true) or not tmpdata
handle:close()
if not data:find("\n", 1, true) and not tmpdata then
error("hit unexpected EOF")
end
data = data:match("^[^\n]+")
local output = {}
if data:sub(1, 1) == "P" then
output.type = "package"
elseif data:sub(1, 1) == "G" then
output.type = "group"
elseif data:sub(1, 1) == "V" then
output.type = "virtual-package"
else
error("unknown package type")
end
data = "." .. data:sub(2, -1)
for series in data:gmatch("%.([dDcp][^.]*)") do
local seriesOutput
if series:sub(1, 1) == "d" then
output.dependencies = {}
seriesOutput = output.dependencies
elseif series:sub(1, 1) == "D" then
output.reverseDependencies = {}
seriesOutput = output.reverseDependencies
elseif series:sub(1, 1) == "c" then
output.conflicts = {}
seriesOutput = output.conflicts
elseif series:sub(1, 1) == "p" then
output.packages = {}
seriesOutput = output.packages
elseif series:sub(1, 1) == "v" then
output.version = series:sub(2, -1)
goto SkipSeries
end
-- Finally a case where Lua's weird table linking shenanigans are actually useful
series = series:sub(2, -1)
for seriesItem in series:gmatch("[^;]+") do
table.insert(seriesOutput, seriesItem)
end
::SkipSeries::
end
return output
end
function solvitdb.list(path)
checkArg(1, path, "string")
local handle, patLength = checkValidityAndOpen(path)
local pat = readPat(handle, patLength)
handle:close()
local list = {}
for index, _ in pairs(pat) do
table.insert(list, index)
end
setmetatable(list, {
__call = function(self)
i, value = next(self, i)
return i, value
end,
})
return list
end
function solvitdb.remove()
end
return solvitdb
+102
View File
@@ -0,0 +1,102 @@
local unicodeLib
local LLunicode
if table.copy then
unicodeLib = table.copy(unicode)
LLunicode = table.copy(unicode)
else
unicodeLib = {}
LLunicode = unicode
end
function unicodeLib.readCodePoint(readByte)
checkArg(1,readByte,"function")
local function inRange(min,max,...)
for _,v in ipairs({...}) do
if not (v and v>=min and v<max) then return false end
end
return true
end
local byte = readByte()
if byte==nil then return end
if byte < 0x80 then
-- ASCII character (0xxxxxxx)
return byte
elseif byte < 0xC0 then
-- Continuation byte (10xxxxxx), invalid at start position
return nil
elseif byte < 0xE0 then
-- 2-byte sequence (110xxxxx 10xxxxxx)
local byte2 = readByte()
if byte2==nil then return nil end
if inRange(0x80,0xC0,byte2) then
local code_point = ((byte & 0x1F) << 6) | (byte2 & 0x3F)
return code_point
end
elseif byte < 0xF0 then
-- 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx)
local byte2, byte3 = readByte(), readByte()
if byte2==nil and byte3==nil then return nil end
if inRange(0x80,0xC0,byte2,byte3)then
local code_point = ((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F)
return code_point
end
elseif byte < 0xF8 then
-- 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
local byte2, byte3, byte4 = readByte(), readByte(), readByte()
if byte2==nil and byte3==nil and byte4==nil then return nil end
if inRange(0x80,0xC0,byte2,byte3,byte4) then
local code_point = ((byte & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F)
return code_point
end
end
-- Invalid UTF-8 byte sequence
return nil
end
function unicodeLib.readChar(readByte)
checkArg(1,readByte,"function")
return LLunicode.char(unicodeLib.readCodePoint(readByte))
end
function unicodeLib.codepoint(chr)
checkArg(1,chr,"string")
local ptr = 1
return unicode.readCodePoint(function()
local byte = chr:byte(ptr)
ptr=ptr+1
return byte
end),ptr-1
end
function unicodeLib.iterate(readByte)
checkArg(1,readByte,"string","function")
if type(readByte)=="string" then
local str,ptr = readByte,0
readByte = function()
ptr=ptr+1
return str:byte(ptr)
end
end
return function()
local point = unicodeLib.readCodePoint(readByte)
if point==nil then return nil end
return LLunicode.char(point),point
end
end
unicodeLib.char = LLunicode.char
unicodeLib.charWidth = LLunicode.charWidth
unicodeLib.isWide = LLunicode.isWide
unicodeLib.len = LLunicode.len
unicodeLib.lower = LLunicode.lower
unicodeLib.reverse = LLunicode.reverse
unicodeLib.sub = LLunicode.sub
unicodeLib.upper = LLunicode.upper
unicodeLib.wlen = LLunicode.wlen
unicodeLib.wtrunc = LLunicode.wtrunc
return unicodeLib