summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/lualatex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2020-08-14 20:28:12 +0000
committerKarl Berry <karl@freefriends.org>2020-08-14 20:28:12 +0000
commit1c1b1c09b9631a7f0be0aedd24c6f37f7658e9ee (patch)
treed0f9d4d5425086b8f5b2c66460fb43af0775f006 /Master/texmf-dist/tex/lualatex
parent5362413c4d74031afac3641a2d24f6861d526603 (diff)
luaprogtable (14aug20)
git-svn-id: svn://tug.org/texlive/trunk@56113 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/lualatex')
-rw-r--r--Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-parse.lua116
-rw-r--r--Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-stringbuffer.lua74
-rw-r--r--Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-utility.lua109
-rw-r--r--Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua559
-rw-r--r--Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.sty237
5 files changed, 1095 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-parse.lua b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-parse.lua
new file mode 100644
index 00000000000..8947535a227
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-parse.lua
@@ -0,0 +1,116 @@
+require "luaprogtable-utility.lua"
+
+local _std_shape = {1, 1}
+
+function _lpt_parse_new_cell()
+ local cur_cell = {data={}, shape=_std_shape}
+ return cur_cell
+end
+
+function _lpt_parse_table_view(str)
+ local str = _lpt_strip(str)
+
+ local level = 0
+ local pos = 1
+
+ local cur_cell = nil
+ local cur_row = {}
+ local ret = {cur_row}
+
+ function _add_str(s)
+ if level > 0 then
+ table.insert(cur_cell["data"], s)
+ end
+ end
+
+ while pos <= str:len() do
+ local chr = str:sub(pos, pos)
+ local next_pos = pos + 1
+
+ if chr == "\\" then
+ local next_chr = str:sub(pos + 1, pos + 1)
+ if next_chr == nil then
+ -- do nothing
+ elseif next_chr == "\\" then
+ if level == 0 then
+ cur_row = {}
+ -- change of row
+ table.insert(ret, cur_row)
+ end
+ elseif next_chr == "{" or next_chr == "}" then
+ next_pos = pos + 2
+ _add_str(str:sub(pos, pos + 1))
+ else
+ _add_str(chr)
+ end
+ elseif chr == "{" then
+ if level == 0 then
+ cur_cell = _lpt_parse_new_cell()
+ else
+ _add_str(chr)
+ end
+ level = level + 1
+ elseif chr == "}" then
+ level = level - 1
+ if level < 0 then
+ raise_error("extra '}' found when parsing table view expression")
+ elseif level == 0 then
+ local data_str = table.concat(cur_cell["data"], "")
+ cur_cell["data"] = data_str
+ table.insert(cur_row, cur_cell)
+ cur_cell = nil
+ else
+ _add_str(chr)
+ end
+ elseif chr == "[" then
+ if level == 0 then
+ local close = string.find(str, "]", pos + 1)
+
+ if close == nil then
+ raise_error("unable to find closing ']'")
+ end
+
+ local size_str = str:sub(pos + 1, close - 1)
+ size_str = _lpt_strip(size_str)
+
+ local size_1 = size_str:sub(1, 1)
+ local shape = nil
+
+ if size_1 == "|" then
+ local nrow = _lpt_tonumber(size_str:sub(2))
+ shape = {nrow, 1}
+ elseif size_1 == "-" then
+ local ncol = _lpt_tonumber(size_str:sub(2))
+ shape = {1, ncol}
+ else
+ raise_error(string.format("invalid size '[%s]' in table view, size_str", size_str))
+ end
+
+ if shape[1] < 1 or shape[2] < 1 then
+ raise_error(string.format("invalid size '[%s]' in table view, size_str", size_str))
+ end
+
+ local n_cell = #cur_row
+ local cell = cur_row[n_cell]
+ if cell == nil then
+ raise_error(string.format("no suitable cell for size [%s]", size_str))
+ end
+ cell["shape"] = shape
+
+ pos = close + 1
+ else
+ _add_str(chr)
+ end
+ else
+ _add_str(chr)
+ end
+
+ pos = next_pos
+ end
+
+ if level ~= 0 then
+ raise_error("extra '{' found when parsing table view expression")
+ end
+
+ return ret
+end \ No newline at end of file
diff --git a/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-stringbuffer.lua b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-stringbuffer.lua
new file mode 100644
index 00000000000..80ba990776e
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-stringbuffer.lua
@@ -0,0 +1,74 @@
+TeXStringBuffer = {content={}, finished=false}
+
+function TeXStringBuffer:new()
+ o = {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function TeXStringBuffer:clear()
+ while #self.content ~= 0 do rawset(self.content, #self.content, nil) end
+end
+
+function TeXStringBuffer:content_to_string()
+ return table.concat(self.content, "")
+end
+
+function TeXStringBuffer:show()
+ tex.write(self:content_to_string())
+end
+
+function TeXStringBuffer:append(data)
+ table.insert(self.content, data)
+end
+
+function TeXStringBuffer:append_carriage_return(data)
+ self:append("\r")
+end
+
+function _tex_buffer_remove_callback(name, description)
+ for k, v in pairs(luatexbase.callback_descriptions(name)) do
+ if v == description then
+ texio.write("\nsafely removing callback " .. name .. " : " .. description)
+ luatexbase.remove_from_callback(name, description)
+ end
+ end
+end
+
+function tex_buffer_remove_callback()
+ _tex_buffer_remove_callback("find_read_file", "tex_file_buffer_find")
+ _tex_buffer_remove_callback("open_read_file", "tex_file_buffer_read")
+end
+
+function tex_file_buffer_reader(env)
+ local ret = nil
+ if not env["finished"] then
+ ret = env["content"]
+ env["finished"] = true
+ -- remove callback immediately
+ tex_buffer_remove_callback()
+ end
+ return ret
+end
+
+function tex_file_buffer_find(id_number, asked_name)
+ -- arguments and return value doesn't matter
+ texio.write("\nTeXStringBuffer tries to find ".. asked_name .. " id=" .. id_number)
+ return asked_name
+end
+
+function TeXStringBuffer:register_callback()
+ tex_file_buffer_read = function (filename)
+ local env = {}
+ texio.write("\nTeXStringBuffer opens ".. filename)
+ env["finished"] = false
+ env["content"] = self:content_to_string()
+ env["reader"] = tex_file_buffer_reader
+ return env
+ end
+
+ -- register callback
+ luatexbase.add_to_callback("find_read_file", tex_file_buffer_find, "tex_file_buffer_find")
+ luatexbase.add_to_callback("open_read_file", tex_file_buffer_read, "tex_file_buffer_read")
+end
diff --git a/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-utility.lua b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-utility.lua
new file mode 100644
index 00000000000..4ac10f5fd50
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable-utility.lua
@@ -0,0 +1,109 @@
+inspect = require "inspect.lua"
+
+function raise_error(...)
+ local arg = {...}
+ local description = "a luaprogtable package exception occured:\n" .. table.concat(arg, ", ")
+ tex.error(description)
+ error(description)
+end
+
+local _spacer = lpeg.S(" \t\f\v\n\r")
+local _non_spacer = 1 - _spacer
+local _stripper = _spacer^0 * lpeg.C((_spacer^0 * _non_spacer^1)^0)
+
+
+function _lpt_strip(s)
+ return s and lpeg.match(_stripper,s) or ""
+end
+
+function _lpt_split(s, sep)
+ sep = lpeg.P(sep)
+ local elem = lpeg.C((1 - sep)^0)
+ local p = lpeg.Ct(elem * (sep * elem)^0) -- make a table capture
+ return lpeg.match(p, s)
+end
+
+
+function _lpt_tonumber(s)
+ local n = tonumber(s)
+ if n == nil then
+ raise_error(string.format("'{%s}' is not a valid number expression", s))
+ end
+ return n
+end
+
+
+function _lpt_parse_index_expr(expr)
+ local expr = _lpt_strip(expr)
+ local parts = _lpt_split(expr, ",")
+ local ret = {}
+
+ for i=1,#parts do
+ local subexpr = _lpt_strip(parts[i])
+
+ if subexpr:len() == 0 then
+ raise_error(string.format("empty field in index expression '%s'", expr))
+ end
+
+ local colon_pos = subexpr:find(":")
+ local str_l = ""
+ local str_r = ""
+
+ if colon_pos == nil then
+ str_l = subexpr
+ str_r = subexpr
+ else
+ local subsubexpr = _lpt_split(subexpr, ":")
+ str_l = _lpt_strip(subsubexpr[1])
+ str_r = _lpt_strip(subsubexpr[2])
+ if str_l:len() == 0 then
+ str_l = "1"
+ end
+ if str_r:len() == 0 then
+ str_r = "-1"
+ end
+ end
+
+ local n_l = _lpt_tonumber(str_l)
+ local n_r = _lpt_tonumber(str_r)
+
+ table.insert(ret, {n_l, n_r})
+ end
+
+ return ret
+end
+
+function _lpt_parse_shape(shape_expr)
+ local shape_expr = _lpt_strip(shape_expr)
+ local parts = _lpt_split(shape_expr, ",")
+ if #parts ~= 2 then
+ raise_error(string.format("invalid shape: '%s'", shape_expr))
+ end
+ local shape1 = _lpt_tonumber(parts[1])
+ local shape2 = _lpt_tonumber(parts[2])
+ if shape1 < 1 or shape2 < 1 then
+ raise_error(string.format("invalid shape: '%s'", shape_expr))
+ end
+ return {shape1, shape2}
+end
+
+function table.slice(tbl, first, last, step)
+ local sliced = {}
+
+ for i = first or 1, last or #tbl, step or 1 do
+ sliced[#sliced+1] = tbl[i]
+ end
+
+ return sliced
+end
+
+function table.find(tbl, item, index)
+ for i=index or 1, #tbl do
+ local tbl_item = tbl[i]
+ if tbl_item == item then
+ return i
+ end
+ end
+ return nil
+end
+
diff --git a/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua
new file mode 100644
index 00000000000..1e3c2a76570
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.lua
@@ -0,0 +1,559 @@
+require "luaprogtable-parse.lua"
+require "luaprogtable-stringbuffer.lua"
+
+TeXTable = {}
+TeXTable.__index = TeXTable
+
+function TeXTable:new()
+ local ret = {
+ rows = {}
+ }
+ setmetatable(ret, TeXTable)
+ return ret
+end
+
+TeXTableCell = {}
+TeXTableCell.__index = TeXTableCell
+
+local _std_shape = {1, 1}
+
+function TeXTableCell:new(default_val)
+ local ret = {
+ data=default_val,
+ shape=_std_shape,
+ parent=nil
+ }
+ setmetatable(ret, TeXTableCell)
+ return ret
+end
+
+
+TeXTableRow = {}
+TeXTableRow.__index = TeXTableRow
+
+function TeXTableRow:new(tex_table)
+
+ local ncols = tex_table.ncols
+ local default_val = rawget(tex_table, "default value")
+
+ local cells = {}
+
+ for i=1,ncols do
+ table.insert(cells, TeXTableCell:new(default_val))
+ end
+
+ local ret = {
+ ["before line"] = "",
+ ["after line"] = rawget(tex_table, "default after line"),
+ ["after spacing"] = rawget(tex_table, "default after spacing"),
+ ["cells"] = cells
+ }
+
+ setmetatable(ret, TeXTableRow)
+ return ret
+end
+
+
+function TeXTable:resize(nrows, ncols)
+ local now_nrows = #self.rows
+ local now_ncols = self.ncols
+
+ if nrows < now_nrows or ncols < now_ncols then
+ raise_error("'TeXTable:resize' doesn not support table truncation")
+ end
+
+ local nrows_delta = nrows - now_nrows
+ local ncols_delta = ncols - now_ncols
+
+ -- change column for existing rows
+ self.ncols = ncols
+ for i=1,now_nrows do
+ local row = self.rows[i]
+ for j=1,ncols_delta do
+ table.insert(row.cells, TeXTableCell:new(rawget(self, "default value")))
+ end
+ end
+
+ -- add new rows
+ for i=1,nrows_delta do
+ table.insert(self.rows, TeXTableRow:new(self))
+ end
+end
+
+function TeXTable:to_string(no_linebreak)
+ local ret = {}
+ local no_linebreak = no_linebreak or false
+ local lb = "\n"
+ if no_linebreak then
+ lb = " "
+ end
+
+ table.insert(ret, string.format([[\begin{%s}{%s}]], self.backend, rawget(self, "table preamble")))
+
+ local nrows = #self.rows
+ for i=1,nrows do
+
+ local row = self.rows[i]
+
+ local cell_data = {}
+ local j = 1
+
+ while j <= self.ncols do
+ local cell = row.cells[j]
+
+ local next_j = nil
+
+ if cell.shape ~= nil then
+ table.insert(cell_data, cell.data)
+ next_j = j + cell.shape[2]
+ else
+ local parent_row = cell.parent[1]
+ local parent_col = cell.parent[2]
+ local parent_cell = self:get_cell(parent_row, parent_col)
+ table.insert(cell_data, "")
+ next_j = j + parent_cell.shape[2]
+ end
+
+ j = next_j
+ end
+
+ local row_str = ""
+
+ if string.len(rawget(row, "before line")) > 0 then
+ row_str = rawget(row, "before line") .. lb .. row_str
+ end
+
+ row_str = row_str .. table.concat(cell_data, " & ") .. [[ \\]]
+
+ if string.len(rawget(row, "after spacing")) > 0 then
+ row_str = row_str .. "[" .. rawget(row, "after spacing") .. "]"
+ end
+
+ row_str = row_str .. rawget(row, "after line")
+
+ table.insert(ret, row_str)
+ end
+
+ table.insert(ret, string.format([[\end{%s}]], self.backend))
+
+ return table.concat(ret, lb)
+
+end
+
+
+function TeXTable:get_cell(row_id, cell_id)
+ local row = self.rows[row_id]
+ if row == nil then
+ raise_error(string.format("row id '%d' is invalid for table '%s'", row_id, rawget(self, "table name")))
+ end
+ local cell = row.cells[cell_id]
+ if cell == nil then
+ raise_error(string.format("cell location '(%d,%d)' is invalid for table '%s'", row_id, cell_id, rawget(self, "table name")))
+ end
+ return cell
+end
+
+function TeXTable:set_cell(row_id, cell_id, data, shape)
+ local cell = self:get_cell(row_id, cell_id)
+
+ -- check if we can modify this cell
+ if cell.parent ~= nil then
+ local parent_1 = cell.parent[1]
+ local parent_2 = cell.parent[2]
+ raise_error(string.format("cell location '(%d,%d)' of table '%s' is not acceesible because it is overlapped by cell '(%d,%d)'", row_id, cell_id, rawget(self, "table name"), parent_1, parent_2))
+ end
+
+
+ -- check cell boundary
+ local max_row = row_id + shape[1] - 1
+ local max_col = cell_id + shape[2] - 1
+ if max_row > #self.rows or max_col > self.ncols then
+ raise_error(string.format("cell '(%d,%d)' is too large for table '%s'", row_id, col_id, rawget(self, "table name")))
+ end
+
+
+ local old_shape = cell.shape
+
+ local std_shape = {1, 1}
+ -- clear control info for children
+ for i=1, old_shape[1] do
+ local row_id = row_id + i - 1
+ for j = 1, old_shape[2] do
+ local col_id = cell_id + j - 1
+ local other_cell = self:get_cell(row_id, col_id)
+ other_cell.parent = nil
+ other_cell.shape = std_shape
+ end
+ end
+
+ local parent_t = {row_id, cell_id}
+ -- adjust control info based on new shape
+ for i=1, shape[1] do
+ local row_id = row_id + i - 1
+ for j = 1, shape[2] do
+ local col_id = cell_id + j - 1
+ local other_cell = self:get_cell(row_id, col_id)
+ other_cell.parent = parent_t
+ other_cell.shape = nil
+ end
+ end
+
+ -- adjust control info for this cell
+ cell.parent = nil
+ cell.shape = shape
+ cell.data = data
+end
+
+function TeXTable:fill(data)
+ local nrows = #self.rows
+ for i=1,nrows do
+ for j=1,self.ncols do
+ local cell = self:get_cell(i, j)
+ cell.shape = _std_shape
+ cell.parent = nil
+ cell.data = data
+ end
+ end
+end
+
+function TeXTable:fix_row_index(index_pair)
+ local ret = {index_pair[1], index_pair[2]}
+
+ if ret[1] < 0 then
+ ret[1] = #self.rows + ret[1] + 1
+ end
+ if ret[2] < 0 then
+ ret[2] = #self.rows + ret[2] + 1
+ end
+
+ if ret[2] < ret[1] then
+ raise_error("invalid index expression: second element must not be smaller than the first")
+ end
+
+ if ret[1] <= 0 then
+ raise_error("invalid index expression: index must be greater than zero after conversion")
+ end
+
+ return ret
+end
+
+function TeXTable:fix_col_index(index_pair)
+ local ret = {index_pair[1], index_pair[2]}
+
+ if ret[1] < 0 then
+ ret[1] = self.ncols + ret[1] + 1
+ end
+ if ret[2] < 0 then
+ ret[2] = self.ncols + ret[2] + 1
+ end
+
+ if ret[2] < ret[1] then
+ raise_error("invalid index expression: second element must not be smaller than the first")
+ end
+
+ if ret[1] <= 0 then
+ raise_error("invalid index expression: index must be greater than zero after conversion")
+ end
+
+ return ret
+end
+
+-- fix the index of a single cell
+function TeXTable:fix_cell_index(index_expr)
+ local the_table = self
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ if #index_parse ~= 2 then
+ raise_error(string.format("invalid index expression for cell: '%s'", index_expr))
+ end
+
+ local row_expr = the_table:fix_row_index(index_parse[1])
+ local col_expr = the_table:fix_col_index(index_parse[2])
+
+ if row_expr[1] ~= row_expr[2] then
+ raise_error(string.format("invalid row index expression for cell: '%s'", index_expr))
+ end
+ if col_expr[1] ~= col_expr[2] then
+ raise_error(string.format("invalid column index expression for cell: '%s'", index_expr))
+ end
+
+ return {row_expr[1], col_expr[1]}
+end
+
+-- make sure table views do not cut through multicol/multirow
+function TeXTable:check_view_validity(start_row, start_col, row_span, col_span)
+ local max_row = start_row + row_span - 1
+ local max_col = start_col + col_span - 1
+
+ for i = 1, row_span do
+ local row_id = start_row + i - 1
+ for j=1,col_span do
+ local col_id = start_col + j - 1
+ local cell = self:get_cell(row_id, col_id)
+ if cell.shape ~= nil then
+ local boundary_1 = row_id + cell.shape[1] - 1
+ local boundary_2 = col_id + cell.shape[2] - 1
+ if boundary_1 > max_row or boundary_2 > max_col then
+ raise_error(string.format("invalid table view: cell '(%d, %d)' has shape '(%d, %d)', which does not fit into viewed region", row_id, col_id, cell.shape[1], cell.shape[2]))
+ end
+ end
+ end
+ end
+end
+
+
+lpt_info = {}
+
+function _lpt_get_table(table_name)
+ local table_name = _lpt_strip(table_name)
+ local the_table = lpt_info[table_name]
+ if the_table == nil then
+ raise_error(string.format("table '%s' does not exist", table_name))
+ end
+ return the_table
+end
+
+
+_verb_lines = {}
+_verb_end_env = ""
+
+function _lpt_store_verbatim_lines(str)
+ if string.find(str , _verb_end_env) then
+ luatexbase.remove_from_callback("process_input_buffer" , "lpt_store_verbatim_lines")
+ return _verb_end_env
+ else
+ if str ~= nil then
+ table.insert(_verb_lines, str)
+ end
+ return ""
+ end
+end
+
+function _lpt_register_verbatim(end_env)
+ for k in next, _verb_lines do rawset(_verb_lines, k, nil) end
+ _verb_end_env = end_env
+ luatexbase.add_to_callback("process_input_buffer", _lpt_store_verbatim_lines, "lpt_store_verbatim_lines")
+end
+
+local _lpt_ignored_fields = {nrows=true}
+local _lpt_numeric_fields = {ncols=true}
+
+
+
+function lpt_new_table(param)
+ local new_table = TeXTable:new()
+
+ for key, val in pairs(param) do
+ if _lpt_numeric_fields[key] then
+ rawset(new_table, key, _lpt_tonumber(val))
+ elseif _lpt_ignored_fields[key] then
+ -- do nothing
+ else
+ rawset(new_table, key, val)
+ end
+ end
+
+ local table_name = new_table["table name"]
+
+ local table_fetch = lpt_info[table_name]
+ if table_fetch ~= nil then
+ raise_error(string.format("table '%s' already exists", table_name))
+ end
+
+ local nrows = _lpt_tonumber(param["nrows"])
+
+ -- integrity check
+ if nrows < 0 then
+ raise_error(string.format("'nrows' (%d) of table '%s' is invalid", nrows, table_name))
+ end
+ if new_table.ncols <= 0 then
+ raise_error(string.format("'ncols' (%d) of table '%s' is invalid", new_table.ncols, table_name))
+ end
+
+ -- create table
+ new_table:resize(nrows, new_table.ncols)
+
+ lpt_info[table_name] = new_table
+end
+
+function lpt_table_fill(table_name, index_expr, env_content)
+ local the_table = _lpt_get_table(table_name)
+ local env_content = env_content or table.concat(_verb_lines, " ")
+
+ if index_expr:len() == 0 then
+ the_table:fill(env_content)
+ else
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ if #index_parse ~= 2 then
+ raise_error(string.format("invalid index expr '%s' for table '%s'", index_expr, table_name))
+ end
+
+ local row_expr = the_table:fix_row_index(index_parse[1])
+ local col_expr = the_table:fix_col_index(index_parse[2])
+
+ local row_span = row_expr[2] - row_expr[1] + 1
+ local col_span = col_expr[2] - col_expr[1] + 1
+
+ the_table:check_view_validity(row_expr[1], col_expr[1], row_span, col_span)
+
+ for i=1,row_span do
+ local row_id = i + row_expr[1] - 1
+ for j=1,col_span do
+ local col_id = j + col_expr[1] - 1
+ the_table:set_cell(row_id, col_id, env_content, _std_shape)
+ end
+ end
+ end
+end
+
+
+function lpt_table_view(table_name, index_expr)
+ local env_content = table.concat(_verb_lines, "\n")
+ local the_table = _lpt_get_table(table_name)
+
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ if #index_parse ~= 2 then
+ raise_error(string.format("invalid index expr '%s' for table '%s'", index_expr, table_name))
+ end
+
+ local row_expr = the_table:fix_row_index(index_parse[1])
+ local col_expr = the_table:fix_col_index(index_parse[2])
+
+ local row_span = row_expr[2] - row_expr[1] + 1
+ local col_span = col_expr[2] - col_expr[1] + 1
+
+ the_table:check_view_validity(row_expr[1], col_expr[1], row_span, col_span)
+
+ local parsed_table = _lpt_parse_table_view(env_content)
+
+ if #parsed_table ~= row_span then
+ raise_error(string.format("lpttableview: expecting '%d' rows, but get '%d' instead for table '%s'", row_span, #parsed_table, table_name))
+ end
+
+ for i=1,row_span do
+ local row_id = i + row_expr[1] - 1
+ local cols = parsed_table[i]
+
+ -- sum the number of total columns in this row
+ local n_table_cols = 0
+ local max_row_size = 0
+ for _, val in ipairs(cols) do
+ n_table_cols = n_table_cols + val["shape"][2]
+ max_row_size = math.max(max_row_size, val["shape"][1])
+ end
+
+ local max_row = row_id + max_row_size - 1
+ if max_row > row_expr[2] then
+ raise_error("lpttableview: the height of table is greater than table view")
+ end
+
+
+ -- count the numbers of columns needs to be filled in this row
+ local pending_table_cols = 0
+ for j=1,col_span do
+ local col_id = j + col_expr[1] - 1
+ local cell = the_table:get_cell(row_id, col_id)
+ if cell.shape ~= nil then
+ pending_table_cols = pending_table_cols + 1
+ else
+ local parent_row = cell.parent[1]
+ -- if the parent is in the same row,
+ -- then this space needs to be filled in the same row
+ if parent_row == row_id then
+ pending_table_cols = pending_table_cols + 1
+ end
+ end
+ end
+
+ if n_table_cols ~= pending_table_cols then
+ raise_error(string.format("lpttableview: expecting '%d' cols for row '%d', but get '%d' instead for table '%s'", pending_table_cols, i, n_table_cols, table_name))
+ end
+
+
+ --[[
+ if n_table_cols ~= col_span then
+ raise_error(string.format("lpttableview: expecting '%d' cols for row '%d', but get '%d' instead for table '%s'", col_span, i, n_table_cols, table_name))
+ end
+ --]]
+
+ local k = 1
+ for j=1,col_span do
+ local col_id = j + col_expr[1] - 1
+ local cell = the_table:get_cell(row_id, col_id)
+ if cell.shape ~= nil then
+ local col_data = cols[k]["data"]
+ local col_shape = cols[k]["shape"]
+ the_table:set_cell(row_id, col_id, col_data, col_shape)
+ k = k + 1
+ end
+ end
+
+ end
+
+
+end
+
+local _s_buf = TeXStringBuffer:new()
+
+function lpt_use_table(table_name)
+ local the_table = _lpt_get_table(table_name)
+ local input_method = rawget(the_table, "input method")
+
+ if input_method == "stringbuffer" then
+ local table_str = the_table:to_string(true)
+ _s_buf:clear()
+ _s_buf:append(table_str)
+ _s_buf:register_callback()
+ tex.print([[\input{stringbuffer}]])
+ elseif input_method == "file" then
+ local table_str = the_table:to_string()
+ local filename = tex.jobname .. "-" .. table_name .. ".table"
+ local outfile = io.open(filename, "w")
+ outfile:write(table_str)
+ outfile:close()
+ tex.print(string.format([[\input{%s}]], filename))
+ else
+ raise_error(string.format("invalid input method '%s' for table '%s'", input_method, table_name))
+ end
+
+end
+
+function lpt_set_row_prop(table_name, index_expr, param)
+
+ local the_table = _lpt_get_table(table_name)
+ local index_parse = _lpt_parse_index_expr(index_expr)
+ for key, val in ipairs(index_parse) do
+ local fixed_val = the_table:fix_row_index(val)
+
+ local l = fixed_val[1]
+ local r = fixed_val[2]
+
+ for i=l,r do
+ local row = the_table.rows[i]
+ if row == nil then
+ raise_error(string.format("lpt_set_row_prop: invalid index expression '%s'", index_expr))
+ end
+ for kkey, vval in pairs(param) do
+ if rawget(row, kkey) == nil then
+ raise_error(string.format("row '%d' of table '%s' does not have attribute '%s'", i, table_name, kkey))
+ end
+ rawset(row, kkey, vval)
+ end
+ end
+ end
+end
+
+
+function lpt_add_row(table_name, param)
+ local the_table = _lpt_get_table(table_name)
+ local nrows = #the_table.rows
+ local ncols = the_table.ncols
+ the_table:resize(nrows+1, ncols)
+ lpt_set_row_prop(table_name, "-1", param)
+end
+
+function lpt_get_table_shape(table_name)
+ local the_table = _lpt_get_table(table_name)
+ local nrows = #the_table.rows
+ local ncols = the_table.ncols
+ tex.print(string.format("{%d}{%d}", nrows, ncols))
+end \ No newline at end of file
diff --git a/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.sty b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.sty
new file mode 100644
index 00000000000..d05a633c218
--- /dev/null
+++ b/Master/texmf-dist/tex/lualatex/luaprogtable/luaprogtable.sty
@@ -0,0 +1,237 @@
+\RequirePackage{expl3}
+\RequirePackage{xparse}
+\RequirePackage{luatexbase}
+\RequirePackage{iftex}
+
+\RequireLuaTeX
+
+\ProvidesExplPackage {luaprogtable}{08/06/2020}{1.0}{Programmatic table interface}
+
+\makeatletter
+\savecatcodetable 20996
+
+\directlua{
+ require~"luaprogtable.lua"
+}
+
+\tl_new:N \g__luaprogtable_tmpa_tl
+\tl_new:N \g__luaprogtable_tmpb_tl
+\tl_new:N \g__luaprogtable_tmpc_tl
+
+\str_new:N \g__luaprogtable_tmpa_str
+\str_new:N \g__luaprogtable_tmpb_str
+\str_new:N \g__luaprogtable_tmpc_str
+
+\int_new:N \g__luaprogtable_tmpa_int
+\int_new:N \g__luaprogtable_tmpb_int
+\int_new:N \g__luaprogtable_tmpc_int
+
+\cs_set:Npn \__luaprogtable_lua_table_item:nn #1#2 {
+ ["#1"]="#2"
+}
+
+\seq_new:N \g__luaprogtable_prop_item_seq
+
+\cs_set:Npn \__luaprogtable_add_lua_table_item:nn #1#2 {
+ \str_gset:Nx \g__luaprogtable_tmpa_str {\luaescapestring{\tl_to_str:n {#1}}}
+ \str_gset:Nx \g__luaprogtable_tmpb_str {\luaescapestring{\tl_to_str:n {#2}}}
+ \seq_gput_right:Nx \g__luaprogtable_prop_item_seq {
+ \exp_args:NVV \__luaprogtable_lua_table_item:nn \g__luaprogtable_tmpa_str \g__luaprogtable_tmpb_str
+ }
+}
+
+\str_new:N \g__luaprogtable_lua_table_str
+
+\cs_set:Npn \__luaprogtable_prop_to_lua_table:N #1 {
+ \seq_gclear:N \g__luaprogtable_prop_item_seq
+ \prop_map_function:NN #1 \__luaprogtable_add_lua_table_item:nn
+ \str_gclear:N \g__luaprogtable_lua_table_str
+ \str_gput_right:NV \g__luaprogtable_lua_table_str \c_left_brace_str
+ \str_gput_right:Nx \g__luaprogtable_lua_table_str {\seq_use:Nn \g__luaprogtable_prop_item_seq {,~}}
+ \str_gput_right:NV \g__luaprogtable_lua_table_str \c_right_brace_str
+}
+
+\prop_new:N \g__luaprogtable_new_prop
+\keys_define:nn { __luaprogtable_new }
+{
+ backend .prop_gput:N = \g__luaprogtable_new_prop,
+ ncols .prop_gput:N = \g__luaprogtable_new_prop,
+ nrows .prop_gput:N = \g__luaprogtable_new_prop,
+ default~before~line .prop_gput:N = \g__luaprogtable_new_prop,
+ default~after~line .prop_gput:N = \g__luaprogtable_new_prop,
+ default~after~spacing .prop_gput:N = \g__luaprogtable_new_prop,
+ input~method .prop_gput:N = \g__luaprogtable_new_prop
+}
+
+\str_new:N \g__luaprogtable_lua_cur_table_str
+\DeclareDocumentCommand{\LPTSetCurrentTable}{m}{
+ \directlua{
+ local _test_table = _lpt_get_table("\luaescapestring{#1}")
+ }
+ \str_gset:Nx \g__luaprogtable_lua_cur_table_str {\luaescapestring{#1}}
+}
+
+\newcommand{\LPTGetCurrentTable}{
+ \str_use:N \g__luaprogtable_lua_cur_table_str
+}
+
+\DeclareDocumentCommand{\LPTNewTable}{mmmO{}}{
+ \prop_gclear:N \g__luaprogtable_new_prop
+ \prop_gput:Nnn \g__luaprogtable_new_prop {backend} {tabular}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {nrows} {0}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {default~before~line} {}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {default~after~line} {}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {default~after~spacing} {}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {input~method} {stringbuffer}
+
+ \keys_set:nn { __luaprogtable_new } {#4}
+
+ \prop_gput:Nnn \g__luaprogtable_new_prop {table~name} {#1}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {ncols} {#2}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {table~preamble} {#3}
+ \prop_gput:Nnn \g__luaprogtable_new_prop {default~value} {}
+
+ \__luaprogtable_prop_to_lua_table:N \g__luaprogtable_new_prop
+
+ \directlua{
+ local~_tmp_table = \str_use:N \g__luaprogtable_lua_table_str;
+ lpt_new_table(_tmp_table);
+ }
+}
+
+\newcommand{\LPTUseTable}{
+ \directlua{
+ lpt_use_table("\LPTGetCurrentTable");
+ }
+}
+
+\DeclareDocumentEnvironment{lptview}{m}{
+ \directlua{
+ _lpt_register_verbatim([[\string\end{lptview}]])
+ }
+}{
+ % now that verbatim material is passed to lua, call corresponding handler
+ \directlua{
+ lpt_table_view("\LPTGetCurrentTable", "\luaescapestring{#1}")
+ }
+}
+
+\DeclareDocumentEnvironment{lptfill}{m}{
+ \directlua{
+ _lpt_register_verbatim([[\string\end{lptfill}]])
+ }
+}{
+ \directlua{
+ lpt_table_fill("\LPTGetCurrentTable", "\luaescapestring{#1}")
+ }
+}
+
+\DeclareDocumentCommand{\LPTFill}{mm}{
+ \directlua{
+ lpt_table_fill("\LPTGetCurrentTable", "\luaescapestring{#1}", "\luaescapestring{\tl_to_str:n {#2}}")
+ }
+}
+
+\prop_new:N \g__luaprogtable_row_prop
+\keys_define:nn { __luaprogtable_row }
+{
+ before~line .prop_gput:N = \g__luaprogtable_row_prop,
+ after~line .prop_gput:N = \g__luaprogtable_row_prop,
+ after~spacing .prop_gput:N = \g__luaprogtable_row_prop
+}
+
+\DeclareDocumentCommand{\LPTSetRowProp}{mm}{
+ \prop_gclear:N \g__luaprogtable_row_prop
+ \keys_set:nn { __luaprogtable_row } {#2}
+
+ \__luaprogtable_prop_to_lua_table:N \g__luaprogtable_row_prop
+ \directlua{
+ local~_tmp_table = \str_use:N \g__luaprogtable_lua_table_str;
+ lpt_set_row_prop("\LPTGetCurrentTable", "\luaescapestring{#1}", _tmp_table)
+ }
+}
+
+\DeclareDocumentCommand{\LPTAddRow}{O{}}{
+ \prop_gclear:N \g__luaprogtable_row_prop
+ \keys_set:nn { __luaprogtable_row } {#1}
+
+ \__luaprogtable_prop_to_lua_table:N \g__luaprogtable_row_prop
+ \directlua{
+ local~_tmp_table = \str_use:N \g__luaprogtable_lua_table_str;
+ lpt_add_row("\LPTGetCurrentTable", _tmp_table)
+ }
+}
+
+\DeclareDocumentCommand{\LPTSetCell}{mO{1,1}m}{
+ \directlua{
+ local~_table = _lpt_get_table("\LPTGetCurrentTable");
+ local~_index = _table:fix_cell_index("\luaescapestring{#1}");
+ local~_shape = _lpt_parse_shape("\luaescapestring{#2}");
+ _table:set_cell(_index[1], _index[2], "\luaescapestring{ \tl_to_str:n {#3} }", _shape)
+ }
+}
+
+\newcommand{\LPTGetTableShape}{
+ \directlua{
+ lpt_get_table_shape("\LPTGetCurrentTable")
+ }
+}
+
+\ExplSyntaxOff
+
+\newcommand{\LPTGetTableNames}{%
+ \directlua{
+ local _names = {}
+ for key, val in pairs(lpt_info) do
+ table.insert(_names, key)
+ end
+ table.sort(_names)
+ local _ret = table.concat(_names, ",")
+ tex.print(_ret)
+ }%
+}
+
+\newcommand{\LPTGetCellData}[1]{%
+ \directlua{
+ local _table = _lpt_get_table("\LPTGetCurrentTable");
+ local _index = _table:fix_cell_index("\luaescapestring{#1}");
+ local _cell = _table:get_cell(_index[1], _index[2]);
+ tex.print([[\string\unexpanded{]] .. _cell.data .. "}")
+ }%
+}
+
+\newcommand{\LPTGetCellMetaIndex}[2]{%
+ \directlua{
+ local _table = _lpt_get_table("\LPTGetCurrentTable");
+ local _index = _table:fix_cell_index("\luaescapestring{#2}");
+ local _cell = _table:get_cell(_index[1], _index[2]);
+ local _field = "\luaescapestring{#1}"
+ local _ret = rawget(_cell, _field)
+ if _ret == nil then
+ tex.print(20996, [[\string\c_novalue_tl]])
+ else
+ tex.print("{".. tostring(_ret[1]) .. "}{".. tostring(_ret[2]) .. "}")
+ end
+ }%
+}
+
+\newcommand{\LPTGetCellShape}[1]{%
+ \LPTGetCellMetaIndex{shape}{#1}%
+}
+
+\newcommand{\LPTGetCellParent}[1]{%
+ \LPTGetCellMetaIndex{parent}{#1}%
+}
+
+\newcommand{\LPTDeleteTable}[1]{
+ \directlua{
+ local _name = "\luaescapestring{#1}"
+ local _table = _lpt_get_table(_name)
+ lpt_info[_name] = nil
+ }
+}
+
+
+\makeatother
+
+\endinput \ No newline at end of file