Update CapyOS

This commit is contained in:
Alessandro Proto 2023-01-27 21:59:22 +01:00
parent 27bfd6d363
commit bc87363f7c
28 changed files with 786 additions and 656 deletions

View file

@ -0,0 +1,94 @@
-- Mandelbrot in Capy64
local gpu = require("gpu")
local timer = require("timer")
local event = require("event")
local term = require("term")
-- lower = closer = slower
local scale = 4
-- higher = more detailed = slower
local iterations = 100
local pscale = scale
local w, h = gpu.getSize()
local px = pscale / w
local colorUnit = math.floor(0xffffff / iterations)
-- todo: make it interactive
local dx, dy = 0, 0
local cx, cy = math.floor(w / 2), math.floor(h / 2)
-- z = z^2 + c
local function mandelbrot(zr, zi, cr, ci)
return zr ^ 2 - zi ^ 2 + cr,
2 * zr * zi + ci
end
local function iter(cr, ci)
local zr, zi = 0, 0
for i = 1, iterations do
zr, zi = mandelbrot(zr, zi, cr, ci)
if math.abs(zr) >= (pscale >= 2 and pscale or 2) or math.abs(zi) >= (pscale >= 2 and pscale or 2) then
return false, colorUnit, i
end
end
return true, 0, iterations
end
local function draw()
local buffer <close> = gpu.newBuffer()
for y = 0, h - 1 do
for x = 0, w - 1 do
local _, _, i = iter((x - cx + dx * pscale) * px, (y - cy + dy * pscale) * px)
buffer[y * w + x] = colorUnit * (iterations - i)
end
end
gpu.setBuffer(buffer)
end
-- no idea why it's needed
timer.sleep(1)
draw()
local tw, th = term.getSize()
while true do
term.setPos(1, th)
term.setBackground(0)
term.setForeground(0xffffff)
term.write("X: " .. dx .. "; Y: " .. dy .. "; S: " .. pscale .. "; " .. px .. "!")
local ev = { event.pull("key_down") }
if ev[1] == "key_down" then
local key = ev[3]
if key == "up" then
dy = dy - 10 / pscale
elseif key == "down" then
dy = dy + 10 / pscale
elseif key == "right" then
dx = dx + 10 / pscale
elseif key == "left" then
dx = dx - 10 / pscale
elseif key == "enter" then
draw()
elseif key == "page_down" then
pscale = pscale * 1.25
dx = dx * pscale
dy = dy * pscale
elseif key == "page_up" then
pscale = pscale / 1.25
dx = dx / pscale
dy = dy / pscale
elseif key == "r" then
pscale = scale
dx = 0
dy = 0
end
end
px = pscale / w
end

View file

@ -0,0 +1,73 @@
local gpu = require("gpu")
local timer = require("timer")
local event = require("event")
local colors = require("colors")
local parallel = require("parallel")
local melts = 2 ^ 12
local function contains(arr, val)
for k, v in ipairs(arr) do
if v == val then
return true
end
end
return false
end
local function melt()
local w, h = gpu.getSize()
local x, y = 0, 0
while true do
local buffer <close> = gpu.getBuffer()
for i = 1, melts do
local nx = math.random(x, w)
local ny = math.random(y, h)
local c = buffer[ny * w + nx]
buffer[(ny + 1) * w + nx] = c
end
gpu.setBuffer(buffer)
timer.sleep(10)
end
end
local function draw()
local ox, oy
while true do
local ev, b, x, y = event.pull("mouse_move", "mouse_down")
if ev == "mouse_down" then
if b == 1 then
ox = x
oy = y
end
elseif ev == "mouse_move" then
if contains(b, 1) then
gpu.plot(x, y, colors.red)
gpu.drawLine(x, y, ox, oy, colors.red)
ox, oy = x, y
end
end
end
end
local function random()
local w, h = gpu.getSize()
while true do
for i = 1, 24 do
gpu.drawString(
math.random(-7, w),
math.random(-13, h),
math.random(0, 0xffffff),
string.char(math.random(32, 127))
)
end
timer.sleep(100)
end
end
parallel.waitForAny(draw, melt, random)

View file

@ -5,7 +5,7 @@ local term = require("term")
local function slowPrint(text, delay)
for i = 1, #text do
local ch = text:sub(i, i)
write(ch)
io.write(ch)
timer.sleep(delay)
end
print()

