Skip to content

Instantly share code, notes, and snippets.

@zr-tex8r
Last active February 3, 2021 14:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zr-tex8r/c7901658a866adfcd3cd66b6dfa86997 to your computer and use it in GitHub Desktop.
Save zr-tex8r/c7901658a866adfcd3cd66b6dfa86997 to your computer and use it in GitHub Desktop.
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
@zr-tex8r
Copy link
Author

zr-tex8r commented Dec 22, 2018

前提環境

  • フォーマット: LaTeX [2015/01/01]以降
  • エンジン: LuaTeX v0.75以降
  • 依存パッケージ:
    • ifluatex
    • luatexbase

インストール方法

  • bxrawstr.sty, bxrawstr.lua$TEXMF/tex/lualatex/bxrawstr/

ライセンス

MITライセンスが適用される。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment