diff options
author | Karl Berry <karl@freefriends.org> | 2020-08-14 20:28:12 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2020-08-14 20:28:12 +0000 |
commit | 1c1b1c09b9631a7f0be0aedd24c6f37f7658e9ee (patch) | |
tree | d0f9d4d5425086b8f5b2c66460fb43af0775f006 /Master/texmf-dist/tex/lualatex | |
parent | 5362413c4d74031afac3641a2d24f6861d526603 (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')
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 |