util-fil.lua / last modification: 2020-01-30 14:16
if not modules then modules = { } end modules ['util-fil'] = {
    version   = 1.001,
    comment   = "companion to luat-lib.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

local tonumber = tonumber
local byte = string.byte
local char = string.char

-- Here are a few helpers (the starting point were old ones I used for parsing
-- flac files). In Lua 5.3 we can probably do this better. Some code will move
-- here.

-- We could comment those that are in fio and sio.

utilities       = utilities or { }
local files     = { }
utilities.files = files

-- we could have a gc method that closes but files auto close anyway

local zerobased = { }

function files.open(filename,zb)
    local f = io.open(filename,"rb")
    if f then
        zerobased[f] = zb or false
    end
    return f
end

function files.close(f)
    zerobased[f] = nil
    f:close()
end

function files.size(f)
    local current = f:seek()
    local size = f:seek("end")
    f:seek("set",current)
    return size
end

files.getsize = files.size

function files.setposition(f,n)
    if zerobased[f] then
        f:seek("set",n)
    else
        f:seek("set",n - 1)
    end
end

function files.getposition(f)
    if zerobased[f] then
        return f:seek()
    else
        return f:seek() + 1
    end
end

function files.look(f,n,chars)
    local p = f:seek()
    local s = f:read(n)
    f:seek("set",p)
    if chars then
        return s
    else
        return byte(s,1,#s)
    end
end

function files.skip(f,n)
    if n == 1 then
        f:read(n)
    else
        f:seek("set",f:seek()+n)
    end
end

function files.readbyte(f)
    return byte(f:read(1))
end

function files.readbytes(f,n)
    return byte(f:read(n),1,n)
end

function files.readbytetable(f,n)
 -- return { byte(f:read(n),1,n) }
    local s = f:read(n or 1)
    return { byte(s,1,#s) } -- best use the real length
end

function files.readchar(f)
    return f:read(1)
end

function files.readstring(f,n)
    return f:read(n or 1)
end

function files.readinteger1(f)  -- one byte
    local n = byte(f:read(1))
    if n >= 0x80 then
        return n - 0x100
    else
        return n
    end
end

files.readcardinal1  = files.readbyte  -- one byte
files.readcardinal   = files.readcardinal1
files.readinteger    = files.readinteger1
files.readsignedbyte = files.readinteger1

function files.readcardinal2(f)
    local a, b = byte(f:read(2),1,2)
    return 0x100 * a + b
end

function files.readcardinal2le(f)
    local b, a = byte(f:read(2),1,2)
    return 0x100 * a + b
end

function files.readinteger2(f)
    local a, b = byte(f:read(2),1,2)
    if a >= 0x80 then
        return 0x100 * a + b - 0x10000
    else
        return 0x100 * a + b
    end
end

function files.readinteger2le(f)
    local b, a = byte(f:read(2),1,2)
    if a >= 0x80 then
        return 0x100 * a + b - 0x10000
    else
        return 0x100 * a + b
    end
end

function files.readcardinal3(f)
    local a, b, c = byte(f:read(3),1,3)
    return 0x10000 * a + 0x100 * b + c
end

function files.readcardinal3le(f)
    local c, b, a = byte(f:read(3),1,3)
    return 0x10000 * a + 0x100 * b + c
end

function files.readinteger3(f)
    local a, b, c = byte(f:read(3),1,3)
    if a >= 0x80 then
        return 0x10000 * a + 0x100 * b + c - 0x1000000
    else
        return 0x10000 * a + 0x100 * b + c
    end
end

function files.readinteger3le(f)
    local c, b, a = byte(f:read(3),1,3)
    if a >= 0x80 then
        return 0x10000 * a + 0x100 * b + c - 0x1000000
    else
        return 0x10000 * a + 0x100 * b + c
    end
end

function files.readcardinal4(f)
    local a, b, c, d = byte(f:read(4),1,4)
    return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
end

function files.readcardinal4le(f)
    local d, c, b, a = byte(f:read(4),1,4)
    return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
end

function files.readinteger4(f)
    local a, b, c, d = byte(f:read(4),1,4)
    if a >= 0x80 then
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d - 0x100000000
    else
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
    end
end

function files.readinteger4le(f)
    local d, c, b, a = byte(f:read(4),1,4)
    if a >= 0x80 then
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d - 0x100000000
    else
        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
    end
end

function files.readfixed2(f)
    local a, b = byte(f:read(2),1,2)
    if a >= 0x80 then
        tonumber((a - 0x100) .. "." .. b)
    else
        tonumber(( a       ) .. "." .. b)
    end
end

-- (real) (n>>16) + ((n&0xffff)/65536.0)) but no cast in lua (we could use unpack)

function files.readfixed4(f)
    local a, b, c, d = byte(f:read(4),1,4)
    if a >= 0x80 then
        tonumber((0x100 * a + b - 0x10000) .. "." .. (0x100 * c + d))
    else
        tonumber((0x100 * a + b          ) .. "." .. (0x100 * c + d))
    end
end

-- (real) ((n<<16)>>(16+14)) + ((n&0x3fff)/16384.0))

if bit32 then

    local extract = bit32.extract
    local band    = bit32.band

    function files.read2dot14(f)
        local a, b = byte(f:read(2),1,2)
        if a >= 0x80 then
            local n = -(0x100 * a + b)
            return - (extract(n,14,2) + (band(n,0x3FFF) / 16384.0))
        else
            local n =   0x100 * a + b
            return   (extract(n,14,2) + (band(n,0x3FFF) / 16384.0))
        end
    end

end

function files.skipshort(f,n)
    f:read(2*(n or 1))
end

function files.skiplong(f,n)
    f:read(4*(n or 1))
end

-- writers (kind of slow)

if bit32 then

    local rshift = bit32.rshift

    function files.writecardinal2(f,n)
        local a = char(n % 256)
        n = rshift(n,8)
        local b = char(n % 256)
        f:write(b,a)
    end

    function files.writecardinal4(f,n)
        local a = char(n % 256)
        n = rshift(n,8)
        local b = char(n % 256)
        n = rshift(n,8)
        local c = char(n % 256)
        n = rshift(n,8)
        local d = char(n % 256)
        f:write(d,c,b,a)
    end

    function files.writecardinal2le(f,n)
        local a = char(n % 256)
        n = rshift(n,8)
        local b = char(n % 256)
        f:write(a,b)
    end

    function files.writecardinal4le(f,n)
        local a = char(n % 256)
        n = rshift(n,8)
        local b = char(n % 256)
        n = rshift(n,8)
        local c = char(n % 256)
        n = rshift(n,8)
        local d = char(n % 256)
        f:write(a,b,c,d)
    end

else

    local floor = math.floor

    function files.writecardinal2(f,n)
        local a = char(n % 256)
        n = floor(n/256)
        local b = char(n % 256)
        f:write(b,a)
    end

    function files.writecardinal4(f,n)
        local a = char(n % 256)
        n = floor(n/256)
        local b = char(n % 256)
        n = floor(n/256)
        local c = char(n % 256)
        n = floor(n/256)
        local d = char(n % 256)
        f:write(d,c,b,a)
    end

    function files.writecardinal2le(f,n)
        local a = char(n % 256)
        n = floor(n/256)
        local b = char(n % 256)
        f:write(a,b)
    end

    function files.writecardinal4le(f,n)
        local a = char(n % 256)
        n = floor(n/256)
        local b = char(n % 256)
        n = floor(n/256)
        local c = char(n % 256)
        n = floor(n/256)
        local d = char(n % 256)
        f:write(a,b,c,d)
    end

end

function files.writestring(f,s)
    f:write(char(byte(s,1,#s)))
end

function files.writebyte(f,b)
    f:write(char(b))
end

if fio and fio.readcardinal1 then

    files.readcardinal1   = fio.readcardinal1
    files.readcardinal2   = fio.readcardinal2
    files.readcardinal3   = fio.readcardinal3
    files.readcardinal4   = fio.readcardinal4

    files.readcardinal1le = fio.readcardinal1le or files.readcardinal1le
    files.readcardinal2le = fio.readcardinal2le or files.readcardinal2le
    files.readcardinal3le = fio.readcardinal3le or files.readcardinal3le
    files.readcardinal4le = fio.readcardinal4le or files.readcardinal4le

    files.readinteger1    = fio.readinteger1
    files.readinteger2    = fio.readinteger2
    files.readinteger3    = fio.readinteger3
    files.readinteger4    = fio.readinteger4

    files.readinteger1le  = fio.readinteger1le or files.readinteger1le
    files.readinteger2le  = fio.readinteger2le or files.readinteger2le
    files.readinteger3le  = fio.readinteger3le or files.readinteger3le
    files.readinteger4le  = fio.readinteger4le or files.readinteger4le

    files.readfixed2      = fio.readfixed2
    files.readfixed4      = fio.readfixed4
    files.read2dot14      = fio.read2dot14
    files.setposition     = fio.setposition
    files.getposition     = fio.getposition

    files.readbyte        = files.readcardinal1
    files.readsignedbyte  = files.readinteger1
    files.readcardinal    = files.readcardinal1
    files.readinteger     = files.readinteger1

    local skipposition    = fio.skipposition
    files.skipposition    = skipposition

    files.readbytes       = fio.readbytes
    files.readbytetable   = fio.readbytetable

    function files.skipshort(f,n)
        skipposition(f,2*(n or 1))
    end

    function files.skiplong(f,n)
        skipposition(f,4*(n or 1))
    end

end

if fio and fio.writecardinal1 then

    files.writecardinal1   = fio.writecardinal1
    files.writecardinal2   = fio.writecardinal2
    files.writecardinal3   = fio.writecardinal3
    files.writecardinal4   = fio.writecardinal4

    files.writecardinal1le = fio.writecardinal1le
    files.writecardinal2le = fio.writecardinal2le
    files.writecardinal3le = fio.writecardinal3le
    files.writecardinal4le = fio.writecardinal4le

    files.writeinteger1    = fio.writeinteger1 or fio.writecardinal1
    files.writeinteger2    = fio.writeinteger2 or fio.writecardinal2
    files.writeinteger3    = fio.writeinteger3 or fio.writecardinal3
    files.writeinteger4    = fio.writeinteger4 or fio.writecardinal4

    files.writeinteger1le  = files.writeinteger1le or fio.writecardinal1le
    files.writeinteger2le  = files.writeinteger2le or fio.writecardinal2le
    files.writeinteger3le  = files.writeinteger3le or fio.writecardinal3le
    files.writeinteger4le  = files.writeinteger4le or fio.writecardinal4le

end

if fio and fio.readcardinaltable then

    files.readcardinaltable = fio.readcardinaltable
    files.readintegertable  = fio.readintegertable

else

    local readcardinal1 = files.readcardinal1
    local readcardinal2 = files.readcardinal2
    local readcardinal3 = files.readcardinal3
    local readcardinal4 = files.readcardinal4

    function files.readcardinaltable(f,n,b)
        local t = { }
            if b == 1 then for i=1,n do t[i] = readcardinal1(f) end
        elseif b == 2 then for i=1,n do t[i] = readcardinal2(f) end
        elseif b == 3 then for i=1,n do t[i] = readcardinal3(f) end
        elseif b == 4 then for i=1,n do t[i] = readcardinal4(f) end end
        return t
    end

    local readinteger1 = files.readinteger1
    local readinteger2 = files.readinteger2
    local readinteger3 = files.readinteger3
    local readinteger4 = files.readinteger4

    function files.readintegertable(f,n,b)
        local t = { }
            if b == 1 then for i=1,n do t[i] = readinteger1(f) end
        elseif b == 2 then for i=1,n do t[i] = readinteger2(f) end
        elseif b == 3 then for i=1,n do t[i] = readinteger3(f) end
        elseif b == 4 then for i=1,n do t[i] = readinteger4(f) end end
        return t
    end

end