4 luatexbase.provides_module({
5 name = 'luatexja.base',
10 module('luatexja.base', package.seeall)
11 local err, warn, info, log = luatexbase.errwarinf(_NAME)
13 local ltb = luatexbase
14 local tostring = tostring
15 local node, table, tex, token = node, table, tex, token
19 public_name = 'luatexja'
20 public_version = 'alpha'
22 -------------------- Fully-expandable error messaging
24 --! LaTeX 形式のエラーメッセージ(\PackageError 等)を
32 local function message_cont(str, c)
33 return str:gsub(err_break, LF .. c)
35 local function into_lines(str)
36 return str:gsub(err_break, LF):explode(LF)
39 function _error_set_break(str)
43 function _error_set_message(msgcont, main, help)
44 err_main = message_cont(main, msgcont)
45 err_help = into_lines(help)
48 function _error_show(escchar)
49 local escapechar = tex.escapechar
50 local newlinechar = tex.newlinechar
51 local errorcontextlines = tex.errorcontextlines
52 if not escchar then tex.escapechar = -1 end
54 tex.errorcontextlines = -1
55 tex.error(err_main, err_help)
56 tex.escapechar = escapechar
57 tex.newlinechar = newlinechar
58 tex.errorcontextlines = errorcontextlines
61 local message_a = "Type H <return> for immediate help"
63 function generic_error(msgcont, main, ref, help)
64 local mainref = main..".\n\n"..ref.."\n"..message_a
65 _error_set_message(msgcont, mainref, help)
69 function _generic_warn_info(msgcont, main, warn, line)
70 local mainc = message_cont(main, msgcont)
71 local br = warn and "\n" or ""
72 local out = warn and "term and log" or "log"
73 local on_line = line and (" on input line "..tex.inputlineno) or ""
74 local newlinechar = tex.newlinechar
76 texio.write_nl(out, br..main..on_line.."."..br)
77 tex.newlinechar = newlinechar
80 function generic_warning(msgcont, main)
81 _generic_warn_info(msgcont, main, true, true)
83 function generic_warning_no_line(msgcont, main)
84 _generic_warn_info(msgcont, main, true, false)
86 function generic_info(msgcont, main)
87 _generic_warn_info(msgcont, main, false, true)
89 function generic_info_no_line(msgcont, main)
90 _generic_warn_info(msgcont, main, false, false)
93 function package_error(pkgname, main, help)
94 generic_error("("..pkgname.." ",
95 "Package "..pkgname.." Error: "..main,
96 "See the "..pkgname.." package documentation for explanation.",
99 function package_warning(pkgname, main)
100 generic_warning("("..pkgname.." ",
101 "Package "..pkgname.." Warning: "..main)
103 function package_warning_no_line(pkgname, main)
104 generic_warning_no_line("("..pkgname.." ",
105 "Package "..pkgname.." Warning: "..main)
107 function package_info(pkgname, main)
108 generic_info("("..pkgname.." ",
109 "Package "..pkgname.." Info: "..main)
111 function package_info_no_line(pkgname, main)
112 generic_info_no_line("("..pkgname.." ",
113 "Package "..pkgname.." Info: "..main)
116 function ltj_error(main, help)
117 package_error(public_name, main, help)
119 function ltj_warning_no_line(main)
120 package_warning_no_line(public_name, main, help)
124 -------------------- TeX stream I/O
127 --! ixbase.print() と同じ
128 --- Extension to tex.print(). Each argument string may contain
129 -- newline characters, in which case the string is output (to
130 -- TeX input stream) as multiple lines.
131 -- @param ... (string) string to output
135 if type(arg[1]) == "number" then
136 table.insert(lines, arg[1])
139 for _, cnk in ipairs(arg) do
140 local ls = cnk:explode("\n")
141 if ls[#ls] == "" then
142 table.remove(ls, #ls)
144 for _, l in ipairs(ls) do
145 table.insert(lines, l)
148 return tex.print(unpack(lines))
152 -------------------- Handling of TeX values
155 local glue_spec_id = node.id("glue_spec")
157 local function copy_skip(s1, s2)
159 s1 = node.new(glue_spec_id)
161 s1.width = s2.width or 0
162 s1.stretch = s2.stretch or 0
163 s1.stretch_order = s2.stretch_order or 0
164 s1.shrink = s2.shrink or 0
165 s1.shrink_order = s2.shrink_order or 0
169 --! ixbase.to_dimen() と同じ
170 function to_dimen(val)
173 elseif type(val) == "number" then
176 return tex.sp(tostring(val))
180 local function parse_dimen(val)
181 val = tostring(val):lower()
182 local r, fil = val:match("([-.%d]+)fi(l*)")
184 val, fil = r.."pt", fil:len() + 1
188 return tex.sp(val), fil
191 --! ixbase.to_skip() と同じ
192 function to_skip(val)
193 if type(val) == "userdata" then
196 local res = node.new(glue_spec_id)
199 elseif type(val) == "number" then
201 elseif type(val) == "table" then
204 local t = tostring(val):lower():explode()
205 local w, p, m = t[1], t[3], t[5]
206 if t[2] == "minus" then
209 res.width = tex.sp(t[1])
211 res.stretch, res.stretch_order = parse_dimen(t[3])
214 res.shrink, res.shrink_order = parse_dimen(t[5])
220 function dump_skip(s)
221 print(("%s+%s<%s>-%s<%s>"):format(
222 s.width or 0, s.stretch or 0, s.stretch_order or 0,
223 s.shrink or 0, s.shrink_order or 0))
227 -------------------- Virtual table for LaTeX counters
230 --! ixbase.counter と同じ
232 local mt_counter = {}
233 setmetatable(counter, mt_counter)
235 function mt_counter.__index(tbl, key)
236 return tex.count['c@'..key]
238 function mt_counter.__newindex(tbl, key, val)
239 tex.count['c@'..key] = val
242 --! ixbase.length は tex.skip と全く同じなので不要.
245 -------------------- Number handling in TeX source
248 local tok_escape = token.create("ltj@@q@escape")
249 local tok_num = token.create("ltj@@q@escapenum")
250 local c_id_assign_int = token.command_id("assign_int")
251 local c_id_char_given = token.command_id("char_given")
253 local function error_scan()
254 _M.package_error("luatexja",
255 "Missing number of a permitted form, treated as zero",
256 "A number should have been here; I inserted '0'.")
259 local function get_expd_next()
260 local next = token.get_next()
261 while token.is_expandable(next) do
263 next = token.get_next()
268 local function grab_decimal(next, res)
269 table.insert(res, next)
271 next = get_expd_next()
272 if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
275 table.insert(res, next)
277 if next[1] == 10 then next = nil end
281 local function grab_hexa(next, res)
283 table.insert(res, next)
285 next = get_expd_next()
286 if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
287 ((next[1] == 12 or next[1] == 11) and
288 (0x41 <= next[2] and next[2] <= 0x46))) then
292 table.insert(res, next)
294 if next[1] == 10 then next = nil end
298 local function grab_octal(next, res)
300 table.insert(res, next)
302 next = get_expd_next()
303 if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
307 table.insert(res, next)
309 if next[1] == 10 then next = nil end
313 local function grab_charnum(next, res)
314 table.insert(res, next)
315 next = token.get_next()
316 table.insert(res, next)
317 next = get_expd_next()
318 if next[1] == 10 then next = nil end
322 local function scan_with(delay, scanner)
323 local function proc()
325 if delay > 0 then delay = delay - 1 end
326 return token.get_next()
328 local cont, back = scanner()
330 ltb.remove_from_callback("token_filter", "ltj@grab@num")
335 ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
338 function scan_brace()
339 scan_with(1, function()
340 local next = token.get_next()
342 return false, { tok_escape, next }
343 elseif next[1] == 10 then
344 return true, { next }
346 return false, { next }
351 function scan_number()
352 scan_with(1, function()
353 local next = get_expd_next()
354 local res, ok = { tok_num }, false
356 if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
357 table.insert(res, next)
358 elseif next[1] ~= 10 then
361 next = get_expd_next()
363 if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
364 ok, next = grab_decimal(next, res)
365 elseif next[1] == 12 and next[2] == 0x22 then
366 ok, next = grab_hexa(next, res)
367 elseif next[1] == 12 and next[2] == 0x27 then
368 ok, next = grab_octal(next, res)
369 elseif next[1] == 12 and next[2] == 0x60 then
370 ok, next = grab_charnum(next, res)
371 elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
372 table.insert(res, next)
376 table.insert(res, tok_num)
381 if next then table.insert(res, next) end
387 -------------------- TeX register allocation
389 local cmod_base_count = token.create('ltj@@count@zero')[2]
390 local cmod_base_attr = token.create('ltj@@attr@zero')[2]
391 local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
392 local cmod_base_skip = token.create('ltj@@skip@zero')[2]
394 function const_number(name)
395 if name:sub(1, 1) == '\\' then name = name:sub(2) end
396 return token.create(name)[2]
399 function count_number(name)
400 if name:sub(1, 1) == '\\' then name = name:sub(2) end
401 return token.create(name)[2] - cmod_base_count
404 function attribute_number(name)
405 if name:sub(1, 1) == '\\' then name = name:sub(2) end
406 return token.create(name)[2] - cmod_base_attr
409 function dimen_number(name)
410 if name:sub(1, 1) == '\\' then name = name:sub(2) end
411 return token.create(name)[2] - cmod_base_dimen
414 function skip_number(name)
415 if name:sub(1, 1) == '\\' then name = name:sub(2) end
416 return token.create(name)[2] - cmod_base_skip
420 -------------------- mock of debug logger
422 if not _M.debug or _M.debug == _G.debug then
423 local function no_op() end
425 package_debug = no_op
428 function debug_logger()
433 -------------------- all done