T wo T r:reader.lua
From LuaTeXWiki
--- Copyright (c) 2021 by Toadstone Enterprises.
--- ISC-type license, see License.txt for details.
-----------------------------------------------------------------
--- We maintain a stack of 'readers'.
--- push_reader will push a new 'reader' onto the stack, and
--- pop_reader will pop it. push_reader should be given a string or a
--- filename (of the form "file:filename") If a string is passed in,
--- its contents will be used by the 'reader'. If a filename is
--- passed in, the file will be read, and its contents will form the
--- contents of the 'reader'.
--- The top 'reader' is the current one, and read_value will return a
--- char (really a Unicode integer value) from that 'reader'.
-----------------------------------------------------------------
--- The index of the current 'reader'. No 'readers' yet.
local n = 0
local explode = function(s)
-- This is the lower level function used by push_reader to create
-- a 'reader'.
--
-- Take the string s, and break it into unicode values (integers)
-- and place these into the table, text, indexed by position.
-- text.max is the maximum index.
-- text.pos is the current position to be read from. Initially the
-- start of the text.
assert((type(s) == "string"),
"explode was given a " .. type(s) .. " instead of a string.")
local text = {}
local i = 0
for v in string.utfvalues(s) do
i = i + 1
text[i] = v
end
text.max = i
text.pos = 1
return text
end
local push_reader = function(data)
-- Create a 'reader'. That is, use explode to create a table
-- containing the text from data (either a file pointer or the
-- text itself). Then place this table into the next index of
-- the 'reader' table, Reader, in the next available index.
local text
assert((type(data) == "string"), "Bad arg to push_reader.")
if (unicode.utf8.sub(data, 1, 5) == "file:") then
local f = io.open(unicode.utf8.sub(data, 6, -1))
assert(f, "File " .. unicode.utf8.sub(data, 6, -1) .. "failed to open.")
text = f:read("a")
else
text = data
end
local reader = explode(text)
n = n + 1
Reader[n] = reader
end
local pop_reader = function()
-- Remove the currently active 'reader' from Reader.
assert((n > 0), "No reader to pop!")
Reader[n] = nil
n = n - 1
end
local read_value = function()
-- Get the char (a Unicode integer) from the currenly active
-- 'reader'.
local value
assert((n > 0), "No reader to read from!")
local reader = Reader[n]
local max = reader.max
local pos = reader.pos
if (pos <= max) then
value = reader[pos]
else
-- We are already at the end.
return nil
end
reader.pos = pos + 1
return value
end
local unread_value = function()
-- Back the indexed position of the currently active 'reader'
-- by one.
assert((n > 0), "No reader to unread to!")
local reader = Reader[n]
local pos = reader.pos
assert((pos > 1), "Already at start of text!")
reader.pos = pos - 1
end
local replace_text = function(text, start, stop)
-- Insert some text (a string) into the current 'reader' replacing
-- the text from the indices start to stop, and reset the 'reader'
-- to the beginning of inserted text
-- There are three special cases,
-- 1. prepend some text, before any reading
-- 2. insert without replacing any text
-- 3. deleting text, without inserting
-- These can be handled by using
-- 1. start = 0, stop = -1
-- 2. stop = start -1
-- 3. text = ""
assert((n > 0), "No reader to replace text in!")
local reader = Reader[n]
local max = reader.max
assert((type(text) == "string"), "Text is not a string!")
assert((start >= 0), "Start must be a non-negative integer")
assert((stop <= max), "Stop must be less than max")
local insert = explode(text)
local difference = insert.max - (stop - start + 1)
if (difference > 0) then
-- move up
for i = max, stop + 1, -1 do
reader[i + difference] = reader[i]
end
elseif (difference < 0) then
-- move down
for i = stop + 1, max do
reader[i + difference] = reader[i]
end
end
-- insert
-- If we are prepending some text (special case 1. above)
-- we need to reset start from 0 to 1.
if (start == 0) then start = 1 end
for i = 1, insert.max do
reader[start + i - 1] = insert[i]
end
reader.max = reader.max + difference
reader.pos = start
end
local pos = function()
-- Return the position of the current 'reader'.
assert((n > 0), "No reader to replace text in!")
local reader = Reader[n]
return reader.pos
end
Reader = {push_reader = push_reader,
pop_reader = pop_reader,
read_value = read_value,
unread_value = unread_value,
replace_text = replace_text,
pos = pos,}
return Reader