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