OSDN Git Service

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