Skip to content

Commit

Permalink
Rewrite as_inlines, as_blocks with focus on performance
Browse files Browse the repository at this point in the history
This also fixes unwanted behavior of `as_blocks`, which would treat a
list of Inline elements as a list of singleton Plain elements, leading
to bad results.
  • Loading branch information
tarleb committed Nov 27, 2024
1 parent 48bb28b commit 6512bbe
Showing 1 changed file with 63 additions and 47 deletions.
110 changes: 63 additions & 47 deletions src/resources/pandoc/datadir/_utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -265,59 +265,75 @@ local function get_type(v)
return pandoc_type
end

local function as_inlines(v)
if v == nil then
return pandoc.Inlines({})
end
local t = pandoc.utils.type(v)
if t == "Inlines" then
---@cast v pandoc.Inlines
return v
elseif t == "Blocks" then
return pandoc.utils.blocks_to_inlines(v)
elseif t == "Inline" then
return pandoc.Inlines({v})
elseif t == "Block" then
return pandoc.utils.blocks_to_inlines({v})
end
--- Blocks metatable
local BlocksMT = getmetatable(pandoc.Blocks{})
--- Inlines metatable
local InlinesMT = getmetatable(pandoc.Inlines{})

if type(v) == "table" then
local result = pandoc.Inlines({})
for i, v in ipairs(v) do
tappend(result, as_inlines(v))
--- Turns the given object into a `Inlines` list.
--
-- Works mostly like `pandoc.Inlines`, but doesn't a do a full
-- unmarshal/marshal roundtrip. This buys performance, at the cost of
-- less thorough type checks.
--
-- NOTE: The input object might be modified *destructively*!
local function as_inlines(obj)
local pt = pandoc.utils.type(obj)
if pt == 'Inlines' then
return obj
elseif pt == "Inline" then
-- Faster than calling pandoc.Inlines
return setmetatable({obj}, InlinesMT)
elseif pt == 'List' or pt == 'table' then
if obj[1] and pandoc.utils.type(obj[1]) == 'Block' then
return pandoc.utils.blocks_to_inlines(obj)
end
return result
-- Faster than calling pandoc.Inlines
return setmetatable(obj, InlinesMT)
elseif pt == "Block" then
return pandoc.utils.blocks_to_inlines({obj})
elseif pt == "Blocks" then
return pandoc.utils.blocks_to_inlines(obj)
else
return pandoc.Inlines(obj or {})
end

-- luacov: disable
fatal("as_inlines: invalid type " .. t)
return pandoc.Inlines({})
-- luacov: enable
end

local function as_blocks(v)
if v == nil then
return pandoc.Blocks({})
end
local t = pandoc.utils.type(v)
if t == "Blocks" then
return v
elseif t == "Inlines" then
return pandoc.Blocks({pandoc.Plain(v)})
elseif t == "Block" then
return pandoc.Blocks({v})
elseif t == "Inline" then
return pandoc.Blocks({pandoc.Plain(v)})
end

if type(v) == "table" then
return pandoc.Blocks(v)
--- Turns the given object into a `Blocks` list.
--
-- Works mostly like `pandoc.Blocks`, but doesn't a do a full
-- unmarshal/marshal roundtrip. This buys performance, at the cost of
-- less thorough type checks.
--
-- NOTE: The input object might be modified *destructively*!
--
-- This might need some benchmarking.
local function as_blocks(obj)
local pt = pandoc.utils.type(obj)
if pt == 'Blocks' then
return obj
elseif pt == 'Block' then
-- Assigning a metatable directly is faster than calling
-- `pandoc.Blocks`.
return setmetatable({obj}, BlocksMT)
elseif pt == 'Inline' then
return setmetatable({pandoc.Plain{obj}}, BlocksMT)
elseif pt == 'Inlines' then
if next(obj) then
return setmetatable({pandoc.Plain(obj)}, BlocksMT)
end
return setmetatable({}, BlocksMT)
elseif pt == 'List' or (pt == 'table' and obj[1]) then
if pandoc.utils.type(obj[1]) == 'Inline' then
obj = {pandoc.Plain(obj)}
end
return setmetatable(obj, BlocksMT)
elseif (pt == 'table' and obj.long) or pt == 'Caption' then
-- Looks like a Caption
return as_blocks(obj.long)
else
return pandoc.Blocks(obj or {})
end

-- luacov: disable
fatal("as_blocks: invalid type " .. t)
return pandoc.Blocks({})
-- luacov: enable
end

local function match_fun(reset, ...)
Expand Down

0 comments on commit 6512bbe

Please sign in to comment.