summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/luatex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2022-06-10 21:08:52 +0000
committerKarl Berry <karl@freefriends.org>2022-06-10 21:08:52 +0000
commitdc6f87f93a7df5ddba299019708c004b0d9bc663 (patch)
tree61246077aebbf516fbb6052aadd79f559f0cfa2a /Master/texmf-dist/tex/luatex
parentd202d200c4c69a85022f53a8193514901bcb49a6 (diff)
luakeys (10jun22)
git-svn-id: svn://tug.org/texlive/trunk@63537 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/luatex')
-rw-r--r--Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty2
-rw-r--r--Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex7
-rw-r--r--Master/texmf-dist/tex/luatex/luakeys/luakeys.lua1269
-rw-r--r--Master/texmf-dist/tex/luatex/luakeys/luakeys.sty2
4 files changed, 869 insertions, 411 deletions
diff --git a/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty b/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty
index 83ae86b08b4..7fcb483741c 100644
--- a/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty
+++ b/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty
@@ -17,6 +17,6 @@
% luakeys-debug.sty and luakeys-debug.tex.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys-debug}[2022/04/04 v0.5 Debug package for luakeys.]
+\ProvidesPackage{luakeys-debug}[2022/06/09 v0.6 Debug package for luakeys.]
\input luakeys-debug.tex
diff --git a/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex b/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex
index ffc449ac9b9..e7758cce0a5 100644
--- a/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex
+++ b/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex
@@ -37,7 +37,10 @@
}%
\def\luakeysdebug@parse@options#1{
\directlua{
- luakeys.save('debug_options', luakeys.parse('#1'))
+ luakeys.save(
+ 'debug_options',
+ luakeys.parse('#1', { format_keys = { 'snake', 'lower' } })
+ )
}
}%
\def\luakeysdebug@output#1{
@@ -47,7 +50,7 @@
\directlua{
local result = luakeys.parse('\luaescapestring{\unexpanded{#1}}', luakeys.get('debug_options'))
tex.print(luakeys.stringify(result, true))
- luakeys.print(result)
+ luakeys.debug(result)
}
}
}%
diff --git a/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua b/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua
index c5ccf6b01d6..ae689d02c14 100644
--- a/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua
+++ b/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua
@@ -15,59 +15,120 @@
--
-- This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
-- luakeys-debug.sty and luakeys-debug.tex.
-
--- A key-value parser written with Lpeg.
--
--- Explanations of some LPeg notation forms:
---
--- * `patt ^ 0` = `expression *`
--- * `patt ^ 1` = `expression +`
--- * `patt ^ -1` = `expression ?`
--- * `patt1 * patt2` = `expression1 expression2`: Sequence
--- * `patt1 + patt2` = `expression1 / expression2`: Ordered choice
---
--- * [TUGboat article: Parsing complex data formats in LuaTEX with LPEG](https://tug.org/TUGboat/tb40-2/tb125menke-Patterndf)
---
-- @module luakeys
-
local lpeg = require('lpeg')
-local Variable = lpeg.V
-local Pattern = lpeg.P
-local Set = lpeg.S
-local Range = lpeg.R
-local CaptureGroup = lpeg.Cg
-local CaptureFolding = lpeg.Cf
-local CaptureTable = lpeg.Ct
-local CaptureConstant = lpeg.Cc
-local CaptureSimple = lpeg.C
if not tex then
- tex = {}
+ tex = {
+ -- Dummy function for the tests.
+ sp = function(input)
+ return 1234567
+ end,
+ }
+end
+
+if not token then
+ token = {
+ set_macro = function(csname, content, global)
+ end,
+ }
+end
+
+local utils = {
+ --- Get the size of an array like table `{ 'one', 'two', 'three' }` = 3.
+ --
+ -- @tparam table value A table or any input.
+ --
+ -- @treturn number The size of the array like table. 0 if the input is
+ -- no table or the table is empty.
+ get_array_size = function(value)
+ local count = 0
+ if type(value) == 'table' then
+ for _ in ipairs(value) do
+ count = count + 1
+ end
+ end
+ return count
+ end,
+
+ --- Get the size of a table `{ one = 'one', 'two', 'three' }` = 3.
+ --
+ -- @tparam table value A table or any input.
+ --
+ -- @treturn number The size of the array like table. 0 if the input is
+ -- no table or the table is empty.
+ get_table_size = function(value)
+ local count = 0
+ if type(value) == 'table' then
+ for _ in pairs(value) do
+ count = count + 1
+ end
+ end
+ return count
+ end,
+
+ remove_from_array = function(array, element)
+ for index, value in pairs(array) do
+ if element == value then
+ array[index] = nil
+ return value
+ end
+ end
+ end,
+}
- -- Dummy function for the tests.
- tex['sp'] = function (input)
- return 1234567
+--- https://stackoverflow.com/a/1283608/10193818
+local function merge_tables(target, t2)
+ for k, v in pairs(t2) do
+ if type(v) == 'table' then
+ if type(target[k] or false) == 'table' then
+ merge_tables(target[k] or {}, t2[k] or {})
+ elseif target[k] == nil then
+ target[k] = v
+ end
+ elseif target[k] == nil then
+ target[k] = v
+ end
end
+ return target
end
---- Option handling
--- @section
+--- http://lua-users.org/wiki/CopyTable
+local function clone_table(orig)
+ local orig_type = type(orig)
+ local copy
+ if orig_type == 'table' then
+ copy = {}
+ for orig_key, orig_value in next, orig, nil do
+ copy[clone_table(orig_key)] = clone_table(orig_value)
+ end
+ setmetatable(copy, clone_table(getmetatable(orig)))
+ else -- number, string, boolean, etc
+ copy = orig
+ end
+ return copy
+end
--- This table stores all allowed option keys.
-local option_keys = {
- 'convert_dimensions',
- 'unpack_single_array_values',
- 'standalone_as_true',
- 'converter',
- 'case_insensitive_keys'
+local all_options = {
+ convert_dimensions = false,
+ debug = false,
+ default = true,
+ defaults = false,
+ defs = false,
+ format_keys = false,
+ hooks = {},
+ naked_as_value = false,
+ no_error = false,
+ postprocess = false,
+ preprocess = false,
+ unpack = true,
}
--- The default options.
-local default_options = {
- convert_dimensions = true,
- unpack_single_array_values = true,
- standalone_as_true = false,
-}
+local default_options = clone_table(all_options)
local function throw_error(message)
if type(tex.error) == 'function' then
@@ -77,58 +138,142 @@ local function throw_error(message)
end
end
---- Convert a key so that it can be written as a table field without
--- quotes and square brackets (for example `one 2` becomes `one_2`).
--- The key can then reference values from a table using dot notation.
--- (`table["one 2"]` becomes `table.one_2`).
---
--- @tparam string key The key to be converted.
---
--- @treturn string The converted key.
-local function luafy_key(key)
- return key:gsub('[^%w]+', '_')
-end
+local l3_code_cctab = 10
---- Convert all keys in a table to strings containig only alphanumeric
--- characters and underscores.
+--- Convert back to strings
+-- @section
+
+--- The function `render(tbl)` reverses the function
+-- `parse(kv_string)`. It takes a Lua table and converts this table
+-- into a key-value string. The resulting string usually has a
+-- different order as the input table. In Lua only tables with
+-- 1-based consecutive integer keys (a.k.a. array tables) can be
+-- parsed in order.
--
--- @param raw_options Some raw options.
+-- @tparam table result A table to be converted into a key-value string.
--
--- @treturn table Returns always a table. If the input value is not a
--- an empty table is returned.
-local function luafy_options(raw_options)
- if type(raw_options) ~= 'table' then
- raw_options = {}
- end
- local options = {}
- for key, value in pairs(raw_options) do
- options[luafy_key(key)] = value
+-- @treturn string A key-value string that can be passed to a TeX
+-- macro.
+local function render(result)
+ local function render_inner(result)
+ local output = {}
+ local function add(text)
+ table.insert(output, text)
+ end
+ for key, value in pairs(result) do
+ if (key and type(key) == 'string') then
+ if (type(value) == 'table') then
+ if (next(value)) then
+ add(key .. '={')
+ add(render_inner(value))
+ add('},')
+ else
+ add(key .. '={},')
+ end
+ else
+ add(key .. '=' .. tostring(value) .. ',')
+ end
+ else
+ add(tostring(value) .. ',')
+ end
+ end
+ return table.concat(output)
end
- return options
+ return render_inner(result)
end
---- All option keys can be written with underscores or with spaces as
--- separators.
--- For the LaTeX version of the macro
--- `\luakeysdebug[options]{kv-string}`.
+--- The function `stringify(tbl, for_tex)` converts a Lua table into a
+-- printable string. Stringify a table means to convert the table into
+-- a string. This function is used to realize the `debug` function.
+-- `stringify(tbl, true)` (`for_tex = true`) generates a string which
+-- can be embeded into TeX documents. The macro `\luakeysdebug{}` uses
+-- this option. `stringify(tbl, false)` or `stringify(tbl)` generate a
+-- string suitable for the terminal.
--
--- @tparam table options_raw Options in a raw format. The table may be
--- empty or some keys are not set.
+-- @tparam table result A table to stringify.
--
--- @treturn table
-local function normalize_parse_options (options_raw)
- options_raw = luafy_options(options_raw)
- local options = {}
-
- for _, option_name in ipairs(option_keys) do
- if options_raw[option_name] ~= nil then
- options[option_name] = options_raw[option_name]
- else
- options[option_name] = default_options[option_name]
+-- @tparam boolean for_tex Stringify the table into a text string that
+-- can be embeded inside a TeX document via tex.print(). Curly braces
+-- and whites spaces are escaped.
+--
+-- https://stackoverflow.com/a/54593224/10193818
+local function stringify(result, for_tex)
+ local line_break, start_bracket, end_bracket, indent
+
+ if for_tex then
+ line_break = '\\par'
+ start_bracket = '$\\{$'
+ end_bracket = '$\\}$'
+ indent = '\\ \\ '
+ else
+ line_break = '\n'
+ start_bracket = '{'
+ end_bracket = '}'
+ indent = ' '
+ end
+
+ local function stringify_inner(input, depth)
+ local output = {}
+ depth = depth or 0
+
+ local function add(depth, text)
+ table.insert(output, string.rep(indent, depth) .. text)
+ end
+
+ local function format_key(key)
+ if (type(key) == 'number') then
+ return string.format('[%s]', key)
+ else
+ return string.format('[\'%s\']', key)
+ end
end
+
+ if type(input) ~= 'table' then
+ return tostring(input)
+ end
+
+ for key, value in pairs(input) do
+ if (key and type(key) == 'number' or type(key) == 'string') then
+ key = format_key(key)
+
+ if (type(value) == 'table') then
+ if (next(value)) then
+ add(depth, key .. ' = ' .. start_bracket)
+ add(0, stringify_inner(value, depth + 1))
+ add(depth, end_bracket .. ',');
+ else
+ add(depth, key .. ' = ' .. start_bracket .. end_bracket .. ',')
+ end
+ else
+ if (type(value) == 'string') then
+ value = string.format('\'%s\'', value)
+ else
+ value = tostring(value)
+ end
+
+ add(depth, key .. ' = ' .. value .. ',')
+ end
+ end
+ end
+
+ return table.concat(output, line_break)
end
- return options
+ return
+ start_bracket .. line_break .. stringify_inner(result, 1) .. line_break ..
+ end_bracket
+end
+
+--- The function `debug(tbl)` pretty prints a Lua table to standard
+-- output (stdout). It is a utility function that can be used to
+-- debug and inspect the resulting Lua table of the function
+-- `parse`. You have to compile your TeX document in a console to
+-- see the terminal output.
+--
+-- @tparam table result A table to be printed to standard output for
+-- debugging purposes.
+local function debug(result)
+ print('\n' .. stringify(result, false))
end
--- Parser / Lpeg related
@@ -136,18 +281,42 @@ end
--- Generate the PEG parser using Lpeg.
--
+-- Explanations of some LPeg notation forms:
+--
+-- * `patt ^ 0` = `expression *`
+-- * `patt ^ 1` = `expression +`
+-- * `patt ^ -1` = `expression ?`
+-- * `patt1 * patt2` = `expression1 expression2`: Sequence
+-- * `patt1 + patt2` = `expression1 / expression2`: Ordered choice
+--
+-- * [TUGboat article: Parsing complex data formats in LuaTEX with LPEG](https://tug.org/TUGboat/tb40-2/tb125menke-Patterndf)
+--
-- @treturn userdata The parser.
-local function generate_parser(options)
+local function generate_parser(initial_rule, convert_dimensions)
+ if convert_dimensions == nil then
+ convert_dimensions = false
+ end
+
+ local Variable = lpeg.V
+ local Pattern = lpeg.P
+ local Set = lpeg.S
+ local Range = lpeg.R
+ local CaptureGroup = lpeg.Cg
+ local CaptureFolding = lpeg.Cf
+ local CaptureTable = lpeg.Ct
+ local CaptureConstant = lpeg.Cc
+ local CaptureSimple = lpeg.C
+
-- Optional whitespace
local white_space = Set(' \t\n\r')
--- Match literal string surrounded by whitespace
local ws = function(match)
- return white_space^0 * Pattern(match) * white_space^0
+ return white_space ^ 0 * Pattern(match) * white_space ^ 0
end
- local capture_dimension = function (input)
- if options.convert_dimensions then
+ local capture_dimension = function(input)
+ if convert_dimensions then
return tex.sp(input)
else
return input
@@ -180,8 +349,9 @@ local function generate_parser(options)
end
end
+ -- LuaFormatter off
return Pattern({
- 'list',
+ [1] = initial_rule,
-- list_item*
list = CaptureFolding(
@@ -220,6 +390,9 @@ local function generate_parser(options)
Variable('string_quoted') * -Variable('value') +
Variable('string_unquoted'),
+ -- for is.boolean()
+ boolean_only = Variable('boolean') * -1,
+
-- boolean_true / boolean_false
boolean =
(
@@ -237,18 +410,31 @@ local function generate_parser(options)
Pattern('FALSE') +
Pattern('False'),
+ -- for is.dimension()
+ dimension_only = Variable('dimension') * -1,
+
dimension = (
- Variable('sign')^0 * white_space^0 *
Variable('tex_number') * white_space^0 *
Variable('unit')
) / capture_dimension,
- number =
- (white_space^0 * (Variable('lua_number') / tonumber) * white_space^0) ,
+ -- for is.number()
+ number_only = Variable('number') * -1,
+
+ -- capture number
+ number = Variable('tex_number') / tonumber,
+ -- sign? white_space? (integer+ fractional? / fractional)
tex_number =
- (Variable('integer')^1 * (Pattern('.') * Variable('integer')^1)^0) +
- (Pattern('.') * Variable('integer')^1),
+ Variable('sign')^0 * white_space^0 *
+ (Variable('integer')^1 * Variable('fractional')^0) +
+ Variable('fractional'),
+
+ sign = Set('-+'),
+
+ fractional = Pattern('.') * Variable('integer')^1,
+
+ integer = Range('09')^1,
-- 'bp' / 'BP' / 'cc' / etc.
-- https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua
@@ -267,18 +453,6 @@ local function generate_parser(options)
Pattern('pt') + Pattern('PT') +
Pattern('sp') + Pattern('SP'),
- lua_number =
- Variable('int') *
- Variable('frac')^-1,
-
- int = Variable('sign')^-1 * (
- Range('19') * Variable('integer') + Variable('integer')
- ),
-
- frac = Pattern('.') * Variable('integer'),
- sign = Set('-+'),
- integer = Range('09')^1,
-
-- '"' ('\"' / !'"')* '"'
string_quoted =
white_space^0 * Pattern('"') *
@@ -294,68 +468,25 @@ local function generate_parser(options)
word_unquoted = (1 - white_space - Set('{},='))^1
})
+-- LuaFormatter on
end
---- Get the size of an array like table `{ 'one', 'two', 'three' }` = 3.
---
--- @tparam table value A table or any input.
---
--- @treturn number The size of the array like table. 0 if the input is
--- no table or the table is empty.
-local function get_array_size(value)
- local count = 0
- if type(value) == 'table' then
- for _ in ipairs(value) do count = count + 1 end
- end
- return count
-end
-
---- Get the size of a table `{ one = 'one', 'two', 'three' }` = 3.
---
--- @tparam table value A table or any input.
---
--- @treturn number The size of the array like table. 0 if the input is
--- no table or the table is empty.
-local function get_table_size(value)
- local count = 0
- if type(value) == 'table' then
- for _ in pairs(value) do count = count + 1 end
- end
- return count
-end
-
---- Unpack a single valued array table like `{ 'one' }` into `one` or
--- `{ 1 }` into `1`.
---
--- @treturn If the value is a array like table with one non table typed
--- value in it, the unpacked value, else the unchanged input.
-local function unpack_single_valued_array_table(value, options)
- if
- type(value) == 'table' and
- get_array_size(value) == 1 and
- get_table_size(value) == 1 and
- type(value[1]) ~= 'table'
- then
- if type(value[1]) == 'string' and options.standalone_as_true then
- return value
- else
- return value[1]
- end
- end
- return value
-end
-
-local function visit_parse_tree(parse_tree, callback_func)
- if type(parse_tree) ~= 'table' then
- throw_error('Parse tree has to be a table')
+local function visit_tree(tree, callback_func)
+ if type(tree) ~= 'table' then
+ throw_error('Parameter “tree” has to be a table, got: ' ..
+ tostring(tree))
end
- local function visit_parse_tree_recursive(root_table, current_table, result, depth, callback_func)
- for key, value in pairs(current_table) do
+ local function visit_tree_recursive(tree,
+ current,
+ result,
+ depth,
+ callback_func)
+ for key, value in pairs(current) do
if type(value) == 'table' then
- value = visit_parse_tree_recursive(root_table, value, {}, depth + 1, callback_func)
+ value = visit_tree_recursive(tree, value, {}, depth + 1, callback_func)
end
- key, value = callback_func(key, value, depth, current_table, root_table)
+ key, value = callback_func(key, value, depth, current, tree)
if key ~= nil and value ~= nil then
result[key] = value
@@ -366,255 +497,574 @@ local function visit_parse_tree(parse_tree, callback_func)
end
end
- return visit_parse_tree_recursive(parse_tree, parse_tree, {}, 1, callback_func)
+ local result = visit_tree_recursive(tree, tree, {}, 1, callback_func)
+
+ if result == nil then
+ return {}
+ end
+ return result
end
---- Normalize the result tables of the LPeg parser. This normalization
--- tasks are performed on the raw input table coming directly from the
--- PEG parser:
---
--- * Unpack all single valued array like tables: `{ 'text' }` into
--- `text`
---
--- @tparam table raw The raw input table coming directly from the PEG
--- parser
---
--- @tparam table options Some options. A table with the key
--- `unpack_single_array_values`
---
--- @treturn table A normalized table ready for the outside world.
-local function normalize(raw, options)
- local function normalize_recursive(raw, result, options)
- for key, value in pairs(raw) do
- if options.unpack_single_array_values then
- value = unpack_single_valued_array_table(value, options)
- end
- if type(value) == 'table' then
- result[key] = normalize_recursive(value, {}, options)
- else
- result[key] = value
- end
+local is = {
+ boolean = function(value)
+ if value == nil then
+ return false
end
- return result
- end
- raw = normalize_recursive(raw, {}, options)
+ if type(value) == 'boolean' then
+ return true
+ end
+ local parser = generate_parser('boolean_only', false)
+ local result = parser:match(value)
+ return result ~= nil
+ end,
+
+ dimension = function(value)
+ if value == nil then
+ return false
+ end
+ local parser = generate_parser('dimension_only', false)
+ local result = parser:match(value)
+ return result ~= nil
+ end,
+
+ integer = function(value)
+ local n = tonumber(value)
+ if n == nil then
+ return false
+ end
+ return n == math.floor(n)
+ end,
- if options.standalone_as_true then
- raw = visit_parse_tree(raw, function (key, value)
- if type(key) == 'number' and type(value) == 'string' then
- return value, true
- end
- return key, value
- end)
- end
+ number = function(value)
+ if value == nil then
+ return false
+ end
+ if type(value) == 'number' then
+ return true
+ end
+ local parser = generate_parser('number_only', false)
+ local result = parser:match(value)
+ return result ~= nil
+ end,
+
+ string = function(value)
+ return type(value) == 'string'
+ end,
+}
- if options.case_insensitive_keys then
- raw = visit_parse_tree(raw, function (key, value)
- if type(key) == 'string' then
- return key:lower(), value
- end
- return key, value
- end)
+--- Apply the key-value-pair definitions (defs) on an input table in a
+--- recursive fashion.
+---
+---@param defs table A table containing all definitions.
+---@param opts table The parse options table.
+---@param input table The current input table.
+---@param output table The current output table.
+---@param unknown table Always the root unknown table.
+---@param key_path table An array of key names leading to the current
+---@param input_root table The root input table
+--- input and output table.
+local function apply_definitions(defs,
+ opts,
+ input,
+ output,
+ unknown,
+ key_path,
+ input_root)
+ local exclusive_groups = {}
+
+ local function add_to_key_path(key_path, key)
+ local new_key_path = {}
+
+ for index, value in ipairs(key_path) do
+ new_key_path[index] = value
+ end
+
+ table.insert(new_key_path, key)
+ return new_key_path
end
- return raw
-end
+ local function set_default_value(def)
+ if def.default ~= nil then
+ return def.default
+ elseif opts ~= nil and opts.default ~= nil then
+ return opts.default
+ end
+ return true
+ end
---- Parse a LaTeX/TeX style key-value string into a Lua table. With
--- this function you should be able to parse key-value strings like
--- this example:
---
--- show,
--- hide,
--- key with spaces = String without quotes,
--- string="String with double quotes: ,{}=",
--- dimension = 1cm,
--- number = -1.2,
--- list = {one,two,three},
--- key value list = {one=one,two=two,three=three},
--- nested key = {
--- nested key 2= {
--- key = value,
--- },
--- },
---
--- The string above results in this Lua table:
---
--- {
--- 'show',
--- 'hide',
--- ['key with spaces'] = 'String without quotes',
--- string = 'String with double quotes: ,{}=',
--- dimension = 1864679,
--- number = -1.2,
--- list = {'one', 'two', 'three'},
--- key value list = {
--- one = 'one',
--- three = 'three',
--- two = 'two'
--- },
--- ['nested key'] = {
--- ['nested key 2'] = {
--- key = 'value'
--- }
--- },
--- }
---
--- @tparam string kv_string A string in the TeX/LaTeX style key-value
--- format as described above.
---
--- @tparam table options A table containing
--- settings: `convert_dimensions`, `unpack_single_array_values`, `standalone_as_true`, `converter`
---
--- @treturn table A hopefully properly parsed table you can do
--- something useful with.
-local function parse (kv_string, options)
- if kv_string == nil then
- return {}
+ local function find_value(search_key, def)
+ if input[search_key] ~= nil then
+ local value = input[search_key]
+ input[search_key] = nil
+ return value
+ -- naked keys: values with integer keys
+ elseif utils.remove_from_array(input, search_key) ~= nil then
+ return set_default_value(def)
+ end
end
- options = normalize_parse_options(options)
- local parser = generate_parser(options)
- local parse_tree = parser:match(kv_string)
+ local apply = {
+ alias = function(value, key, def)
+ if type(def.alias) == 'string' then
+ def.alias = { def.alias }
+ end
+ local alias_value
+ local used_alias_key
+ -- To get an error if the key and an alias is present
+ if value ~= nil then
+ alias_value = value
+ used_alias_key = key
+ end
+ for _, alias in ipairs(def.alias) do
+ local v = find_value(alias, def)
+ if v ~= nil then
+ if alias_value ~= nil then
+ throw_error(string.format(
+ 'Duplicate aliases “%s” and “%s” for key “%s”!',
+ used_alias_key, alias, key))
+ end
+ used_alias_key = alias
+ alias_value = v
+ end
+ end
+ if alias_value ~= nil then
+ return alias_value
+ end
+ end,
- if options.converter ~= nil and type(options.converter) == 'function' then
- parse_tree = visit_parse_tree(parse_tree, options.converter)
- end
- return normalize(parse_tree, options)
-end
+ always_present = function(value, key, def)
+ if value == nil and def.always_present then
+ return set_default_value(def)
+ end
+ end,
---- Convert back to strings
--- @section
+ choices = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.choices ~= nil and type(def.choices) == 'table' then
+ local is_in_choices = false
+ for _, choice in ipairs(def.choices) do
+ if value == choice then
+ is_in_choices = true
+ end
+ end
+ if not is_in_choices then
+ throw_error('The value “' .. value ..
+ '” does not exist in the choices: ' ..
+ table.concat(def.choices, ', ') .. '!')
+ end
+ end
+ end,
---- The function `render(tbl)` reverses the function
--- `parse(kv_string)`. It takes a Lua table and converts this table
--- into a key-value string. The resulting string usually has a
--- different order as the input table. In Lua only tables with
--- 1-based consecutive integer keys (a.k.a. array tables) can be
--- parsed in order.
---
--- @tparam table tbl A table to be converted into a key-value string.
---
--- @treturn string A key-value string that can be passed to a TeX
--- macro.
-local function render (tbl)
- local function render_inner(tbl)
- local output = {}
- local function add(text)
- table.insert(output, text)
- end
- for key, value in pairs(tbl) do
- if (key and type(key) == 'string') then
- if (type(value) == 'table') then
- if (next(value)) then
- add(key .. '={')
- add(render_inner(value))
- add('},')
+ data_type = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.data_type ~= nil then
+ local converted
+ -- boolean
+ if def.data_type == 'boolean' then
+ if value == 0 or value == '' or not value then
+ converted = false
else
- add(key .. '={},')
+ converted = true
end
+ -- dimension
+ elseif def.data_type == 'dimension' then
+ if is.dimension(value) then
+ converted = value
+ end
+ -- integer
+ elseif def.data_type == 'integer' then
+ if is.number(value) then
+ converted = math.floor(tonumber(value))
+ end
+ -- number
+ elseif def.data_type == 'number' then
+ if is.number(value) then
+ converted = tonumber(value)
+ end
+ -- string
+ elseif def.data_type == 'string' then
+ converted = tostring(value)
else
- add(key .. '=' .. tostring(value) .. ',')
+ throw_error('Unknown data type: ' .. def.data_type)
end
- else
- add(tostring(value) .. ',')
+ if converted == nil then
+ throw_error('The value “' .. value .. '” of the key “' .. key ..
+ '” could not be converted into the data type “' ..
+ def.data_type .. '”!')
+ else
+ return converted
+ end
+ end
+ end,
+
+ exclusive_group = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.exclusive_group ~= nil then
+ if exclusive_groups[def.exclusive_group] ~= nil then
+ throw_error('The key “' .. key ..
+ '” belongs to a mutually exclusive group “' ..
+ def.exclusive_group .. '” and the key “' ..
+ exclusive_groups[def.exclusive_group] ..
+ '” is already present!')
+ else
+ exclusive_groups[def.exclusive_group] = key
+ end
+ end
+ end,
+
+ l3_tl_set = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.l3_tl_set ~= nil then
+ tex.print(l3_code_cctab, '\\tl_set:Nn \\g_' .. def.l3_tl_set .. '_tl')
+ tex.print('{' .. value .. '}')
+ end
+ end,
+
+ macro = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.macro ~= nil then
+ token.set_macro(def.macro, value, 'global')
+ end
+ end,
+
+ match = function(value, key, def)
+ if value == nil then
+ return
end
+ if def.match ~= nil then
+ if type(def.match) ~= 'string' then
+ throw_error('def.match has to be a string')
+ end
+ local match = string.match(value, def.match)
+ if match == nil then
+ throw_error('The value “' .. value .. '” of the key “' .. key ..
+ '” does not match “' .. def.match .. '”!')
+ else
+ return match
+ end
+ end
+ end,
+
+ opposite_keys = function(value, key, def)
+ if def.opposite_keys ~= nil then
+ local true_value = def.opposite_keys[true]
+ local false_value = def.opposite_keys[false]
+ if true_value == nil or false_value == nil then
+ throw_error(
+ 'Usage opposite_keys = { [true] = "...", [false] = "..." }')
+ end
+ if utils.remove_from_array(input, true_value) ~= nil then
+ return true
+ elseif utils.remove_from_array(input, false_value) ~= nil then
+ return false
+ end
+ end
+ end,
+
+ process = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.process ~= nil and type(def.process) == 'function' then
+ return def.process(value, input_root, output, unknown)
+ end
+ end,
+
+ required = function(value, key, def)
+ if def.required ~= nil and def.required and value == nil then
+ throw_error(string.format('Missing required key “%s”!', key))
+ end
+ end,
+
+ sub_keys = function(value, key, def)
+ if def.sub_keys ~= nil then
+ local v
+ -- To get keys defined with always_present
+ if value == nil then
+ v = {}
+ elseif type(value) == 'string' then
+ v = { value }
+ elseif type(value) == 'table' then
+ v = value
+ end
+ v = apply_definitions(def.sub_keys, opts, v, output[key], unknown,
+ add_to_key_path(key_path, key), input_root)
+ if utils.get_table_size(v) > 0 then
+ return v
+ end
+ end
+ end,
+ }
+
+ --- standalone values are removed.
+ -- For some callbacks and the third return value of parse, we
+ -- need an unchanged raw result from the parse function.
+ input = clone_table(input)
+ if output == nil then
+ output = {}
+ end
+ if unknown == nil then
+ unknown = {}
+ end
+ if key_path == nil then
+ key_path = {}
+ end
+
+ for index, def in pairs(defs) do
+ --- Find key and def
+ local key
+ if type(def) == 'table' and def.name == nil and type(index) == 'string' then
+ key = index
+ elseif type(def) == 'table' and def.name ~= nil then
+ key = def.name
+ elseif type(index) == 'number' and type(def) == 'string' then
+ key = def
+ def = { default = true }
end
- return table.concat(output)
+
+ if type(def) ~= 'table' then
+ throw_error('Key definition must be a table')
+ end
+
+ if key == nil then
+ throw_error('key name couldn’t be detected!')
+ end
+
+ local value = find_value(key, def)
+
+ for _, def_opt in ipairs({
+ 'alias',
+ 'opposite_keys',
+ 'always_present',
+ 'required',
+ 'data_type',
+ 'choices',
+ 'match',
+ 'exclusive_group',
+ 'macro',
+ 'l3_tl_set',
+ 'process',
+ 'sub_keys',
+ }) do
+ if def[def_opt] ~= nil then
+ local tmp_value = apply[def_opt](value, key, def)
+ if tmp_value ~= nil then
+ value = tmp_value
+ end
+ end
+ end
+
+ output[key] = value
end
- return render_inner(tbl)
+
+ if utils.get_table_size(input) > 0 then
+ -- Move to the current unknown table.
+ local current_unknown = unknown
+ for _, key in ipairs(key_path) do
+ if current_unknown[key] == nil then
+ current_unknown[key] = {}
+ end
+ current_unknown = current_unknown[key]
+ end
+
+ -- Copy all unknown key-value-pairs to the current unknown table.
+ for key, value in pairs(input) do
+ current_unknown[key] = value
+ end
+ end
+
+ return output, unknown
end
---- The function `stringify(tbl, for_tex)` converts a Lua table into a
--- printable string. Stringify a table means to convert the table into
--- a string. This function is used to realize the `print` function.
--- `stringify(tbl, true)` (`for_tex = true`) generates a string which
--- can be embeded into TeX documents. The macro `\luakeysdebug{}` uses
--- this option. `stringify(tbl, false)` or `stringify(tbl)` generate a
--- string suitable for the terminal.
---
--- @tparam table input A table to stringify.
---
--- @tparam boolean for_tex Stringify the table into a text string that
--- can be embeded inside a TeX document via tex.print(). Curly braces
--- and whites spaces are escaped.
+--- Parse a LaTeX/TeX style key-value string into a Lua table.
+---
+---@param kv_string string A string in the TeX/LaTeX style key-value format as described above.
+---@param opts table A table containing the settings:
+--- `convert_dimensions`, `unpack`, `naked_as_value`, `converter`,
+--- `debug`, `preprocess`, `postprocess`.
--
--- https://stackoverflow.com/a/54593224/10193818
-local function stringify(input, for_tex)
- local line_break, start_bracket, end_bracket, indent
-
- if for_tex then
- line_break = '\\par'
- start_bracket = '$\\{$'
- end_bracket = '$\\}$'
- indent = '\\ \\ '
- else
- line_break = '\n'
- start_bracket = '{'
- end_bracket = '}'
- indent = ' '
+---@return table result The final result of all individual parsing and normalization steps.
+---@return table unknown A table with unknown, undefinied key-value pairs.
+---@return table raw The unprocessed, raw result of the LPeg parser.
+local function parse(kv_string, opts)
+ if kv_string == nil then
+ return {}
end
- local function stringify_inner(input, depth)
- local output = {}
- depth = depth or 0
+ --- Normalize the parse options.
+ ---
+ --- @param opts table Options in a raw format. The table may be empty or some keys are not set.
+ ---
+ --- @return table
+ local function normalize_opts(opts)
+ if type(opts) ~= 'table' then
+ opts = {}
+ end
+ for key, _ in pairs(opts) do
+ if all_options[key] == nil then
+ throw_error('Unknown parse option: ' .. tostring(key) .. '!')
+ end
+ end
+ local old_opts = opts
+ opts = {}
+ for name, _ in pairs(all_options) do
+ if old_opts[name] ~= nil then
+ opts[name] = old_opts[name]
+ else
+ opts[name] = default_options[name]
+ end
+ end
- local function add(depth, text)
- table.insert(output, string.rep(indent, depth) .. text)
+ local hooks = {
+ kv_string = true,
+ keys_before_opts = true,
+ result_before_opts = true,
+ keys_before_def = true,
+ result_before_def = true,
+ keys = true,
+ result = true,
+ }
+
+ for hook in pairs(opts.hooks) do
+ if hooks[hook] == nil then
+ throw_error('Unknown hook: ' .. tostring(hook) .. '!')
+ end
end
+ return opts
+ end
+ opts = normalize_opts(opts)
- local function format_key(key)
- if (type(key) == 'number') then
- return string.format('[%s]', key)
+ if type(opts.hooks.kv_string) == 'function' then
+ kv_string = opts.hooks.kv_string(kv_string)
+ end
+
+ local result = generate_parser('list', opts.convert_dimensions):match(
+ kv_string)
+ local raw = clone_table(result)
+
+ local function apply_hook(name)
+ if type(opts.hooks[name]) == 'function' then
+ if name:match('^keys') then
+ result = visit_tree(result, opts.hooks[name])
else
- return string.format('[\'%s\']', key)
+ opts.hooks[name](result)
+ end
+
+ if opts.debug then
+ print('After the execution of the hook: ' .. name)
+ debug(result)
end
end
+ end
- if type(input) ~= 'table' then
- return tostring(input)
+ local function apply_hooks(at)
+ if at ~= nil then
+ at = '_' .. at
+ else
+ at = ''
end
+ apply_hook('keys' .. at)
+ apply_hook('result' .. at)
+ end
- for key, value in pairs(input) do
- if (key and type(key) == 'number' or type(key) == 'string') then
- key = format_key(key)
+ apply_hooks('before_opts')
- if (type(value) == 'table') then
- if (next(value)) then
- add(depth, key .. ' = ' .. start_bracket)
- add(0, stringify_inner(value, depth + 1))
- add(depth, end_bracket .. ',');
- else
- add(depth, key .. ' = ' .. start_bracket .. end_bracket .. ',')
- end
- else
- if (type(value) == 'string') then
- value = string.format('\'%s\'', value)
- else
- value = tostring(value)
- end
+ --- Normalize the result table of the LPeg parser. This normalization
+ -- tasks are performed on the raw input table coming directly from
+ -- the PEG parser:
+ --
+ --- @param result table The raw input table coming directly from the PEG parser
+ --- @param opts table Some options.
+ local function apply_opts(result, opts)
+ local callbacks = {
+ unpack = function(key, value)
+ if type(value) == 'table' and utils.get_array_size(value) == 1 and
+ utils.get_table_size(value) == 1 and type(value[1]) ~= 'table' then
+ return key, value[1]
+ end
+ return key, value
+ end,
- add(depth, key .. ' = ' .. value .. ',')
+ process_naked = function(key, value)
+ if type(key) == 'number' and type(value) == 'string' then
+ return value, opts.default
+ end
+ return key, value
+ end,
+
+ format_key = function(key, value)
+ if type(key) == 'string' then
+ for _, style in ipairs(opts.format_keys) do
+ if style == 'lower' then
+ key = key:lower()
+ elseif style == 'snake' then
+ key = key:gsub('[^%w]+', '_')
+ elseif style == 'upper' then
+ key = key:upper()
+ else
+ throw_error('Unknown style to format keys: ' .. tostring(style) ..
+ ' Allowed styles are: lower, snake, upper')
+ end
+ end
end
+ return key, value
+ end,
+ }
+
+ if opts.unpack then
+ result = visit_tree(result, callbacks.unpack)
+ end
+
+ if not opts.naked_as_value and opts.defs == false then
+ result = visit_tree(result, callbacks.process_naked)
+ end
+
+ if opts.format_keys then
+ if type(opts.format_keys) ~= 'table' then
+ throw_error('The option “format_keys” has to be a table not ' ..
+ type(opts.format_keys))
end
+ result = visit_tree(result, callbacks.format_key)
end
- return table.concat(output, line_break)
+ return result
+ end
+ result = apply_opts(result, opts)
+
+ -- All unknown keys are stored in this table
+ local unknown = nil
+ if type(opts.defs) == 'table' then
+ apply_hooks('before_defs')
+ result, unknown = apply_definitions(opts.defs, opts, result, {}, {}, {},
+ clone_table(result))
end
- return start_bracket .. line_break .. stringify_inner(input, 1) .. line_break .. end_bracket
-end
+ apply_hooks()
---- The function `pretty_print(tbl)` pretty prints a Lua table to standard
--- output (stdout). It is a utility function that can be used to
--- debug and inspect the resulting Lua table of the function
--- `parse`. You have to compile your TeX document in a console to
--- see the terminal output.
---
--- @tparam table tbl A table to be printed to standard output for
--- debugging purposes.
-local function pretty_print(tbl)
- print(stringify(tbl, false))
+ if opts.defaults ~= nil and type(opts.defaults) == 'table' then
+ merge_tables(result, opts.defaults)
+ end
+
+ if opts.debug then
+ debug(result)
+ end
+
+ -- no_error
+ if not opts.no_error and type(unknown) == 'table' and
+ utils.get_table_size(unknown) > 0 then
+ throw_error('Unknown keys: ' .. render(unknown))
+ end
+ return result, unknown, raw
end
--- Store results
@@ -623,38 +1073,12 @@ end
--- A table to store parsed key-value results.
local result_store = {}
---- The function `save(identifier, result): void` saves a result (a
--- table from a previous run of `parse`) under an identifier.
--- Therefore, it is not necessary to pollute the global namespace to
--- store results for the later usage.
---
--- @tparam string identifier The identifier under which the result is
--- saved.
---
--- @tparam table result A result to be stored and that was created by
--- the key-value parser.
-local function save(identifier, result)
- result_store[identifier] = result
-end
-
---- The function `get(identifier): table` retrieves a saved result
--- from the result store.
---
--- @tparam string identifier The identifier under which the result was
--- saved.
-local function get(identifier)
- -- if result_store[identifier] == nil then
- -- throw_error('No stored result was found for the identifier \'' .. identifier .. '\'')
- -- end
- return result_store[identifier]
-end
-
--- Exports
-- @section
local export = {
--- @see default_options
- default_options = default_options,
+ opts = default_options,
--- @see stringify
stringify = stringify,
@@ -662,27 +1086,58 @@ local export = {
--- @see parse
parse = parse,
+ define = function(defs, opts)
+ return function(kv_string, inner_opts)
+ local options
+ if inner_opts ~= nil then
+ options = inner_opts
+ elseif opts ~= nil then
+ options = opts
+ end
+
+ if options == nil then
+ options = {}
+ end
+
+ options.defs = defs
+
+ return parse(kv_string, options)
+ end
+ end,
+
--- @see render
render = render,
- --- @see pretty_print
- print = pretty_print,
+ --- @see debug
+ debug = debug,
- --- @see save
- save = save,
-
- --- @see get
- get = get,
+ --- The function `save(identifier, result): void` saves a result (a
+ -- table from a previous run of `parse`) under an identifier.
+ -- Therefore, it is not necessary to pollute the global namespace to
+ -- store results for the later usage.
+ --
+ -- @tparam string identifier The identifier under which the result is
+ -- saved.
+ --
+ -- @tparam table result A result to be stored and that was created by
+ -- the key-value parser.
+ save = function(identifier, result)
+ result_store[identifier] = result
+ end,
+
+ --- The function `get(identifier): table` retrieves a saved result
+ -- from the result store.
+ --
+ -- @tparam string identifier The identifier under which the result was
+ -- saved.
+ get = function(identifier)
+ -- if result_store[identifier] == nil then
+ -- throw_error('No stored result was found for the identifier \'' .. identifier .. '\'')
+ -- end
+ return result_store[identifier]
+ end,
+
+ is = is,
}
--- http://olivinelabs.com/busted/#private
-if _TEST then
- export.luafy_key = luafy_key
- export.luafy_options = luafy_options
- export.normalize = normalize
- export.normalize_parse_options = normalize_parse_options
- export.unpack_single_valued_array_table = unpack_single_valued_array_table
- export.visit_parse_tree = visit_parse_tree
-end
-
return export
diff --git a/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty b/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty
index 2cf22b837a4..51e2c9bec5f 100644
--- a/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty
+++ b/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty
@@ -17,5 +17,5 @@
% luakeys-debug.sty and luakeys-debug.tex.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys}[2022/04/04 v0.5 Parsing key-value options using Lua.]
+\ProvidesPackage{luakeys}[2022/06/09 v0.6 Parsing key-value options using Lua.]
\directlua{luakeys = require('luakeys')}