2 -- luatexja/ltj-base.lua
5 local tostring = tostring
6 local node, table, tex, token = node, table, tex, token
8 local cat_lp = luatexbase.catcodetables['latex-package']
12 local public_name = 'luatexja'
13 local public_version = 'alpha'
15 -------------------- Fully-expandable error messaging
16 local _error_set_break, _error_set_message, _error_show
17 local generic_error, _generic_warn_info
18 local generic_warning, generic_warning_no_line
19 local generic_info, generic_info_no_line
20 local package_error, package_warning, package_warning_no_line
21 local package_info, package_info_no_line
22 local ltj_error, ltj_warning_no_line
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 _error_set_break = function (str)
44 _error_set_message = function (msgcont, main, help)
45 err_main = message_cont(main, msgcont)
46 err_help = into_lines(help)
49 _error_show = function (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 generic_error = function (msgcont, main, ref, help)
65 local mainref = main..".\n\n"..ref.."\n"..message_a
66 _error_set_message(msgcont, mainref, help)
70 _generic_warn_info = function (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 generic_warning = function (msgcont, main)
82 _generic_warn_info(msgcont, main, true, true)
84 generic_warning_no_line = function (msgcont, main)
85 _generic_warn_info(msgcont, main, true, false)
87 generic_info = function (msgcont, main)
88 _generic_warn_info(msgcont, main, false, true)
90 generic_info_no_line = function (msgcont, main)
91 _generic_warn_info(msgcont, main, false, false)
94 package_error = function (pkgname, main, help)
95 generic_error("("..pkgname.." ",
96 "Package "..pkgname.." Error: "..main,
97 "See the "..pkgname.." package documentation for explanation.",
100 package_warning = function (pkgname, main)
101 generic_warning("("..pkgname.." ",
102 "Package "..pkgname.." Warning: "..main)
104 package_warning_no_line = function (pkgname, main)
105 generic_warning_no_line("("..pkgname.." ",
106 "Package "..pkgname.." Warning: "..main)
108 package_info = function (pkgname, main)
109 generic_info("("..pkgname.." ",
110 "Package "..pkgname.." Info: "..main)
112 package_info_no_line = function (pkgname, main)
113 generic_info_no_line("("..pkgname.." ",
114 "Package "..pkgname.." Info: "..main)
117 ltj_error = function (main, help)
118 package_error(public_name, main, help)
120 ltj_warning_no_line = function (main)
121 package_warning_no_line(public_name, main, help)
125 -------------------- TeX stream I/O
126 --! ixbase.print() と同じ
127 --- Extension to tex.print(). Each argument string may contain
128 -- newline characters, in which case the string is output (to
129 -- TeX input stream) as multiple lines.
130 -- @param ... (string) string to output
131 local function mprint(...)
134 if type(arg[1]) == "number" then
135 table.insert(lines, arg[1])
138 for _, cnk in ipairs(arg) do
139 local ls = cnk:explode("\n")
140 if ls[#ls] == "" then
141 table.remove(ls, #ls)
143 for _, l in ipairs(ls) do
144 table.insert(lines, l)
147 return tex.print(unpack(lines))
150 -------------------- Handling of TeX values
151 local to_dimen, to_skip, dump_skip
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 to_dimen = function (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 to_skip = function (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 dump_skip = function (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
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
247 local scan_brace, scan_number
251 local tok_escape = token.create("ltj@@q@escape")
252 local tok_num = token.create("ltj@@q@escapenum")
253 local c_id_assign_int = token.command_id("assign_int")
254 local c_id_char_given = token.command_id("char_given")
256 local function error_scan()
257 package_error("luatexja",
258 "Missing number of a permitted form, treated as zero",
259 "A number should have been here; I inserted '0'.")
262 local function get_expd_next()
263 local next = token.get_next()
264 while token.is_expandable(next) do
266 next = token.get_next()
271 local function grab_decimal(next, res)
272 table.insert(res, next)
274 next = get_expd_next()
275 if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
278 table.insert(res, next)
280 if next[1] == 10 then next = nil end
284 local function grab_hexa(next, res)
286 table.insert(res, next)
288 next = get_expd_next()
289 if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
290 ((next[1] == 12 or next[1] == 11) and
291 (0x41 <= next[2] and next[2] <= 0x46))) then
295 table.insert(res, next)
297 if next[1] == 10 then next = nil end
301 local function grab_octal(next, res)
303 table.insert(res, next)
305 next = get_expd_next()
306 if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
310 table.insert(res, next)
312 if next[1] == 10 then next = nil end
316 local function grab_charnum(next, res)
317 table.insert(res, next)
318 next = token.get_next()
319 table.insert(res, next)
320 next = get_expd_next()
321 if next[1] == 10 then next = nil end
325 local function scan_with(delay, scanner)
326 local function proc()
328 if delay > 0 then delay = delay - 1 end
329 return token.get_next()
331 local cont, back = scanner()
333 ltb.remove_from_callback("token_filter", "ltj@grab@num")
338 ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
341 scan_brace = function ()
342 scan_with(1, function()
343 local next = token.get_next()
345 return false, { tok_escape, next }
346 elseif next[1] == 10 then
347 return true, { next }
349 return false, { next }
354 scan_number = function ()
355 scan_with(1, function()
356 local next = get_expd_next()
357 local res, ok = { tok_num }, false
359 if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
360 table.insert(res, next)
361 elseif next[1] ~= 10 then
364 next = get_expd_next()
366 if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
367 ok, next = grab_decimal(next, res)
368 elseif next[1] == 12 and next[2] == 0x22 then
369 ok, next = grab_hexa(next, res)
370 elseif next[1] == 12 and next[2] == 0x27 then
371 ok, next = grab_octal(next, res)
372 elseif next[1] == 12 and next[2] == 0x60 then
373 ok, next = grab_charnum(next, res)
374 elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
375 table.insert(res, next)
379 table.insert(res, tok_num)
384 if next then table.insert(res, next) end
390 -------------------- TeX register allocation
391 local const_number, count_number, attribute_number, dimen_number, skip_number
394 local cmod_base_count = token.create('ltj@@count@zero')[2]
395 local cmod_base_attr = token.create('ltj@@attr@zero')[2]
396 local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
397 local cmod_base_skip = token.create('ltj@@skip@zero')[2]
399 const_number = function (name)
400 if name:sub(1, 1) == '\\' then name = name:sub(2) end
401 return token.create(name)[2]
404 count_number = function (name)
405 if name:sub(1, 1) == '\\' then name = name:sub(2) end
406 return token.create(name)[2] - cmod_base_count
409 attribute_number = function (name)
410 if name:sub(1, 1) == '\\' then name = name:sub(2) end
411 return token.create(name)[2] - cmod_base_attr
414 dimen_number = function (name)
415 if name:sub(1, 1) == '\\' then name = name:sub(2) end
416 return token.create(name)[2] - cmod_base_dimen
419 skip_number = function (name)
420 if name:sub(1, 1) == '\\' then name = name:sub(2) end
421 return token.create(name)[2] - cmod_base_skip
425 -------------------- mock of debug logger
427 if not debug or debug == _G.debug then
428 local function no_op() end
430 package_debug = no_op
433 function debug_logger()
438 -------------------- getting next token
440 local function get_cs(s)
441 cstemp = token.csname_name(token.get_next())
442 tex.sprint(cat_lp,'\\' .. s)
446 -------------------- cache management
447 -- load_cache (filename, outdate)
448 -- * filename: without suffix '.lua'
449 -- * outdate(t): return true iff the cache is outdated
450 -- * return value: non-nil iff the cache is up-to-date
451 -- save_cache (filename, t): no return value
452 -- save_cache_luc (filename, t): no return value
453 -- save_cache always calls save_cache_luc.
454 -- But sometimes we want to create only the precompiled cache,
455 -- when its 'text' version is already present in LuaTeX-ja distribution.
457 require('lualibs-lpeg') -- string.split
458 require('lualibs-os') -- os.type
460 local load_cache, save_cache_luc, save_cache
463 local path = kpse.expand_var("$TEXMFVAR;$TEXMFSYSVAR;$TEXMFCACHE")
464 if os.type~='windows' then path = string.gsub(path, ':', ';') end
465 path = string.split(path, ';')
467 local cache_dir = '/luatexja'
468 local find_file = kpse.find_file
469 local join, isreadable = file.join, file.isreadable
470 local tofile, serialize = table.tofile, table.serialize
471 local luc_suffix = jit and '.lub' or '.luc'
473 -- determine save path
475 for _,v in pairs(path) do
476 local testpath = join(v, cache_dir)
477 if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
478 if lfs.isdir(testpath) then savepath = testpath; break end
481 save_cache_luc = function (filename, t, serialized)
482 local fullpath = savepath .. '/' .. filename .. luc_suffix
483 local s = serialized or serialize(t, 'return', false)
486 local f = io.open(fullpath, 'wb')
488 f:write(string.dump(sa, true))
489 texio.write('(save cache: ' .. fullpath .. ')')
495 save_cache = function (filename, t)
496 local fullpath = savepath .. '/' .. filename .. '.lua'
497 local s = serialize(t, 'return', false)
499 local f = io.open(fullpath, 'w')
502 texio.write('(save cache: ' .. fullpath .. ')')
505 save_cache_luc(filename, t, s)
509 local function load_cache_a (filename, outdate)
511 for _,v in pairs(path) do
512 local fn = join(v, cache_dir, filename)
513 if isreadable(fn) then
514 texio.write('(load cache: ' .. fn .. ')')
515 result = loadfile(fn)
516 result = result and result(); break
519 if (not result) or outdate(result) then
525 load_cache = function (filename, outdate)
526 local r = load_cache_a(filename .. luc_suffix, outdate)
530 local r = load_cache_a(filename .. '.lua', outdate)
531 if r then save_cache_luc(filename, r) end -- update the precompiled cache
540 public_name = public_name,
541 public_version = public_version,
543 _error_set_break = _error_set_break,
544 _error_set_message = _error_set_message,
545 _error_show = _error_show,
546 _generic_warn_info = _generic_warn_info,
548 package_error = package_error,
549 package_warning = package_warning,
550 package_warning_no_line = package_warning_no_line,
551 package_info = package_info,
552 package_info_no_line = package_info_no_line,
554 generic_error = generic_error,
555 generic_warning = generic_warning,
556 generic_warning_no_line = generic_warning_no_line,
557 generic_info = generic_info,
558 generic_info_no_line = generic_info_no_line,
560 ltj_warning_no_line = ltj_warning_no_line,
561 ltj_error = ltj_error,
566 dump_skip = dump_skip,
571 scan_brace = scan_brace,
572 scan_number = scan_number,
574 const_number = const_number,
575 count_number = count_number,
576 attribute_number = attribute_number,
577 dimen_number = dimen_number,
578 skip_number = skip_number,
582 load_cache = load_cache,
583 save_cache_luc = save_cache_luc,
584 save_cache = save_cache,
587 package_debug = package_debug,
588 debug_logger = debug_logger,
589 show_term = show_term,
593 -------------------- all done