OSDN Git Service

Fix lltjext.sty
[luatex-ja/luatexja.git] / src / ltj-base.lua
1 --
2 -- luatexja/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   local glue_spec_id = node.id("glue_spec")
160
161   local function copy_skip(s1, s2)
162     if not s1 then
163       s1 = node.new(glue_spec_id)
164     end
165     s1.width = s2.width or 0
166     s1.stretch = s2.stretch or 0
167     s1.stretch_order = s2.stretch_order or 0
168     s1.shrink = s2.shrink or 0
169     s1.shrink_order = s2.shrink_order or 0
170     return s1
171   end
172
173 --! ixbase.to_dimen() と同じ
174   local function to_dimen(val)
175     if val == nil then
176       return 0
177     elseif type(val) == "number" then
178       return val
179     else
180       return tex.sp(tostring(val))
181     end
182   end
183
184   local function parse_dimen(val)
185     val = tostring(val):lower()
186     local r, fil = val:match("([-.%d]+)fi(l*)")
187     if r then
188       val, fil = r.."pt", fil:len() + 1
189     else
190       fil = 0
191     end
192     return tex.sp(val), fil
193   end
194
195 --! ixbase.to_skip() と同じ
196   local function to_skip(val)
197     if type(val) == "userdata" then
198       return val
199     end
200     local res = node.new(glue_spec_id)
201     if val == nil then
202       res.width = 0
203     elseif type(val) == "number" then
204       res.width = val
205     elseif type(val) == "table" then
206       copy_skip(res, val)
207     else
208       local t = tostring(val):lower():explode()
209       local w, p, m = t[1], t[3], t[5]
210       if t[2] == "minus" then
211         p, m = nil, t[3]
212       end
213       res.width = tex.sp(t[1])
214       if p then
215         res.stretch, res.stretch_order = parse_dimen(p)
216       end
217       if m then
218         res.shrink, res.shrink_order = parse_dimen(m)
219       end
220     end
221     return res
222   end
223
224   local function dump_skip(s)
225     print(("%s+%s<%s>-%s<%s>"):format(
226       s.width or 0, s.stretch or 0, s.stretch_order or 0,
227       s.shrink or 0, s.shrink_order or 0))
228   end
229
230   ltjb.to_dimen = to_dimen
231   ltjb.dump_skip = dump_skip
232   ltjb.to_skip = to_skip
233 end
234
235 -------------------- Virtual table for LaTeX counters
236 -- not used in current LuaTeX-ja
237 do
238 --! ixbase.counter と同じ
239   counter = {}
240   local mt_counter = {}
241   setmetatable(counter, mt_counter)
242
243   function mt_counter.__index(tbl, key)
244     return tex.count['c@'..key]
245   end
246   function mt_counter.__newindex(tbl, key, val)
247     tex.count['c@'..key] = val
248   end
249   ltjb.counter = counter
250
251 --! ixbase.length は tex.skip と全く同じなので不要.
252 end
253
254 -------------------- Number handling in TeX source
255 do
256
257   local tok_escape = token.create("ltj@@q@escape")
258   local tok_num = token.create("ltj@@q@escapenum")
259   local c_id_assign_int = token.command_id("assign_int")
260   local c_id_char_given = token.command_id("char_given")
261
262   local function error_scan()
263     package_error("luatexja",
264       "Missing number of a permitted form, treated as zero",
265       "A number should have been here; I inserted '0'.")
266   end
267
268   local function get_expd_next()
269     local next = token.get_next()
270     while token.is_expandable(next) do
271       token.expand(next)
272       next = token.get_next()
273     end
274     return next
275   end
276
277   local function grab_decimal(next, res)
278     table.insert(res, next)
279     while true do
280       next = get_expd_next()
281       if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
282         break
283       end
284       table.insert(res, next)
285     end
286     if next[1] == 10 then next = nil end
287     return true, next
288   end
289
290   local function grab_hexa(next, res)
291     local ok = false
292     table.insert(res, next)
293     while true do
294       next = get_expd_next()
295       if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
296               ((next[1] == 12 or next[1] == 11) and
297                (0x41 <= next[2] and next[2] <= 0x46))) then
298         break
299       end
300       ok = true
301       table.insert(res, next)
302     end
303     if next[1] == 10 then next = nil end
304     return ok, next
305   end
306
307   local function grab_octal(next, res)
308     local ok = false
309     table.insert(res, next)
310     while true do
311       next = get_expd_next()
312       if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
313         break
314       end
315       ok = true
316       table.insert(res, next)
317     end
318     if next[1] == 10 then next = nil end
319     return ok, next
320   end
321
322   local function grab_charnum(next, res)
323     table.insert(res, next)
324     next = token.get_next()
325     table.insert(res, next)
326     next = get_expd_next()
327     if next[1] == 10 then next = nil end
328     return true, next
329   end
330
331   local function scan_with(delay, scanner)
332     local function proc()
333       if delay ~= 0 then
334         if delay > 0 then delay = delay - 1 end
335         return token.get_next()
336       else
337         local cont, back = scanner()
338         if not cont then
339           ltb.remove_from_callback("token_filter", "ltj@grab@num")
340         end
341         return back
342       end
343     end
344     ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
345   end
346
347   local function scan_brace()
348     scan_with(1, function()
349       local next = token.get_next()
350       if next[1] == 1 then
351         return false, { tok_escape, next }
352       elseif next[1] == 10 then
353         return true, { next }
354       else
355         return false, { next }
356       end
357     end)
358   end
359
360   local function scan_number()
361     scan_with(1, function()
362       local next = get_expd_next()
363       local res, ok = { tok_num }, false
364       while true do
365         if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
366           table.insert(res, next)
367         elseif next[1] ~= 10 then
368           break
369         end
370         next = get_expd_next()
371       end
372       if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
373         ok, next = grab_decimal(next, res)
374       elseif next[1] == 12 and next[2] == 0x22 then
375         ok, next = grab_hexa(next, res)
376       elseif next[1] == 12 and next[2] == 0x27 then
377         ok, next = grab_octal(next, res)
378       elseif next[1] == 12 and next[2] == 0x60 then
379         ok, next = grab_charnum(next, res)
380       elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
381         table.insert(res, next)
382         ok, next = true, nil
383       end
384       if ok then
385          table.insert(res, tok_num)
386       else
387          error_scan()
388          res = { tok_escape }
389       end
390        if next then table.insert(res, next) end
391        return false, res
392     end)
393   end
394
395   ltjb.scan_brace = scan_brace
396   ltjb.scan_number = scan_number
397 end
398
399 -------------------- TeX register allocation
400 -- not used in current LuaTeX-ja
401
402 do
403   local cmod_base_count = token.create('ltj@@count@zero')[2]
404   local cmod_base_attr = token.create('ltj@@attr@zero')[2]
405   local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
406   local cmod_base_skip = token.create('ltj@@skip@zero')[2]
407
408   local function const_number(name)
409     if name:sub(1, 1) == '\\' then name = name:sub(2) end
410     return token.create(name)[2]
411   end
412
413   local function count_number(name)
414     if name:sub(1, 1) == '\\' then name = name:sub(2) end
415     return token.create(name)[2] - cmod_base_count
416   end
417
418   local function attribute_number(name)
419     if name:sub(1, 1) == '\\' then name = name:sub(2) end
420     return token.create(name)[2] - cmod_base_attr
421   end
422
423   local function dimen_number(name)
424     if name:sub(1, 1) == '\\' then name = name:sub(2) end
425     return token.create(name)[2] - cmod_base_dimen
426   end
427
428   local function skip_number(name)
429     if name:sub(1, 1) == '\\' then name = name:sub(2) end
430     return token.create(name)[2] - cmod_base_skip
431   end
432
433   ltjb.const_number = const_number
434   ltjb.count_number = count_number
435   ltjb.attribute_number = attribute_number
436   ltjb.dimen_number = dimen_number
437   ltjb.skip_number = skip_number
438 end
439
440 -------------------- getting next token
441 local cstemp = nil
442 local function get_cs(s)
443    cstemp = token.csname_name(token.get_next())
444    tex.sprint(cat_lp,'\\' .. s)
445 end
446 ltjb.get_cs = get_cs
447
448 -------------------- common error message
449 do
450    local function in_unicode(c, admit_math)
451       local low = admit_math and -1 or 0
452       if type(c)~='number' or c<low or c>0x10FFFF then
453          local s = 'A character number must be between ' .. tostring(low) 
454             .. ' and 0x10ffff.\n'
455             .. (admit_math and "(-1 is used for denoting `math boundary')\n" or '')
456             .. 'So I changed this one to zero.'
457          package_error('luatexja',
458                             'bad character code (' .. tostring(c) .. ')', s)
459          c=0
460       end
461       return c
462    end
463    ltjb.in_unicode = in_unicode
464 end
465
466 -------------------- cache management
467 -- load_cache (filename, outdate)
468 --   * filename: without suffix '.lua'
469 --   * outdate(t): return true iff the cache is outdated
470 --   * return value: non-nil iff the cache is up-to-date
471 -- save_cache (filename, t): no return value
472 -- save_cache_luc (filename, t): no return value
473 --   save_cache always calls save_cache_luc. 
474 --   But sometimes we want to create only the precompiled cache,
475 --   when its 'text' version is already present in LuaTeX-ja distribution.
476
477 require('lualibs-lpeg') -- string.split
478 require('lualibs-os')   -- os.type
479
480 do
481    local kpse_var_value = kpse.var_value
482    local path, pathtmp = kpse_var_value("TEXMFVAR")
483    pathtmp = kpse_var_value("TEXMFSYSVAR")
484    if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
485    pathtmp = kpse_var_value("TEXMFCACHE")
486    if pathtmp then path = (path and path .. ';' or '') .. pathtmp end
487
488    if os.type~='windows' then path = string.gsub(path, ':', ';') end
489    path = table.unique(string.split(path, ';'))
490
491    local cache_dir = '/luatexja'
492    local find_file = kpse.find_file
493    local join, isreadable = file.join, file.isreadable
494    local tofile, serialize = table.tofile, table.serialize
495    local luc_suffix = jit and '.lub' or '.luc'
496
497    -- determine save path
498    local savepath = ''
499    for _,v in pairs(path) do
500       local testpath =  join(v, cache_dir)
501       if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
502       if lfs.isdir(testpath) then savepath = testpath; break end
503    end
504
505    save_cache_luc = function (filename, t, serialized)
506       local fullpath = savepath .. '/' ..  filename .. luc_suffix
507       local s = serialized or serialize(t, 'return', false)
508       if s then
509          local sa = load(s)
510          local f = io.open(fullpath, 'wb')
511          if f and sa then 
512             f:write(string.dump(sa, true)) 
513             texio.write('(save cache: ' .. fullpath .. ')')
514          end
515          f:close()
516       end
517    end
518
519    save_cache = function (filename, t)
520       local fullpath = savepath .. '/' ..  filename .. '.lua'
521       local s = serialize(t, 'return', false)
522       if s then
523          local f = io.open(fullpath, 'w')
524          if f then 
525             f:write(s) 
526             texio.write('(save cache: ' .. fullpath .. ')')
527          end
528          f:close()
529          save_cache_luc(filename, t, s)
530       end
531    end
532
533    local function load_cache_a (filename, outdate)
534       local result
535       for _,v in pairs(path) do
536          local fn = join(v, cache_dir, filename)
537          if isreadable(fn) then 
538             texio.write('(load cache: ' .. fn .. ')')
539             result = loadfile(fn)
540             result = result and result(); break
541          end
542       end
543       if (not result) or outdate(result) then 
544          return nil 
545       else 
546          return result 
547       end
548    end
549    
550    load_cache = function (filename, outdate)
551       local r = load_cache_a(filename ..  luc_suffix, outdate)
552       if r then 
553          return r
554       else
555          local r = load_cache_a(filename .. '.lua', outdate)
556          if r then save_cache_luc(filename, r) end -- update the precompiled cache
557          return r
558       end
559    end
560
561    ltjb.load_cache = load_cache
562    ltjb.save_cache_luc = save_cache_luc
563    ltjb.save_cache = save_cache
564 end
565
566 ----
567 do
568    local tex_set_attr, tex_get_attr = tex.setattribute, tex.getattribute
569    function ltjb.ensure_tex_attr(a, v)
570       if tex_get_attr(a)~=v then
571          tex_set_attr(a, v)
572       end
573    end
574 end
575 ----
576
577 ltjb._error_set_break = _error_set_break
578 ltjb._error_set_message = _error_set_message
579 ltjb._error_show = _error_show
580 ltjb._generic_warn_info = _generic_warn_info
581
582 ltjb.package_error = package_error
583 ltjb.package_warning = package_warning
584 ltjb.package_warning_no_line = package_warning_no_line
585 ltjb.package_info = package_info
586 ltjb.package_info_no_line = package_info_no_line
587
588 ltjb.generic_error = generic_error
589 ltjb.generic_warning = generic_warning
590 ltjb.generic_warning_no_line = generic_warning_no_line
591 ltjb.generic_info = generic_info
592 ltjb.generic_info_no_line = generic_info_no_line
593
594 ltjb.ltj_warning_no_line = ltj_warning_no_line
595 ltjb.ltj_error = ltj_error
596
597 -------------------- mock of debug logger
598 if not ltjb.out_debug then
599    local function no_op() end
600    ltjb.start_time_measure = no_op
601    ltjb.stop_time_measure = no_op
602    ltjb.out_debug = no_op
603    ltjb.package_debug = no_op
604    ltjb.debug_logger = function() return no_op end
605    ltjb.show_term = no_op
606    ltjb.show_log = no_op
607 end
608
609 -------------------- all done
610 -- EOF