OSDN Git Service

5155a1d162ea8bac0cc300b7ae719f8fb6ecfcbc
[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 require('lualibs-gzip') -- gzip.*
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 function save_cache(filename, t)
289       local fullpath = savepath .. '/' ..  filename .. '.lua.gz'
290       local s = serialize(t, 'return', false, serial_spec)
291       if s then
292          gzip.save(fullpath, s, 1)
293          texio.write('log', '(save cache: ' .. fullpath .. ')')
294          save_cache_luc(filename, t, s)
295       end
296    end
297
298    local function load_cache_a(filename, outdate, compressed)
299       local result
300       for _,v in pairs(path) do
301          local fn = join(v, cache_dir, filename)
302          if isreadable(fn) then
303             texio.write('log','(load cache: ' .. filename .. ')')
304             if compressed then
305               result = loadstring(gzip.load(fn))
306             else
307               result = loadfile(fn)
308             end
309             result = result and result()
310             break
311          end
312       end
313       if (not result) or outdate(result) then 
314          return nil 
315       else 
316          return result 
317       end
318    end
319    
320    local function load_cache(filename, outdate)
321       remove_file_if_exist(savepath .. '/' ..  filename .. '.lua')
322       local r = load_cache_a(filename ..  luc_suffix, outdate, false)
323       if r then 
324          return r
325       else
326          local r = load_cache_a(filename .. '.lua.gz', outdate, true)
327          if r then save_cache_luc(filename, r) end -- update the precompiled cache
328          return r
329       end
330    end
331
332    ltjb.remove_cache = remove_cache
333    ltjb.load_cache = load_cache
334    ltjb.save_cache_luc = save_cache_luc
335    ltjb.save_cache = save_cache
336 end
337
338 ----
339 do
340    local tex_set_attr, tex_get_attr = tex.setattribute, tex.getattribute
341    function ltjb.ensure_tex_attr(a, v)
342       if tex_get_attr(a)~=v then
343          tex_set_attr(a, v)
344       end
345    end
346 end
347 ----
348
349 ltjb._error_set_break = _error_set_break
350 ltjb._error_set_message = _error_set_message
351 ltjb._error_show = _error_show
352 ltjb._generic_warn_info = _generic_warn_info
353
354 ltjb.package_error = package_error
355 ltjb.package_warning = package_warning
356 ltjb.package_warning_no_line = package_warning_no_line
357 ltjb.package_info = package_info
358 ltjb.package_info_no_line = package_info_no_line
359
360 ltjb.generic_error = generic_error
361 ltjb.generic_warning = generic_warning
362 ltjb.generic_warning_no_line = generic_warning_no_line
363 ltjb.generic_info = generic_info
364 ltjb.generic_info_no_line = generic_info_no_line
365
366 ltjb.ltj_warning_no_line = ltj_warning_no_line
367 ltjb.ltj_error = ltj_error
368
369 ---- deterministic version of luatexbase.add_to_callback
370 function ltjb.add_to_callback(name,fun,description,priority)
371     local priority= priority
372     if priority==nil then
373         priority=#luatexbase.callback_descriptions(name)+1
374     end
375     if(luatexbase.callbacktypes[name] == 3 and
376     priority == 1 and
377     #luatexbase.callback_descriptions(name)==1) then
378         luatexbase.module_warning("luatexbase",
379         "resetting exclusive callback: " .. name)
380         luatexbase.reset_callback(name)
381     end
382     local saved_callback={}
383     for k,v in ipairs(luatexbase.callback_descriptions(name)) do
384         if k >= priority then
385             local ff,dd = luatexbase.remove_from_callback(name, v)
386             saved_callback[#saved_callback+1]={ff,dd}
387         end
388     end
389     luatexbase.base_add_to_callback(name,fun,description)
390     for _,v in ipairs(saved_callback) do
391         luatexbase.base_add_to_callback(name,v[1],v[2])
392     end
393     return
394 end
395
396 -------------------- mock of debug logger
397 if not ltjb.out_debug then
398    local function no_op() end
399    ltjb.start_time_measure = no_op
400    ltjb.stop_time_measure = no_op
401    ltjb.out_debug = no_op
402    ltjb.package_debug = no_op
403    ltjb.debug_logger = function() return no_op end
404    ltjb.show_term = no_op
405    ltjb.show_log = no_op
406 end
407
408 -------------------- all done
409 -- EOF