View file

@ -19,9 +19,6 @@ local tEnv = {
__tostring = function() return "Call exit() to exit." end,
__call = function() bRunning = false end,
}),
["_echo"] = function(...)
return ...
end,
}
setmetatable(tEnv, { __index = _ENV })
@ -36,7 +33,7 @@ term.setForeground(colours.white)
while bRunning do
term.setForeground(colours.yellow)
write("> ")
io.write("> ")
term.setForeground(colours.white)
local s = io.read(nil, tCommandHistory)
@ -46,7 +43,7 @@ while bRunning do
local nForcePrint = 0
local func, e = load(s, "=lua", "t", tEnv)
local func2 = load("return _echo(" .. s .. ");", "=lua", "t", tEnv)
local func2 = load("return " .. s, "=lua", "t", tEnv)
if not func then
if func2 then
func = func2
@ -69,10 +66,10 @@ while bRunning do
n = n + 1
end
else
print(tResults[2])
io.stderr.print(tResults[2])
end
else
print(e)
io.stderr.print(e)
end
end

View file

@ -1,6 +1,5 @@
local term = require("term")
local colors = require("colors")
local io = require("io")
local fs = require("fs")
local exit = false
@ -17,17 +16,6 @@ local function buildEnvironment()
}, { __index = _G })
end
local function printError(...)
local cfg = {term.getForeground()}
local cbg = {term.getBackground()}
term.setForeground(colors.red)
term.setBackground(colors.black)
print(...)
term.setForeground(table.unpack(cfg))
term.setBackground(table.unpack(cbg))
end
local function tokenise(...)
local sLine = table.concat({ ... }, " ")
local tWords = {}
@ -58,6 +46,10 @@ function shell.resolve(path)
return fs.combine("", path)
end
if path:sub(1, 1) == "~" then
return fs.combine(shell.homePath, path)
end
return fs.combine(currentDir, path)
end
@ -67,7 +59,7 @@ function shell.resolveProgram(path)
end
for seg in shell.path:gmatch("[^;]+") do
local resolved = seg:gsub("%?", path)
local resolved = shell.resolve(seg:gsub("%?", path))
if fs.exists(resolved) and not fs.isDir(resolved) then
return resolved
end
@ -80,7 +72,7 @@ function shell.run(...)
local path = shell.resolveProgram(command)
if not path then
printError("Command not found: " .. command)
io.stderr.print("Command not found: " .. command)
return false
end
@ -89,13 +81,13 @@ function shell.run(...)
local func, err = loadfile(path, "t", env)
if not func then
printError(err)
io.stderr.print(err)
return false
end
local ok, err = pcall(func, table.unpack(args, 2))
if not ok then
printError(err)
io.stderr.print(err)
return false
end
@ -106,17 +98,21 @@ function shell.exit()
exit = true
end
if not fs.exists(shell.homePath) then
fs.makeDir(shell.homePath)
end
local history = {}
local lastExecSuccess = true
while not exit do
term.setBackground(colors.black)
term.setForeground(colors.white)
write(":")
io.write(":")
term.setForeground(colors.lightBlue)
local currentDir = shell.getDir()
if currentDir == shell.homePath then
write("~")
io.write("~")
else
write(currentDir)
io.write(currentDir)
end
if lastExecSuccess then
@ -124,7 +120,7 @@ while not exit do
else
term.setForeground(colors.red)
end
write("$ ")
io.write("$ ")
term.setForeground(colors.white)
local line = io.read(nil, history)

View file

@ -0,0 +1 @@
print(string.format("%s @ %s - %s", os.version(), _HOST, _VERSION))

View file

@ -19,12 +19,14 @@ if not http.checkURL(args[1]) then
error("Invalid URL", 0)
end
local response, err = http.get(args[1])
local response <close>, err = http.get(args[1], nil, {
binary = true,
})
if not response then
error(err, 0)
end
local file = fs.open(outputPath, "w")
local file <close> = fs.open(outputPath, "w")
file:write(response:readAll())
file:close()
response:close()

View file

@ -1 +0,0 @@
os.print = print

View file

@ -0,0 +1,3 @@
package.path = "/lib/?.lua;/lib/?/init.lua;" .. package.path
_G.io = require("io")

View file

