Skip to content

Commit

Permalink
Merge pull request #7148 from quarto-dev/feature/theorem-ast
Browse files Browse the repository at this point in the history
Lua: Theorem is now an AST node with `name` and `div` entries.
  • Loading branch information
cscheid authored Oct 6, 2023
2 parents 219725f + e5faa8d commit b0f25c2
Show file tree
Hide file tree
Showing 12 changed files with 241 additions and 128 deletions.
49 changes: 0 additions & 49 deletions src/resources/filters/common/theorems.lua
Original file line number Diff line number Diff line change
@@ -1,55 +1,6 @@
-- theorems.lua
-- Copyright (C) 2020-2022 Posit Software, PBC

-- available theorem types
theoremTypes = {
thm = {
env = "theorem",
style = "plain",
title = "Theorem"
},
lem = {
env = "lemma",
style = "plain",
title = "Lemma"
},
cor = {
env = "corollary",
style = "plain",
title = "Corollary",
},
prp = {
env = "proposition",
style = "plain",
title = "Proposition",
},
cnj = {
env = "conjecture",
style = "plain",
title = "Conjecture"
},
def = {
env = "definition",
style = "definition",
title = "Definition",
},
exm = {
env = "example",
style = "definition",
title = "Example",
},
exr = {
env = "exercise",
style = "definition",
title = "Exercise"
}
}

function hasTheoremRef(el)
local type = refType(el.attr.identifier)
return theoremTypes[type] ~= nil
end

