OSDN Git Service

Merge branch 'master' into kitagawa_test
[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 require('lualibs-lpeg') -- string.split
230 require('lualibs-os')   -- os.type
231 require('lualibs-gzip') -- gzip.*
232
233 do
234    local kpse_var_value = kpse.var_value
235    local path, pathtmp = kpse_var_value("TEXMFVAR")
236    pathtmp = kpse_var_value("TEXMFSYSVAR")
237    if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
238    pathtmp = kpse_var_value("TEXMFCACHE")
239    if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
240
241    if os.type~='windows' then path = string.gsub(path, ':', ';') end
242    path = table.unique(string.split(path, ';'))
243
244    local cache_dir = '/luatexja'
245    local find_file = kpse.find_file
246    local join, isreadable = file.join, file.isreadable
247    local tofile, serialize = table.tofile, table.serialize
248    local luc_suffix = jit and '.lub' or '.luc'
249    local dump = string.dump
250
251    -- determine save path
252    local savepath = ''
253    for _,v in pairs(path) do
254       local testpath =  join(v, cache_dir)
255       if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
256       if lfs.isdir(testpath) then savepath = testpath; break end
257    end
258    local serial_spec = {functions=false, noquotes=true}
259
260    local function remove_file_if_exist(name)
261      if os.rename(name,name) then os.remove(name) end
262    end
263    local function remove_cache (filename)
264       local fullpath_wo_ext = savepath .. '/' ..  filename .. '.lu'
265       remove_file_if_exist(fullpath_wo_ext .. 'a')
266       remove_file_if_exist(fullpath_wo_ext .. 'a.gz')
267       remove_file_if_exist(fullpath_wo_ext .. 'b')
268       remove_file_if_exist(fullpath_wo_ext .. 'c')
269    end
270
271    local function save_cache_luc(filename, t, serialized)
272       local fullpath = savepath .. '/' ..  filename .. luc_suffix
273       local s = serialized or serialize(t, 'return', false, serial_spec)
274       if s then
275          local sa = load(s)
276          local f = io.open(fullpath, 'wb')
277          if f and sa then 
278             f:write(dump(sa, true)) 
279             texio.write('log', '(save cache: ' .. fullpath .. ')')
280             f:close()
281          end
282       end
283    end
284
285    local function save_cache(filename, t)
286       local fullpath = savepath .. '/' ..  filename .. '.lua.gz'
287       local s = serialize(t, 'return', false, serial_spec)
288       if s then
289          gzip.save(fullpath, s, 1)
290          texio.write('log', '(save cache: ' .. fullpath .. ')')
291          save_cache_luc(filename, t, s)
292       end
293    end
294
295    local function load_cache_a(filename, outdate, compressed)
296       local result
297       for _,v in pairs(path) do
298          local fn = join(v, cache_dir, filename)
299          if isreadable(fn) then
300             texio.write('log','(load cache: ' .. filename .. ')')
301             if compressed then
302               result = loadstring(gzip.load(fn))
303             else
304               result = loadfile(fn)
305             end
306             result = result and result()
307             break
308          end
309       end
310       if (not result) or outdate(result) then 
311          return nil 
312       else 
313          return result 
314       end
315    end
316    
317    local function load_cache(filename, outdate)
318       remove_file_if_exist(savepath .. '/' ..  filename .. '.lua')
319       local r = load_cache_a(filename ..  luc_suffix, outdate, false)
320       if r then 
321          return r
322       else
323          local r = load_cache_a(filename .. '.lua.gz', outdate, true)
324          if r then save_cache_luc(filename, r) end -- update the precompiled cache
325          return r
326       end
327    end
328
329    ltjb.remove_cache = remove_cache
330    ltjb.load_cache = load_cache
331    ltjb.save_cache_luc = save_cache_luc
332    ltjb.save_cache = save_cache
333 end
334
335 ----
336 do
337    local tex_set_attr, tex_get_attr = tex.setattribute, tex.getattribute
338    function ltjb.ensure_tex_attr(a, v)
339       if tex_get_attr(a)~=v then
340          tex_set_attr(a, v)
341       end
342    end
343 end
344 ----
345
346 ltjb._error_set_message = _error_set_message
347 ltjb._error_show = _error_show
348 ltjb._generic_warn_info = _generic_warn_info
349
350 ltjb.package_error = package_error
351 ltjb.package_warning = package_warning
352 ltjb.package_warning_no_line = package_warning_no_line
353 ltjb.package_info = package_info
354 ltjb.package_info_no_line = package_info_no_line
355
356 ltjb.generic_error = generic_error
357 ltjb.generic_warning = generic_warning
358 ltjb.generic_warning_no_line = generic_warning_no_line
359 ltjb.generic_info = generic_info
360 ltjb.generic_info_no_line = generic_info_no_line
361
362 ltjb.ltj_warning_no_line = ltj_warning_no_line
363 ltjb.ltj_error = ltj_error
364
365 ---- deterministic version of luatexbase.add_to_callback
366 function ltjb.add_to_callback(name,fun,description,priority)
367     local priority= priority
368     if priority==nil then
369         priority=#luatexbase.callback_descriptions(name)+1
370     end
371     if(luatexbase.callbacktypes[name] == 3 and
372     priority == 1 and
373     #luatexbase.callback_descriptions(name)==1) then
374         luatexbase.module_warning("luatexbase",
375         "resetting exclusive callback: " .. name)
376         luatexbase.reset_callback(name)
377     end
378     local saved_callback={}
379     for k,v in ipairs(luatexbase.callback_descriptions(name)) do
380         if k >= priority then
381             local ff,dd = luatexbase.remove_from_callback(name, v)
382             saved_callback[#saved_callback+1]={ff,dd}
383         end
384     end
385     luatexbase.base_add_to_callback(name,fun,description)
386     for _,v in ipairs(saved_callback) do
387         luatexbase.base_add_to_callback(name,v[1],v[2])
388     end
389     return
390 end
391
392 -------------------- mock of debug logger
393 if not ltjb.out_debug then
394    local function no_op() end
395    ltjb.start_time_measure = no_op
396    ltjb.stop_time_measure = no_op
397    ltjb.out_debug = no_op
398    ltjb.package_debug = no_op
399    ltjb.debug_logger = function() return no_op end
400    ltjb.show_term = no_op
401    ltjb.show_log = no_op
402 end
403
404 -------------------- all done
405 -- EOF