Compare commits

..

10 Commits

Author SHA1 Message Date
56af6d1ade Fix document generation on Windows (using Git Bash)
* Made finddesc.lua execute commands using `sh -c 'command'`, which ensures that Unix-like shell is used instead of cmd.exe
* Made finddesc.lua avoid a situation where it would end up with a double '/' in `outpath`
* Redirect output of deleting the document to null to avoid pointless "No such file or directory" errors.
2020-05-12 16:29:44 +01:00
b89ff14d5c added type annotations and descriptions for functions where appropriate 2020-05-12 17:55:05 +10:00
7ddece288b added doc() support for pre-compiled API documentation, wrote a generator for it, and included that in the build script 2020-05-12 16:59:17 +10:00
cf373668a9 added a title for PDF generation 2020-05-12 16:11:51 +10:00
b3cfeb13ec remove replaced kernel modules 2020-05-12 12:08:01 +10:00
b4ee8ed8a6 point build.sh to the right finddesc 2020-05-12 12:04:47 +10:00
4f3bac551e updated finddesc to use the new doc library, allowing type annotations and building a directory of documentation 2020-05-12 12:01:44 +10:00
a917016a66 fix type annotation in event.ignore 2020-05-12 11:44:05 +10:00
d47a0748bd added type annotations to documentation for various libraries 2020-05-12 10:57:13 +10:00
405ee6408d Merge pull request 'Fix the build process so that it works on the Bash shell for Git on Windows and fix markdown output' (#2) from Skye/OC-PsychOS2:git-windows-bash-fixes into master
Doesn't seem to break anything, though I wish Microsoft would fix their filesystems.

Markdown changes are a nice touch, though they'll be replaced soon.

Might think about using #!/usr/bin/env for Lua at some point.
2020-05-12 10:55:37 +10:00
28 changed files with 238 additions and 437 deletions

3
.gitignore vendored
View File

@ -1,5 +1,4 @@
apidoc.md
apidoc.html
*.cpio *.cpio
*.af *.af
/target /target
/doc

View File

@ -1,11 +1,14 @@
#!/bin/bash #!/bin/bash
LUA=${LUA:-lua} LUA=${LUA:-lua}
rm -r target/* rm -r target/*
mkdir target &>/dev/null mkdir -p target/doc &>/dev/null
$LUA luapreproc.lua module/init.lua target/init.lua $LUA luapreproc.lua module/init.lua target/init.lua
echo _OSVERSION=\"PsychOS 2.0a2-$(git rev-parse --short HEAD)\" > target/version.lua echo _OSVERSION=\"PsychOS 2.0a2-$(git rev-parse --short HEAD)\" > target/version.lua
cat target/version.lua target/init.lua > target/tinit.lua cat target/version.lua target/init.lua > target/tinit.lua
mv target/tinit.lua target/init.lua mv target/tinit.lua target/init.lua
cp -r service/ lib/ cfg/ target/ cp -r service/ lib/ cfg/ target/
$LUA finddesc.lua $(find module/ -type f) $(find lib/ -type f) > apidoc.md
rm target/version.lua rm target/version.lua
rm -r doc/ &>/dev/null
$LUA finddesc.lua doc/ $(find lib/ module/ -type f|sort)
$LUA gendoc.lua target/doc/kernel.dict $(find module/ -type f|sort)
pandoc doc/apidoc.md docs-metadata.yml --template=template.tex -o doc/apidoc.pdf

3
docs-metadata.yml Normal file
View File

@ -0,0 +1,3 @@
---
title: 'PsychOS API Documentation'
---

View File

@ -1,27 +1,60 @@
#!/usr/bin/env lua
local doc = require "lib/doc"
local tA = {...} local tA = {...}
local docfiles = {}
for _,file in pairs(tA) do local outpath = table.remove(tA,1)
docfiles[file] = {}
local f = io.open(file) print(outpath)
local lines = {}
for l in f:read("*a"):gmatch("[^\n]+") do local function formatDocs(fd)
if l:find("function") and not l:find("local") then local rs = ""
lines[#lines+1] = l for name,finfo in pairs(fd) do
if rs:len() > 0 then
rs = rs .. "\n\n"
end end
local as = ""
for k,v in pairs(finfo.args) do
if k > 1 then
as = as .. ", "
end
as = as .. v[1]
if v[2] then
as = as .. "^"..v[2].."^"
end
end
local rt = ""
for k,v in pairs(finfo.outtypes or {}) do
if rt:len() > 0 then
rt = rt .. ", "
else
rt = ": "
end
rt = rt .. v
end
rs = string.format("%s## %s(%s)%s\n%s",rs,name,as,rt,finfo.description)
end end
for k,v in pairs(lines) do return rs
local name, args, desc = v:match("function%s+(.+)%s*%((.*)%)%s*%-%-%s*(.+)") end
if name and args and desc then
docfiles[file][#docfiles[file]+1] = string.format("## %s(%s)\n%s\n",name,args,desc) os.execute("sh -c 'mkdir -p "..outpath .. "'")
end if outpath:sub(#outpath) == "/" then outpath = outpath:sub(1, #outpath - 1) end
local ad = io.open(outpath.."/apidoc.md","w")
for k,v in pairs(tA) do
local fd = doc.parsefile(v)
local ds = formatDocs(fd)
print(string.format("%s: %i",v,ds:len()))
if ds and ds:len() > 0 then
os.execute("sh -c 'mkdir -p $(dirname \""..outpath.."/"..v.."\")'")
local f = io.open(outpath.."/"..v:gsub("%.lua$",".md"),"wb")
f:write(string.format("# %s\n\n",v))
f:write(ds)
f:write("\n\n")
f:close()
ad:write(string.format("# %s\n\n",v))
ad:write(ds)
ad:write("\n\n")
end end
end end
for k,v in pairs(docfiles) do ad:close()
if #v > 0 then
print("\n# "..k)
for l,m in pairs(v) do
print(m)
end
end
end

51
gendoc.lua Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env lua
local doc = require "lib/doc"
local ser = require "lib/serialization"
local tA = {...}
local outpath = table.remove(tA,1)
print(outpath)
local function formatDocs(fd)
local rs = ""
for name,finfo in pairs(fd) do
if rs:len() > 0 then
rs = rs .. "\n\n"
end
local as = ""
for k,v in pairs(finfo.args) do
if k > 1 then
as = as .. ", "
end
as = as .. v[1]
if v[2] then
as = as .. "^"..v[2].."^"
end
end
local rt = ""
for k,v in pairs(finfo.outtypes or {}) do
if rt:len() > 0 then
rt = rt .. ", "
else
rt = ": "
end
rt = rt .. v
end
rs = string.format("%s## %s(%s)%s\n%s",rs,name,as,rt,finfo.description)
end
return rs
end
local ad = io.open(outpath,"wb")
for k,v in pairs(tA) do
local fd = doc.parsefile(v)
local ds = ser.serialize(fd)
local tn = v:match("/(.+)$")
if ds:len() > 3 then
ad:write(tn.."\t"..ds:gsub("\n",""):gsub(", +",",").."\n")
end
end
ad:close()

View File

@ -1,3 +1,4 @@
local _,serial = pcall(require,"serialization")
local doc = {} local doc = {}
doc.searchers = {} doc.searchers = {}
doc.tctab = { doc.tctab = {
@ -96,6 +97,20 @@ function doc.searchers.lib(name) -- string -- string string -- Tries to find a d
if not dt then return false, "unable to find documentation for "..tostring(name) end if not dt then return false, "unable to find documentation for "..tostring(name) end
return doc.format(dt) return doc.format(dt)
end end
function doc.searchers.cdoc(topic) -- string -- string string -- Searches for documentation labelled as *topic* in .dict files under /boot/doc/
if not serial then return end
for k,v in ipairs(fs.list("/boot/doc")) do
if v:sub(-5) == ".dict" then
local f=io.open("/boot/doc/"..v,"rb")
for line in f:lines() do
local mname, docs = line:match("^(.-)\t(.+)$")
if mname == topic or mname == topic..".lua" then
return doc.format(serial.unserialize(docs))
end
end
end
end
end
function doc.docs(topic) -- string -- boolean -- Displays the documentation for *topic*, returning true, or errors. Also callable as just doc(). function doc.docs(topic) -- string -- boolean -- Displays the documentation for *topic*, returning true, or errors. Also callable as just doc().
local lib = os.getenv("LIB") or "/boot/lib" local lib = os.getenv("LIB") or "/boot/lib"

View File

@ -1,5 +1,5 @@
local event = {} local event = {}
function event.pull(t,...) -- return an event, optionally with timeout *t* and filter *...*. function event.pull(t,...) -- number -- -- return an event, optionally with timeout *t* and filter *...*.
local tA = {...} local tA = {...}
if type(t) == "string" then if type(t) == "string" then
table.insert(tA,1,t) table.insert(tA,1,t)
@ -26,7 +26,7 @@ function event.pull(t,...) -- return an event, optionally with timeout *t* and f
return nil return nil
end end
function event.listen(e,f) -- run function *f* for every occurance of event *e* function event.listen(e,f) -- string function -- -- run function *f* for every occurance of event *e*
os.spawn(function() while true do os.spawn(function() while true do
local tEv = {coroutine.yield()} local tEv = {coroutine.yield()}
if tEv[1] == e then if tEv[1] == e then
@ -36,7 +36,7 @@ function event.listen(e,f) -- run function *f* for every occurance of event *e*
end end,string.format("[%d] %s listener",os.pid(),e)) end end,string.format("[%d] %s listener",os.pid(),e))
end end
function event.ignore(e,f) -- stop function *f* running for every occurance of event *e* function event.ignore(e,f) -- string function -- -- stop function *f* running for every occurance of event *e*
computer.pushSignal("unlisten",e,tostring(f)) computer.pushSignal("unlisten",e,tostring(f))
end end

View File

@ -12,7 +12,7 @@ net.minport = 32768
net.maxport = 65535 net.maxport = 65535
net.openports = {} net.openports = {}
function net.genPacketID() -- generate a random 16-character string, for use in packet IDs function net.genPacketID() -- -- string -- generate a random 16-character string, for use in packet IDs
local npID = "" local npID = ""
for i = 1, 16 do for i = 1, 16 do
npID = npID .. string.char(math.random(32,126)) npID = npID .. string.char(math.random(32,126))
@ -20,11 +20,11 @@ function net.genPacketID() -- generate a random 16-character string, for use in
return npID return npID
end end
function net.usend(to,port,data,npID) -- send an unreliable packet to host *to* on port *port* with data *data*, optionally with the packet ID *npID* function net.usend(to,port,data,npID) -- string number string string -- -- send an unreliable packet to host *to* on port *port* with data *data*, optionally with the packet ID *npID*
computer.pushSignal("net_send",0,to,port,data,npID) computer.pushSignal("net_send",0,to,port,data,npID)
end end
function net.rsend(to,port,data,block) -- send a reliable packet to host *to* on port *port* with data *data*, with *block* set to true to disable blocking function net.rsend(to,port,data,block) -- string number string boolean -- boolean -- send a reliable packet to host *to* on port *port* with data *data*, with *block* set to true to disable blocking
local pid, stime = net.genPacketID(), computer.uptime() + net.streamdelay local pid, stime = net.genPacketID(), computer.uptime() + net.streamdelay
computer.pushSignal("net_send",1,to,port,data,pid) computer.pushSignal("net_send",1,to,port,data,pid)
if block then return false end if block then return false end
@ -37,7 +37,7 @@ end
-- ordered packet delivery, layer 4? -- ordered packet delivery, layer 4?
function net.send(to,port,ldata) -- send arbitrary data *ldata* reliably to host *to* on port *port* function net.send(to,port,ldata) -- string number string -- boolean -- send arbitrary data *ldata* reliably to host *to* on port *port*
local tdata = {} local tdata = {}
if ldata:len() > net.mtu then if ldata:len() > net.mtu then
for i = 1, ldata:len(), net.mtu do for i = 1, ldata:len(), net.mtu do
@ -112,7 +112,7 @@ local function socket(addr,port,sclose)
return conn return conn
end end
function net.open(to,port) -- open a socket to host *to* on port *port* function net.open(to,port) -- string number -- buffer -- open a socket to host *to* on port *port*
if not net.rsend(to,port,"openstream") then return false, "no ack from host" end if not net.rsend(to,port,"openstream") then return false, "no ack from host" end
local st = computer.uptime()+net.streamdelay local st = computer.uptime()+net.streamdelay
local est = false local est = false
@ -139,7 +139,7 @@ function net.open(to,port) -- open a socket to host *to* on port *port*
return socket(to,data,sclose) return socket(to,data,sclose)
end end
function net.listen(port) -- listen for connections on port *port* in a blocking manner function net.listen(port) -- number -- buffer -- listen for connections on port *port* in a blocking manner
repeat repeat
_, from, rport, data = event.pull("net_msg") _, from, rport, data = event.pull("net_msg")
until rport == port and data == "openstream" until rport == port and data == "openstream"
@ -150,7 +150,7 @@ function net.listen(port) -- listen for connections on port *port* in a blocking
return socket(from,nport,sclose) return socket(from,nport,sclose)
end end
function net.flisten(port,listener) -- run function *listener* on a connection to *port* function net.flisten(port,listener) -- number function -- function -- run function *listener* on a connection to *port*
local function helper(_,from,rport,data) local function helper(_,from,rport,data)
if rport == port and data == "openstream" then if rport == port and data == "openstream" then
local nport = math.random(net.minport,net.maxport) local nport = math.random(net.minport,net.maxport)

View File

@ -22,7 +22,7 @@ local function saveConfig()
return true return true
end end
function rc.load(name,force) function rc.load(name,force) -- string boolean -- table -- Attempts to load service *name*, and if *force* is true, replaces the current instance.
if force then if force then
rc.stop(name) rc.stop(name)
service[name] = nil service[name] = nil
@ -35,7 +35,7 @@ function rc.load(name,force)
return res return res
end end
function rc.stop(name,...) function rc.stop(name,...) -- string -- boolean string -- Stops service *name*, supplying *...* to the stop function. Returns false and a reason if this fails.
if not service[name] then return false, "service not found" end if not service[name] then return false, "service not found" end
if service[name].stop then if service[name].stop then
service[name].stop(...) service[name].stop(...)
@ -47,7 +47,7 @@ function rc.stop(name,...)
rc.pids[name] = nil rc.pids[name] = nil
end end
function rc.start(name,...) function rc.start(name,...) -- string -- boolean string -- Stops service *name*, supplying *...* to the stop function. Returns false and a reason if this fails.
rc.load(name) rc.load(name)
if not service[name] then return false, "service not found" end if not service[name] then return false, "service not found" end
local rv = {service[name].start(...)} local rv = {service[name].start(...)}
@ -56,19 +56,19 @@ function rc.start(name,...)
end end
end end
function rc.restart(name) function rc.restart(name) -- string -- -- Restarts service *name* using rc.stop and rc.start.
rc.stop(name) rc.stop(name)
rc.start(name) rc.start(name)
end end
function rc.enable(name) function rc.enable(name) -- string -- -- Enables service *name* being started on startup.
for k,v in pairs(cfg.enabled) do for k,v in pairs(cfg.enabled) do
if v == name then return false end if v == name then return false end
end end
cfg.enabled[#cfg.enabled+1] = name cfg.enabled[#cfg.enabled+1] = name
saveConfig() saveConfig()
end end
function rc.disable(name) function rc.disable(name) -- string -- -- Disables service *name* being started on startup.
local disabled = false local disabled = false
for k,v in pairs(cfg.enabled) do for k,v in pairs(cfg.enabled) do
if v == name then table.remove(cfg.enabled,k) disabled = true break end if v == name then table.remove(cfg.enabled,k) disabled = true break end

View File

@ -27,7 +27,7 @@ function rpcf.list()
return rt return rt
end end
function rpc.call(hostname,fn,...) function rpc.call(hostname,fn,...) -- string string -- boolean -- Calls exported function *fn* on host *hostname*, with parameters *...*, returning whatever the function returns, or false.
if hostname == "localhost" then if hostname == "localhost" then
return rpcf[fn](...) return rpcf[fn](...)
end end
@ -44,7 +44,7 @@ function rpc.call(hostname,fn,...)
end end
return false return false
end end
function rpc.proxy(hostname,filter) function rpc.proxy(hostname,filter) -- string string -- table -- Returns a component.proxy()-like table from the functions on *hostname* with names matching *filter*.
filter=(filter or "").."(.+)" filter=(filter or "").."(.+)"
local fnames = rpc.call(hostname,"list") local fnames = rpc.call(hostname,"list")
if not fnames then return false end if not fnames then return false end
@ -59,7 +59,7 @@ function rpc.proxy(hostname,filter)
end end
return rt return rt
end end
function rpc.register(name,fn) function rpc.register(name,fn) -- string function -- -- Registers a function to be exported by the RPC library.
local rpcrunning = false local rpcrunning = false
for k,v in pairs(os.tasks()) do for k,v in pairs(os.tasks()) do
if os.taskInfo(v).name == "rpc daemon" then if os.taskInfo(v).name == "rpc daemon" then

View File

@ -3,7 +3,7 @@ local local_pairs=function(tbl)
local mt=getmetatable(tbl) local mt=getmetatable(tbl)
return (mt and mt.__pairs or pairs)(tbl) return (mt and mt.__pairs or pairs)(tbl)
end end
function serial.serialize(value,af) -- serialize *value* into a string. If *af* is true, allow functions. This breaks unserialization. function serial.serialize(value,af) -- boolean -- string -- serialize *value* into a string. If *af* is true, allow functions. This breaks unserialization.
local kw={["and"]=true,["break"]=true,["do"]=true,["else"]=true,["elseif"]=true,["end"]=true,["false"]=true,["for"]=true,["function"]=true,["goto"]=true,["if"]=true,["in"]=true,["local"]=true,["nil"]=true,["not"]=true,["or"]=true,["repeat"]=true,["return"]=true,["then"]=true,["true"]=true,["until"]=true,["while"]=true} local kw={["and"]=true,["break"]=true,["do"]=true,["else"]=true,["elseif"]=true,["end"]=true,["false"]=true,["for"]=true,["function"]=true,["goto"]=true,["if"]=true,["in"]=true,["local"]=true,["nil"]=true,["not"]=true,["or"]=true,["repeat"]=true,["return"]=true,["then"]=true,["true"]=true,["until"]=true,["while"]=true}
local id="^[%a_][%w_]*$" local id="^[%a_][%w_]*$"
local ts={} local ts={}
@ -40,7 +40,7 @@ function serial.serialize(value,af) -- serialize *value* into a string. If *af*
else error("ut "..t) end end else error("ut "..t) end end
return s(value, 1) return s(value, 1)
end end
function serial.unserialize(data) -- return *data*, but unserialized function serial.unserialize(data) -- string -- -- return *data*, but unserialized
checkArg(1, data, "string") checkArg(1, data, "string")
local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}}) local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}})
if not result then return nil, reason end if not result then return nil, reason end

View File

@ -19,7 +19,7 @@ local function wrapUnits(n)
return tostring(math.floor(n))..(scale[count] or "") return tostring(math.floor(n))..(scale[count] or "")
end end
function shutil.import(lib) function shutil.import(lib) -- string -- boolean -- Imports the functions from library *lib* into the shell environment.
local cE = os.getenv("INCLUDE") or shell.include local cE = os.getenv("INCLUDE") or shell.include
local nE = {} local nE = {}
for k,v in pairs(cE) do for k,v in pairs(cE) do
@ -31,7 +31,7 @@ function shutil.import(lib)
return true return true
end end
function shutil.unimport(lib) function shutil.unimport(lib) -- string -- boolean -- Removes the functions from *lib* from the shell environment.
local cE = os.getenv("INCLUDE") or shell.include local cE = os.getenv("INCLUDE") or shell.include
local nE = {} local nE = {}
for k,v in pairs(cE) do for k,v in pairs(cE) do
@ -43,7 +43,7 @@ function shutil.unimport(lib)
return true return true
end end
function shutil.ls(...) function shutil.ls(...) -- string -- -- Prints contents of directories specified as *...*.
local tA = {...} local tA = {...}
if not tA[1] then tA[1] = "." end if not tA[1] then tA[1] = "." end
for _,d in ipairs(tA) do for _,d in ipairs(tA) do
@ -56,7 +56,7 @@ function shutil.ls(...)
end end
end end
function shutil.cat(...) function shutil.cat(...) -- string -- -- Outputs the contents of files specified in *...* to the standard output.
for _,fn in ipairs({...}) do for _,fn in ipairs({...}) do
local f = io.open(fn,"rb") local f = io.open(fn,"rb")
io.write(f:read("*a")) io.write(f:read("*a"))
@ -64,7 +64,7 @@ function shutil.cat(...)
end end
end end
function shutil.ps() function shutil.ps() -- Prints the processes running on the system.
print("PID# Parent | Name") print("PID# Parent | Name")
for k,v in pairs(os.tasks()) do for k,v in pairs(os.tasks()) do
local t = os.taskInfo(v) local t = os.taskInfo(v)
@ -72,7 +72,7 @@ function shutil.ps()
end end
end end
function shutil.df() function shutil.df() -- Prints free disk space.
local mt = fs.mounts() local mt = fs.mounts()
local ml = 0 local ml = 0
for k,v in pairs(mt) do for k,v in pairs(mt) do
@ -88,7 +88,7 @@ function shutil.df()
end end
end end
function shutil.mount(addr,path) function shutil.mount(addr,path) -- string string -- boolean string -- Mounts filesystem component with address *addr* to *path* in the filesystem.
if not addr then if not addr then
local mt = fs.mounts() local mt = fs.mounts()
for k,v in pairs(mt) do for k,v in pairs(mt) do
@ -114,7 +114,7 @@ function shutil.mount(addr,path)
end end
end end
function shutil.free() function shutil.free() -- Displays used and free memory.
print("Total Used Free") print("Total Used Free")
print(string.format("%5s %5s %5s",wrapUnits(computer.totalMemory()),wrapUnits(computer.totalMemory()-computer.freeMemory()),wrapUnits(computer.freeMemory()))) print(string.format("%5s %5s %5s",wrapUnits(computer.totalMemory()),wrapUnits(computer.totalMemory()-computer.freeMemory()),wrapUnits(computer.freeMemory())))
end end

View File

@ -4,7 +4,7 @@ local function normalise(path)
return table.concat(fs.segments(path),"/") return table.concat(fs.segments(path),"/")
end end
function unionfs.create(...) function unionfs.create(...) -- string -- table -- Returns a unionfs object of the directories specified in *...*.
local paths,fids,fc = {...}, {}, 0 local paths,fids,fc = {...}, {}, 0
for k,v in pairs(paths) do for k,v in pairs(paths) do
paths[k] = "/"..normalise(v) paths[k] = "/"..normalise(v)

View File

@ -1,5 +1,5 @@
local vtansi = {} local vtansi = {}
function vtansi.vtemu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF} local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
local mx, my = gpu.maxResolution() local mx, my = gpu.maxResolution()
local cx, cy = 1, 1 local cx, cy = 1, 1
@ -142,7 +142,7 @@ function vtansi.vtemu(gpu) -- takes GPU component proxy *gpu* and returns a func
return termwrite return termwrite
end end
function vtansi.vtsession(gpua,scra) -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions. function vtansi.vtsession(gpua,scra) -- string string -- table -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
local gpu = component.proxy(gpua) local gpu = component.proxy(gpua)
gpu.bind(scra) gpu.bind(scra)
local write = vtansi.vtemu(gpu) local write = vtansi.vtemu(gpu)

View File

@ -181,6 +181,7 @@ env:process(arg[1])
local tmpfile = os.tmpname() local tmpfile = os.tmpname()
if tmpfile:sub(#tmpfile) == "." then tmpfile = tmpfile:sub(1, #tmpfile - 1) end if tmpfile:sub(#tmpfile) == "." then tmpfile = tmpfile:sub(1, #tmpfile - 1) end
local tmpf = io.open(tmpfile, "wb") local tmpf = io.open(tmpfile, "wb")
env.code = env.code:gsub("%-%-.-\n","\n"):gsub("\n\n","\n")
tmpf:write(env.code) tmpf:write(env.code)
tmpf:close() tmpf:close()
--if (os.execute("lua minify.lua "..tmpfile.." > "..arg[2])) then --if (os.execute("lua minify.lua "..tmpfile.." > "..arg[2])) then

View File

@ -2,7 +2,7 @@
buffer = {} buffer = {}
function buffer.new(mode, stream) -- create a new buffer in mode *mode* backed by stream object *stream* function buffer.new(mode, stream) -- string table -- table -- create a new buffer in mode *mode* backed by stream object *stream*
local result = { local result = {
mode = {}, mode = {},
stream = stream, stream = stream,

View File

@ -64,7 +64,7 @@ end
devfs.component.address = "devfs" devfs.component.address = "devfs"
devfs.component.type = "devfs" devfs.component.type = "devfs"
function devfs.register(fname,fopen) -- Register a new devfs node with the name *fname* that will run the function *fopen* when opened. This function should return a function for read, a function for write, function for close, and optionally, a function for seek, in that order. function devfs.register(fname,fopen) -- string function -- -- Register a new devfs node with the name *fname* that will run the function *fopen* when opened. This function should return a function for read, a function for write, function for close, and optionally, a function for seek, in that order.
devfs.files[fname] = fopen devfs.files[fname] = fopen
end end

View File

@ -3,14 +3,14 @@ fs = {}
local fsmounts = {} local fsmounts = {}
-- basics -- basics
function fs.segments(path) -- splits *path* on each / function fs.segments(path) -- string -- table -- Splits *path* on each /
local segments = {} local segments = {}
for segment in path:gmatch("[^/]+") do for segment in path:gmatch("[^/]+") do
segments[#segments+1] = segment segments[#segments+1] = segment
end end
return segments return segments
end end
function fs.resolve(path) -- resolves *path* to a specific filesystem mount and path function fs.resolve(path) -- string -- string string -- Resolves *path* to a specific filesystem mount and path
if not path or path == "." then path = os.getenv("PWD") end if not path or path == "." then path = os.getenv("PWD") end
if path:sub(1,1) ~= "/" then path=(os.getenv("PWD") or "").."/"..path end if path:sub(1,1) ~= "/" then path=(os.getenv("PWD") or "").."/"..path end
local segments, rpath, rfs= fs.segments(path) local segments, rpath, rfs= fs.segments(path)
@ -44,7 +44,7 @@ local function fclose(self)
return fsmounts[self.fs].close(self.fid) return fsmounts[self.fs].close(self.fid)
end end
function fs.open(path,mode) -- opens file *path* with mode *mode* function fs.open(path,mode) -- string string -- table -- Opens file *path* with mode *mode*, returning a file object.
mode = mode or "rb" mode = mode or "rb"
local fsi,path = fs.resolve(path) local fsi,path = fs.resolve(path)
if not fsmounts[fsi] then return false end if not fsmounts[fsi] then return false end
@ -62,7 +62,7 @@ function fs.open(path,mode) -- opens file *path* with mode *mode*
return false return false
end end
function fs.copy(from,to) -- copies a file from *from* to *to* function fs.copy(from,to) -- string string -- boolean -- copies a file from *from* to *to*
local of = fs.open(from,"rb") local of = fs.open(from,"rb")
local df = fs.open(to,"wb") local df = fs.open(to,"wb")
if not of or not df then if not of or not df then
@ -71,33 +71,34 @@ function fs.copy(from,to) -- copies a file from *from* to *to*
df:write(of:read("*a")) df:write(of:read("*a"))
df:close() df:close()
of:close() of:close()
return true
end end
function fs.rename(from,to) -- moves file *from* to *to* function fs.rename(from,to) -- string string -- boolean -- Moves file *from* to *to*
local ofsi, opath = fs.resolve(from) local ofsi, opath = fs.resolve(from)
local dfsi, dpath = fs.resolve(to) local dfsi, dpath = fs.resolve(to)
if ofsi == dfsi then if ofsi == dfsi then
fsmounts[ofsi].rename(opath,dpath) fsmounts[ofsi].rename(opath,dpath)
return true return true
end end
fs.copy(from,to) if not fs.copy(from,to) then return false end
fs.remove(from) if not fs.remove(from) then return false end
return true return true
end end
function fs.mount(path,proxy) -- mounts the filesystem *proxy* to the mount point *path* if it is a directory. BYO proxy. function fs.mount(path,proxy) -- string table -- boolean -- Mounts the filesystem *proxy* to the mount point *path* if it is a directory. BYO proxy.
if fs.isDirectory(path) and not fsmounts[table.concat(fs.segments(path),"/")] then if fs.isDirectory(path) and not fsmounts[table.concat(fs.segments(path),"/")] then
fsmounts[table.concat(fs.segments(path),"/")] = proxy fsmounts[table.concat(fs.segments(path),"/")] = proxy
return true return true
end end
return false, "path is not a directory" return false, "path is not a directory"
end end
function fs.umount(path) function fs.umount(path) -- string -- -- Unmounts filesystem from *path*.
local fsi,_ = fs.resolve(path) local fsi,_ = fs.resolve(path)
fsmounts[fsi] = nil fsmounts[fsi] = nil
end end
function fs.mounts() -- returns a table containing the mount points of all mounted filesystems function fs.mounts() -- -- table -- Returns a table containing the mount points of all mounted filesystems
local rt = {} local rt = {}
for k,v in pairs(fsmounts) do for k,v in pairs(fsmounts) do
rt[#rt+1] = k,v.address or "unknown" rt[#rt+1] = k,v.address or "unknown"
@ -105,11 +106,11 @@ function fs.mounts() -- returns a table containing the mount points of all mount
return rt return rt
end end
function fs.address(path) -- returns the address of the filesystem at a given path, if applicable; do not expect a sensical response function fs.address(path) -- string -- string -- Returns the address of the filesystem at a given path, if applicable; do not expect a sensical response
local fsi,_ = fs.resolve(path) local fsi,_ = fs.resolve(path)
return fsmounts[fsi].address return fsmounts[fsi].address
end end
function fs.type(path) -- returns the component type of the filesystem at a given path, if applicable function fs.type(path) -- string -- string -- Returns the component type of the filesystem at a given path, if applicable
local fsi,_ = fs.resolve(path) local fsi,_ = fs.resolve(path)
return fsmounts[fsi].type or "filesystem" return fsmounts[fsi].type or "filesystem"
end end

View File

@ -1,12 +1,12 @@
io = {} io = {}
function io.open(path,mode) -- Open file *path* in *mode*. Returns a buffer object. function io.open(path,mode) -- string string -- table -- Open file *path* in *mode*. Returns a buffer object.
local f,e = fs.open(path, mode) local f,e = fs.open(path, mode)
if not f then return false, e end if not f then return false, e end
return buffer.new(mode,f) return buffer.new(mode,f)
end end
function io.input(fd) -- Sets the default input stream to *fd* if provided, either as a buffer as a path. Returns the default input stream. function io.input(fd) -- table -- table -- Sets the default input stream to *fd* if provided, either as a buffer as a path. Returns the default input stream.
if type(fd) == "string" then if type(fd) == "string" then
fd=io.open(fd,"rb") fd=io.open(fd,"rb")
end end
@ -15,7 +15,7 @@ function io.input(fd) -- Sets the default input stream to *fd* if provided, eith
end end
return os.getenv("STDIN") return os.getenv("STDIN")
end end
function io.output(fd) -- Sets the default output stream to *fd* if provided, either as a buffer as a path. Returns the default output stream. function io.output(fd) -- table -- table -- Sets the default output stream to *fd* if provided, either as a buffer as a path. Returns the default output stream.
if type(fd) == "string" then if type(fd) == "string" then
fd=io.open(fd,"wb") fd=io.open(fd,"wb")
end end

View File

@ -1,19 +1,19 @@
function loadfile(p) -- reads file *p* and returns a function if possible function loadfile(p) -- string -- function -- reads file *p* and returns a function if possible
local f = io.open(p,"rb") local f = io.open(p,"rb")
local c = f:read("*a") local c = f:read("*a")
f:close() f:close()
return load(c,p,"t") return load(c,p,"t")
end end
function runfile(p,...) -- runs file *p* with arbitrary arguments in the current thread function runfile(p,...) -- string -- -- runs file *p* with arbitrary arguments in the current thread
return loadfile(p)(...) return loadfile(p)(...)
end end
function os.spawnfile(p,n,...) -- spawns a new process from file *p* with name *n*, with arguments following *n*. function os.spawnfile(p,n,...) -- string string -- number -- spawns a new process from file *p* with name *n*, with arguments following *n*.
local tA = {...} local tA = {...}
return os.spawn(function() local res={pcall(loadfile(p), table.unpack(tA))} computer.pushSignal("process_finished", os.pid(), table.unpack(res)) dprint(table.concat(res)) end,n or p) return os.spawn(function() local res={pcall(loadfile(p), table.unpack(tA))} computer.pushSignal("process_finished", os.pid(), table.unpack(res)) dprint(table.concat(res)) end,n or p)
end end
_G.package = {} _G.package = {}
package.loaded = {computer=computer,component=component,fs=fs,buffer=buffer} package.loaded = {computer=computer,component=component,fs=fs,buffer=buffer}
function require(f,force) -- searches for a library with name *f* and returns what the library returns, if possible. if *force* is set, loads the library even if it is cached function require(f,force) -- string boolean -- table -- searches for a library with name *f* and returns what the library returns, if possible. if *force* is set, loads the library even if it is cached
if not package.loaded[f] or force then if not package.loaded[f] or force then
local lib = os.getenv("LIB") or "/boot/lib" local lib = os.getenv("LIB") or "/boot/lib"
for d in lib:gmatch("[^\n]+") do for d in lib:gmatch("[^\n]+") do
@ -29,6 +29,6 @@ function require(f,force) -- searches for a library with name *f* and returns wh
end end
error("library not found: "..f) error("library not found: "..f)
end end
function reload(f) function reload(f) -- string -- table -- Reloads library *f* from disk into memory.
return require(f,true) return require(f,true)
end end

View File

@ -1,141 +0,0 @@
function vt100emu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
local mx, my = gpu.maxResolution()
local cx, cy = 1, 1
local pc = " "
local lc = ""
local mode = 0 -- 0 normal, 1 escape, 2 command
local lw = true
local sx, sy = 1,1
local cs = ""
local bg, fg = 0, 0xFFFFFF
-- setup
gpu.setResolution(mx,my)
gpu.fill(1,1,mx,my," ")
local function checkCursor()
if cx > mx and lw then
cx, cy = 1, cy+1
end
if cy > my then
gpu.copy(1,2,mx,my-1,0,-1)
gpu.fill(1,my,mx,1," ")
cy=my
end
if cy < 1 then cy = 1 end
if cx < 1 then cx = 1 end
end
local function termwrite(s)
local wb = ""
local lb, ec = nil, nil
local function flushwb()
while wb:len() > 0 do
checkCursor()
local wl = wb:sub(1,mx-cx+1)
wb = wb:sub(wl:len()+1)
gpu.set(cx, cy, wl)
cx = cx + wl:len()
end
end
local rs = ""
s=s:gsub("\8","\27[D")
pc = gpu.get(cx,cy)
gpu.setForeground(fg)
gpu.setBackground(bg)
gpu.set(cx,cy,pc)
for cc in s:gmatch(".") do
if mode == 0 then
if cc == "\n" then
flushwb()
cx,cy = 1, cy+1
elseif cc == "\t" then
wb=wb..(" "):rep(8*((cx+9)//8))
elseif cc == "\27" then
flushwb()
mode = 1
else
wb = wb .. cc
end
elseif mode == 1 then
if cc == "[" then
mode = 2
else
mode = 0
end
elseif mode == 2 then
if cc:match("[%d;]") then
cs = cs .. cc
else
mode = 0
local tA = {}
for s in cs:gmatch("%d+") do
tA[#tA+1] = tonumber(s)
end
if cc == "H" then
cx, cy = tA[1] or 1, tA[2] or 1
elseif cc == "A" then
cy = cy - (tA[1] or 1)
elseif cc == "B" then
cy = cy + (tA[1] or 1)
elseif cc == "C" then
cx = cx + (tA[1] or 1)
elseif cc == "D" then
cx = cx - (tA[1] or 1)
elseif cc == "s" then
sx, sy = cx, cy
elseif cc == "u" then
cx, cy = sx, sy
elseif cc == "n" and tA[1] == 6 then
rs = string.format("%s\27[%d;%dR",rs,cx,cy)
elseif cc == "K" and tA[1] == 1 then
gpu.fill(1,cy,cx,1," ")
elseif cc == "K" and tA[1] == 2 then
gpu.fill(cx,cy,mx,1," ")
elseif cc == "K" then
gpu.fill(1,cy,mx,1," ")
elseif cc == "J" and tA[1] == 1 then
gpu.fill(1,1,mx,cy," ")
elseif cc == "J" and tA[1] == 2 then
gpu.fill(1,1,mx,my," ")
cx, cy = 1, 1
elseif cc == "J" then
gpu.fill(1,cy,mx,my," ")
elseif cc == "m" then
for _,num in ipairs(tA) do
if num == 0 then
fg,bg,ec,lb = 0xFFFFFF,0,true,true
elseif num == 7 then
local nfg,nbg = bg, fg
fg, bg = nfg, nbg
elseif num > 29 and num < 38 then
fg = colours[num-29]
elseif num > 39 and num < 48 then
bg = colours[num-39]
elseif num == 100 then -- disable local echo
ec = false
elseif num == 101 then -- disable line mode
lb = false
end
end
gpu.setForeground(fg)
gpu.setBackground(bg)
end
cs = ""
checkCursor()
end
end
end
flushwb()
checkCursor()
pc = gpu.get(cx,cy)
gpu.setForeground(bg)
gpu.setBackground(fg)
gpu.set(cx,cy,pc)
gpu.setForeground(fg)
gpu.setBackground(bg)
return rs, lb, ec
end
return termwrite
end

View File

@ -1,4 +1,4 @@
function os.chdir(p) -- changes the current working directory of the calling process to the directory specified in *p*, returning true or false, error function os.chdir(p) -- string -- boolean string -- changes the current working directory of the calling process to the directory specified in *p*, returning true or false, error
if not (p:sub(1,1) == "/") then if not (p:sub(1,1) == "/") then
local np = {} local np = {}
for k,v in pairs(fs.segments(os.getenv("PWD").."/"..p)) do for k,v in pairs(fs.segments(os.getenv("PWD").."/"..p)) do

View File

@ -1,6 +1,6 @@
do do
local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, event timeout, current PID local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, event timeout, current PID
function os.spawn(f,n) -- creates a process from function *f* with name *n* function os.spawn(f,n) -- function string -- number -- creates a process from function *f* with name *n*
tTasks[nPid] = { tTasks[nPid] = {
c=coroutine.create(f), -- actual coroutine c=coroutine.create(f), -- actual coroutine
n=n, -- process name n=n, -- process name
@ -16,20 +16,20 @@ function os.spawn(f,n) -- creates a process from function *f* with name *n*
nPid = nPid + 1 nPid = nPid + 1
return nPid - 1 return nPid - 1
end end
function os.kill(pid) -- removes process *pid* from the task list function os.kill(pid) -- number -- -- removes process *pid* from the task list
tTasks[pid] = nil tTasks[pid] = nil
end end
function os.pid() -- returns the current process' PID function os.pid() -- -- number -- returns the current process' PID
return cPid return cPid
end end
function os.tasks() -- returns a table of process IDs function os.tasks() -- -- table -- returns a table of process IDs
local rt = {} local rt = {}
for k,v in pairs(tTasks) do for k,v in pairs(tTasks) do
rt[#rt+1] = k rt[#rt+1] = k
end end
return rt return rt
end end
function os.taskInfo(pid) -- returns info on process *pid* as a table with name and parent values function os.taskInfo(pid) -- number -- table -- returns info on process *pid* as a table with name and parent values
pid = pid or os.pid() pid = pid or os.pid()
if not tTasks[pid] then return false end if not tTasks[pid] then return false end
return {name=tTasks[pid].n,parent=tTasks[pid].P} return {name=tTasks[pid].n,parent=tTasks[pid].P}

View File

@ -9,6 +9,7 @@ syslog.notice = 5
syslog.info = 6 syslog.info = 6
syslog.debug = 7 syslog.debug = 7
-- function syslog(msg, level, service) -- string number string -- -- Output *msg* to the system log, with severity *level*, from *service*.
local rdprint=dprint or function() end local rdprint=dprint or function() end
setmetatable(syslog,{__call = function(_,msg, level, service) setmetatable(syslog,{__call = function(_,msg, level, service)
level, service = level or syslog.info, service or (os.taskInfo(os.pid()) or {}).name or "unknown" level, service = level or syslog.info, service or (os.taskInfo(os.pid()) or {}).name or "unknown"

View File

@ -1,5 +0,0 @@
--#include "module/vt-task.lua"
do
local r,w = vtemu(component.list("gpu")(),component.list("screen")())
devfs.register("tty0", function() return r,w,function() end end)
end

View File

@ -1,58 +0,0 @@
do
--#include "module/nvt100.lua"
function vtemu(gpua,scra) -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
local gpu = component.proxy(gpua)
gpu.bind(scra)
local write = vt100emu(gpu)
local kba = {}
for k,v in ipairs(component.invoke(scra,"getKeyboards")) do
kba[v]=true
end
local buf, lbuf, echo = "", true, true
os.spawn(function() dprint(pcall(function()
while true do
local ty,ka,ch = coroutine.yield()
if ty == "key_down" and kba[ka] then
if ch == 13 then ch = 10 end
if ch == 8 then
if buf:len() > 0 then
if echo then write("\8 \8") end
buf = buf:sub(1,-2)
end
elseif ch > 0 then
if echo then write(string.char(ch)) end
buf=buf..string.char(ch)
end
end
end
end)) end,string.format("ttyd[%s:%s]",gpua:sub(1,8),scra:sub(1,8)))
local function bread(n)
local r
if lbuf then
while not buf:find("\n") do
coroutine.yield()
end
local n = buf:find("\n")
r, buf = buf:sub(1,n), buf:sub(n+1)
else
r = buf
buf = ""
coroutine.yield()
end
return r
end
local function bwrite(d)
local ba, lb, ec = write(d)
buf = buf .. ba
if lb ~= nil then
dprint("local buffer mode: "..tostring(lb))
lbuf = lb
end
if ec ~= nil then
dprint("echo mode: "..tostring(ec))
echo = ec
end
end
return bread, bwrite, function() io.write("\27[2J\27[H") end
end
end

View File

@ -1,148 +0,0 @@
function vt100emu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
local mx, my = gpu.maxResolution()
local cx, cy = 1, 1
local pc = " "
local lc = ""
local mode = "n"
local lw = true
local sx, sy = 1,1
local cs = ""
local bg, fg = 0, 0xFFFFFF
-- setup
gpu.setResolution(mx,my)
gpu.fill(1,1,mx,my," ")
local function termwrite(s)
local rs = ""
s=s:gsub("\8","\27[D")
pc = gpu.get(cx,cy)
gpu.setForeground(fg)
gpu.setBackground(bg)
gpu.set(cx,cy,pc)
for i = 1, s:len() do
local cc = s:sub(i,i)
if mode == "n" then
if cc == "\n" then -- line feed
cx, cy = 1, cy+1
elseif cc == "\r" then -- cursor home
cx = 1
elseif cc == "\27" then -- escape
mode = "e"
elseif cc == "\t" then
cx = 8*((cx+9)//8)
elseif string.byte(cc) > 31 and string.byte(cc) < 127 then -- printable, I guess
gpu.set(cx, cy, cc)
cx = cx + 1
end
elseif mode == "e" then
if cc == "[" then
mode = "v"
cs = ""
elseif cc == "D" then -- scroll down
gpu.copy(1,2,mx,my-1,0,-1)
gpu.fill(1,my,mx,1," ")
cy=cy+1
mode = "n"
elseif cc == "M" then -- scroll up
gpu.copy(1,1,mx,my-1,0,1)
gpu.fill(1,1,mx,1," ")
mode = "n"
else
mode = "n"
end
elseif mode == "v" then
mode = "n"
if cc == "s" then -- save cursor
sx, sy = cx, cy
elseif cc == "u" then -- restore cursor
cx, cy = sx, sy
elseif cc == "H" then -- cursor home or to
local tx, ty = cs:match("(%d+);(%d+)")
tx, ty = tx or "1", ty or "1"
cx, cy = tonumber(tx), tonumber(ty)
elseif cc == "A" then -- cursor up
cy = cy - (tonumber(cs) or 1)
elseif cc == "B" then -- cursor down
cy = cy + (tonumber(cs) or 1)
elseif cc == "C" then -- cursor right
cx = cx + (tonumber(cs) or 1)
elseif cc == "D" then -- cursor left
cx = cx - (tonumber(cs) or 1)
elseif cc == "h" and lc == "7" then -- enable line wrap
lw = true
elseif cc == "l" and lc == "7" then -- disable line wrap
lw = false
elseif cc == "c" then
rs = string.format("%s\27[%d;%d0c",rs,mx,my)
elseif cc == "n" and lc == "6" then
rs = string.format("%s\27[%d;%dR",rs,cx,cy)
elseif cc == "K" then
if lc == "1" then
gpu.fill(1,cy,cx,1," ")
elseif lc == "2" then
gpu.fill(cx,cy,mx,1," ")
else
gpu.fill(1,cy,mx,1," ")
end
elseif cc == "J" then
if lc == "1" then
gpu.fill(1,1,mx,cy," ")
elseif lc == "2" then
gpu.full(1,1,mx,my," ")
cx,cy = 1, 1
else
gpu.fill(1,cy,mx,my," ")
end
elseif cc == "m" then
for num in cs:gmatch("%d+") do
num=tonumber(num)
if num == 0 then
fg,bg = 0xFFFFFF,0
elseif num == 7 then
local nfg,nbg = bg, fg
fg, bg = nfg, nbg
elseif num > 29 and num < 38 then
fg = colours[num-29]
elseif num > 39 and num < 48 then
bg = colours[num-39]
end
end
gpu.setForeground(fg)
gpu.setBackground(bg)
else
cs = cs .. cc
if cc:match("[%d;]") then
mode = "v"
end
end
end
if cx > mx and lw then
cx, cy = 1, cy+1
end
if cy > my then
gpu.copy(1,2,mx,my-1,0,-1)
gpu.fill(1,my,mx,1," ")
cy=my
end
if cy < 1 then cy = 1 end
if cx < 1 then cx = 1 end
lc = cc
end
pc = gpu.get(cx,cy)
gpu.setForeground(bg)
gpu.setBackground(fg)
gpu.set(cx,cy,pc)
gpu.setForeground(fg)
gpu.setBackground(bg)
return rs
end
return termwrite
end

46
template.tex Normal file
View File

@ -0,0 +1,46 @@
\documentclass[11pt,twoside,a4paper]{article}
\usepackage{hyperref}
\usepackage{multicol}
\usepackage{standalone}
\usepackage{graphicx}
\usepackage{pdfpages}
\usepackage{listings}
\usepackage{color}
\usepackage{sectsty}
\usepackage[cm]{fullpage}
\lstset{
commentstyle=\color{cyan}, % comment style
keywordstyle=\color{cyan}, % keyword style
stringstyle=\color{red}, % string literal style
numbers=left, % where to put the line-numbers; possible values are (none, left, right)
numbersep=5pt, % how far the line-numbers are from the code
numberstyle=\tiny\color{gray}, % the style that is used for the line-numbers
}
\hypersetup{
colorlinks=true
}
\subsectionfont{\ttfamily}
% pandoc stuff
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\let\stdsection\section
\renewcommand\section{\newpage\stdsection}
\lstset{basicstyle=\footnotesize\ttfamily,breaklines=true}
\newcommand{\ignore}[1]{}
\title{$title$$if(thanks)$\thanks{$thanks$}$endif$}
\author{$for(author)$$author$$sep$ \and $endfor$}
\date{}
\begin{document}
\pagenumbering{gobble}
\maketitle
\newpage
\pagenumbering{alph}
\tableofcontents
\newpage
\pagenumbering{arabic}
$body$
\end{document}