proofTypes = {
proof = {
env = 'proof',
Expand Down
3 changes: 2 additions & 1 deletion src/resources/filters/crossref/crossref.lua
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import("../customnodes/decoratedcodeblock.lua")
import("../customnodes/callout.lua")
import("../customnodes/panel-tabset.lua")
import("../customnodes/floatreftarget.lua")
import("../customnodes/theorem.lua")

import("../quarto-init/metainit.lua")

Expand Down Expand Up @@ -171,7 +172,7 @@ local quarto_normalize_filters = {
name = "normalize-combine-2",
filter = combineFilters({
parse_md_in_html_rawblocks(),
parse_floats(),
parse_reftargets(),
}),
},
}
Expand Down
2 changes: 1 addition & 1 deletion src/resources/filters/crossref/refs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function isValidRefType(type)
end

function validRefTypes()
local types = tkeys(theoremTypes)
local types = tkeys(theorem_types)
for k, _ in pairs(crossref.categories.by_ref_type) do
table.insert(types, k)
-- if v.type ~= nil and not tcontains(types, v.type) then
Expand Down
84 changes: 11 additions & 73 deletions src/resources/filters/crossref/theorems.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

-- preprocess theorem to ensure that embedded headings are unnumered
function crossref_preprocess_theorems()
local types = theoremTypes
local types = theorem_types
return {
Div = function(el)
local type = refType(el.attr.identifier)
Expand All @@ -22,84 +22,22 @@ end

function crossref_theorems()

local types = theoremTypes
local types = theorem_types

return {
Theorem = function(thm)
local label = thm.identifier
local type = refType(label)
thm.order = indexNextOrder(type)
indexAddEntry(label, nil, thm.order, { thm.name })
return thm
end,
Div = function(el)

local type = refType(el.attr.identifier)
local theoremType = types[type]
if theoremType then

-- add class for type
el.attr.classes:insert("theorem")
if theoremType.env ~= "theorem" then
el.attr.classes:insert(theoremType.env)
end

-- capture then remove name
local name = markdownToInlines(el.attr.attributes["name"])
if not name or #name == 0 then
name = resolveHeadingCaption(el)
end
el.attr.attributes["name"] = nil

-- add to index
local label = el.attr.identifier
local order = indexNextOrder(type)
indexAddEntry(label, nil, order, name)

-- If this theorem has no content, then create a placeholder
if #el.content == 0 or el.content[1].t ~= "Para" then
tprepend(el.content, {pandoc.Para({pandoc.Str '\u{a0}'})})
end

if _quarto.format.isLatexOutput() then
local preamble = pandoc.Para(pandoc.RawInline("latex",
"\\begin{" .. theoremType.env .. "}"))
preamble.content:insert(pandoc.RawInline("latex", "["))
if name then
tappend(preamble.content, name)
end
preamble.content:insert(pandoc.RawInline("latex", "]"))
preamble.content:insert(pandoc.RawInline("latex",
"\\protect\\hypertarget{" .. label .. "}{}\\label{" .. label .. "}")
)
el.content:insert(1, preamble)
el.content:insert(pandoc.Para(pandoc.RawInline("latex",
"\\end{" .. theoremType.env .. "}"
)))
-- Remove id on those div to avoid Pandoc inserting \hypertaget #3776
el.attr.identifier = ""
elseif _quarto.format.isJatsOutput() then

-- JATS XML theorem
local lbl = captionPrefix(nil, type, theoremType, order)
el = jatsTheorem(el, lbl, name)

else
-- create caption prefix
local captionPrefix = captionPrefix(name, type, theoremType, order)
local prefix = {
pandoc.Span(
pandoc.Strong(captionPrefix),
pandoc.Attr("", { "theorem-title" })
)
}

-- prepend the prefix
local caption = el.content[1]

if caption.content == nil then
-- https://github.com/quarto-dev/quarto-cli/issues/2228
-- caption doesn't always have a content field; in that case,
-- use the parent?
tprepend(el.content, prefix)
else
tprepend(caption.content, prefix)
end
end

internal_error()
else
-- see if this is a proof, remark, or solution
local proof = proofType(el)
Expand Down Expand Up @@ -237,7 +175,7 @@ end
function theoremLatexIncludes()

-- determine which theorem types we are using
local types = theoremTypes
local types = theorem_types
local refs = tkeys(crossref.index.entries)
local usingTheorems = crossref.usingTheorems
for k,v in pairs(crossref.index.entries) do
Expand Down
165 changes: 165 additions & 0 deletions src/resources/filters/customnodes/theorem.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
-- theorem.lua
-- custom AST node for theorems, lemmata, etc.
--
-- Copyright (C) 2023 Posit Software, PBC

-- available theorem types
theorem_types = {
thm = {
env = "theorem",
style = "plain",
title = "Theorem"
},
lem = {
env = "lemma",
style = "plain",
title = "Lemma"
},
cor = {
env = "corollary",
style = "plain",
title = "Corollary",
},
prp = {
env = "proposition",
style = "plain",
title = "Proposition",
},
cnj = {
env = "conjecture",
style = "plain",
title = "Conjecture"
},
def = {
env = "definition",
style = "definition",
title = "Definition",
},
exm = {
env = "example",
style = "definition",
title = "Example",
},
exr = {
env = "exercise",
style = "definition",
title = "Exercise"
}
}

function has_theorem_ref(el)
local type = refType(el.attr.identifier)
return theorem_types[type] ~= nil
end

function is_theorem_div(div)
return div.t == "Div" and has_theorem_ref(div)
end

_quarto.ast.add_handler({

-- empty table so this handler is only called programmatically
class_name = {},

-- the name of the ast node, used as a key in extended ast filter tables
ast_name = "Theorem",

-- generic names this custom AST node responds to
-- this is still unimplemented
interfaces = {"Crossref"},

-- Theorems are always blocks
kind = "Block",

parse = function(div)
-- luacov: disable
internal_error()
-- luacov: enable
end,

slots = { "div", "name" },

constructor = function(tbl)
return {
name = tbl.name,
div = tbl.div,
identifier = tbl.identifier
}
end
})

_quarto.ast.add_renderer("Theorem", function()
return true
end, function(thm)
local el = thm.div
if pandoc.utils.type(el) == "Blocks" then
el = pandoc.Div(el)
end

el.identifier = thm.identifier -- restore identifier to render correctly
local label = thm.identifier
local type = refType(thm.identifier)
local name = quarto.utils.as_inlines(thm.name)
local theorem_type = theorem_types[refType(thm.identifier)]
local order = thm.order

-- add class for type
el.attr.classes:insert("theorem")
if theorem_type.env ~= "theorem" then
el.attr.classes:insert(theorem_type.env)
end

-- If this theorem has no content, then create a placeholder
if #el.content == 0 or el.content[1].t ~= "Para" then
tprepend(el.content, {pandoc.Para({pandoc.Str '\u{a0}'})})
end

if _quarto.format.isLatexOutput() then
local preamble = pandoc.Para(pandoc.RawInline("latex",
"\\begin{" .. theorem_type.env .. "}"))
preamble.content:insert(pandoc.RawInline("latex", "["))
if name then
tappend(preamble.content, name)
end
preamble.content:insert(pandoc.RawInline("latex", "]"))
preamble.content:insert(pandoc.RawInline("latex",
"\\protect\\hypertarget{" .. label .. "}{}\\label{" .. label .. "}")
)
el.content:insert(1, preamble)
el.content:insert(pandoc.Para(pandoc.RawInline("latex",
"\\end{" .. theorem_type.env .. "}"
)))
-- Remove id on those div to avoid Pandoc inserting \hypertaget #3776
el.attr.identifier = ""
elseif _quarto.format.isJatsOutput() then

-- JATS XML theorem
local lbl = captionPrefix(nil, type, theorem_type, order)
el = jatsTheorem(el, lbl, name)

else
-- create caption prefix
local captionPrefix = captionPrefix(name, type, theorem_type, order)
local prefix = {
pandoc.Span(
pandoc.Strong(captionPrefix),
pandoc.Attr("", { "theorem-title" })
)
}

-- prepend the prefix
local caption = el.content[1]

if caption.content == nil then
-- https://github.com/quarto-dev/quarto-cli/issues/2228
-- caption doesn't always have a content field; in that case,
-- use the parent?
tprepend(el.content, prefix)
else
tprepend(caption.content, prefix)
end
end

return el

end)
1 change: 1 addition & 0 deletions src/resources/filters/main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ import("./customnodes/decoratedcodeblock.lua")
import("./customnodes/callout.lua")
import("./customnodes/panel-tabset.lua")
import("./customnodes/floatreftarget.lua")
import("./customnodes/theorem.lua")

import("./layout/confluence.lua")
import("./layout/ipynb.lua")
Expand Down
2 changes: 1 addition & 1 deletion src/resources/filters/normalize/astpipeline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function quarto_ast_pipeline()
name = "normalize-combine-2",
filter = combineFilters({
parse_md_in_html_rawblocks(),
parse_floats(),
parse_reftargets(),
}),
},
}
Expand Down
2 changes: 1 addition & 1 deletion src/resources/filters/normalize/flags.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function compute_flags()
end,
Div = function(node)
local type = refType(node.attr.identifier)
if theoremTypes[type] ~= nil or proofType(node) ~= nil then
if theorem_types[type] ~= nil or proofType(node) ~= nil then
flags.has_theorem_refs = true
end

Expand Down
Loading

0 comments on commit b0f25c2

Please sign in to comment.