@ -1 +0,0 @@
package.path = "/lib/?.lua;/lib/?/init.lua;" .. package.path

View file

@ -0,0 +1,15 @@
function _G.print(...)
local args = { ... }
local size = #args
local lines = 0
for n, v in ipairs(args) do
local s = tostring(v)
if n < size then
s = s .. "\t"
end
lines = lines + io.write(s)
end
lines = lines + io.write("\n")
return lines
end

View file

@ -31,16 +31,16 @@ function http.request(url, body, headers, options)
end
local requestId = http.requestAsync(url, body, headers, options)
local ev, id, par
local ev, id, data, info
repeat
ev, id, par = event.pull("http_response", "http_failure")
ev, id, data, info = event.pull("http_response", "http_failure")
until id == requestId
if ev == "http_failure" then
return nil, par
return nil, data
end
return par
return data, info
end
function http.get(url, headers, options)

View file

@ -1,79 +0,0 @@
local term = require("term")
local expect = require("expect").expect
function write(sText)
expect(1, sText, "string", "number")
local w, h = term.getSize()
local x, y = term.getPos()
local nLinesPrinted = 0
local function newLine()
if y + 1 <= h then
term.setPos(1, y + 1)
else
term.setPos(1, h)
term.scroll(1)
end
x, y = term.getPos()
nLinesPrinted = nLinesPrinted + 1
end
-- Print the line with proper word wrapping
sText = tostring(sText)
while #sText > 0 do
local whitespace = string.match(sText, "^[ \t]+")
if whitespace then
-- Print whitespace
term.write(whitespace)
x, y = term.getPos()
sText = string.sub(sText, #whitespace + 1)
end
local newline = string.match(sText, "^\n")
if newline then
-- Print newlines
newLine()
sText = string.sub(sText, 2)
end
local text = string.match(sText, "^[^ \t\n]+")
if text then
sText = string.sub(sText, #text + 1)
if #text > w then
-- Print a multiline word
while #text > 0 do
if x > w then
newLine()
end
term.write(text)
text = string.sub(text, w - x + 2)
x, y = term.getPos()
end
else
-- Print a word normally
if x + #text - 1 > w then
newLine()
end
term.write(text)
x, y = term.getPos()
end
end
end
return nLinesPrinted
end
function print(...)
local nLinesPrinted = 0
local nLimit = select("#", ...)
for n = 1, nLimit do
local s = tostring(select(n, ...))
if n < nLimit then
s = s .. "\t"
end
nLinesPrinted = nLinesPrinted + write(s)
end
nLinesPrinted = nLinesPrinted + write("\n")
return nLinesPrinted
end

View file

@ -1,29 +1,14 @@
local term = require("term")
local colors = require("colors")
term.setSize(51, 19)
term.setForeground(0x59c9ff)
term.setBackground(colors.black)
term.clear()
term.setPos(1, 1)
print(os.version())
term.write(os.version())
term.setPos(1, 2)
local func, err = loadfile("/bin/shell.lua")
if func then
while true do
local ok, err = pcall(func)
if not ok then
print(err)
end
end
else
print(err)
end
dofile("/bin/shell.lua")
print("Press any key to continue...")
coroutine.yield("key_down")
os.shutdown(false)

View file

@ -1,6 +1,47 @@
local fs = require("fs")
print("Starting CapyOS")
local term = require("term")
local fs = require("fs")
local gpu = require("gpu")
local nPrint = print
local function showError(err)
nPrint(err)
local x, y = term.getPos()
term.setForeground(0xff0000)
term.setPos(1, y)
term.write(err)
term.setPos(1, y + 1)
term.setForeground(0xffffff)
term.write("Press any key to continue")
coroutine.yield("key_down")
end
function os.version()
return "CapyOS 0.0.1"
end
term.setSize(51, 19)
gpu.setScale(2)
term.setPos(1, 1)
term.write(_HOST)
local files = fs.list("/boot")
for k, v in ipairs(files) do
dofile("/boot/" .. v)
local func, err = loadfile("/boot/" .. v)
if not func then
showError(err)
break
end
local ok, err = pcall(func)
if not ok then
showError(debug.traceback(err))
break
end
end

View file

@ -1,3 +1,5 @@
local expect = require("expect")
local palette = {
{
"white",
@ -65,29 +67,28 @@ local palette = {
}
}
--[[local colors = {
white = 0xf0f0f0,
orange = 0xf2b233,
magenta = 0xe57fd8,
lightBlue = 0x99b2f2,
yellow = 0xdede6c,
lime = 0x7fcc19,
pink = 0xf2b2cc,
gray = 0x4c4c4c,
lightGray = 0x999999,
cyan = 0x4c99b2,
purple = 0xb266e5,
blue = 0x3366cc,
brown = 0x7f664c,
green = 0x57a64e,
red = 0xcc4c4c,
black = 0x111111,
}]]
local colors = {}
for k, v in ipairs(palette) do
colors[v[1]] = v[2]
colors[k] = v[2]
end
function colors.packRGB(r, g, b)
expect(1, r, "number")
expect(2, g, "number")
expect(3, b, "number")
return (r << 16) +
(g << 8) +
b
end
function colors.unpackRGB(rgb)
expect(1, rgb, "number")
return (rgb >> 16) & 0xff,
(rgb >> 8) & 0xff,
rgb & 0xff
end
return colors;

View file

@ -1,123 +1,52 @@
--[[- The @{cc.expect} library provides helper functions for verifying that
function arguments are well-formed and of the correct type.
-- Credits: https://github.com/Ocawesome101/recrafted
@module cc.expect
@since 1.84.0
@changed 1.96.0 The module can now be called directly as a function, which wraps around `expect.expect`.
@usage Define a basic function and check it has the correct arguments.
-- cc.expect
local expect = require "cc.expect"
local expect, field = expect.expect, expect.field
local _expect = {}
local function add_person(name, info)
expect(1, name, "string")
expect(2, info, "table", "nil")
local function checkType(index, valueType, value, ...)
local expected = table.pack(...)
local isType = false
if info then
print("Got age=", field(info, "age", "number"))
print("Got gender=", field(info, "gender", "string", "nil"))
for i = 1, expected.n, 1 do
if type(value) == expected[i] then
isType = true
break
end
end
add_person("Anastazja") -- `info' is optional
add_person("Kion", { age = 23 }) -- `gender' is optional
add_person("Caoimhin", { age = 23, gender = true }) -- error!
]]
local native_select, native_type = select, type
local function get_type_names(...)
local types = table.pack(...)
for i = types.n, 1, -1 do
if types[i] == "nil" then table.remove(types, i) end
if not isType then
error(string.format("bad %s %s (%s expected, got %s)", valueType,
index, table.concat(expected, " or "), type(value)), 3)
end
if #types <= 1 then
return tostring(...)
else
return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
return value
end
function _expect.expect(index, value, ...)
return checkType(("#%d"):format(index), "argument", value, ...)
end
function _expect.field(tbl, index, ...)
_expect.expect(1, tbl, "table")
_expect.expect(2, index, "string")
return checkType(("%q"):format(index), "field", tbl[index], ...)
end
function _expect.range(num, min, max)
_expect.expect(1, num, "number")
_expect.expect(2, min, "number", "nil")
_expect.expect(3, max, "number", "nil")
min = min or -math.huge
max = max or math.huge
if num < min or num > max then
error(("number outside of range (expected %d to be within %d and %d")
:format(num, min, max), 2)
end
end
--- Expect an argument to have a specific type.
--
-- @tparam number index The 1-based argument index.
-- @param value The argument's value.
-- @tparam string ... The allowed types of the argument.
-- @return The given `value`.
-- @throws If the value is not one of the allowed types.
local function expect(index, value, ...)
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
setmetatable(_expect, { __call = function(_, ...)
return _expect.expect(...)
end })
-- If we can determine the function name with a high level of confidence, try to include it.
local name
local ok, info = pcall(debug.getinfo, 3, "nS")
if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
local type_names = get_type_names(...)
if name then
error(("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3)
else
error(("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3)
end
end
--- Expect an field to have a specific type.
--
-- @tparam table tbl The table to index.
-- @tparam string index The field name to check.
-- @tparam string ... The allowed types of the argument.
-- @return The contents of the given field.
-- @throws If the field is not one of the allowed types.
local function field(tbl, index, ...)
expect(1, tbl, "table")
expect(2, index, "string")
local value = tbl[index]
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
if value == nil then
error(("field '%s' missing from table"):format(index), 3)
else
error(("bad field '%s' (expected %s, got %s)"):format(index, get_type_names(...), t), 3)
end
end
local function is_nan(num)
return num ~= num
end
--- Expect a number to be within a specific range.
--
-- @tparam number num The value to check.
-- @tparam number min The minimum value, if nil then `-math.huge` is used.
-- @tparam number max The maximum value, if nil then `math.huge` is used.
-- @return The given `value`.
-- @throws If the value is outside of the allowed range.
-- @since 1.96.0
local function range(num, min, max)
expect(1, num, "number")
min = expect(2, min, "number", "nil") or -math.huge
max = expect(3, max, "number", "nil") or math.huge
if min > max then
error("min must be less than or equal to max)", 2)
end
if is_nan(num) or num < min or num > max then
error(("number outside of range (expected %s to be within %s and %s)"):format(num, min, max), 3)
end
return num
end
return setmetatable({
expect = expect,
field = field,
range = range,
}, { __call = function(_, ...) return expect(...) end })
return _expect

View file

@ -4,280 +4,279 @@ local term = require("term")
local io = {}
function io.read(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
expect(1, _sReplaceChar, "string", "nil")
expect(2, _tHistory, "table", "nil")
expect(3, _fnComplete, "function", "nil")
expect(4, _sDefault, "string", "nil")
function io.write(text)
text = tostring(text)
local lines = 0
local w, h = term.getSize()
local function inc_cy(cy)
lines = lines + 1
if cy > h - 1 then
term.scroll(1)
return cy
else
return cy + 1
end
end
while #text > 0 do
local nl = text:find("\n") or #text
local chunk = text:sub(1, nl)
text = text:sub(#chunk + 1)
local has_nl = chunk:sub(-1) == "\n"
if has_nl then chunk = chunk:sub(1, -2) end
local cx, cy = term.getPos()
while #chunk > 0 do
if cx > w then
term.setPos(1, inc_cy(cy))
cx, cy = term.getPos()
end
local to_write = chunk:sub(1, w - cx + 1)
term.write(to_write)
chunk = chunk:sub(#to_write + 1)
cx, cy = term.getPos()
end
if has_nl then
term.setPos(1, inc_cy(cy))
end
end
return lines
end
local empty = {}
function io.read(replace, history, complete, default)
expect(1, replace, "string", "nil")
expect(2, history, "table", "nil")
expect(3, complete, "function", "nil")
expect(4, default, "string", "nil")
if replace then replace = replace:sub(1, 1) end
local hist = history or {}
history = {}
for i = 1, #hist, 1 do
history[i] = hist[i]
end
local buffer = default or ""
local prev_buf = buffer
history[#history + 1] = buffer
local hist_pos = #history
local cursor_pos = 0
local stx, sty = term.getPos()
local w, h = term.getSize()
local dirty = false
local completions = {}
local comp_id = 0
local function clearCompletion()
if completions[comp_id] then
write((" "):rep(#completions[comp_id]))
end
end
local function full_redraw(force)
if force or dirty then
if complete and buffer ~= prev_buf then
completions = complete(buffer) or empty
comp_id = math.min(1, #completions)
end
prev_buf = buffer
term.setPos(stx, sty)
local text = buffer
if replace then text = replace:rep(#text) end
local ln = io.write(text)
if completions[comp_id] then
local oldfg = term.getForeground()
local oldbg = term.getBackground()
term.setForeground(colors.white)
term.setBackground(colors.gray)
ln = ln + write(completions[comp_id])
term.setForeground(oldfg)
term.setBackground(oldbg)
else
ln = ln + io.write(" ")
end
if sty + ln > h then
sty = sty - (sty + ln - h)
end
end
-- set cursor to the appropriate spot
local cx, cy = stx, sty
cx = cx + #buffer - cursor_pos -- + #(completions[comp_id] or "")
while cx > w do
cx = cx - w
cy = cy + 1
end
term.setPos(cx, cy)
end
term.setBlink(true)
local sLine
if type(_sDefault) == "string" then
sLine = _sDefault
else
sLine = ""
end
local nHistoryPos
local nPos, nScroll = #sLine, 0
if _sReplaceChar then
_sReplaceChar = string.sub(_sReplaceChar, 1, 1)
end
local tCompletions
local nCompletion
local function recomplete()
if _fnComplete and nPos == #sLine then
tCompletions = _fnComplete(sLine)
if tCompletions and #tCompletions > 0 then
nCompletion = 1
else
nCompletion = nil
end
else
tCompletions = nil
nCompletion = nil
end
end
local function uncomplete()
tCompletions = nil
nCompletion = nil
end
local w = term.getSize()
local sx = term.getPos()
local function redraw(_bClear)
local cursor_pos = nPos - nScroll
if sx + cursor_pos >= w then
-- We've moved beyond the RHS, ensure we're on the edge.
nScroll = sx + nPos - w
elseif cursor_pos < 0 then
-- We've moved beyond the LHS, ensure we're on the edge.
nScroll = nPos
end
local _, cy = term.getPos()
term.setPos(sx, cy)
local sReplace = _bClear and " " or _sReplaceChar
if sReplace then
term.write(string.rep(sReplace, math.max(#sLine - nScroll, 0)))
else
term.write(string.sub(sLine, nScroll + 1))
end
if nCompletion then
local sCompletion = tCompletions[nCompletion]
local oldText, oldBg
if not _bClear then
oldText = term.getForeground()
oldBg = term.getBackground()
term.setForeground(colors.white)
term.setBackground(colors.gray)
end
if sReplace then
term.write(string.rep(sReplace, #sCompletion))
else
term.write(sCompletion)
end
if not _bClear then
term.setForeground(oldText)
term.setBackground(oldBg)
end
end
term.setPos(sx + nPos - nScroll, cy)
end
local function clear()
redraw(true)
end
recomplete()
redraw()
local function acceptCompletion()
if nCompletion then
-- Clear
clear()
-- Find the common prefix of all the other suggestions which start with the same letter as the current one
local sCompletion = tCompletions[nCompletion]
sLine = sLine .. sCompletion
nPos = #sLine
-- Redraw
recomplete()
redraw()
end
end
while true do
local sEvent, param, param1, param2 = event.pull()
if sEvent == "char" then
-- Typed key
clear()
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
nPos = nPos + 1
recomplete()
redraw()
full_redraw()
-- get input
local evt, par1, par2 = event.pull()
elseif sEvent == "paste" then
-- Pasted text
clear()
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
nPos = nPos + #param
recomplete()
redraw()
elseif sEvent == "key_down" then
if param1 == "enter" then
-- Enter/Numpad Enter
if nCompletion then
clear()
uncomplete()
redraw()
end
break
elseif param1 == "left" then
-- Left
if nPos > 0 then
clear()
nPos = nPos - 1
recomplete()
redraw()
end
elseif param1 == "right" then
-- Right
if nPos < #sLine then
-- Move right
clear()
nPos = nPos + 1
recomplete()
redraw()
if evt == "char" then
dirty = true
clearCompletion()
if cursor_pos == 0 then
buffer = buffer .. par1
elseif cursor_pos == #buffer then
buffer = par1 .. buffer
else
-- Accept autocomplete
acceptCompletion()
buffer = buffer:sub(0, -cursor_pos - 1) .. par1 .. buffer:sub(-cursor_pos)
end
elseif param1 == "up" or param1 == "down" then
-- Up or down
if nCompletion then
-- Cycle completions
clear()
if param == "up" then
nCompletion = nCompletion - 1
if nCompletion < 1 then
nCompletion = #tCompletions
end
elseif param == "down" then
nCompletion = nCompletion + 1
if nCompletion > #tCompletions then
nCompletion = 1
end
end
redraw()
elseif _tHistory then
-- Cycle history
clear()
if param1 == "up" then
-- Up
if nHistoryPos == nil then
if #_tHistory > 0 then
nHistoryPos = #_tHistory
end
elseif nHistoryPos > 1 then
nHistoryPos = nHistoryPos - 1
end
elseif evt == "paste" then
dirty = true
clearCompletion()
if cursor_pos == 0 then
buffer = buffer .. par1
elseif cursor_pos == #buffer then
buffer = par1 .. buffer
else
-- Down
if nHistoryPos == #_tHistory then
nHistoryPos = nil
elseif nHistoryPos ~= nil then
nHistoryPos = nHistoryPos + 1
buffer = buffer:sub(0, -cursor_pos - 1) .. par1 ..
buffer:sub(-cursor_pos + (#par1 - 1))
end
elseif evt == "key_down" then
if par2 == "back" and #buffer > 0 then
dirty = true
if cursor_pos == 0 then
buffer = buffer:sub(1, -2)
clearCompletion()
elseif cursor_pos < #buffer then
buffer = buffer:sub(0, -cursor_pos - 2) .. buffer:sub(-cursor_pos)
end
if nHistoryPos then
sLine = _tHistory[nHistoryPos]
nPos, nScroll = #sLine, 0
elseif par2 == "delete" and cursor_pos > 0 then
dirty = true
if cursor_pos == #buffer then
buffer = buffer:sub(2)
elseif cursor_pos == 1 then
buffer = buffer:sub(1, -2)
else
sLine = ""
nPos, nScroll = 0, 0
buffer = buffer:sub(0, -cursor_pos - 1) .. buffer:sub(-cursor_pos + 1)
end
uncomplete()
redraw()
cursor_pos = cursor_pos - 1
elseif par2 == "up" then
if #completions > 1 then
dirty = true
clearCompletion()
if comp_id > 1 then
comp_id = comp_id - 1
else
comp_id = #completions
end
elseif param1 == "back" then
-- Backspace
if nPos > 0 then
clear()
sLine = string.sub(sLine, 1, nPos - 1) .. string.sub(sLine, nPos + 1)
nPos = nPos - 1
if nScroll > 0 then nScroll = nScroll - 1 end
recomplete()
redraw()
elseif hist_pos > 1 then
cursor_pos = 0
history[hist_pos] = buffer
hist_pos = hist_pos - 1
buffer = (" "):rep(#buffer)
full_redraw(true)
buffer = history[hist_pos]
dirty = true
end
elseif param1 == "home" then
-- Home
if nPos > 0 then
clear()
nPos = 0
recomplete()
redraw()
elseif par2 == "down" then
if #completions > 1 then
dirty = true
clearCompletion()
if comp_id < #completions then
comp_id = comp_id + 1
else
comp_id = 1
end
elseif param1 == "delete" then
-- Delete
if nPos < #sLine then
clear()
sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2)
recomplete()
redraw()
elseif hist_pos < #history then
cursor_pos = 0
history[hist_pos] = buffer
hist_pos = hist_pos + 1
buffer = (" "):rep(#buffer)
full_redraw(true)
buffer = history[hist_pos]
dirty = true
end
elseif param1 == "end" then
-- End
if nPos < #sLine then
clear()
nPos = #sLine
recomplete()
redraw()
elseif par2 == "left" then
if cursor_pos < #buffer then
clearCompletion()
cursor_pos = cursor_pos + 1
end
elseif param1 == "tab" then
-- Tab (accept autocomplete)
acceptCompletion()
elseif par2 == "right" then
if cursor_pos > 0 then
cursor_pos = cursor_pos - 1
elseif comp_id > 0 then
dirty = true
buffer = buffer .. completions[comp_id]
end
elseif sEvent == "mouse_down" or sEvent == "mouse_move" and param == 1 then
local _, cy = term.getPos()
if param1 >= sx and param1 <= w and param2 == cy then
-- Ensure we don't scroll beyond the current line
nPos = math.min(math.max(nScroll + param1 - sx, 0), #sLine)
redraw()
elseif par2 == "tab" then
if comp_id > 0 then
dirty = true
buffer = buffer .. completions[comp_id]
end
elseif sEvent == "term_resize" then
-- Terminal resized
w = term.getSize()
redraw()
elseif par2 == "home" then
cursor_pos = #buffer
end
end
elseif par2 == "end" then
cursor_pos = 0
local _, cy = term.getPos()
term.setBlink(false)
term.setPos(w + 1, cy)
elseif par2 == "enter" then
clearCompletion()
print()
break
end
end
end
return sLine
term.setBlink(false)
return buffer
end
io.stderr = {}
function io.stderr.write(text)
local fg = term.getForeground()
term.setForeground(0xff0000)
io.write(text)
term.setForeground(fg)
end
function io.stderr.print(...)
local fg = term.getForeground()
term.setForeground(0xff0000)
print(...)
term.setForeground(fg)
end
return io

View file

@ -0,0 +1,61 @@
local expect = require("expect")
local parallel = {}
local function contains(array, value)
for k, v in pairs(array) do
if v == value then
return true
end
end
return false
end
local function run(threads, exitOnAny)
local alive = #threads
local filters = {}
local ev = {}
while true do
for i, thread in pairs(threads) do
if not filters[i] or #filters[i] == 0 or contains(filters[i], ev[1]) or ev[1] == "interrupt" then
local pars = table.pack(coroutine.resume(thread, table.unpack(ev)))
if pars[1] then
filters[i] = table.pack(table.unpack(pars, 2))
else
error(pars[2], 0)
end
end
if coroutine.status(thread) == "dead" then
alive = alive - 1
if exitOnAny or alive <= 0 then
return
end
end
end
ev = table.pack(coroutine.yield())
end
end
function parallel.waitForAll(...)
local threads = {}
for k, v in ipairs({ ... }) do
expect(k, v, "function")
table.insert(threads, coroutine.create(v))
end
return run(threads, false)
end
function parallel.waitForAny(...)
local threads = {}
for k, v in ipairs({ ... }) do
expect(k, v, "function")
table.insert(threads, coroutine.create(v))
end
return run(threads, true)
end
return parallel

View file

@ -40,10 +40,12 @@ function UTFString:find(pattern, init, plain)
expect(1, pattern, "string", "table")
expect(2, init, "number", "nil")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local s, e = string.find(self.str, pattern, init and utf8.offset(self.str, init), plain)
if not s then return nil end
return utf8.len(string.sub(self.str, 1, s - 1)) + 1, utf8.len(string.sub(self.str, 1, e - 1)) + 1
@ -61,10 +63,12 @@ end
function UTFString:gmatch(pattern)
expect(1, pattern, "string", "table")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local iter = string.gmatch(self.str, pattern)
return function()
local matches = table.pack(iter())
@ -82,12 +86,14 @@ function UTFString:gsub(pattern, repl, n)
expect(2, repl, "string", "table", "function")
expect(3, n, "number", "nil")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
if type(repl) == "table" and getmetatable(repl) == UTFString_mt then repl = repl.str
elseif type(repl) == "string" then repl = toUTF8(repl) end
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local s, total = string.gsub(self.str, pattern, repl, n)
return UTFString(s), total
end
@ -111,10 +117,12 @@ function UTFString:match(pattern, init)
expect(1, pattern, "string", "table")
expect(2, init, "number", "nil")
if type(pattern) == "table" then
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)", 2) end
if getmetatable(pattern) ~= UTFString_mt then error("bad argument #1 (expected string or UTFString, got table)",
2) end
pattern = pattern.str
else pattern = toUTF8(pattern) end
pattern = string.gsub(string.gsub(pattern, "(.)%.", function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
pattern = string.gsub(string.gsub(pattern, "(.)%.",
function(s) if s == "%" then return "%." else return s .. utf8.charpattern end end), "^%.", utf8.charpattern)
local matches = table.pack(string.match(self.str, pattern, init and utf8.offset(self.str, init)))
for i = 1, matches.n do
local tt = type(matches[i])
@ -191,8 +199,10 @@ function UTFString_mt.__concat(a, b)
if type(a) == "string" then return UTFString(a .. b.str)
elseif type(b) == "string" then return UTFString(a.str .. b)
else
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to concatenate " .. type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to concatenate UTFString and " .. type(b), 2) end
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to concatenate " ..
type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to concatenate UTFString and "
.. type(b), 2) end
return UTFString(a.str .. b.str)
end
end
@ -208,8 +218,10 @@ function UTFString_mt.__lt(a, b)
if type(a) == "string" then return a < b.str
elseif type(b) == "string" then return a.str < b
else
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " .. type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " .. type(b), 2) end
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " ..
type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " ..
type(b), 2) end
return a.str < b.str
end
end
@ -218,8 +230,10 @@ function UTFString_mt.__le(a, b)
if type(a) == "string" then return a <= b.str
elseif type(b) == "string" then return a.str <= b
else
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " .. type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " .. type(b), 2) end
if type(a) ~= "table" or getmetatable(a) ~= UTFString_mt then error("attempt to compare " ..
type(a) .. " and UTFString", 2)
elseif type(b) ~= "table" or getmetatable(b) ~= UTFString_mt then error("attempt to compare UTFString and " ..
type(b), 2) end
return a.str <= b.str
end
end