OSDN Git Service

bf24ba6900a6a5662f6b53df96340a66e4d399af
[luatex-ja/luatexja.git] / src / luatexja / base.lua
1 --
2 -- luatexja/base.lua
3 --
4 luatexbase.provides_module({
5   name = 'luatexja.base',
6   date = '2011/04/01',
7   version = '0.1',
8   description = '',
9 })
10 module('luatexja.base', package.seeall)
11 local err, warn, info, log = luatexbase.errwarinf(_NAME)
12
13 local ltb = luatexbase
14 local tostring = tostring
15 local node, table, tex, token = node, table, tex, token
16
17 -------------------- 
18
19 public_name = 'luatexja'
20 public_version = 'alpha'
21
22 -------------------- Fully-expandable error messaging
23 do
24 --! LaTeX 形式のエラーメッセージ(\PackageError 等)を
25 --! Lua 関数の呼び出しで行う.
26
27   local LF = "\n"
28   local err_break = ""
29   local err_main = ""
30   local err_help = ""
31
32   local function message_cont(str, c)
33     return str:gsub(err_break, LF .. c)
34   end
35   local function into_lines(str)
36     return str:gsub(err_break, LF):explode(LF)
37   end
38
39   function _error_set_break(str)
40     err_break = str
41   end
42
43   function _error_set_message(msgcont, main, help)
44     err_main = message_cont(main, msgcont)
45     err_help = into_lines(help)
46   end
47
48   function _error_show(escchar)
49     local escapechar = tex.escapechar
50     local newlinechar = tex.newlinechar
51     local errorcontextlines = tex.errorcontextlines
52     if not escchar then tex.escapechar = -1 end
53     tex.newlinechar = 10
54     tex.errorcontextlines = -1
55     tex.error(err_main, err_help)
56     tex.escapechar = escapechar
57     tex.newlinechar = newlinechar
58     tex.errorcontextlines = errorcontextlines
59   end
60
61   local message_a = "Type  H <return>  for immediate help"
62
63   function generic_error(msgcont, main, ref, help)
64     local mainref = main..".\n\n"..ref.."\n"..message_a
65     _error_set_message(msgcont, mainref, help)
66     _error_show(true)
67   end
68
69   function _generic_warn_info(msgcont, main, warn, line)
70     local mainc = message_cont(main, msgcont)
71     local br = warn and "\n" or ""
72     local out = warn and "term and log" or "log"
73     local on_line = line and (" on input line "..tex.inputlineno) or ""
74     local newlinechar = tex.newlinechar
75     tex.newlinechar = -1
76     texio.write_nl(out, br..main..on_line.."."..br)
77     tex.newlinechar = newlinechar
78   end
79
80   function generic_warning(msgcont, main)
81     _generic_warn_info(msgcont, main, true, true)
82   end
83   function generic_warning_no_line(msgcont, main)
84     _generic_warn_info(msgcont, main, true, false)
85   end
86   function generic_info(msgcont, main)
87     _generic_warn_info(msgcont, main, false, true)
88   end
89   function generic_info_no_line(msgcont, main)
90     _generic_warn_info(msgcont, main, false, false)
91   end
92
93   function package_error(pkgname, main, help)
94     generic_error("("..pkgname.."                ",
95       "Package "..pkgname.." Error: "..main,
96       "See the "..pkgname.." package documentation for explanation.",
97       help)
98   end
99   function package_warning(pkgname, main)
100     generic_warning("("..pkgname.."                ",
101       "Package "..pkgname.." Warning: "..main)
102   end
103   function package_warning_no_line(pkgname, main)
104     generic_warning_no_line("("..pkgname.."                ",
105       "Package "..pkgname.." Warning: "..main)
106   end
107   function package_info(pkgname, main)
108     generic_info("("..pkgname.."             ",
109       "Package "..pkgname.." Info: "..main)
110   end
111   function package_info_no_line(pkgname, main)
112     generic_info_no_line("("..pkgname.."             ",
113       "Package "..pkgname.." Info: "..main)
114   end
115
116   function ltj_error(main, help)
117     package_error(public_name, main, help)
118   end
119   function ltj_warning_no_line(main)
120     package_warning_no_line(public_name, main, help)
121   end
122
123 end
124 -------------------- TeX stream I/O
125 do
126
127 --! ixbase.print() と同じ
128   --- Extension to tex.print(). Each argument string may contain
129   -- newline characters, in which case the string is output (to
130   -- TeX input stream) as multiple lines.
131   -- @param ... (string) string to output 
132   function mprint(...)
133     local arg = {...}
134     local lines = {}
135     if type(arg[1]) == "number" then
136       table.insert(lines, arg[1])
137       table.remove(arg, 1)
138     end
139     for _, cnk in ipairs(arg) do
140       local ls = cnk:explode("\n")
141       if ls[#ls] == "" then
142         table.remove(ls, #ls)
143       end
144       for _, l in ipairs(ls) do
145         table.insert(lines, l)
146       end
147     end
148     return tex.print(unpack(lines))
149   end
150
151 end
152 -------------------- Handling of TeX values
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   function to_dimen(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   function to_skip(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   function dump_skip(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 do
229
230 --! ixbase.counter と同じ
231   counter = {}
232   local mt_counter = {}
233   setmetatable(counter, mt_counter)
234
235   function mt_counter.__index(tbl, key)
236     return tex.count['c@'..key]
237   end
238   function mt_counter.__newindex(tbl, key, val)
239     tex.count['c@'..key] = val
240   end
241
242 --! ixbase.length は tex.skip と全く同じなので不要.
243
244 end
245 -------------------- Number handling in TeX source
246 do
247
248   local tok_escape = token.create("ltj@@q@escape")
249   local tok_num = token.create("ltj@@q@escapenum")
250   local c_id_assign_int = token.command_id("assign_int")
251   local c_id_char_given = token.command_id("char_given")
252
253   local function error_scan()
254     _M.package_error("luatexja",
255       "Missing number of a permitted form, treated as zero",
256       "A number should have been here; I inserted '0'.")
257   end
258
259   local function get_expd_next()
260     local next = token.get_next()
261     while token.is_expandable(next) do
262       token.expand(next)
263       next = token.get_next()
264     end
265     return next
266   end
267
268   local function grab_decimal(next, res)
269     table.insert(res, next)
270     while true do
271       next = get_expd_next()
272       if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
273         break
274       end
275       table.insert(res, next)
276     end
277     if next[1] == 10 then next = nil end
278     return true, next
279   end
280
281   local function grab_hexa(next, res)
282     local ok = false
283     table.insert(res, next)
284     while true do
285       next = get_expd_next()
286       if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
287               ((next[1] == 12 or next[1] == 11) and
288                (0x41 <= next[2] and next[2] <= 0x46))) then
289         break
290       end
291       ok = true
292       table.insert(res, next)
293     end
294     if next[1] == 10 then next = nil end
295     return ok, next
296   end
297
298   local function grab_octal(next, res)
299     local ok = false
300     table.insert(res, next)
301     while true do
302       next = get_expd_next()
303       if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
304         break
305       end
306       ok = true
307       table.insert(res, next)
308     end
309     if next[1] == 10 then next = nil end
310     return ok, next
311   end
312
313   local function grab_charnum(next, res)
314     table.insert(res, next)
315     next = token.get_next()
316     table.insert(res, next)
317     next = get_expd_next()
318     if next[1] == 10 then next = nil end
319     return true, next
320   end
321
322   local function scan_with(delay, scanner)
323     local function proc()
324       if delay ~= 0 then
325         if delay > 0 then delay = delay - 1 end
326         return token.get_next()
327       else
328         local cont, back = scanner()
329         if not cont then
330           ltb.remove_from_callback("token_filter", "ltj@grab@num")
331         end
332         return back
333       end
334     end
335     ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
336   end
337
338   function scan_brace()
339     scan_with(1, function()
340       local next = token.get_next()
341       if next[1] == 1 then
342         return false, { tok_escape, next }
343       elseif next[1] == 10 then
344         return true, { next }
345       else
346         return false, { next }
347       end
348     end)
349   end
350
351   function scan_number()
352     scan_with(1, function()
353       local next = get_expd_next()
354       local res, ok = { tok_num }, false
355       while true do
356         if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
357           table.insert(res, next)
358         elseif next[1] ~= 10 then
359           break
360         end
361         next = get_expd_next()
362       end
363       if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
364         ok, next = grab_decimal(next, res)
365       elseif next[1] == 12 and next[2] == 0x22 then
366         ok, next = grab_hexa(next, res)
367       elseif next[1] == 12 and next[2] == 0x27 then
368         ok, next = grab_octal(next, res)
369       elseif next[1] == 12 and next[2] == 0x60 then
370         ok, next = grab_charnum(next, res)
371       elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
372         table.insert(res, next)
373         ok, next = true, nil
374       end
375       if ok then
376          table.insert(res, tok_num)
377       else
378          error_scan()
379          res = { tok_escape }
380       end
381        if next then table.insert(res, next) end
382        return false, res
383     end)
384   end
385
386 end
387 -------------------- TeX register allocation
388 do
389   local cmod_base_count = token.create('ltj@@count@zero')[2]
390   local cmod_base_attr = token.create('ltj@@attr@zero')[2]
391   local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
392   local cmod_base_skip = token.create('ltj@@skip@zero')[2]
393
394   function const_number(name)
395     if name:sub(1, 1) == '\\' then name = name:sub(2) end
396     return token.create(name)[2]
397   end
398
399   function count_number(name)
400     if name:sub(1, 1) == '\\' then name = name:sub(2) end
401     return token.create(name)[2] - cmod_base_count
402   end
403
404   function attribute_number(name)
405     if name:sub(1, 1) == '\\' then name = name:sub(2) end
406     return token.create(name)[2] - cmod_base_attr
407   end
408
409   function dimen_number(name)
410     if name:sub(1, 1) == '\\' then name = name:sub(2) end
411     return token.create(name)[2] - cmod_base_dimen
412   end
413
414   function skip_number(name)
415     if name:sub(1, 1) == '\\' then name = name:sub(2) end
416     return token.create(name)[2] - cmod_base_skip
417   end
418
419 end
420 -------------------- mock of debug logger
421
422 if not _M.debug or _M.debug == _G.debug then
423   local function no_op() end
424   debug = no_op
425   package_debug = no_op
426   show_term = no_op
427   show_log = no_op
428   function debug_logger()
429     return no_op
430   end
431 end
432
433 -------------------- all done
434 -- EOF