OSDN Git Service

Commit 2f7b22e is somehow reverted.
[luatex-ja/luatexja.git] / src / ltj-base.lua
1 --
2 -- luatexja/base.lua
3 --
4 luatexbase.provides_module({
5   name = 'luatexja.base',
6   date = '2013/12/22',
7   description = '',
8 })
9 module('luatexja.base', package.seeall)
10 local err, warn, info, log = luatexbase.errwarinf(_NAME)
11
12 local ltb = luatexbase
13 local tostring = tostring
14 local node, table, tex, token = node, table, tex, token
15
16 local cat_lp = luatexbase.catcodetables['latex-package']
17
18 -------------------- 
19
20 public_name = 'luatexja'
21 public_version = 'alpha'
22
23 -------------------- Fully-expandable error messaging
24 do
25 --! LaTeX 形式のエラーメッセージ(\PackageError 等)を
26 --! Lua 関数の呼び出しで行う.
27
28   local LF = "\n"
29   local err_break = ""
30   local err_main = ""
31   local err_help = ""
32
33   local function message_cont(str, c)
34     return str:gsub(err_break, LF .. c)
35   end
36   local function into_lines(str)
37     return str:gsub(err_break, LF):explode(LF)
38   end
39
40   function _error_set_break(str)
41     err_break = str
42   end
43
44   function _error_set_message(msgcont, main, help)
45     err_main = message_cont(main, msgcont)
46     err_help = into_lines(help)
47   end
48
49   function _error_show(escchar)
50     local escapechar = tex.escapechar
51     local newlinechar = tex.newlinechar
52     local errorcontextlines = tex.errorcontextlines
53     if not escchar then tex.escapechar = -1 end
54     tex.newlinechar = 10
55     tex.errorcontextlines = -1
56     tex.error(err_main, err_help)
57     tex.escapechar = escapechar
58     tex.newlinechar = newlinechar
59     tex.errorcontextlines = errorcontextlines
60   end
61
62   local message_a = "Type  H <return>  for immediate help"
63
64   function generic_error(msgcont, main, ref, help)
65     local mainref = main..".\n\n"..ref.."\n"..message_a
66     _error_set_message(msgcont, mainref, help)
67     _error_show(true)
68   end
69
70   function _generic_warn_info(msgcont, main, warn, line)
71     local mainc = message_cont(main, msgcont)
72     local br = warn and "\n" or ""
73     local out = warn and "term and log" or "log"
74     local on_line = line and (" on input line "..tex.inputlineno) or ""
75     local newlinechar = tex.newlinechar
76     tex.newlinechar = -1
77     texio.write_nl(out, br..main..on_line.."."..br)
78     tex.newlinechar = newlinechar
79   end
80
81   function generic_warning(msgcont, main)
82     _generic_warn_info(msgcont, main, true, true)
83   end
84   function generic_warning_no_line(msgcont, main)
85     _generic_warn_info(msgcont, main, true, false)
86   end
87   function generic_info(msgcont, main)
88     _generic_warn_info(msgcont, main, false, true)
89   end
90   function generic_info_no_line(msgcont, main)
91     _generic_warn_info(msgcont, main, false, false)
92   end
93
94   function package_error(pkgname, main, help)
95     generic_error("("..pkgname.."                ",
96       "Package "..pkgname.." Error: "..main,
97       "See the "..pkgname.." package documentation for explanation.",
98       help)
99   end
100   function package_warning(pkgname, main)
101     generic_warning("("..pkgname.."                ",
102       "Package "..pkgname.." Warning: "..main)
103   end
104   function package_warning_no_line(pkgname, main)
105     generic_warning_no_line("("..pkgname.."                ",
106       "Package "..pkgname.." Warning: "..main)
107   end
108   function package_info(pkgname, main)
109     generic_info("("..pkgname.."             ",
110       "Package "..pkgname.." Info: "..main)
111   end
112   function package_info_no_line(pkgname, main)
113     generic_info_no_line("("..pkgname.."             ",
114       "Package "..pkgname.." Info: "..main)
115   end
116
117   function ltj_error(main, help)
118     package_error(public_name, main, help)
119   end
120   function ltj_warning_no_line(main)
121     package_warning_no_line(public_name, main, help)
122   end
123
124 end
125 -------------------- TeX stream I/O
126 do
127
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   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
152 end
153 -------------------- Handling of TeX values
154 do
155
156   local glue_spec_id = node.id("glue_spec")
157
158   local function copy_skip(s1, s2)
159     if not s1 then
160       s1 = node.new(glue_spec_id)
161     end
162     s1.width = s2.width or 0
163     s1.stretch = s2.stretch or 0
164     s1.stretch_order = s2.stretch_order or 0
165     s1.shrink = s2.shrink or 0
166     s1.shrink_order = s2.shrink_order or 0
167     return s1
168   end
169
170 --! ixbase.to_dimen() と同じ
171   function to_dimen(val)
172     if val == nil then
173       return 0
174     elseif type(val) == "number" then
175       return val
176     else
177       return tex.sp(tostring(val))
178     end
179   end
180
181   local function parse_dimen(val)
182     val = tostring(val):lower()
183     local r, fil = val:match("([-.%d]+)fi(l*)")
184     if r then
185       val, fil = r.."pt", fil:len() + 1
186     else
187       fil = 0
188     end
189     return tex.sp(val), fil
190   end
191
192 --! ixbase.to_skip() と同じ
193   function to_skip(val)
194     if type(val) == "userdata" then
195       return val
196     end
197     local res = node.new(glue_spec_id)
198     if val == nil then
199       res.width = 0
200     elseif type(val) == "number" then
201       res.width = val
202     elseif type(val) == "table" then
203       copy_skip(res, val)
204     else
205       local t = tostring(val):lower():explode()
206       local w, p, m = t[1], t[3], t[5]
207       if t[2] == "minus" then
208         p, m = nil, t[3]
209       end
210       res.width = tex.sp(t[1])
211       if t[3] then
212         res.stretch, res.stretch_order = parse_dimen(t[3])
213       end
214       if t[5] then
215         res.shrink, res.shrink_order = parse_dimen(t[5])
216       end
217     end
218     return res
219   end
220
221   function dump_skip(s)
222     print(("%s+%s<%s>-%s<%s>"):format(
223       s.width or 0, s.stretch or 0, s.stretch_order or 0,
224       s.shrink or 0, s.shrink_order or 0))
225   end
226
227 end
228 -------------------- Virtual table for LaTeX counters
229 do
230
231 --! ixbase.counter と同じ
232   counter = {}
233   local mt_counter = {}
234   setmetatable(counter, mt_counter)
235
236   function mt_counter.__index(tbl, key)
237     return tex.count['c@'..key]
238   end
239   function mt_counter.__newindex(tbl, key, val)
240     tex.count['c@'..key] = val
241   end
242
243 --! ixbase.length は tex.skip と全く同じなので不要.
244
245 end
246 -------------------- Number handling in TeX source
247 do
248
249   local tok_escape = token.create("ltj@@q@escape")
250   local tok_num = token.create("ltj@@q@escapenum")
251   local c_id_assign_int = token.command_id("assign_int")
252   local c_id_char_given = token.command_id("char_given")
253
254   local function error_scan()
255     _M.package_error("luatexja",
256       "Missing number of a permitted form, treated as zero",
257       "A number should have been here; I inserted '0'.")
258   end
259
260   local function get_expd_next()
261     local next = token.get_next()
262     while token.is_expandable(next) do
263       token.expand(next)
264       next = token.get_next()
265     end
266     return next
267   end
268
269   local function grab_decimal(next, res)
270     table.insert(res, next)
271     while true do
272       next = get_expd_next()
273       if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
274         break
275       end
276       table.insert(res, next)
277     end
278     if next[1] == 10 then next = nil end
279     return true, next
280   end
281
282   local function grab_hexa(next, res)
283     local ok = false
284     table.insert(res, next)
285     while true do
286       next = get_expd_next()
287       if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
288               ((next[1] == 12 or next[1] == 11) and
289                (0x41 <= next[2] and next[2] <= 0x46))) then
290         break
291       end
292       ok = true
293       table.insert(res, next)
294     end
295     if next[1] == 10 then next = nil end
296     return ok, next
297   end
298
299   local function grab_octal(next, res)
300     local ok = false
301     table.insert(res, next)
302     while true do
303       next = get_expd_next()
304       if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
305         break
306       end
307       ok = true
308       table.insert(res, next)
309     end
310     if next[1] == 10 then next = nil end
311     return ok, next
312   end
313
314   local function grab_charnum(next, res)
315     table.insert(res, next)
316     next = token.get_next()
317     table.insert(res, next)
318     next = get_expd_next()
319     if next[1] == 10 then next = nil end
320     return true, next
321   end
322
323   local function scan_with(delay, scanner)
324     local function proc()
325       if delay ~= 0 then
326         if delay > 0 then delay = delay - 1 end
327         return token.get_next()
328       else
329         local cont, back = scanner()
330         if not cont then
331           ltb.remove_from_callback("token_filter", "ltj@grab@num")
332         end
333         return back
334       end
335     end
336     ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
337   end
338
339   function scan_brace()
340     scan_with(1, function()
341       local next = token.get_next()
342       if next[1] == 1 then
343         return false, { tok_escape, next }
344       elseif next[1] == 10 then
345         return true, { next }
346       else
347         return false, { next }
348       end
349     end)
350   end
351
352   function scan_number()
353     scan_with(1, function()
354       local next = get_expd_next()
355       local res, ok = { tok_num }, false
356       while true do
357         if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
358           table.insert(res, next)
359         elseif next[1] ~= 10 then
360           break
361         end
362         next = get_expd_next()
363       end
364       if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
365         ok, next = grab_decimal(next, res)
366       elseif next[1] == 12 and next[2] == 0x22 then
367         ok, next = grab_hexa(next, res)
368       elseif next[1] == 12 and next[2] == 0x27 then
369         ok, next = grab_octal(next, res)
370       elseif next[1] == 12 and next[2] == 0x60 then
371         ok, next = grab_charnum(next, res)
372       elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
373         table.insert(res, next)
374         ok, next = true, nil
375       end
376       if ok then
377          table.insert(res, tok_num)
378       else
379          error_scan()
380          res = { tok_escape }
381       end
382        if next then table.insert(res, next) end
383        return false, res
384     end)
385   end
386
387 end
388 -------------------- TeX register allocation
389 do
390   local cmod_base_count = token.create('ltj@@count@zero')[2]
391   local cmod_base_attr = token.create('ltj@@attr@zero')[2]
392   local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
393   local cmod_base_skip = token.create('ltj@@skip@zero')[2]
394
395   function const_number(name)
396     if name:sub(1, 1) == '\\' then name = name:sub(2) end
397     return token.create(name)[2]
398   end
399
400   function count_number(name)
401     if name:sub(1, 1) == '\\' then name = name:sub(2) end
402     return token.create(name)[2] - cmod_base_count
403   end
404
405   function attribute_number(name)
406     if name:sub(1, 1) == '\\' then name = name:sub(2) end
407     return token.create(name)[2] - cmod_base_attr
408   end
409
410   function dimen_number(name)
411     if name:sub(1, 1) == '\\' then name = name:sub(2) end
412     return token.create(name)[2] - cmod_base_dimen
413   end
414
415   function skip_number(name)
416     if name:sub(1, 1) == '\\' then name = name:sub(2) end
417     return token.create(name)[2] - cmod_base_skip
418   end
419
420 end
421 -------------------- mock of debug logger
422
423 if not _M.debug or _M.debug == _G.debug then
424   local function no_op() end
425   debug = no_op
426   package_debug = no_op
427   show_term = no_op
428   show_log = no_op
429   function debug_logger()
430     return no_op
431   end
432 end
433
434 -------------------- getting next token
435 cstemp = nil
436 function get_cs(s)
437    cstemp = token.csname_name(token.get_next())
438    tex.sprint(cat_lp,'\\' .. s)
439 end
440
441
442 -------------------- cache management
443 -- load_cache (filename, outdate)
444 --   * filename: without suffix '.lua'
445 --   * outdate(t): return true iff the cache is outdated
446 --   * return value: non-nil iff the cache is up-to-date
447 -- save_cache (filename, t): no return value
448 -- save_cache_luc (filename, t): no return value
449 --   save_cache always calls save_cache_luc. 
450 --   But sometimes we want to create only the precompiled cache,
451 --   when its 'text' version is already present in LuaTeX-ja distribution.
452
453 require('lualibs-lpeg') -- string.split
454 require('lualibs-os')   -- os.type
455 do
456    local path = kpse.expand_var("$TEXMFVAR;$TEXMFSYSVAR;$TEXMFCACHE")
457    if os.type~='windows' then path = string.gsub(path, ':', ';') end
458    path = string.split(path, ';')
459
460    local cache_dir = '/luatexja'
461    local find_file = kpse.find_file
462    local join, isreadable = file.join, file.isreadable
463    local tofile, serialize = table.tofile, table.serialize
464    local luc_suffix = jit and '.lub' or '.luc'
465
466    -- determine save path
467    local savepath = ''
468    for _,v in pairs(path) do
469       local testpath =  join(v, cache_dir)
470       if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
471       if lfs.isdir(testpath) then savepath = testpath; break end
472    end
473
474    function save_cache_luc(filename, t)
475       local fullpath = savepath .. '/' ..  filename .. luc_suffix
476       local s = serialize(t, 'return', false)
477       if s then
478          local sa = load(s)
479          local f = io.open(fullpath, 'wb')
480          if f and sa then 
481             f:write(string.dump(sa, true)) 
482             texio.write('(save cache: ' .. fullpath .. ')')
483          end
484          f:close()
485       end
486    end
487
488    function save_cache(filename, t)
489       local fullpath = savepath .. '/' ..  filename .. '.lua'
490       tofile(fullpath, t, 'return', false)
491       texio.write('(save cache: ' .. fullpath .. ')')
492       save_cache_luc(filename, t)
493    end
494
495    local function luc_load (n)
496       texio.write('(load cache: ' .. n .. ')')
497       local f = loadfile(n, 'b'); return f
498    end
499    local function load_cache_a (filename, outdate, loader)
500       local result
501       for _,v in pairs(path) do
502          local fn = join(v, cache_dir, filename)
503          if isreadable(fn) then 
504             result = loader(fn)
505             if result then result = result(); break end
506          end
507       end
508       if (not result) or outdate(result) then 
509          return nil 
510       else 
511          return result 
512       end
513    end
514    function load_cache (filename, outdate)
515       local r = load_cache_a(filename ..  luc_suffix, outdate, luc_load)
516       if r then 
517          return r
518       else
519          local r = load_cache_a(filename .. '.lua', outdate, loadfile)
520          if r then save_cache_luc(filename, r) end -- update the precompiled cache
521          return r
522       end
523    end
524
525 end
526
527 -------------------- all done
528 -- EOF