LaTeX:「verbatimな文字列」を任意の場所で使う(bxrawstrパッケージ)
-- | |
-- This is file 'bxrawstr.lua'. | |
-- | |
-- Copyright (c) 2018 Takayuki YATO (aka. "ZR") | |
-- GitHub: https://github.com/zr-tex8r | |
-- Twitter: @zr_tex8r | |
-- | |
-- This package is distributed under the MIT License. | |
-- | |
luatexbase.provides_module{ | |
name = 'bxrawstr', | |
date = '2018/12/22', | |
version = '0.2', | |
} | |
local name = 'bxrawstr' | |
local M = {} | |
---------------------------- helpers | |
local ltb = luatexbase | |
local getcount, setcount, gettoks, getcatcode = | |
tex.getcount, tex.setcount, tex.gettoks, tex.getcatcode | |
local unpack = unpack or table.unpack | |
local concat, insert = table.concat, table.insert | |
local format, utfvalues = string.format, string.utfvalues | |
local utfbyte, utfchar = unicode.utf8.byte, unicode.utf8.char | |
local function info(...) ltb.module_info(name, format(...)) end | |
local function alert(...) ltb.module_warning(name, format(...)) end | |
local function abort(...) ltb.module_error(name, format(...)) end | |
---------------------------- interfaces to TeX | |
local c_enabled = ltb.registernumber('bxlrs@enabled') | |
local c_newline = ltb.registernumber('bxlrs@newline') | |
local c_rescan = ltb.registernumber('bxlrs@rescan') | |
local c_errorcode = ltb.registernumber('bxlrs@errorcode') | |
local t_delimiters = ltb.registernumber('bxlrs@delimiters') | |
local cc_latex = assert(ltb.catcodetables.latex) | |
local cc_other = assert(ltb.catcodetables.other) | |
local cmd_init, cmd_term, cmd_do, cmd_to = | |
'\\bxlrsS*', '\\bxlrsE*', '\\bxlrsDo', '\\bxlrsTo' | |
local E_good, E_baddelim, E_badopt, E_inproc = 0, 1, 2, -1 | |
local N_space, N_delete, N_keep = 0, 1, 2 | |
local R_none, R_rescan, R_verbish = 0, 1, 2 | |
local function set_error(code) | |
setcount(c_errorcode, code) | |
end | |
---------------------------- delimiters | |
do | |
local pspec, pini, ptrm | |
function M.get_delimiters() | |
local spec = gettoks(t_delimiters) | |
if pspec == spec then | |
return pini, ptrm | |
end | |
local ini, trm = spec:match('^{(.*)}{(.*)}$') | |
ini = (ini or ''):gsub('[ \\\t\n\r]', '') | |
trm = (trm or ''):gsub('[ \\\t\n\r]', '') | |
if ini:match('^[^*0-9A-Za-z]') and trm:match('[^*0-9A-Za-z]$') then | |
pspec, pini, ptrm = spec, ini, trm | |
info("The delimiters are changed:\n '%s','%s'\n", pini, ptrm) | |
return ini, trm | |
end | |
end | |
function M.check_delimiters() | |
if M.is_in_process() then | |
return set_error(E_inproc) | |
elseif M.get_delimiters() then | |
return set_error(E_good) | |
else | |
return set_error(E_baddelim) | |
end | |
end | |
end | |
---------------------------- option parser | |
do | |
function M.parse_option(optstr) | |
local ok, newline, rescan = true, N_space, R_none | |
if type(optstr) == 'string' then | |
for c in optstr:gmatch('%S') do | |
if c == 's' then newline = N_space | |
elseif c == 'd' then newline = N_delete | |
elseif c == 'k' then newline = N_keep | |
elseif c == 'R' then rescan = R_none | |
elseif c == 'r' then rescan = R_rescan | |
elseif c == 'v' then rescan = R_verbish | |
else ok = false | |
end | |
end | |
else ok = false | |
end | |
if ok then | |
setcount(c_newline, newline) | |
setcount(c_rescan, rescan) | |
end | |
set_error(ok and E_good or E_badopt) | |
end | |
end | |
---------------------------- rs-encoding | |
do | |
local ltjflg = utfchar(0xFFFFF)..'\n$' | |
local f1, f2, f3 = '*%02X', '*u%04X', '*U%06X' | |
function M.encode(str) | |
return (str:gsub('[^0-9A-Za-z]+', function (ss) | |
local t = {} | |
for b in utfvalues(ss) do | |
t[#t+1] = format( | |
(b < 0x100) and f1 or (b < 0x10000) and f2 or f3, b) | |
end | |
return concat(t) | |
end)) | |
end | |
function M.encode_tail(str) | |
return M.encode((str..'\n'):gsub(ltjflg, '')) | |
end | |
function M.decode(rstr) | |
local chex = function(s) return utfchar(tonumber(s, 16)) end | |
return (rstr:gsub(' ', ''):gsub('%*U(......)', chex) | |
:gsub('%*u(....)', chex):gsub('%*(..)', chex)) | |
end | |
end | |
---------------------------- line processor | |
do | |
local in_process = false | |
function M.is_in_process() | |
return in_process | |
end | |
local function append(t, s) | |
if t then t[#t+1] = s else t = {s} end | |
return t | |
end | |
local function encode_part(line, ini, trm) | |
local pos, t = 1, nil | |
while pos do | |
if in_process then | |
local npos = line:find(trm, pos, true) | |
if npos then | |
t = append(t, M.encode(line:sub(pos, npos-1))..cmd_term) | |
in_process, pos = false, npos + #ini | |
elseif t then | |
t = append(t, M.encode_tail(line:sub(pos))) | |
pos = nil | |
else | |
return M.encode_tail(line) | |
end | |
else | |
local npos = line:find(ini, pos, true) | |
if npos then | |
t = append(t, line:sub(pos, npos-1)..cmd_init) | |
in_process, pos = true, npos + #ini | |
elseif t then | |
t = append(t, line:sub(pos)) | |
pos = nil | |
else | |
return line | |
end | |
end | |
end | |
return concat(t) | |
end | |
function M.process_line(buf) | |
-- NB: callback may be called even when disabled | |
if getcount(c_enabled) == 0 then | |
return nil | |
elseif getcatcode(0x5C) ~= 0 then | |
return nil | |
end | |
-- | |
local ini, trm = M.get_delimiters() | |
if ini and trm then | |
return encode_part(buf, ini, trm) | |
end | |
return nil | |
end | |
end | |
---------------------------- rawstring processors | |
do | |
local function sanitize(rstr) | |
rstr = rstr:gsub('[ \t\r\n]', '') | |
local p = rstr:find('[^*0-9A-Za-z]') | |
if p then | |
alert("Bad character (U+%04X)\nfound in rs-encoded sections", | |
utfbyte(rstr:sub(p))) | |
return '*2ARSERROR*2A' | |
end | |
return rstr | |
end | |
local function subst_newline(nl, txt) | |
if nl == N_space then return txt:gsub('\n', ' ') | |
elseif nl == N_delete then return txt:gsub('\n', '') | |
else return txt | |
end | |
end | |
function M.direct(rstr) | |
local txt = subst_newline(E_space, M.decode(sanitize(rstr))) | |
tex.sprint(-2, txt) | |
end | |
function M.indirect(nl, rstr) | |
local txt = subst_newline(nl, M.decode(sanitize(rstr))) | |
print("-----"..tostring(nl)..txt.."-----") | |
tex.sprint(cc_latex, cmd_to..'{') | |
tex.sprint(cc_other, txt) | |
tex.sprint(cc_latex, '}') | |
end | |
local mark = utfchar(0xFDD8) | |
function M.rescan(nl, resc, nocmd, rstr) | |
local txt, gtxt = subst_newline(nl, M.decode(sanitize(rstr))) | |
if nocmd == 1 then gtxt = txt | |
elseif resc == R_rescan then gtxt = '{'..txt..'}' | |
elseif resc == R_verbish then gtxt = mark..txt..mark | |
end | |
tex.sprint(cc_latex, '\\expandafter'..cmd_do..'\\scantextokens{') | |
for _, lin in ipairs(gtxt:explode('\n')) do | |
tex.print(cc_other, lin) | |
end | |
tex.sprint(cc_latex, '}') | |
end | |
end | |
---------------------------- register callbacks | |
do | |
local desc = name..'.process_input_buffer' | |
function M.enable(val) | |
if M.is_in_process() then | |
return set_error(E_inproc) | |
end | |
if val then | |
if not ltb.in_callback('process_input_buffer', desc) then | |
ltb.add_to_callback('process_input_buffer', M.process_line, desc) | |
end | |
elseif tex.currentgrouplevel == 0 then | |
if ltb.in_callback('process_input_buffer', desc) then | |
ltb.remove_from_callback('process_input_buffer', desc) | |
end | |
end | |
return set_error(E_good) | |
end | |
end | |
---------------------------- done | |
return M | |
-- EOF |
%% | |
%% This is file 'bxrawstr.sty'. | |
%% | |
%% Copyright (c) 2018 Takayuki YATO (aka. "ZR") | |
%% GitHub: https://github.com/zr-tex8r | |
%% Twitter: @zr_tex8r | |
%% | |
%% This package is distributed under the MIT License. | |
%% | |
%% package declaration | |
\NeedsTeXFormat{LaTeX2e} | |
\ProvidesPackage{bxrawstr}[2018/12/22 v0.2] | |
%% preparation | |
\def\bxlrs@pkgname{bxrawstr} | |
\providecommand\bxDebug[1]{} | |
%--------------------------------------- environment check | |
%% check engine | |
\RequirePackage{ifluatex}[2010/03/01]% v1.3 | |
\ifluatex\else | |
\PackageError\bxlrs@pkgname | |
{This package requires LuaTeX} | |
{Package loading is aborted.} | |
\expandafter\endinput\fi\relax | |
\directlua{tex.enableprimitives("", | |
tex.extraprimitives("omega", "aleph", "luatex"))} | |
\ifnum\luatexversion<75 | |
\PackageError\bxlrs@pkgname | |
{LuaTeX v0.75 or later is required} | |
{Package loading is aborted.} | |
\expandafter\endinput\fi\relax | |
%% employ luatexbase to use multi-callback | |
\RequirePackage{luatexbase}[] | |
%--------------------------------------- general | |
%% packages | |
\RequirePackage{etoolbox}[2011/01/03]% v2.1 | |
%% variables | |
\newcount\bxlrs@enabled | |
\newcount\bxlrs@newline | |
\newcount\bxlrs@rescan | |
\newcount\bxlrs@errorcode | |
\newtoks\bxlrs@delimiters | |
%% \bxlrs@cond\ifXXX...\fi{<yes>}{<no>} | |
\@gobbletwo\if\if \def\bxlrs@cond#1\fi{% | |
#1\expandafter\@firstoftwo \else \expandafter\@secondoftwo \fi} | |
%% \bxlrs@get@option{<cont>}... | |
\def\bxlrs@get@option#1#2{% | |
\bxlrs@cond\ifx[#2\fi{\bxlrs@get@option@a{#1}}{#1{}#2}} | |
\def\bxlrs@get@option@a#1#2]{#1{#2}} | |
%% \bxlrs@str{<string>} | |
\def\bxlrs@str#1{% | |
'\luaescapestring{#1}'} | |
%% \bxlrs@check@error | |
\def\bxlrs@check@error{% | |
\ifcase\bxlrs@errorcode % E_good | |
\or % E_baddelim | |
\PackageError\bxlrs@pkgname | |
{Bad delimiter string}{\@eha} | |
\or % E_badopt | |
\PackageError\bxlrs@pkgname | |
{Bad option character}{\@eha} | |
\else | |
\PackageError\bxlrs@pkgname | |
{INTERNAL ERROR (\the\bxlrs@errorcode)}{\@ehc} | |
\fi} | |
%--------------------------------------- Lua modules | |
\ifdefined\bxUseDebug | |
\directlua{ bxlrs = require "bxrawstr" } | |
\else % supress errors | |
\directlua{pcall(function() bxlrs = require "bxrawstr" end)} | |
\fi | |
\ifnum % check if module is successfully loaded | |
\directlua{tex.sprint(bxlrs and '1' or '0')}=\z@ | |
\PackageError\bxlrs@pkgname | |
{Failure in loading Lua module} | |
{Package loading is aborted.} | |
\expandafter\endinput\fi\relax | |
%--------------------------------------- rawstr processors | |
%% \bxlrsTo{<arg>}{<command>} | |
\long\def\bxlrsTo#1#2{% | |
\ifstrempty{#2}{#1}{#2{#1}}} | |
%% \bxlrsS*<rs-section>\bxlrsE* | |
% Prints the string. | |
\def\bxlrsS*#1\bxlrsE*{% | |
\directlua{bxlrs.direct(\bxlrs@str{#1})}} | |
%% \rawstr[<option>]\CS...\bxlrsS*<rs-section>\bxlrsE* | |
\newcommand*\rawstr{% | |
\bxlrs@get@option\bxlrs@rawstr@a} | |
\def\bxlrs@rawstr@a#1{% | |
\directlua{bxlrs.parse_option(\bxlrs@str{#1})}% | |
\bxlrs@check@error | |
\bxlrs@cond\ifnum\bxlrs@rescan=\z@\fi{% | |
\bxlrs@rawstr@indirect}{%else | |
\bxlrs@rawstr@rescan}} | |
\long\def\bxlrs@rawstr@indirect#1\bxlrsS*#2\bxlrsE*{% | |
\directlua{bxlrs.indirect(\the\bxlrs@newline,\bxlrs@str{#2})}% | |
{#1}} | |
\protected\long\def\bxlrs@rawstr@rescan#1\bxlrsS*#2\bxlrsE*{% | |
\def\bxlrsDo{#1}% | |
\directlua{bxlrs.rescan(\the\bxlrs@newline,\the\bxlrs@rescan,% | |
\ifx\bxlrsDo\@empty 1\else0\fi,% | |
\bxlrs@str{#2})}} | |
%--------------------------------------- other user interface | |
%%<*> \rawstrenable | |
\newrobustcmd\rawstrenable{% | |
\bxlrs@enabled\@ne | |
\directlua{bxlrs.enable(true)}% | |
\bxlrs@check@error} | |
%%<*> \rawstrdisable | |
\newrobustcmd\rawstrdisable{% | |
\bxlrs@enabled\z@ | |
\directlua{bxlrs.enable(false)}% | |
\bxlrs@check@error} | |
%%<+> \rawstrSetDelimiters | |
\newrobustcmd\rawstrSetDelimiters[2]{% | |
\bxlrs@delimiters{{#1}{#2}}% | |
\directlua{bxlrs.check_delimiters()}% | |
\bxlrs@check@error} | |
%--------------------------------------- initial setup | |
%% set delimiters | |
\rawstrSetDelimiters{[[|}{|]]} | |
%--------------------------------------- all done | |
\endinput | |
%% EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
前提環境
インストール方法
bxrawstr.sty
,bxrawstr.lua
→$TEXMF/tex/lualatex/bxrawstr/
ライセンス
MITライセンスが適用される。