OSDN Git Service

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