4 luatexbase.provides_module({
5 name = 'luatexja.base',
9 module('luatexja.base', package.seeall)
10 local err, warn, info, log = luatexbase.errwarinf(_NAME)
12 local ltb = luatexbase
13 local tostring = tostring
14 local node, table, tex, token = node, table, tex, token
16 local cat_lp = luatexbase.catcodetables['latex-package']
20 public_name = 'luatexja'
21 public_version = 'alpha'
23 -------------------- Fully-expandable error messaging
25 --! LaTeX 形式のエラーメッセージ(\PackageError 等)を
33 local function message_cont(str, c)
34 return str:gsub(err_break, LF .. c)
36 local function into_lines(str)
37 return str:gsub(err_break, LF):explode(LF)
40 function _error_set_break(str)
44 function _error_set_message(msgcont, main, help)
45 err_main = message_cont(main, msgcont)
46 err_help = into_lines(help)
49 function _error_show(escchar)
50 local escapechar = tex.escapechar
51 local newlinechar = tex.newlinechar
52 local errorcontextlines = tex.errorcontextlines
53 if not escchar then tex.escapechar = -1 end
55 tex.errorcontextlines = -1
56 tex.error(err_main, err_help)
57 tex.escapechar = escapechar
58 tex.newlinechar = newlinechar
59 tex.errorcontextlines = errorcontextlines
62 local message_a = "Type H <return> for immediate help"
64 function generic_error(msgcont, main, ref, help)
65 local mainref = main..".\n\n"..ref.."\n"..message_a
66 _error_set_message(msgcont, mainref, help)
70 function _generic_warn_info(msgcont, main, warn, line)
71 local mainc = message_cont(main, msgcont)
72 local br = warn and "\n" or ""
73 local out = warn and "term and log" or "log"
74 local on_line = line and (" on input line "..tex.inputlineno) or ""
75 local newlinechar = tex.newlinechar
77 texio.write_nl(out, br..main..on_line.."."..br)
78 tex.newlinechar = newlinechar
81 function generic_warning(msgcont, main)
82 _generic_warn_info(msgcont, main, true, true)
84 function generic_warning_no_line(msgcont, main)
85 _generic_warn_info(msgcont, main, true, false)
87 function generic_info(msgcont, main)
88 _generic_warn_info(msgcont, main, false, true)
90 function generic_info_no_line(msgcont, main)
91 _generic_warn_info(msgcont, main, false, false)
94 function package_error(pkgname, main, help)
95 generic_error("("..pkgname.." ",
96 "Package "..pkgname.." Error: "..main,
97 "See the "..pkgname.." package documentation for explanation.",
100 function package_warning(pkgname, main)
101 generic_warning("("..pkgname.." ",
102 "Package "..pkgname.." Warning: "..main)
104 function package_warning_no_line(pkgname, main)
105 generic_warning_no_line("("..pkgname.." ",
106 "Package "..pkgname.." Warning: "..main)
108 function package_info(pkgname, main)
109 generic_info("("..pkgname.." ",
110 "Package "..pkgname.." Info: "..main)
112 function package_info_no_line(pkgname, main)
113 generic_info_no_line("("..pkgname.." ",
114 "Package "..pkgname.." Info: "..main)
117 function ltj_error(main, help)
118 package_error(public_name, main, help)
120 function ltj_warning_no_line(main)
121 package_warning_no_line(public_name, main, help)
125 -------------------- TeX stream I/O
128 --! ixbase.print() と同じ
129 --- Extension to tex.print(). Each argument string may contain
130 -- newline characters, in which case the string is output (to
131 -- TeX input stream) as multiple lines.
132 -- @param ... (string) string to output
136 if type(arg[1]) == "number" then
137 table.insert(lines, arg[1])
140 for _, cnk in ipairs(arg) do
141 local ls = cnk:explode("\n")
142 if ls[#ls] == "" then
143 table.remove(ls, #ls)
145 for _, l in ipairs(ls) do
146 table.insert(lines, l)
149 return tex.print(unpack(lines))
153 -------------------- Handling of TeX values
156 local glue_spec_id = node.id("glue_spec")
158 local function copy_skip(s1, s2)
160 s1 = node.new(glue_spec_id)
162 s1.width = s2.width or 0
163 s1.stretch = s2.stretch or 0
164 s1.stretch_order = s2.stretch_order or 0
165 s1.shrink = s2.shrink or 0
166 s1.shrink_order = s2.shrink_order or 0
170 --! ixbase.to_dimen() と同じ
171 function to_dimen(val)
174 elseif type(val) == "number" then
177 return tex.sp(tostring(val))
181 local function parse_dimen(val)
182 val = tostring(val):lower()
183 local r, fil = val:match("([-.%d]+)fi(l*)")
185 val, fil = r.."pt", fil:len() + 1
189 return tex.sp(val), fil
192 --! ixbase.to_skip() と同じ
193 function to_skip(val)
194 if type(val) == "userdata" then
197 local res = node.new(glue_spec_id)
200 elseif type(val) == "number" then
202 elseif type(val) == "table" then
205 local t = tostring(val):lower():explode()
206 local w, p, m = t[1], t[3], t[5]
207 if t[2] == "minus" then
210 res.width = tex.sp(t[1])
212 res.stretch, res.stretch_order = parse_dimen(t[3])
215 res.shrink, res.shrink_order = parse_dimen(t[5])
221 function dump_skip(s)
222 print(("%s+%s<%s>-%s<%s>"):format(
223 s.width or 0, s.stretch or 0, s.stretch_order or 0,
224 s.shrink or 0, s.shrink_order or 0))
228 -------------------- Virtual table for LaTeX counters
231 --! ixbase.counter と同じ
233 local mt_counter = {}
234 setmetatable(counter, mt_counter)
236 function mt_counter.__index(tbl, key)
237 return tex.count['c@'..key]
239 function mt_counter.__newindex(tbl, key, val)
240 tex.count['c@'..key] = val
243 --! ixbase.length は tex.skip と全く同じなので不要.
246 -------------------- Number handling in TeX source
249 local tok_escape = token.create("ltj@@q@escape")
250 local tok_num = token.create("ltj@@q@escapenum")
251 local c_id_assign_int = token.command_id("assign_int")
252 local c_id_char_given = token.command_id("char_given")
254 local function error_scan()
255 _M.package_error("luatexja",
256 "Missing number of a permitted form, treated as zero",
257 "A number should have been here; I inserted '0'.")
260 local function get_expd_next()
261 local next = token.get_next()
262 while token.is_expandable(next) do
264 next = token.get_next()
269 local function grab_decimal(next, res)
270 table.insert(res, next)
272 next = get_expd_next()
273 if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
276 table.insert(res, next)
278 if next[1] == 10 then next = nil end
282 local function grab_hexa(next, res)
284 table.insert(res, next)
286 next = get_expd_next()
287 if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
288 ((next[1] == 12 or next[1] == 11) and
289 (0x41 <= next[2] and next[2] <= 0x46))) then
293 table.insert(res, next)
295 if next[1] == 10 then next = nil end
299 local function grab_octal(next, res)
301 table.insert(res, next)
303 next = get_expd_next()
304 if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
308 table.insert(res, next)
310 if next[1] == 10 then next = nil end
314 local function grab_charnum(next, res)
315 table.insert(res, next)
316 next = token.get_next()
317 table.insert(res, next)
318 next = get_expd_next()
319 if next[1] == 10 then next = nil end
323 local function scan_with(delay, scanner)
324 local function proc()
326 if delay > 0 then delay = delay - 1 end
327 return token.get_next()
329 local cont, back = scanner()
331 ltb.remove_from_callback("token_filter", "ltj@grab@num")
336 ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
339 function scan_brace()
340 scan_with(1, function()
341 local next = token.get_next()
343 return false, { tok_escape, next }
344 elseif next[1] == 10 then
345 return true, { next }
347 return false, { next }
352 function scan_number()
353 scan_with(1, function()
354 local next = get_expd_next()
355 local res, ok = { tok_num }, false
357 if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
358 table.insert(res, next)
359 elseif next[1] ~= 10 then
362 next = get_expd_next()
364 if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
365 ok, next = grab_decimal(next, res)
366 elseif next[1] == 12 and next[2] == 0x22 then
367 ok, next = grab_hexa(next, res)
368 elseif next[1] == 12 and next[2] == 0x27 then
369 ok, next = grab_octal(next, res)
370 elseif next[1] == 12 and next[2] == 0x60 then
371 ok, next = grab_charnum(next, res)
372 elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
373 table.insert(res, next)
377 table.insert(res, tok_num)
382 if next then table.insert(res, next) end
388 -------------------- TeX register allocation
390 local cmod_base_count = token.create('ltj@@count@zero')[2]
391 local cmod_base_attr = token.create('ltj@@attr@zero')[2]
392 local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
393 local cmod_base_skip = token.create('ltj@@skip@zero')[2]
395 function const_number(name)
396 if name:sub(1, 1) == '\\' then name = name:sub(2) end
397 return token.create(name)[2]
400 function count_number(name)
401 if name:sub(1, 1) == '\\' then name = name:sub(2) end
402 return token.create(name)[2] - cmod_base_count
405 function attribute_number(name)
406 if name:sub(1, 1) == '\\' then name = name:sub(2) end
407 return token.create(name)[2] - cmod_base_attr
410 function dimen_number(name)
411 if name:sub(1, 1) == '\\' then name = name:sub(2) end
412 return token.create(name)[2] - cmod_base_dimen
415 function skip_number(name)
416 if name:sub(1, 1) == '\\' then name = name:sub(2) end
417 return token.create(name)[2] - cmod_base_skip
421 -------------------- mock of debug logger
423 if not _M.debug or _M.debug == _G.debug then
424 local function no_op() end
426 package_debug = no_op
429 function debug_logger()
434 -------------------- getting next token
437 cstemp = token.csname_name(token.get_next())
438 tex.sprint(cat_lp,'\\' .. s)
442 -------------------- cache management
443 -- load_cache (filename, outdate)
444 -- * filename: without suffix '.lua'
445 -- * outdate(t): return true iff the cache is outdated
446 -- * return value: non-nil iff the cache is up-to-date
447 -- save_cache (filename, t): no return value
448 -- save_cache_luc (filename, t): no return value
449 -- save_cache always calls save_cache_luc.
450 -- But sometimes we want to create only the precompiled cache,
451 -- when its 'text' version is already present in LuaTeX-ja distribution.
453 require('lualibs-lpeg') -- string.split
454 require('lualibs-os') -- os.type
456 local path = kpse.expand_var("$TEXMFVAR;$TEXMFSYSVAR;$TEXMFCACHE")
457 if os.type~='windows' then path = string.gsub(path, ':', ';') end
458 path = string.split(path, ';')
460 local cache_dir = '/luatexja'
461 local find_file = kpse.find_file
462 local join, isreadable = file.join, file.isreadable
463 local tofile, serialize = table.tofile, table.serialize
464 local luc_suffix = jit and '.lub' or '.luc'
466 -- determine save path
468 for _,v in pairs(path) do
469 local testpath = join(v, cache_dir)
470 if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
471 if lfs.isdir(testpath) then savepath = testpath; break end
474 function save_cache_luc(filename, t)
475 local fullpath = savepath .. '/' .. filename .. luc_suffix
476 local s = serialize(t, 'return', false)
478 local f = io.open(fullpath, 'wb')
480 f:write(string.dump(load(s), true))
481 texio.write('(save cache: ' .. fullpath .. ')')
487 function save_cache(filename, t)
488 local fullpath = savepath .. '/' .. filename .. '.lua'
489 tofile(fullpath, t, 'return', false)
490 texio.write('(save cache: ' .. fullpath .. ')')
491 save_cache_luc(filename, t)
494 local function luc_load (n)
495 texio.write('(load cache: ' .. n .. ')')
496 local f = loadfile(n, 'b'); return f
498 function load_cache (filename, outdate)
499 local r = load_cache_a(filename .. luc_suffix, outdate, luc_load)
503 local r = load_cache_a(filename .. '.lua', outdate, loadfile)
504 if r then save_cache_luc(filename, r) end -- update the precompiled cache
509 function load_cache_a (filename, outdate, loader)
511 for _,v in pairs(path) do
512 local fn = join(v, cache_dir, filename)
513 if isreadable(fn) then
515 if result then result = result(); break end
518 if (not result) or outdate(result) then
527 -------------------- all done