-- vim: ft=terra
local util = dofile('common.lua')
local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')
lib = {
init = {};
load = function(lst)
for _, l in pairs(lst) do
local path = {}
for m in l:gmatch('([^:]+)') do path[#path+1]=m end
local tgt = lib
for i=1,#path-1 do
if tgt[path[i]] == nil then tgt[path[i]] = {} end
tgt = tgt[path[i]]
end
tgt[path[#path]] = terralib.loadfile(l:gsub(':','/') .. '.t')()
end
end;
loadlib = function(name,hdr)
local p = config.pkg[name]
-- for _,v in pairs(p.dylibs) do
-- terralib.linklibrary(p.libdir .. '/' .. v)
-- end
return terralib.includec(p.incdir .. '/' .. hdr)
end;
dispatch = function(tbl)
return macro(function(v,...)
for ty,fn in pairs(tbl) do
if v.tree.type == ty then return fn(v,...) end
end
return (tbl[false])(v,...)
end)
end;
emit_unitary = function(nl,fd,...)
local code = {}
for i,v in ipairs{...} do
if type(v) == 'string' or type(v) == 'number' then
local str = tostring(v)
code[#code+1] = `lib.io.send(2, str, [#str])
elseif type(v) == 'table' and #v == 2 then
code[#code+1] = `lib.io.send(2, [v[1]], [v[2]])
elseif v.tree:is 'constant' then
local str = tostring(v:asvalue())
code[#code+1] = `lib.io.send(2, str, [#str])
else
code[#code+1] = quote var n = v in
lib.io.send(2, n, lib.str.sz(n)) end
end
end
if nl then code[#code+1] = `lib.io.send(fd, '\n', 1) end
return code
end;
emitv = function(nl,fd,...)
local vec = {}
local defs = {}
for i,v in ipairs{...} do
local str, ct
if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then
if v.tree.type.convertible == 'tuple' then
str = `v._0
ct = `v._1
else
local n = symbol(v.tree.type)
defs[#defs + 1] = quote var [n] = v end
str = n
ct = `lib.str.sz(n)
end
else
if type(v) == 'string' or type(v) == 'number' then
str = tostring(v)
else--if v.tree:is 'constant' then
str = tostring(v:asvalue())
end
ct = ct or #str
end
vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct}
end
if nl then vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1} end
return quote
[defs]
var strs = array( [vec] )
in lib.uio.writev(fd, strs, [#vec]) end
end;
trn = macro(function(cond, i, e)
return quote
var c: bool = [cond]
var r: i.tree.type
if c == true then r = i else r = e end
in r end
end);
coalesce = macro(function(...)
local args = {...}
local ty = args[1].tree.type
local val = symbol(ty)
local empty if ty.type == 'integer'
then empty = `0
else empty = `nil
end
local exp = quote val = [empty] end
for i=#args, 1, -1 do
local v = args[i]
exp = quote
if [v] ~= [empty]
then val = v
else [exp]
end
end
end
local q = quote
var [val]
[exp]
in val end
return q
end);
proc = {
exit = terralib.externfunction('exit', int -> {});
getenv = terralib.externfunction('getenv', rawstring -> rawstring);
};
io = {
send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
recv = terralib.externfunction('read', {int, rawstring, intptr} -> ptrdiff);
say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end);
fmt = terralib.externfunction('printf',
terralib.types.funcpointer({rawstring},{int},true));
};
str = { sz = terralib.externfunction('strlen', rawstring -> intptr) };
copy = function(tbl)
local new = {}
for k,v in pairs(tbl) do new[k] = v end
setmetatable(new, getmetatable(tbl))
return new
end;
osclock = terralib.includec 'time.h';
}
if config.posix then
lib.uio = terralib.includec 'sys/uio.h';
lib.emit = lib.emitv -- use more efficient call where available
else lib.emit = lib.emit_unitary end
local starttime = global(lib.osclock.time_t)
local lastnoisetime = global(lib.osclock.time_t)
local noise = global(uint8,1)
local noise_header = function(code,txt,mod)
if mod then
return string.format('\27[%s;1m(%s %s)\27[m ', code,mod,txt)
else
return string.format('\27[%s;1m(%s)\27[m ', code,txt)
end
end
local terra timehdr()
var now = lib.osclock.time(nil)
var diff = now - lastnoisetime
if diff > 30 then -- print cur time
lastnoisetime = now
var curtime: int8[26]
lib.osclock.ctime_r(&now, &curtime[0])
for i=0,26 do if curtime[i] == @'\n' then curtime[i] = 0 break end end -- :/
[ lib.emit(false, 2, '\27[1m[', `&curtime[0], ']\27[;36m\n +00 ') ]
else -- print time since last msg
var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
end
end
local defrep = function(level,n,code)
return macro(function(...)
local q = lib.emit(true, 2, noise_header(code,n), ...)
return quote if noise >= level then timehdr(); [q] end end
end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
local q = lib.emit(true, 2, noise_header('31','fatal'), ...)
return quote
timehdr(); [q]
lib.proc.exit(1)
end
end);
lib.stat = terralib.memoize(function(ty)
local n = struct {
ok: bool
union {
error: uint8
val: ty
}
}
n.name = string.format("stat<%s>", ty.name)
n.stat_basetype = ty
return n
end)
lib.enum = function(tbl)
local ty = uint8
if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe
elseif #tbl >= 2^16 then ty = uint32
elseif #tbl >= 2^8 then ty = uint16 end
local o = { t = ty }
local strings = {}
for i, name in ipairs(tbl) do
o[name] = i - 1
strings[i] = `[lib.mem.ref(int8)]{ptr=[name], ct=[#name]}
end
o._str = terra(val: ty)
var l = array([strings])
return l[val]
end
return o
end
lib.set = function(tbl)
local bytes = math.ceil(#tbl / 8)
local o = {}
for i, name in ipairs(tbl) do o[name] = i end
local struct set { _store: uint8[bytes] }
local struct bit { _v: intptr _set: &set}
terra set:clear() for i=0,bytes do self._store[i] = 0 end end
terra set:fill() for i=0,bytes do self._store[i] = 0xFF end end
set.members = tbl
set.name = string.format('set<%s>', table.concat(tbl, '|'))
set.metamethods.__entrymissing = macro(function(val, obj)
if o[val] == nil then error('value ' .. val .. ' not in set') end
return `bit { _v=[o[val] - 1], _set = &(obj) }
end)
terra set:sz()
var ct: intptr = 0
for i = 0, [#tbl] do
if (self._store[i/8] and (1 << i % 8)) ~= 0 then ct = ct + 1 end
end
return ct
end
set.methods.dump = macro(function(self)
local q = quote lib.io.say('dumping set:\n') end
for i,v in ipairs(tbl) do
q = quote
[q]
if [bool](self.[v])
then lib.io.say([' - ' .. v .. ': true\n'])
else lib.io.say([' - ' .. v .. ': false\n'])
end
end
end
return q
end)
set.metamethods.__add = macro(function(self,other)
local new = symbol(set)
local q = quote var [new] new:clear() end
for i = 0, bytes - 1 do
q = quote [q]
new._store[i] = self._store[i] or other._store[i]
end
end
return quote [q] in new end
end)
bit.metamethods.__cast = function(from,to,e)
local q = quote var s = e
in (s._set._store[s._v/8] and (1 << s._v % 8)) end
if to == bit then error('casting to bit is not meaningful')
elseif to == bool then return `([q] ~= 0)
elseif to:isintegral() then return q
elseif from == bit then error('cannot cast bit to ' .. tostring(to))
else return nil end
end
bit.metamethods.__apply = terra(self: &bit): bool return @self end
bit.metamethods.__lshift = terra(self: &bit, hl: bool)
var byte = self._v / 8
var bit = self._v % 8
if hl then
self._set._store[byte] = self._set._store[byte] or (1 << bit)
else
self._set._store[byte] = self._set._store[byte] and not (1 << bit)
end
end
return set
end
lib.err = lib.loadlib('mbedtls','mbedtls/error.h')
lib.rsa = lib.loadlib('mbedtls','mbedtls/rsa.h')
lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')
lib.load {
'mem', 'math', 'str', 'file', 'crypt';
'http', 'session', 'tpl', 'store';
}
local be = {}
for _, b in pairs(config.backends) do
be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))
lib.cmdparse = terralib.loadfile('cmdparse.t')()
do local collate = function(path,f, ...)
return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
view = collate('view','load');
static = {};
stmap = global(lib.mem.ref(int8)[#config.embeds]); -- array of pointers to static content
} end
for i,e in ipairs(config.embeds) do local v = e[1]
local fh = io.open('static/' .. v,'r')
if fh == nil then error('static file ' .. v .. ' missing') end
data.static[v] = fh:read('*a') fh:close()
end
-- slightly silly -- because we're using plain lua to gather up
-- the template sources, they have to be actually turned into
-- templates in the terra code, so we "mutate" them here
for k,v in pairs(data.view) do
local t = lib.tpl.mk { body = v, id = 'view/'..k }
data.view[k] = t
end
lib.load {
'srv';
'render:nav';
'render:login';
'render:profile';
'render:userpage';
'render:compose';
'route';
}
do
local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
terra version() lib.io.send(1, p, [#p]) end
end
terra noise_init()
starttime = lib.osclock.time(nil)
lastnoisetime = 0
var n = lib.proc.getenv('parsav_noise')
if n ~= nil then
if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
noise = n[0] - 0x30
return
end
end
noise = 1
end
local options = lib.cmdparse {
version = {'V', 'display information about the binary build and exit'};
quiet = {'q', 'do not print to standard out'};
help = {'h', 'display this list'};
backend_file = {'b', 'init from specified backend file', 1};
static_dir = {'S', 'directory with overrides for static content', 1};
builtin_data = {'B', 'do not load static content overrides at runtime under any circumstances'};
}
local static_setup = quote end
local mapin = quote end
local odir = symbol(rawstring)
local pathbuf = symbol(lib.str.acc)
for i,e in ipairs(config.embeds) do local v = e[1]
local d = data.static[v]
static_setup = quote [static_setup]
([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] { ptr = [d], ct = [#d] })
end
mapin = quote [mapin]
var osz = pathbuf.sz
pathbuf:push([v],[#v])
var f = lib.file.open(pathbuf.buf, [lib.file.mode.read])
if f.ok then defer f.val:close()
var map = f.val:mapin(0,0) -- currently maps are leaked, maybe do something more elegant in future
lib.report('loading static override content from ', pathbuf.buf)
([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] {
ptr = [rawstring](map.addr);
ct = map.sz;
})
end
pathbuf.sz = osz
end
end
local terra static_init(mode: &options)
[static_setup]
if mode.builtin_data then return end
var [odir] = lib.proc.getenv('parsav_override_dir')
if mode.static_dir ~= nil then
odir=@mode.static_dir
end
if odir == nil then return end
var [pathbuf] defer pathbuf:free()
pathbuf:compose(odir,'/')
[mapin]
end
terra entry(argc: int, argv: &rawstring): int
if argc < 1 then lib.bail('bad invocation!') end
noise_init()
[lib.init]
-- shut mongoose the fuck up
lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)
var srv: lib.srv.overlord
do var mode: options
mode:parse(argc,argv) defer mode:free()
static_init(&mode)
if mode.version then version() return 0 end
if mode.help then
lib.io.send(1, [options.helptxt], [#options.helptxt])
return 0
end
var cnf: rawstring
if mode.backend_file ~= nil
then cnf = @mode.backend_file
else cnf = lib.proc.getenv('parsav_backend_file')
end
if cnf == nil then cnf = "backend.conf" end
srv:start(cnf)
end
lib.report('listening for requests')
while true do
srv:poll()
end
srv:shutdown()
return 0
end
local bflag = function(long,short)
if short and util.has(buildopts, short) then return true end
if long and util.has(buildopts, long) then return true end
return false
end
if bflag('dump-config','C') then
print(util.dump(config))
os.exit(0)
end
local holler = print
local out = config.exe and 'parsav' or ('parsav.' .. config.outform)
local linkargs = {'-O4'}
if bflag('quiet','q') then holler = function() end end
if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end
if config.posix then
linkargs[#linkargs+1] = '-pthread'
end
for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
holler('linking with args',util.dump(linkargs))
terralib.saveobj(out, {
main = entry
},
linkargs,
config.tgttrip and terralib.newtarget {
Triple = config.tgttrip;
CPU = config.tgtcpu;
FloatABIHard = config.tgthf;
} or nil)