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
17 local cat_lp = luatexbase.catcodetables['latex-package']
21 public_name = 'luatexja'
22 public_version = 'alpha'
24 -------------------- Fully-expandable error messaging
26 --! LaTeX 形式のエラーメッセージ(\PackageError 等)を
34 local function message_cont(str, c)
35 return str:gsub(err_break, LF .. c)
37 local function into_lines(str)
38 return str:gsub(err_break, LF):explode(LF)
41 function _error_set_break(str)
45 function _error_set_message(msgcont, main, help)
46 err_main = message_cont(main, msgcont)
47 err_help = into_lines(help)
50 function _error_show(escchar)
51 local escapechar = tex.escapechar
52 local newlinechar = tex.newlinechar
53 local errorcontextlines = tex.errorcontextlines
54 if not escchar then tex.escapechar = -1 end
56 tex.errorcontextlines = -1
57 tex.error(err_main, err_help)
58 tex.escapechar = escapechar
59 tex.newlinechar = newlinechar
60 tex.errorcontextlines = errorcontextlines
63 local message_a = "Type H <return> for immediate help"
65 function generic_error(msgcont, main, ref, help)
66 local mainref = main..".\n\n"..ref.."\n"..message_a
67 _error_set_message(msgcont, mainref, help)
71 function _generic_warn_info(msgcont, main, warn, line)
72 local mainc = message_cont(main, msgcont)
73 local br = warn and "\n" or ""
74 local out = warn and "term and log" or "log"
75 local on_line = line and (" on input line "..tex.inputlineno) or ""
76 local newlinechar = tex.newlinechar
78 texio.write_nl(out, br..main..on_line.."."..br)
79 tex.newlinechar = newlinechar
82 function generic_warning(msgcont, main)
83 _generic_warn_info(msgcont, main, true, true)
85 function generic_warning_no_line(msgcont, main)
86 _generic_warn_info(msgcont, main, true, false)
88 function generic_info(msgcont, main)
89 _generic_warn_info(msgcont, main, false, true)
91 function generic_info_no_line(msgcont, main)
92 _generic_warn_info(msgcont, main, false, false)
95 function package_error(pkgname, main, help)
96 generic_error("("..pkgname.." ",
97 "Package "..pkgname.." Error: "..main,
98 "See the "..pkgname.." package documentation for explanation.",
101 function package_warning(pkgname, main)
102 generic_warning("("..pkgname.." ",
103 "Package "..pkgname.." Warning: "..main)
105 function package_warning_no_line(pkgname, main)
106 generic_warning_no_line("("..pkgname.." ",
107 "Package "..pkgname.." Warning: "..main)
109 function package_info(pkgname, main)
110 generic_info("("..pkgname.." ",
111 "Package "..pkgname.." Info: "..main)
113 function package_info_no_line(pkgname, main)
114 generic_info_no_line("("..pkgname.." ",
115 "Package "..pkgname.." Info: "..main)
118 function ltj_error(main, help)
119 package_error(public_name, main, help)
121 function ltj_warning_no_line(main)
122 package_warning_no_line(public_name, main, help)
126 -------------------- TeX stream I/O
129 --! ixbase.print() と同じ
130 --- Extension to tex.print(). Each argument string may contain
131 -- newline characters, in which case the string is output (to
132 -- TeX input stream) as multiple lines.
133 -- @param ... (string) string to output
137 if type(arg[1]) == "number" then
138 table.insert(lines, arg[1])
141 for _, cnk in ipairs(arg) do
142 local ls = cnk:explode("\n")
143 if ls[#ls] == "" then
144 table.remove(ls, #ls)
146 for _, l in ipairs(ls) do
147 table.insert(lines, l)
150 return tex.print(unpack(lines))
154 -------------------- Handling of TeX values
157 local glue_spec_id = node.id("glue_spec")
159 local function copy_skip(s1, s2)
161 s1 = node.new(glue_spec_id)
163 s1.width = s2.width or 0
164 s1.stretch = s2.stretch or 0
165 s1.stretch_order = s2.stretch_order or 0
166 s1.shrink = s2.shrink or 0
167 s1.shrink_order = s2.shrink_order or 0
171 --! ixbase.to_dimen() と同じ
172 function to_dimen(val)
175 elseif type(val) == "number" then
178 return tex.sp(tostring(val))
182 local function parse_dimen(val)
183 val = tostring(val):lower()
184 local r, fil = val:match("([-.%d]+)fi(l*)")
186 val, fil = r.."pt", fil:len() + 1
190 return tex.sp(val), fil
193 --! ixbase.to_skip() と同じ
194 function to_skip(val)
195 if type(val) == "userdata" then
198 local res = node.new(glue_spec_id)
201 elseif type(val) == "number" then
203 elseif type(val) == "table" then
206 local t = tostring(val):lower():explode()
207 local w, p, m = t[1], t[3], t[5]
208 if t[2] == "minus" then
211 res.width = tex.sp(t[1])
213 res.stretch, res.stretch_order = parse_dimen(t[3])
216 res.shrink, res.shrink_order = parse_dimen(t[5])
222 function dump_skip(s)
223 print(("%s+%s<%s>-%s<%s>"):format(
224 s.width or 0, s.stretch or 0, s.stretch_order or 0,
225 s.shrink or 0, s.shrink_order or 0))
229 -------------------- Virtual table for LaTeX counters
232 --! ixbase.counter と同じ
234 local mt_counter = {}
235 setmetatable(counter, mt_counter)
237 function mt_counter.__index(tbl, key)
238 return tex.count['c@'..key]
240 function mt_counter.__newindex(tbl, key, val)
241 tex.count['c@'..key] = val
244 --! ixbase.length は tex.skip と全く同じなので不要.
247 -------------------- Number handling in TeX source
250 local tok_escape = token.create("ltj@@q@escape")
251 local tok_num = token.create("ltj@@q@escapenum")
252 local c_id_assign_int = token.command_id("assign_int")
253 local c_id_char_given = token.command_id("char_given")
255 local function error_scan()
256 _M.package_error("luatexja",
257 "Missing number of a permitted form, treated as zero",
258 "A number should have been here; I inserted '0'.")
261 local function get_expd_next()
262 local next = token.get_next()
263 while token.is_expandable(next) do
265 next = token.get_next()
270 local function grab_decimal(next, res)
271 table.insert(res, next)
273 next = get_expd_next()
274 if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
277 table.insert(res, next)
279 if next[1] == 10 then next = nil end
283 local function grab_hexa(next, res)
285 table.insert(res, next)
287 next = get_expd_next()
288 if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
289 ((next[1] == 12 or next[1] == 11) and
290 (0x41 <= next[2] and next[2] <= 0x46))) then
294 table.insert(res, next)
296 if next[1] == 10 then next = nil end
300 local function grab_octal(next, res)
302 table.insert(res, next)
304 next = get_expd_next()
305 if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
309 table.insert(res, next)
311 if next[1] == 10 then next = nil end
315 local function grab_charnum(next, res)
316 table.insert(res, next)
317 next = token.get_next()
318 table.insert(res, next)
319 next = get_expd_next()
320 if next[1] == 10 then next = nil end
324 local function scan_with(delay, scanner)
325 local function proc()
327 if delay > 0 then delay = delay - 1 end
328 return token.get_next()
330 local cont, back = scanner()
332 ltb.remove_from_callback("token_filter", "ltj@grab@num")
337 ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
340 function scan_brace()
341 scan_with(1, function()
342 local next = token.get_next()
344 return false, { tok_escape, next }
345 elseif next[1] == 10 then
346 return true, { next }
348 return false, { next }
353 function scan_number()
354 scan_with(1, function()
355 local next = get_expd_next()
356 local res, ok = { tok_num }, false
358 if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
359 table.insert(res, next)
360 elseif next[1] ~= 10 then
363 next = get_expd_next()
365 if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
366 ok, next = grab_decimal(next, res)
367 elseif next[1] == 12 and next[2] == 0x22 then
368 ok, next = grab_hexa(next, res)
369 elseif next[1] == 12 and next[2] == 0x27 then
370 ok, next = grab_octal(next, res)
371 elseif next[1] == 12 and next[2] == 0x60 then
372 ok, next = grab_charnum(next, res)
373 elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
374 table.insert(res, next)
378 table.insert(res, tok_num)
383 if next then table.insert(res, next) end
389 -------------------- TeX register allocation
391 local cmod_base_count = token.create('ltj@@count@zero')[2]
392 local cmod_base_attr = token.create('ltj@@attr@zero')[2]
393 local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
394 local cmod_base_skip = token.create('ltj@@skip@zero')[2]
396 function const_number(name)
397 if name:sub(1, 1) == '\\' then name = name:sub(2) end
398 return token.create(name)[2]
401 function count_number(name)
402 if name:sub(1, 1) == '\\' then name = name:sub(2) end
403 return token.create(name)[2] - cmod_base_count
406 function attribute_number(name)
407 if name:sub(1, 1) == '\\' then name = name:sub(2) end
408 return token.create(name)[2] - cmod_base_attr
411 function dimen_number(name)
412 if name:sub(1, 1) == '\\' then name = name:sub(2) end
413 return token.create(name)[2] - cmod_base_dimen
416 function skip_number(name)
417 if name:sub(1, 1) == '\\' then name = name:sub(2) end
418 return token.create(name)[2] - cmod_base_skip
422 -------------------- mock of debug logger
424 if not _M.debug or _M.debug == _G.debug then
425 local function no_op() end
427 package_debug = no_op
430 function debug_logger()
435 -------------------- getting next token
438 cstemp = token.csname_name(token.get_next())
439 tex.sprint(cat_lp,'\\' .. s)
442 -------------------- all done