OSDN Git Service

test/mfonttest.tex: reconstruction:
[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
12 local public_name = 'luatexja'
13 local public_version = 'alpha'
14
15 -------------------- Fully-expandable error messaging
16 local _error_set_break, _error_set_message, _error_show
17 local generic_error, _generic_warn_info
18 local generic_warning, generic_warning_no_line
19 local generic_info, generic_info_no_line
20 local package_error, package_warning, package_warning_no_line
21 local package_info, package_info_no_line
22 local ltj_error, ltj_warning_no_line
23
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   _error_set_break = function (str)
41     err_break = str
42   end
43
44   _error_set_message = function (msgcont, main, help)
45     err_main = message_cont(main, msgcont)
46     err_help = into_lines(help)
47   end
48
49   _error_show = function (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   generic_error = function (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   _generic_warn_info = function (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   generic_warning = function (msgcont, main)
82     _generic_warn_info(msgcont, main, true, true)
83   end
84   generic_warning_no_line = function (msgcont, main)
85     _generic_warn_info(msgcont, main, true, false)
86   end
87   generic_info = function (msgcont, main)
88     _generic_warn_info(msgcont, main, false, true)
89   end
90   generic_info_no_line = function (msgcont, main)
91     _generic_warn_info(msgcont, main, false, false)
92   end
93
94   package_error = function (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   package_warning = function (pkgname, main)
101     generic_warning("("..pkgname.."                ",
102       "Package "..pkgname.." Warning: "..main)
103   end
104   package_warning_no_line = function (pkgname, main)
105     generic_warning_no_line("("..pkgname.."                ",
106       "Package "..pkgname.." Warning: "..main)
107   end
108   package_info = function (pkgname, main)
109     generic_info("("..pkgname.."             ",
110       "Package "..pkgname.." Info: "..main)
111   end
112   package_info_no_line = function (pkgname, main)
113     generic_info_no_line("("..pkgname.."             ",
114       "Package "..pkgname.." Info: "..main)
115   end
116
117   ltj_error = function (main, help)
118     package_error(public_name, main, help)
119   end
120   ltj_warning_no_line = function (main)
121     package_warning_no_line(public_name, main, help)
122   end
123
124 end
125 -------------------- TeX stream I/O
126 --! ixbase.print() と同じ
127 --- Extension to tex.print(). Each argument string may contain
128 -- newline characters, in which case the string is output (to
129 -- TeX input stream) as multiple lines.
130 -- @param ... (string) string to output 
131 local function mprint(...)
132    local arg = {...}
133    local lines = {}
134    if type(arg[1]) == "number" then
135       table.insert(lines, arg[1])
136       table.remove(arg, 1)
137    end
138    for _, cnk in ipairs(arg) do
139       local ls = cnk:explode("\n")
140       if ls[#ls] == "" then
141          table.remove(ls, #ls)
142       end
143       for _, l in ipairs(ls) do
144          table.insert(lines, l)
145       end
146    end
147    return tex.print(unpack(lines))
148 end
149
150 -------------------- Handling of TeX values
151 local to_dimen, to_skip, dump_skip 
152
153 do
154
155   local glue_spec_id = node.id("glue_spec")
156
157   local function copy_skip(s1, s2)
158     if not s1 then
159       s1 = node.new(glue_spec_id)
160     end
161     s1.width = s2.width or 0
162     s1.stretch = s2.stretch or 0
163     s1.stretch_order = s2.stretch_order or 0
164     s1.shrink = s2.shrink or 0
165     s1.shrink_order = s2.shrink_order or 0
166     return s1
167   end
168
169 --! ixbase.to_dimen() と同じ
170   to_dimen = function (val)
171     if val == nil then
172       return 0
173     elseif type(val) == "number" then
174       return val
175     else
176       return tex.sp(tostring(val))
177     end
178   end
179
180   local function parse_dimen(val)
181     val = tostring(val):lower()
182     local r, fil = val:match("([-.%d]+)fi(l*)")
183     if r then
184       val, fil = r.."pt", fil:len() + 1
185     else
186       fil = 0
187     end
188     return tex.sp(val), fil
189   end
190
191 --! ixbase.to_skip() と同じ
192   to_skip = function (val)
193     if type(val) == "userdata" then
194       return val
195     end
196     local res = node.new(glue_spec_id)
197     if val == nil then
198       res.width = 0
199     elseif type(val) == "number" then
200       res.width = val
201     elseif type(val) == "table" then
202       copy_skip(res, val)
203     else
204       local t = tostring(val):lower():explode()
205       local w, p, m = t[1], t[3], t[5]
206       if t[2] == "minus" then
207         p, m = nil, t[3]
208       end
209       res.width = tex.sp(t[1])
210       if t[3] then
211         res.stretch, res.stretch_order = parse_dimen(t[3])
212       end
213       if t[5] then
214         res.shrink, res.shrink_order = parse_dimen(t[5])
215       end
216     end
217     return res
218   end
219
220   dump_skip = function (s)
221     print(("%s+%s<%s>-%s<%s>"):format(
222       s.width or 0, s.stretch or 0, s.stretch_order or 0,
223       s.shrink or 0, s.shrink_order or 0))
224   end
225
226 end
227 -------------------- Virtual table for LaTeX counters
228 local counter
229
230 do
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 end
245
246 -------------------- Number handling in TeX source
247 local scan_brace, scan_number 
248
249 do
250
251   local tok_escape = token.create("ltj@@q@escape")
252   local tok_num = token.create("ltj@@q@escapenum")
253   local c_id_assign_int = token.command_id("assign_int")
254   local c_id_char_given = token.command_id("char_given")
255
256   local function error_scan()
257     package_error("luatexja",
258       "Missing number of a permitted form, treated as zero",
259       "A number should have been here; I inserted '0'.")
260   end
261
262   local function get_expd_next()
263     local next = token.get_next()
264     while token.is_expandable(next) do
265       token.expand(next)
266       next = token.get_next()
267     end
268     return next
269   end
270
271   local function grab_decimal(next, res)
272     table.insert(res, next)
273     while true do
274       next = get_expd_next()
275       if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
276         break
277       end
278       table.insert(res, next)
279     end
280     if next[1] == 10 then next = nil end
281     return true, next
282   end
283
284   local function grab_hexa(next, res)
285     local ok = false
286     table.insert(res, next)
287     while true do
288       next = get_expd_next()
289       if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
290               ((next[1] == 12 or next[1] == 11) and
291                (0x41 <= next[2] and next[2] <= 0x46))) then
292         break
293       end
294       ok = true
295       table.insert(res, next)
296     end
297     if next[1] == 10 then next = nil end
298     return ok, next
299   end
300
301   local function grab_octal(next, res)
302     local ok = false
303     table.insert(res, next)
304     while true do
305       next = get_expd_next()
306       if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
307         break
308       end
309       ok = true
310       table.insert(res, next)
311     end
312     if next[1] == 10 then next = nil end
313     return ok, next
314   end
315
316   local function grab_charnum(next, res)
317     table.insert(res, next)
318     next = token.get_next()
319     table.insert(res, next)
320     next = get_expd_next()
321     if next[1] == 10 then next = nil end
322     return true, next
323   end
324
325   local function scan_with(delay, scanner)
326     local function proc()
327       if delay ~= 0 then
328         if delay > 0 then delay = delay - 1 end
329         return token.get_next()
330       else
331         local cont, back = scanner()
332         if not cont then
333           ltb.remove_from_callback("token_filter", "ltj@grab@num")
334         end
335         return back
336       end
337     end
338     ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
339   end
340
341   scan_brace = function ()
342     scan_with(1, function()
343       local next = token.get_next()
344       if next[1] == 1 then
345         return false, { tok_escape, next }
346       elseif next[1] == 10 then
347         return true, { next }
348       else
349         return false, { next }
350       end
351     end)
352   end
353
354   scan_number = function ()
355     scan_with(1, function()
356       local next = get_expd_next()
357       local res, ok = { tok_num }, false
358       while true do
359         if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
360           table.insert(res, next)
361         elseif next[1] ~= 10 then
362           break
363         end
364         next = get_expd_next()
365       end
366       if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
367         ok, next = grab_decimal(next, res)
368       elseif next[1] == 12 and next[2] == 0x22 then
369         ok, next = grab_hexa(next, res)
370       elseif next[1] == 12 and next[2] == 0x27 then
371         ok, next = grab_octal(next, res)
372       elseif next[1] == 12 and next[2] == 0x60 then
373         ok, next = grab_charnum(next, res)
374       elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
375         table.insert(res, next)
376         ok, next = true, nil
377       end
378       if ok then
379          table.insert(res, tok_num)
380       else
381          error_scan()
382          res = { tok_escape }
383       end
384        if next then table.insert(res, next) end
385        return false, res
386     end)
387   end
388
389 end
390 -------------------- TeX register allocation
391 local const_number, count_number, attribute_number, dimen_number, skip_number
392
393 do
394   local cmod_base_count = token.create('ltj@@count@zero')[2]
395   local cmod_base_attr = token.create('ltj@@attr@zero')[2]
396   local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
397   local cmod_base_skip = token.create('ltj@@skip@zero')[2]
398
399   const_number = function (name)
400     if name:sub(1, 1) == '\\' then name = name:sub(2) end
401     return token.create(name)[2]
402   end
403
404   count_number = function (name)
405     if name:sub(1, 1) == '\\' then name = name:sub(2) end
406     return token.create(name)[2] - cmod_base_count
407   end
408
409   attribute_number = function (name)
410     if name:sub(1, 1) == '\\' then name = name:sub(2) end
411     return token.create(name)[2] - cmod_base_attr
412   end
413
414   dimen_number = function (name)
415     if name:sub(1, 1) == '\\' then name = name:sub(2) end
416     return token.create(name)[2] - cmod_base_dimen
417   end
418
419   skip_number = function (name)
420     if name:sub(1, 1) == '\\' then name = name:sub(2) end
421     return token.create(name)[2] - cmod_base_skip
422   end
423
424 end
425 -------------------- mock of debug logger
426
427 if not debug or debug == _G.debug then
428   local function no_op() end
429   debug = no_op
430   package_debug = no_op
431   show_term = no_op
432   show_log = no_op
433   function debug_logger()
434     return no_op
435   end
436 end
437
438 -------------------- getting next token
439 local cstemp = nil
440 local function get_cs(s)
441    cstemp = token.csname_name(token.get_next())
442    tex.sprint(cat_lp,'\\' .. s)
443 end
444
445
446 -------------------- cache management
447 -- load_cache (filename, outdate)
448 --   * filename: without suffix '.lua'
449 --   * outdate(t): return true iff the cache is outdated
450 --   * return value: non-nil iff the cache is up-to-date
451 -- save_cache (filename, t): no return value
452 -- save_cache_luc (filename, t): no return value
453 --   save_cache always calls save_cache_luc. 
454 --   But sometimes we want to create only the precompiled cache,
455 --   when its 'text' version is already present in LuaTeX-ja distribution.
456
457 require('lualibs-lpeg') -- string.split
458 require('lualibs-os')   -- os.type
459
460 local load_cache, save_cache_luc, save_cache
461
462 do
463    local path = kpse.expand_var("$TEXMFVAR;$TEXMFSYSVAR;$TEXMFCACHE")
464    if os.type~='windows' then path = string.gsub(path, ':', ';') end
465    path = string.split(path, ';')
466
467    local cache_dir = '/luatexja'
468    local find_file = kpse.find_file
469    local join, isreadable = file.join, file.isreadable
470    local tofile, serialize = table.tofile, table.serialize
471    local luc_suffix = jit and '.lub' or '.luc'
472
473    -- determine save path
474    local savepath = ''
475    for _,v in pairs(path) do
476       local testpath =  join(v, cache_dir)
477       if not lfs.isdir(testpath) then dir.mkdirs(testpath) end
478       if lfs.isdir(testpath) then savepath = testpath; break end
479    end
480
481    save_cache_luc = function (filename, t, serialized)
482       local fullpath = savepath .. '/' ..  filename .. luc_suffix
483       local s = serialized or serialize(t, 'return', false)
484       if s then
485          local sa = load(s)
486          local f = io.open(fullpath, 'wb')
487          if f and sa then 
488             f:write(string.dump(sa, true)) 
489             texio.write('(save cache: ' .. fullpath .. ')')
490          end
491          f:close()
492       end
493    end
494
495    save_cache = function (filename, t)
496       local fullpath = savepath .. '/' ..  filename .. '.lua'
497       local s = serialize(t, 'return', false)
498       if s then
499          local f = io.open(fullpath, 'w')
500          if f then 
501             f:write(s) 
502             texio.write('(save cache: ' .. fullpath .. ')')
503          end
504          f:close()
505          save_cache_luc(filename, t, s)
506       end
507    end
508
509    local function load_cache_a (filename, outdate)
510       local result
511       for _,v in pairs(path) do
512          local fn = join(v, cache_dir, filename)
513          if isreadable(fn) then 
514             texio.write('(load cache: ' .. fn .. ')')
515             result = loadfile(fn)
516             result = result and result(); break
517          end
518       end
519       if (not result) or outdate(result) then 
520          return nil 
521       else 
522          return result 
523       end
524    end
525    load_cache = function (filename, outdate)
526       local r = load_cache_a(filename ..  luc_suffix, outdate)
527       if r then 
528          return r
529       else
530          local r = load_cache_a(filename .. '.lua', outdate)
531          if r then save_cache_luc(filename, r) end -- update the precompiled cache
532          return r
533       end
534    end
535
536 end
537
538
539 luatexja.base = {
540    public_name = public_name, 
541    public_version = public_version, 
542    --
543    _error_set_break = _error_set_break, 
544    _error_set_message = _error_set_message, 
545    _error_show = _error_show, 
546    _generic_warn_info = _generic_warn_info, 
547    --
548    package_error = package_error, 
549    package_warning = package_warning, 
550    package_warning_no_line = package_warning_no_line, 
551    package_info = package_info, 
552    package_info_no_line = package_info_no_line, 
553    --
554    generic_error = generic_error, 
555    generic_warning = generic_warning, 
556    generic_warning_no_line = generic_warning_no_line, 
557    generic_info = generic_info, 
558    generic_info_no_line = generic_info_no_line, 
559    --
560    ltj_warning_no_line = ltj_warning_no_line, 
561    ltj_error = ltj_error, 
562    --
563    mprint = mprint, 
564    --
565    to_dimen = to_dimen, 
566    dump_skip = dump_skip, 
567    to_skip = to_skip, 
568    --
569    counter = counter, 
570    --
571    scan_brace = scan_brace, 
572    scan_number = scan_number, 
573    --
574    const_number = const_number, 
575    count_number = count_number, 
576    attribute_number = attribute_number, 
577    dimen_number = dimen_number, 
578    skip_number = skip_number, 
579    --
580    get_cs = get_cs, 
581    --
582    load_cache = load_cache, 
583    save_cache_luc = save_cache_luc, 
584    save_cache = save_cache, 
585    --
586    debug = debug, 
587    package_debug = package_debug, 
588    debug_logger = debug_logger, 
589    show_term = show_term, 
590    show_log = show_log, 
591    --
592 }
593 -------------------- all done
594 -- EOF