local lpeg = require "lpeg" local util = require "digestif.util" local Manuscript = require "digestif.Manuscript" local Parser = require "digestif.Parser" local path_join, path_split = util.path_join, util.path_split local path_normalize = util.path_normalize local merge = util.merge local format_filename_template = util.format_filename_template local ManuscriptLatex = util.class(Manuscript) ManuscriptLatex.parser = Parser() ManuscriptLatex.format = "latex" ManuscriptLatex.packages = {} ManuscriptLatex.commands = {} ManuscriptLatex.environments = {} ManuscriptLatex.init_callbacks = {} ManuscriptLatex.scan_references_callbacks = {} ManuscriptLatex:add_package("latex") --* Snippets -- Make a snippet for an environment. -- -- Arguments: -- cs: The command name -- args: An argument list -- -- Returns: -- A snippet string -- function ManuscriptLatex:snippet_env(cs, args) local argsnippet = args and self:snippet_args(args) or "" return "begin{" .. cs .. "}" .. argsnippet .. "\n\t$0\n\\end{" .. cs .. "}" end -- Pretty-print an environment signature. function ManuscriptLatex:signature_env(cs, args) return self:signature_args(args, "\\begin{" .. cs .. "}") end --* Helper functions local function first_mand(args) for i, v in ipairs(args) do if not v.optional then return i end end end local function first_opt(args) -- luacheck: ignore first_opt for i, v in ipairs(args) do if v.optional then return i end end end --* Global scanning --** Basic document elements function ManuscriptLatex.init_callbacks.input(self, pos, cs) local cont = self:parse_command(pos, cs).cont local idx = self.child_index local template = self.commands[cs].filename or "?" for r in self:argument_items(first_mand, pos, cs) do local f = format_filename_template(template, self:substring_stripped(r)) local ok = self:add_package(f) if not ok then idx[#idx + 1] = { name = path_normalize(path_join(path_split(self.filename), f)), pos = r.pos, cont = r.cont, manuscript = self } end end return cont end function ManuscriptLatex.init_callbacks.label (m, pos, cs) local idx = m.label_index local args = m.commands[cs].arguments local r = m:parse_command(pos, cs) local i = first_mand(args) if r[i] then local l = m:substring_stripped(r[i]) idx[#idx + 1] = { name = l, pos = r[i].pos, cont = r[i].cont, outer_pos = r.pos, outer_cont = r.cont, manuscript = m } end return r.cont end function ManuscriptLatex.init_callbacks.section(self, pos, cs) local idx = self.section_index for r in self:argument_items(first_mand, pos, cs) do idx[#idx + 1] = { name = self:substring_stripped(r), level = self.commands[cs].section_level, pos = r.pos, cont = r.cont, outer_pos = pos, manuscript = self } end return pos + #cs + 1 end --** Bibliographic items function ManuscriptLatex.init_callbacks.bibitem (m, pos, cs) local idx = m.bib_index local args = m.commands[cs].arguments local r = m:parse_command(pos, cs) local i = first_mand(args) if r[i] then idx[#idx + 1] = { name = m:substring_stripped(r[i]), pos = r[i].pos, cont = r[i].cont, outer_pos = r.pos, outer_cont = r.cont, manuscript = m } end return r.cont end function ManuscriptLatex.init_callbacks.amsrefs_bib(self, pos, cs) local idx = self.bib_index local r = self:parse_command(pos, cs) if r.incomplete then return r.cont end local keys = self:read_keys(r[3]) local authors, title, date = {}, "", "(n.d.)" for i = 1, #keys do local k, v = keys[i].key, keys[i].value if k == "author" then authors[#authors+1] = self.parser.clean(v:match("[^,]+", 2)) elseif k == "title" then title = self.parser.clean(v:sub(2, -2)) elseif k == "date" then date = self.parser.clean(v:match("(%d+)")) end end idx[#idx + 1] = { name = self:substring_stripped(r[1]), text = string.format( "%s %s; %s", table.concat(authors, ", "), date, title), pos = r[1].pos, cont = r[1].cont, outer_pos = r.pos, outer_cont = r.cont, manuscript = self } return r.cont end --** Command definitions, TeX style ManuscriptLatex.init_callbacks.def = require "digestif.ManuscriptPlainTeX".init_callbacks.def --** Command and environment definitions, LaTeX style local to_args = {} for i = 1, 9 do local t = merge(to_args[i-1] or {}) t[i] = {meta = "#" .. i} to_args[i] = t end local function newcommand_args(number, default) local args = to_args[number] if default and args then args = merge(args) -- make a copy args[1] = { meta = "#1", optional = true, delimiters = {"[", "]"}, details = "Default: “" .. default .. "”." } end return args end function ManuscriptLatex.init_callbacks.newcommand(self, pos, cs) local cont = self:parse_command(pos, cs).cont local csname, nargs, optdefault for r in self:argument_items(first_mand, pos, cs) do csname = self:substring_stripped(r):sub(2) end for r in self:argument_items("number", pos, cs) do nargs = tonumber(self:substring_stripped(r)) end for r in self:argument_items("default", pos, cs) do optdefault = self:substring_stripped(r) end if csname then local idx = self:get_index "newcommand" idx[#idx+1] = { name = csname, pos = pos, cont = cont, manuscript = self, arguments = newcommand_args(nargs, optdefault) } if not self.commands[csname] then self.commands[csname] = idx[#idx] end end return cont end function ManuscriptLatex.init_callbacks.newenvironment(self, pos, cs) local cont = self:parse_command(pos, cs).cont local csname, nargs, optdefault for r in self:argument_items(first_mand, pos, cs) do csname = self:substring_stripped(r) end for r in self:argument_items("number", pos, cs) do nargs = tonumber(self:substring_stripped(r)) end for r in self:argument_items("default", pos, cs) do optdefault = self:substring_stripped(r) end if csname then local idx = self:get_index("newenvironment") idx[#idx+1] = { name = csname, pos = pos, cont = cont, manuscript = self, arguments = newcommand_args(nargs, optdefault) } if not self.environments[csname] then self.environments[csname] = idx[#idx] end end return cont end --** Command and environment definitions, xparse style local P, C, Cc, Cg, Ct = lpeg.P, lpeg.C, lpeg.Cc, lpeg.Cg, lpeg.Ct local Pgroup = util.between_balanced("{", "}") local Pdefault = Cg(Pgroup / "Default: “%1”", "details") local xparse_args = util.matcher( Ct(util.many(Ct(util.sequence( P" "^0, (P"+" * Cg(Cc(true), "long"))^-1, P"!"^-1, util.choice( "m", "r" * Cg(Ct(C(1) * C(1)), "delimiters"), "R" * Cg(Ct(C(1) * C(1)), "delimiters") * Pdefault, "v" * Cg(Cc"verbatim", "type"), "o" * Cg(Cc{"[", "]"}, "delimiters") * Cg(Cc(true), "optional"), "O" * Cg(Cc{"[", "]"}, "delimiters") * Cg(Cc(true), "optional") * Pdefault, "d" * Cg(Ct(C(1) * C(1)), "delimiters") * Cg(Cc(true), "optional"), "D" * Cg(Ct(C(1) * C(1)), "delimiters") * Cg(Cc(true), "optional") * Pdefault, "s" * Cg(Cc"*", "literal") * Cg(Cc"literal", "type") * Cg(Cc(true), "optional"), "t" * Cg(C(1), "literal") * Cg(Cc"literal", "type") * Cg(Cc(true), "optional"), "e" * Pgroup, "E" * Pgroup * Pdefault)))))) function ManuscriptLatex.init_callbacks.NewDocumentCommand(self, pos, cs) local cont = self:parse_command(pos, cs).cont local csname, arg_spec for r in self:argument_items("command", pos, cs) do csname = self:substring_stripped(r):sub(2) end for r in self:argument_items("arg spec", pos, cs) do arg_spec = self:substring_stripped(r) end if csname then local idx = self:get_index "newcommand" idx[#idx+1] = { name = csname, pos = pos, cont = cont, manuscript = self, arguments = xparse_args(arg_spec) } if not self.commands[csname] then self.commands[csname] = idx[#idx] end end return cont end function ManuscriptLatex.init_callbacks.NewDocumentEnvironment(self, pos, cs) local cont = self:parse_command(pos, cs).cont local csname, arg_spec for r in self:argument_items("environment", pos, cs) do csname = self:substring_stripped(r) end for r in self:argument_items("arg spec", pos, cs) do arg_spec = self:substring_stripped(r) end if csname then local idx = self:get_index "newenvironment" idx[#idx+1] = { name = csname, pos = pos, cont = cont, manuscript = self, arguments = xparse_args(arg_spec) } if not self.environments[csname] then self.environments[csname] = idx[#idx] end end return cont end --* Scan references callbacks function ManuscriptLatex.scan_references_callbacks.ref(self, pos, cs) local cont = self:parse_command(pos, cs).cont local idx = self.ref_index for r in self:argument_items(first_mand, pos, cs) do idx[#idx + 1] = { name = self:substring_stripped(r), pos = r.pos, cont = r.cont, outer_pos = pos, outer_cont = cont, manuscript = self } end return cont end function ManuscriptLatex.scan_references_callbacks.cite(self, pos, cs) local cont = self:parse_command(pos, cs).cont local idx = self.cite_index for r in self:argument_items(first_mand, pos, cs) do idx[#idx + 1] = { name = self:substring_stripped(r), pos = r.pos, cont = r.cont, outer_pos = pos, outer_cont = cont, manuscript = self } end return cont end return ManuscriptLatex