retex-lua 1.3.0

Embedded Lua and the LuaTeX Lua API for retex
Documentation
-- The sio and fio libraries: binary readers on strings and files.
--
-- Written for retex from the behaviour of LuaTeX's libraries observed
-- through texlua (see the tests in siolib.rs). sio functions take a
-- string and a 1-based position; fio functions read at a file's current
-- position and advance it. Reads past the end return nil (numbers), no
-- values (readbytes) or a shorter table.
local byte = string.byte
local tointeger, tonumber, tostring, type, error = math.tointeger, tonumber, tostring, type, error

local function argerror(lib, name, n, msg)
  error("bad argument #" .. n .. " to '" .. lib .. "." .. name .. "' (" .. msg .. ")", 0)
end

local function typename(v, given)
  if not given then return "no value" end
  return type(v)
end

local function checkstring(lib, name, n, v, given)
  local t = type(v)
  if t == "string" then return v end
  if t == "number" then return tostring(v) end
  argerror(lib, name, n, "string expected, got " .. typename(v, given))
end

local function checkint(lib, name, n, v, given)
  if type(v) == "number" then
    local i = tointeger(v)
    if i then return i end
    argerror(lib, name, n, "number has no integer representation")
  end
  if type(v) == "string" then
    local x = tonumber(v)
    if x and tointeger(x) then return tointeger(x) end
  end
  argerror(lib, name, n, "number expected, got " .. typename(v, given))
end

-- Unsigned big-endian (or little-endian) value of bytes s[p .. p+n-1].
local function cardinal(s, p, n, le)
  if p < 1 or p + n - 1 > #s then return nil end
  local v = 0
  if le then
    for i = p + n - 1, p, -1 do v = v * 256 + byte(s, i) end
  else
    for i = p, p + n - 1 do v = v * 256 + byte(s, i) end
  end
  return v
end

local function signed(v, n)
  if v and v >= 1 << (8 * n - 1) then return v - (1 << (8 * n)) end
  return v
end

-- The value readers: name -> function(s, p) returning value, width.
local readers = {}
for n = 1, 4 do
  readers["readcardinal" .. n] = { n, function(s, p) return cardinal(s, p, n) end }
  readers["readinteger" .. n] = { n, function(s, p) return signed(cardinal(s, p, n), n) end }
  readers["readcardinal" .. n .. "le"] = { n, function(s, p) return cardinal(s, p, n, true) end }
  readers["readinteger" .. n .. "le"] = { n, function(s, p) return signed(cardinal(s, p, n, true), n) end }
end
readers.readfixed2 = { 2, function(s, p)
  local a = cardinal(s, p, 1)
  local b = cardinal(s, p + 1, 1)
  if not (a and b) then return nil end
  return a + b / 256.0
end }
readers.readfixed4 = { 4, function(s, p)
  local a = signed(cardinal(s, p, 2), 2)
  local b = cardinal(s, p + 2, 2)
  if not (a and b) then return nil end
  return a + b / 65536.0
end }
readers.read2dot14 = { 2, function(s, p)
  local v = signed(cardinal(s, p, 2), 2)
  return v and v / 16384.0
end }

local function bytes(s, p, n)
  if p < 1 then return end
  local last = p + n - 1
  if last > #s then last = #s end
  if last < p then return end
  return byte(s, p, last)
end

local function table_of(read, s, p, count, width)
  local t = {}
  for i = 1, count do
    local v = read(s, p + (i - 1) * width)
    if v == nil then break end
    t[i] = v
  end
  return t
end

local sio = {}
for name, r in pairs(readers) do
  local read = r[2]
  sio[name] = function(...)
    local s, p = ...
    local k = select("#", ...)
    s = checkstring("sio", name, 1, s, k >= 1)
    p = checkint("sio", name, 2, p, k >= 2)
    return (read(s, p))
  end
end
sio.readbytes = function(...)
  local s, p, n = ...
  local k = select("#", ...)
  s = checkstring("sio", "readbytes", 1, s, k >= 1)
  p = checkint("sio", "readbytes", 2, p, k >= 2)
  n = checkint("sio", "readbytes", 3, n, k >= 3)
  return bytes(s, p, n)
end
sio.readbytetable = function(...)
  local s, p, n = ...
  local k = select("#", ...)
  s = checkstring("sio", "readbytetable", 1, s, k >= 1)
  p = checkint("sio", "readbytetable", 2, p, k >= 2)
  n = checkint("sio", "readbytetable", 3, n, k >= 3)
  return { bytes(s, p, n) }
end
for _, kind in ipairs { "cardinal", "integer" } do
  local name = "read" .. kind .. "table"
  sio[name] = function(...)
    local s, p, n, w = ...
    local k = select("#", ...)
    s = checkstring("sio", name, 1, s, k >= 1)
    p = checkint("sio", name, 2, p, k >= 2)
    n = checkint("sio", name, 3, n, k >= 3)
    w = checkint("sio", name, 4, w, k >= 4)
    local r = readers["read" .. kind .. w]
    if not r then return {} end
    return table_of(r[2], s, p, n, w)
  end
end

-- fio: the same readers on a file handle, at its current position.
local fio = {}
local function fread(f, n)
  local s = f:read(n)
  return s or ""
end
for name, r in pairs(readers) do
  local width, read = r[1], r[2]
  fio[name] = function(f)
    local s = fread(f, width)
    return (read(s, 1))
  end
end
fio.readbytes = function(f, n)
  local s = fread(f, n)
  return bytes(s, 1, n)
end
fio.readbytetable = function(f, n)
  local s = fread(f, n)
  return { bytes(s, 1, n) }
end
for _, kind in ipairs { "cardinal", "integer" } do
  fio["read" .. kind .. "table"] = function(f, n, w)
    local r = readers["read" .. kind .. w]
    if not r then return {} end
    local s = fread(f, n * w)
    return table_of(r[2], s, 1, n, w)
  end
end
fio.readline = function(f)
  return f:read("l")
end
fio.getposition = function(f)
  return f:seek()
end
fio.setposition = function(f, p)
  f:seek("set", p)
end
fio.skipposition = function(f, n)
  f:seek("cur", n)
end

return sio, fio