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']
13 local public_name = 'luatexja'
14 local public_version = 'alpha'
15 luatexja.base.public_name = public_name
16 luatexja.base.public_version = public_version
19 -------------------- Fully-expandable error messaging
20 local _error_set_break, _error_set_message, _error_show
21 local generic_error, _generic_warn_info
22 local generic_warning, generic_warning_no_line
23 local generic_info, generic_info_no_line
24 local package_error, package_warning, package_warning_no_line
25 local package_info, package_info_no_line
26 local ltj_error, ltj_warning_no_line
29 --! LaTeX 形式のエラーメッセージ(\PackageError 等)を
37 local function message_cont(str, c)
38 return str:gsub(err_break, LF .. c)
40 local function into_lines(str)
41 return str:gsub(err_break, LF):explode(LF)
44 _error_set_break = function (str)
48 _error_set_message = function (msgcont, main, help)
49 err_main = message_cont(main, msgcont)
50 err_help = into_lines(help)
53 _error_show = function (escchar)
54 local escapechar = tex.escapechar
55 local newlinechar = tex.newlinechar
56 local errorcontextlines = tex.errorcontextlines
57 if not escchar then tex.escapechar = -1 end
59 tex.errorcontextlines = -1
60 tex.error(err_main, err_help)
61 tex.escapechar = escapechar
62 tex.newlinechar = newlinechar
63 tex.errorcontextlines = errorcontextlines
66 local message_a = "Type H <return> for immediate help"
68 generic_error = function (msgcont, main, ref, help)
69 local mainref = main..".\n\n"..ref.."\n"..message_a
70 _error_set_message(msgcont, mainref, help)
74 _generic_warn_info = function (msgcont, main, warn, line)
75 local mainc = message_cont(main, msgcont)
76 local br = warn and "\n" or ""
77 local out = warn and "term and log" or "log"
78 local on_line = line and (" on input line "..tex.inputlineno) or ""
79 local newlinechar = tex.newlinechar
81 texio.write_nl(out, br..main..on_line.."."..br)
82 tex.newlinechar = newlinechar
85 generic_warning = function (msgcont, main)
86 _generic_warn_info(msgcont, main, true, true)
88 generic_warning_no_line = function (msgcont, main)
89 _generic_warn_info(msgcont, main, true, false)
91 generic_info = function (msgcont, main)
92 _generic_warn_info(msgcont, main, false, true)
94 generic_info_no_line = function (msgcont, main)
95 _generic_warn_info(msgcont, main, false, false)
98 package_error = function (pkgname, main, help)
99 generic_error("("..pkgname.." ",
100 "Package "..pkgname.." Error: "..main,
101 "See the "..pkgname.." package documentation for explanation.",
104 package_warning = function (pkgname, main)
105 generic_warning("("..pkgname.." ",
106 "Package "..pkgname.." Warning: "..main)
108 package_warning_no_line = function (pkgname, main)
109 generic_warning_no_line("("..pkgname.." ",
110 "Package "..pkgname.." Warning: "..main)
112 package_info = function (pkgname, main)
113 generic_info("("..pkgname.." ",
114 "Package "..pkgname.." Info: "..main)
116 package_info_no_line = function (pkgname, main)
117 generic_info_no_line("("..pkgname.." ",
118 "Package "..pkgname.." Info: "..main)
121 ltj_error = function (main, help)
122 package_error(public_name, main, help)
124 ltj_warning_no_line = function (main)
125 package_warning_no_line(public_name, main, help)
129 -------------------- TeX stream I/O
130 --! ixbase.print() と同じ
131 --- Extension to tex.print(). Each argument string may contain
132 -- newline characters, in which case the string is output (to
133 -- TeX input stream) as multiple lines.
134 -- @param ... (string) string to output
135 local function mprint(...)
138 if type(arg[1]) == "number" then
139 table.insert(lines, arg[1])
142 for _, cnk in ipairs(arg) do
143 local ls = cnk:explode("\n")
144 if ls[#ls] == "" then
145 table.remove(ls, #ls)
147 for _, l in ipairs(ls) do
148 table.insert(lines, l)
151 return tex.print(unpack(lines))
153 luatexja.base.mprint = mprint
155 -------------------- Handling of TeX values
158 local glue_spec_id = node.id("glue_spec")
160 local function copy_skip(s1, s2)
162 s1 = node.new(glue_spec_id)
164 s1.width = s2.width or 0
165 s1.stretch = s2.stretch or 0
166 s1.stretch_order = s2.stretch_order or 0
167 s1.shrink = s2.shrink or 0
168 s1.shrink_order = s2.shrink_order or 0
172 --! ixbase.to_dimen() と同じ
173 local function to_dimen(val)
176 elseif type(val) == "number" then
179 return tex.sp(tostring(val))
183 local function parse_dimen(val)
184 val = tostring(val):lower()
185 local r, fil = val:match("([-.%d]+)fi(l*)")
187 val, fil = r.."pt", fil:len() + 1
191 return tex.sp(val), fil
194 --! ixbase.to_skip() と同じ
195 local function to_skip(val)
196 if type(val) == "userdata" then
199 local res = node.new(glue_spec_id)
202 elseif type(val) == "number" then
204 elseif type(val) == "table" then
207 local t = tostring(val):lower():explode()
208 local w, p, m = t[1], t[3], t[5]
209 if t[2] == "minus" then
212 res.width = tex.sp(t[1])
214 res.stretch, res.stretch_order = parse_dimen(p)
217 res.shrink, res.shrink_order = parse_dimen(m)
223 local function dump_skip(s)
224 print(("%s+%s<%s>-%s<%s>"):format(
225 s.width or 0, s.stretch or 0, s.stretch_order or 0,
226 s.shrink or 0, s.shrink_order or 0))
229 luatexja.base.to_dimen = to_dimen
230 luatexja.base.dump_skip = dump_skip
231 luatexja.base.to_skip = to_skip
234 -------------------- Virtual table for LaTeX counters
235 -- not used in current LuaTeX-ja
237 --! ixbase.counter と同じ
239 local mt_counter = {}
240 setmetatable(counter, mt_counter)
242 function mt_counter.__index(tbl, key)
243 return tex.count['c@'..key]
245 function mt_counter.__newindex(tbl, key, val)
246 tex.count['c@'..key] = val
248 luatexja.base.counter = counter
250 --! ixbase.length は tex.skip と全く同じなので不要.
253 -------------------- Number handling in TeX source
256 local tok_escape = token.create("ltj@@q@escape")
257 local tok_num = token.create("ltj@@q@escapenum")
258 local c_id_assign_int = token.command_id("assign_int")
259 local c_id_char_given = token.command_id("char_given")
261 local function error_scan()
262 package_error("luatexja",
263 "Missing number of a permitted form, treated as zero",
264 "A number should have been here; I inserted '0'.")
267 local function get_expd_next()
268 local next = token.get_next()
269 while token.is_expandable(next) do
271 next = token.get_next()
276 local function grab_decimal(next, res)
277 table.insert(res, next)
279 next = get_expd_next()
280 if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
283 table.insert(res, next)
285 if next[1] == 10 then next = nil end
289 local function grab_hexa(next, res)
291 table.insert(res, next)
293 next = get_expd_next()
294 if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
295 ((next[1] == 12 or next[1] == 11) and
296 (0x41 <= next[2] and next[2] <= 0x46))) then
300 table.insert(res, next)
302 if next[1] == 10 then next = nil end
306 local function grab_octal(next, res)
308 table.insert(res, next)
310 next = get_expd_next()
311 if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
315 table.insert(res, next)
317 if next[1] == 10 then next = nil end
321 local function grab_charnum(next, res)
322 table.insert(res, next)
323 next = token.get_next()
324 table.insert(res, next)
325 next = get_expd_next()
326 if next[1] == 10 then next = nil end
330 local function scan_with(delay, scanner)
331 local function proc()
333 if delay > 0 then delay = delay - 1 end
334 return token.get_next()
336 local cont, back = scanner()
338 ltb.remove_from_callback("token_filter", "ltj@grab@num")
343 ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
346 local function scan_brace()
347 scan_with(1, function()
348 local next = token.get_next()
350 return false, { tok_escape, next }
351 elseif next[1] == 10 then
352 return true, { next }
354 return false, { next }
359 local function scan_number()
360 scan_with(1, function()
361 local next = get_expd_next()
362 local res, ok = { tok_num }, false
364 if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
365 table.insert(res, next)
366 elseif next[1] ~= 10 then
369 next = get_expd_next()
371 if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
372 ok, next = grab_decimal(next, res)
373 elseif next[1] == 12 and next[2] == 0x22 then
374 ok, next = grab_hexa(next, res)
375 elseif next[1] == 12 and next[2] == 0x27 then
376 ok, next = grab_octal(next, res)
377 elseif next[1] == 12 and next[2] == 0x60 then
378 ok, next = grab_charnum(next, res)
379 elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
380 table.insert(res, next)
384 table.insert(res, tok_num)
389 if next then table.insert(res, next) end
394 luatexja.base.scan_brace = scan_brace
395 luatexja.base.scan_number = scan_number
398 -------------------- TeX register allocation
399 -- not used in current LuaTeX-ja
402 local cmod_base_count = token.create('ltj@@count@zero')[2]
403 local cmod_base_attr = token.create('ltj@@attr@zero')[2]
404 local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
405 local cmod_base_skip = token.create('ltj@@skip@zero')[2]
407 local function const_number(name)
408 if name:sub(1, 1) == '\\' then name = name:sub(2) end
409 return token.create(name)[2]
412 local function count_number(name)
413 if name:sub(1, 1) == '\\' then name = name:sub(2) end
414 return token.create(name)[2] - cmod_base_count
417 local function attribute_number(name)
418 if name:sub(1, 1) == '\\' then name = name:sub(2) end
419 return token.create(name)[2] - cmod_base_attr
422 local function dimen_number(name)
423 if name:sub(1, 1) == '\\' then name = name:sub(2) end
424 return token.create(name)[2] - cmod_base_dimen
427 local function skip_number(name)
428 if name:sub(1, 1) == '\\' then name = name:sub(2) end
429 return token.create(name)[2] - cmod_base_skip
432 luatexja.base.const_number = const_number
433 luatexja.base.count_number = count_number
434 luatexja.base.attribute_number = attribute_number
435 luatexja.base.dimen_number = dimen_number
436 luatexja.base.skip_number = skip_number
439 -------------------- mock of debug logger
440 if not debug or debug == _G.debug then
441 local function no_op() end
443 package_debug = no_op
446 function debug_logger()
451 -------------------- getting next token
453 local function get_cs(s)
454 cstemp = token.csname_name(token.get_next())
455 tex.sprint(cat_lp,'\\' .. s)
457 luatexja.base.get_cs = get_cs
459 -------------------- common error message
461 local function in_unicode(c, admit_math)
462 local low = admit_math and -1 or 0
463 if type(c)~='number' or c<low or c>0x10FFFF then
464 local s = 'A character number must be between ' .. tostring(low)
465 .. ' and 0x10ffff.\n'
466 .. (admit_math and "(-1 is used for denoting `math boundary')\n" or '')
467 .. 'So I changed this one to zero.'
468 package_error('luatexja',
469 'bad character code (' .. tostring(c) .. ')', s)
474 luatexja.base.in_unicode = in_unicode
477 -------------------- cache management
478 -- load_cache (filename, outdate)
479 -- * filename: without suffix '.lua'
480 -- * outdate(t): return true iff the cache is outdated
481 -- * return value: non-nil iff the cache is up-to-date
482 -- save_cache (filename, t): no return value
483 -- save_cache_luc (filename, t): no return value
484 -- save_cache always calls save_cache_luc.
485 -- But sometimes we want to create only the precompiled cache,
486 -- when its 'text' version is already present in LuaTeX-ja distribution.
488 require('lualibs-lpeg') -- string.split
489 require('lualibs-os') -- os.type
492 local kpse_var_value = kpse.var_value
493 local path, pathtmp = kpse_var_value("TEXMFVAR")
494 pathtmp = kpse_var_value("TEXMFSYSVAR")
495 if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
496 pathtmp = kpse_var_value("TEXMFCACHE")
497 if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
499 if os.type~='windows' then path = string.gsub(path, ':', ';') end
500 path = table.unique(string.split(path, ';'))
502 local cache_dir = '/luatexja'
503 local find_file = kpse.find_file
504 local join, isreadable = file.join, file.isreadable
505 local tofile, serialize = table.tofile, table.serialize
506 local luc_suffix = jit and '.lub' or '.luc'
508 -- determine save path
510 for _,v in pairs(path) do
511 local testpath = join(v, cache_dir)
512 if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
513 if lfs.isdir(testpath) then savepath = testpath; break end
516 save_cache_luc = function (filename, t, serialized)
517 local fullpath = savepath .. '/' .. filename .. luc_suffix
518 local s = serialized or serialize(t, 'return', false)
521 local f = io.open(fullpath, 'wb')
523 f:write(string.dump(sa, true))
524 texio.write('(save cache: ' .. fullpath .. ')')
530 save_cache = function (filename, t)
531 local fullpath = savepath .. '/' .. filename .. '.lua'
532 local s = serialize(t, 'return', false)
534 local f = io.open(fullpath, 'w')
537 texio.write('(save cache: ' .. fullpath .. ')')
540 save_cache_luc(filename, t, s)
544 local function load_cache_a (filename, outdate)
546 for _,v in pairs(path) do
547 local fn = join(v, cache_dir, filename)
548 if isreadable(fn) then
549 texio.write('(load cache: ' .. fn .. ')')
550 result = loadfile(fn)
551 result = result and result(); break
554 if (not result) or outdate(result) then
561 load_cache = function (filename, outdate)
562 local r = load_cache_a(filename .. luc_suffix, outdate)
566 local r = load_cache_a(filename .. '.lua', outdate)
567 if r then save_cache_luc(filename, r) end -- update the precompiled cache
572 luatexja.base.load_cache = load_cache
573 luatexja.base.save_cache_luc = save_cache_luc
574 luatexja.base.save_cache = save_cache
577 luatexja.base._error_set_break = _error_set_break
578 luatexja.base._error_set_message = _error_set_message
579 luatexja.base._error_show = _error_show
580 luatexja.base._generic_warn_info = _generic_warn_info
582 luatexja.base.package_error = package_error
583 luatexja.base.package_warning = package_warning
584 luatexja.base.package_warning_no_line = package_warning_no_line
585 luatexja.base.package_info = package_info
586 luatexja.base.package_info_no_line = package_info_no_line
588 luatexja.base.generic_error = generic_error
589 luatexja.base.generic_warning = generic_warning
590 luatexja.base.generic_warning_no_line = generic_warning_no_line
591 luatexja.base.generic_info = generic_info
592 luatexja.base.generic_info_no_line = generic_info_no_line
594 luatexja.base.ltj_warning_no_line = ltj_warning_no_line
595 luatexja.base.ltj_error = ltj_error
597 luatexja.base.debug = debug
598 luatexja.base.package_debug = package_debug
599 luatexja.base.debug_logger = debug_logger
600 luatexja.base.show_term = show_term
601 luatexja.base.show_log = show_log
603 -------------------- all done