OSDN Git Service

Releases 20240514.0
[luatex-ja/luatexja.git] / src / ltj-base.lua
1 --
2 -- ltj-base.lua
3 --
4 local ltb = luatexbase
5 local tostring = tostring
6 local node, table, tex, token = node, table, tex, token
7
8 local cat_lp = luatexbase.catcodetables['latex-package']
9
10 --------------------
11 local ltjb = {}
12 luatexja.base = ltjb
13
14 local public_name = 'luatexja'
15 local public_version = 'alpha'
16 ltjb.public_name = public_name
17 ltjb.public_version = public_version
18
19
20 -------------------- Fully-expandable error messaging
21 local _error_set_break, _error_set_message, _error_show
22 local generic_error, _generic_warn_info
23 local generic_warning, generic_warning_no_line
24 local generic_info, generic_info_no_line
25 local package_error, package_warning, package_warning_no_line
26 local package_info, package_info_no_line
27 local ltj_error, ltj_warning_no_line
28
29 do
30 --! LaTeX 形式のエラーメッセージ(\PackageError 等)を
31 --! Lua 関数の呼び出しで行う.
32
33   local LF, BEL = "\n", "\a"
34   local err_main = ""
35   local err_help = ""
36
37   local function message_cont(str, c)
38     return str:gsub(LF, LF .. c)
39   end
40   local function into_lines(str)
41     return str:explode(LF)
42   end
43
44   _error_set_message = function (msgcont, main, help)
45     err_main = message_cont(main, msgcont):gsub(BEL, LF)
46     err_help = (help and help~="") and into_lines(help)
47        or {"Sorry, I don't know how to help in this situation.",
48            "Maybe you should try asking a human?" }
49   end
50
51   _error_show = function (escchar)
52     local escapechar = tex.escapechar
53     local newlinechar = tex.newlinechar
54     local errorcontextlines = tex.errorcontextlines
55     if not escchar then tex.escapechar = -1 end
56     tex.newlinechar = 10
57     tex.errorcontextlines = -1
58     tex.error(err_main, err_help)
59     tex.escapechar = escapechar
60     tex.newlinechar = newlinechar
61     tex.errorcontextlines = errorcontextlines
62   end
63
64   local message_a = "Type  H <return>  for immediate help"
65
66   generic_error = function (msgcont, main, ref, help)
67     local mainref = main..".\a\a"..ref..BEL..message_a
68     _error_set_message(msgcont, mainref, help)
69     _error_show(true)
70   end
71
72   _generic_warn_info = function (msgcont, main, warn, line)
73     local mainc = message_cont(main, msgcont)
74     local br = warn and "\n" or ""
75     local out = warn and "term and log" or "log"
76     local on_line = line and (" on input line "..tex.inputlineno) or ""
77     local newlinechar = tex.newlinechar
78     tex.newlinechar = -1
79     texio.write_nl(out, br..mainc..on_line.."."..br)
80     tex.newlinechar = newlinechar
81   end
82
83   generic_warning = function (msgcont, main)
84     _generic_warn_info(msgcont, main, true, true)
85   end
86   generic_warning_no_line = function (msgcont, main)
87     _generic_warn_info(msgcont, main, true, false)
88   end
89   generic_info = function (msgcont, main)
90     _generic_warn_info(msgcont, main, false, true)
91   end
92   generic_info_no_line = function (msgcont, main)
93     _generic_warn_info(msgcont, main, false, false)
94   end
95
96   package_error = function (pkgname, main, help)
97     generic_error("("..pkgname..")                ",
98       "Package "..pkgname.." Error: "..main,
99       "See the "..pkgname.." package documentation for explanation.",
100       help)
101   end
102   package_warning = function (pkgname, main)
103     generic_warning("("..pkgname..")                ",
104       "Package "..pkgname.." Warning: "..main)
105   end
106   package_warning_no_line = function (pkgname, main)
107     generic_warning_no_line("("..pkgname..")                ",
108       "Package "..pkgname.." Warning: "..main)
109   end
110   package_info = function (pkgname, main)
111     generic_info("("..pkgname..")             ",
112       "Package "..pkgname.." Info: "..main)
113   end
114   package_info_no_line = function (pkgname, main)
115     generic_info_no_line("("..pkgname..")             ",
116       "Package "..pkgname.." Info: "..main)
117   end
118
119   ltj_error = function (main, help)
120     package_error(public_name, main, help)
121   end
122   ltj_warning_no_line = function (main)
123     package_warning_no_line(public_name, main, help)
124   end
125
126 end
127 -------------------- 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
133 local function mprint(...)
134    local arg = {...}
135    local lines = {}
136    if type(arg[1]) == "number" then
137       table.insert(lines, arg[1])
138       table.remove(arg, 1)
139    end
140    for _, cnk in ipairs(arg) do
141       local ls = cnk:explode("\n")
142       if ls[#ls] == "" then
143          table.remove(ls, #ls)
144       end
145       for _, l in ipairs(ls) do
146          table.insert(lines, l)
147       end
148    end
149    return tex.print(unpack(lines))
150 end
151 ltjb.mprint = mprint
152
153 -------------------- Handling of TeX values
154 do
155
156 --! ixbase.to_dimen() と同じ
157   local function to_dimen(val)
158     if val == nil then
159       return 0
160     elseif type(val) == "number" then
161       return val
162     else
163       return tex.sp(tostring(val))
164     end
165   end
166
167   local function parse_dimen(val)
168     val = tostring(val):lower()
169     local r, fil = val:match("([-.%d]+)fi(l*)")
170     if r then
171       val, fil = r.."pt", fil:len() + 1
172     else
173       fil = 0
174     end
175     return tex.sp(val), fil
176   end
177
178   ltjb.to_dimen = to_dimen
179 end
180
181 -------------------- Virtual table for LaTeX counters
182 -- not used in current LuaTeX-ja
183 do
184 --! ixbase.counter と同じ
185   local counter = {}
186   local mt_counter = {}
187   setmetatable(counter, mt_counter)
188
189   function mt_counter.__index(tbl, key)
190     return tex.count['c@'..key]
191   end
192   function mt_counter.__newindex(tbl, key, val)
193     tex.count['c@'..key] = val
194   end
195   ltjb.counter = counter
196
197 --! ixbase.length は tex.skip と全く同じなので不要.
198 end
199
200 -------------------- common error message
201 do
202    local function in_unicode(c, admit_math)
203       local low = admit_math and -1 or 0
204       if type(c)~='number' or c<low or c>0x10FFFF then
205          local s = 'A character number must be between ' .. tostring(low)
206             .. ' and 0x10ffff.\n'
207             .. (admit_math and "(-1 is used for denoting `math boundary')\n" or '')
208             .. 'So I changed this one to zero.'
209          package_error('luatexja',
210                             'bad character code (' .. tostring(c) .. ')', s)
211          c=0
212       end
213       return c
214    end
215    ltjb.in_unicode = in_unicode
216 end
217
218 -------------------- cache management
219 -- load_cache (filename, outdate)
220 --   * filename: without suffix '.lua'
221 --   * outdate(t): return true iff the cache is outdated
222 --   * return value: non-nil iff the cache is up-to-date
223 -- save_cache (filename, t): no return value
224 -- save_cache_luc (filename, t): no return value
225 --   save_cache always calls save_cache_luc.
226 --   But sometimes we want to create only the precompiled cache,
227 --   when its 'text' version is already present in LuaTeX-ja distribution.
228
229 if not os.type then require'lualibs-os' end
230 if not string.split then  require'lualibs-lpeg' end
231 if not gzip then
232   if kpse.find_file('lualibs-util-zip', 'lua') then require'lualibs-util-zip' 
233   else require'lualibs-gzip' end
234 end
235
236 do
237    local kpse_var_value = kpse.var_value
238    local path, pathtmp = kpse_var_value("TEXMFVAR")
239    pathtmp = kpse_var_value("TEXMFSYSVAR")
240    if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
241    pathtmp = kpse_var_value("TEXMFCACHE")
242    if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
243
244    if os.type~='windows' then path = string.gsub(path, ':', ';') end
245    path = table.unique(string.split(path, ';'))
246
247    local cache_dir = '/luatexja'
248    local find_file = kpse.find_file
249    local join, isreadable = file.join, file.isreadable
250    local tofile, serialize = table.tofile, table.serialize
251    local luc_suffix = jit and '.lub' or '.luc'
252    local dump = string.dump
253
254    -- determine save path
255    local savepath = ''
256    for _,v in pairs(path) do
257       local testpath =  join(v, cache_dir)
258       if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
259       if lfs.isdir(testpath) then savepath = testpath; break end
260    end
261    local serial_spec = {functions=false, noquotes=true}
262
263    local function remove_file_if_exist(name)
264      if os.rename(name,name) then os.remove(name) end
265    end
266    local function remove_cache (filename)
267       local fullpath_wo_ext = savepath .. '/' ..  filename .. '.lu'
268       remove_file_if_exist(fullpath_wo_ext .. 'a')
269       remove_file_if_exist(fullpath_wo_ext .. 'a.gz')
270       remove_file_if_exist(fullpath_wo_ext .. 'b')
271       remove_file_if_exist(fullpath_wo_ext .. 'c')
272    end
273
274    local function save_cache_luc(filename, t, serialized)
275       local fullpath = savepath .. '/' ..  filename .. luc_suffix
276       local s = serialized or serialize(t, 'return', false, serial_spec)
277       if s then
278          local sa = load(s)
279          local f = io.open(fullpath, 'wb')
280          if f and sa then
281             f:write(dump(sa, true))
282             texio.write('log', '(save cache: ' .. fullpath .. ')')
283             f:close()
284          end
285       end
286    end
287
288    local compress, decompress = gzip.compress, gzip.decompress
289    local function save_cache(filename, t)
290       local fullpath = savepath .. '/' ..  filename .. '.lua.gz'
291       local s = serialize(t, 'return', false, serial_spec)
292       if s then
293          local f = io.open(fullpath, 'wb')
294          if f then
295             f:write(compress(s, 31))
296             texio.write('log', '(save cache: ' .. fullpath .. ')')
297             f:close()
298          end
299          save_cache_luc(filename, t, s)
300       end
301    end
302
303    local function load_cache_a(filename, outdate, compressed)
304       local result
305       for _,v in pairs(path) do
306          local fn = join(v, cache_dir, filename)
307          if isreadable(fn) then
308             texio.write('log','(load cache: ' .. filename .. ')')
309             if compressed then
310               local f = io.open(fn, 'rb');  result = f:read'*a'; f:close()
311               result = loadstring(decompress(result, 31))
312             else
313               result = loadfile(fn)
314             end
315             result = result and result()
316             break
317          end
318       end
319       if (not result) or outdate(result) then
320          return nil
321       else
322          return result
323       end
324    end
325
326    local function load_cache(filename, outdate)
327       remove_file_if_exist(savepath .. '/' ..  filename .. '.lua')
328       local r = load_cache_a(filename ..  luc_suffix, outdate, false)
329       if r then
330          return r
331       else
332          local r = load_cache_a(filename .. '.lua.gz', outdate, true)
333          if r then save_cache_luc(filename, r) end -- update the precompiled cache
334          return r
335       end
336    end
337
338    ltjb.remove_cache = remove_cache
339    ltjb.load_cache = load_cache
340    ltjb.save_cache_luc = save_cache_luc
341    ltjb.save_cache = save_cache
342 end
343
344 ----
345 do
346    local tex_set_attr, tex_get_attr = tex.setattribute, tex.getattribute
347    function ltjb.ensure_tex_attr(a, v)
348       if tex_get_attr(a)~=v then
349          tex_set_attr(a, v)
350       end
351    end
352 end
353 ----
354
355 ltjb._error_set_message = _error_set_message
356 ltjb._error_show = _error_show
357 ltjb._generic_warn_info = _generic_warn_info
358
359 ltjb.package_error = package_error
360 ltjb.package_warning = package_warning
361 ltjb.package_warning_no_line = package_warning_no_line
362 ltjb.package_info = package_info
363 ltjb.package_info_no_line = package_info_no_line
364
365 ltjb.generic_error = generic_error
366 ltjb.generic_warning = generic_warning
367 ltjb.generic_warning_no_line = generic_warning_no_line
368 ltjb.generic_info = generic_info
369 ltjb.generic_info_no_line = generic_info_no_line
370
371 ltjb.ltj_warning_no_line = ltj_warning_no_line
372 ltjb.ltj_error = ltj_error
373
374 ---- deterministic version of luatexbase.add_to_callback
375 function ltjb.add_to_callback(name,fun,description,priority)
376     local priority= priority
377     if priority==nil then
378         priority=#luatexbase.callback_descriptions(name)+1
379     end
380     if(luatexbase.callbacktypes[name] == 3 and
381     priority == 1 and
382     #luatexbase.callback_descriptions(name)==1) then
383         luatexbase.module_warning("luatexbase",
384         "resetting exclusive callback: " .. name)
385         luatexbase.reset_callback(name)
386     end
387     local saved_callback={}
388     for k,v in ipairs(luatexbase.callback_descriptions(name)) do
389         if k >= priority then
390             local ff,dd = luatexbase.remove_from_callback(name, v)
391             saved_callback[#saved_callback+1]={ff,dd}
392         end
393     end
394     luatexbase.base_add_to_callback(name,fun,description)
395     for _,v in ipairs(saved_callback) do
396         luatexbase.base_add_to_callback(name,v[1],v[2])
397     end
398     return
399 end
400
401 -------------------- mock of debug logger
402 if not ltjb.out_debug then
403    local function no_op() end
404    ltjb.start_time_measure = no_op
405    ltjb.stop_time_measure = no_op
406    ltjb.out_debug = no_op
407    ltjb.package_debug = no_op
408    ltjb.debug_logger = function() return no_op end
409    ltjb.show_term = no_op
410    ltjb.show_log = no_op
411 end
412
413 -------------------- all done
414 -- EOF