--
-- luatexja/base.lua
--
luatexbase.provides_module({
  name = 'luatexja.base',
  date = '2011/04/01',
  version = '0.1',
  description = '',
})
module('luatexja.base', package.seeall)
local err, warn, info, log = luatexbase.errwarinf(_NAME)

local ltb = luatexbase
local tostring = tostring
local node, table, tex, token = node, table, tex, token

-------------------- 

public_name = 'luatexja'
public_version = 'alpha'

-------------------- Fully-expandable error messaging
do
--! LaTeX 形式のエラーメッセージ(\PackageError 等)を
--! Lua 関数の呼び出しで行う.

  local LF = "\n"
  local err_break = ""
  local err_main = ""
  local err_help = ""

  local function message_cont(str, c)
    return str:gsub(err_break, LF .. c)
  end
  local function into_lines(str)
    return str:gsub(err_break, LF):explode(LF)
  end

  function _error_set_break(str)
    err_break = str
  end

  function _error_set_message(msgcont, main, help)
    err_main = message_cont(main, msgcont)
    err_help = into_lines(help)
  end

  function _error_show(escchar)
    local escapechar = tex.escapechar
    local newlinechar = tex.newlinechar
    local errorcontextlines = tex.errorcontextlines
    if not escchar then tex.escapechar = -1 end
    tex.newlinechar = 10
    tex.errorcontextlines = -1
    tex.error(err_main, err_help)
    tex.escapechar = escapechar
    tex.newlinechar = newlinechar
    tex.errorcontextlines = errorcontextlines
  end

  local message_a = "Type  H <return>  for immediate help"

  function generic_error(msgcont, main, ref, help)
    local mainref = main..".\n\n"..ref.."\n"..message_a
    _error_set_message(msgcont, mainref, help)
    _error_show(true)
  end

  function _generic_warn_info(msgcont, main, warn, line)
    local mainc = message_cont(main, msgcont)
    local br = warn and "\n" or ""
    local out = warn and "term and log" or "log"
    local on_line = line and (" on input line "..tex.inputlineno) or ""
    local newlinechar = tex.newlinechar
    tex.newlinechar = -1
    texio.write_nl(out, br..main..on_line.."."..br)
    tex.newlinechar = newlinechar
  end

  function generic_warning(msgcont, main)
    _generic_warn_info(msgcont, main, true, true)
  end
  function generic_warning_no_line(msgcont, main)
    _generic_warn_info(msgcont, main, true, false)
  end
  function generic_info(msgcont, main)
    _generic_warn_info(msgcont, main, false, true)
  end
  function generic_info_no_line(msgcont, main)
    _generic_warn_info(msgcont, main, false, false)
  end

  function package_error(pkgname, main, help)
    generic_error("("..pkgname.."                ",
      "Package "..pkgname.." Error: "..main,
      "See the "..pkgname.." package documentation for explanation.",
      help)
  end
  function package_warning(pkgname, main)
    generic_warning("("..pkgname.."                ",
      "Package "..pkgname.." Warning: "..main)
  end
  function package_warning_no_line(pkgname, main)
    generic_warning_no_line("("..pkgname.."                ",
      "Package "..pkgname.." Warning: "..main)
  end
  function package_info(pkgname, main)
    generic_info("("..pkgname.."             ",
      "Package "..pkgname.." Info: "..main)
  end
  function package_info_no_line(pkgname, main)
    generic_info_no_line("("..pkgname.."             ",
      "Package "..pkgname.." Info: "..main)
  end

  function ltj_error(main, help)
    package_error(public_name, main, help)
  end
  function ltj_warning_no_line(main)
    package_warning_no_line(public_name, main, help)
  end

end
-------------------- TeX stream I/O
do

--! ixbase.print() と同じ
  --- Extension to tex.print(). Each argument string may contain
  -- newline characters, in which case the string is output (to
  -- TeX input stream) as multiple lines.
  -- @param ... (string) string to output 
  function mprint(...)
    local arg = {...}
    local lines = {}
    if type(arg[1]) == "number" then
      table.insert(lines, arg[1])
      table.remove(arg, 1)
    end
    for _, cnk in ipairs(arg) do
      local ls = cnk:explode("\n")
      if ls[#ls] == "" then
        table.remove(ls, #ls)
      end
      for _, l in ipairs(ls) do
        table.insert(lines, l)
      end
    end
    return tex.print(unpack(lines))
  end

end
-------------------- Handling of TeX values
do

  local glue_spec_id = node.id("glue_spec")

  local function copy_skip(s1, s2)
    if not s1 then
      s1 = node.new(glue_spec_id)
    end
    s1.width = s2.width or 0
    s1.stretch = s2.stretch or 0
    s1.stretch_order = s2.stretch_order or 0
    s1.shrink = s2.shrink or 0
    s1.shrink_order = s2.shrink_order or 0
    return s1
  end

--! ixbase.to_dimen() と同じ
  function to_dimen(val)
    if val == nil then
      return 0
    elseif type(val) == "number" then
      return val
    else
      return tex.sp(tostring(val))
    end
  end

  local function parse_dimen(val)
    val = tostring(val):lower()
    local r, fil = val:match("([-.%d]+)fi(l*)")
    if r then
      val, fil = r.."pt", fil:len() + 1
    else
      fil = 0
    end
    return tex.sp(val), fil
  end

--! ixbase.to_skip() と同じ
  function to_skip(val)
    if type(val) == "userdata" then
      return val
    end
    local res = node.new(glue_spec_id)
    if val == nil then
      res.width = 0
    elseif type(val) == "number" then
      res.width = val
    elseif type(val) == "table" then
      copy_skip(res, val)
    else
      local t = tostring(val):lower():explode()
      local w, p, m = t[1], t[3], t[5]
      if t[2] == "minus" then
        p, m = nil, t[3]
      end
      res.width = tex.sp(t[1])
      if t[3] then
        res.stretch, res.stretch_order = parse_dimen(t[3])
      end
      if t[5] then
        res.shrink, res.shrink_order = parse_dimen(t[5])
      end
    end
    return res
  end

  function dump_skip(s)
    print(("%s+%s<%s>-%s<%s>"):format(
      s.width or 0, s.stretch or 0, s.stretch_order or 0,
      s.shrink or 0, s.shrink_order or 0))
  end

end
-------------------- Virtual table for LaTeX counters
do

--! ixbase.counter と同じ
  counter = {}
  local mt_counter = {}
  setmetatable(counter, mt_counter)

  function mt_counter.__index(tbl, key)
    return tex.count['c@'..key]
  end
  function mt_counter.__newindex(tbl, key, val)
    tex.count['c@'..key] = val
  end

--! ixbase.length は tex.skip と全く同じなので不要.

end
-------------------- Number handling in TeX source
do

  local tok_escape = token.create("ltj@@q@escape")
  local tok_num = token.create("ltj@@q@escapenum")
  local c_id_assign_int = token.command_id("assign_int")
  local c_id_char_given = token.command_id("char_given")

  local function error_scan()
    _M.package_error("luatexja",
      "Missing number of a permitted form, treated as zero",
      "A number should have been here; I inserted '0'.")
  end

  local function get_expd_next()
    local next = token.get_next()
    while token.is_expandable(next) do
      token.expand(next)
      next = token.get_next()
    end
    return next
  end

  local function grab_decimal(next, res)
    table.insert(res, next)
    while true do
      next = get_expd_next()
      if not (next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39) then
        break
      end
      table.insert(res, next)
    end
    if next[1] == 10 then next = nil end
    return true, next
  end

  local function grab_hexa(next, res)
    local ok = false
    table.insert(res, next)
    while true do
      next = get_expd_next()
      if not ((next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x39)) or
              ((next[1] == 12 or next[1] == 11) and
               (0x41 <= next[2] and next[2] <= 0x46))) then
        break
      end
      ok = true
      table.insert(res, next)
    end
    if next[1] == 10 then next = nil end
    return ok, next
  end

  local function grab_octal(next, res)
    local ok = false
    table.insert(res, next)
    while true do
      next = get_expd_next()
      if not (next[1] == 12 and (0x30 <= next[2] and next[2] <= 0x37)) then
        break
      end
      ok = true
      table.insert(res, next)
    end
    if next[1] == 10 then next = nil end
    return ok, next
  end

  local function grab_charnum(next, res)
    table.insert(res, next)
    next = token.get_next()
    table.insert(res, next)
    next = get_expd_next()
    if next[1] == 10 then next = nil end
    return true, next
  end

  local function scan_with(delay, scanner)
    local function proc()
      if delay ~= 0 then
        if delay > 0 then delay = delay - 1 end
        return token.get_next()
      else
        local cont, back = scanner()
        if not cont then
          ltb.remove_from_callback("token_filter", "ltj@grab@num")
        end
        return back
      end
    end
    ltb.add_to_callback("token_filter", proc, "ltj@grab@num", 1)
  end

  function scan_brace()
    scan_with(1, function()
      local next = token.get_next()
      if next[1] == 1 then
        return false, { tok_escape, next }
      elseif next[1] == 10 then
        return true, { next }
      else
        return false, { next }
      end
    end)
  end

  function scan_number()
    scan_with(1, function()
      local next = get_expd_next()
      local res, ok = { tok_num }, false
      while true do
        if next[1] == 12 and (next[2] == 0x2B or next[2] == 0x2D) then
          table.insert(res, next)
        elseif next[1] ~= 10 then
          break
        end
        next = get_expd_next()
      end
      if next[1] == 12 and 0x30 <= next[2] and next[2] <= 0x39 then
        ok, next = grab_decimal(next, res)
      elseif next[1] == 12 and next[2] == 0x22 then
        ok, next = grab_hexa(next, res)
      elseif next[1] == 12 and next[2] == 0x27 then
        ok, next = grab_octal(next, res)
      elseif next[1] == 12 and next[2] == 0x60 then
        ok, next = grab_charnum(next, res)
      elseif next[1] == c_id_assign_int or next[1] == c_id_char_given then
        table.insert(res, next)
        ok, next = true, nil
      end
      if ok then
         table.insert(res, tok_num)
      else
         error_scan()
         res = { tok_escape }
      end
       if next then table.insert(res, next) end
       return false, res
    end)
  end

end
-------------------- TeX register allocation
do
  local cmod_base_count = token.create('ltj@@count@zero')[2]
  local cmod_base_attr = token.create('ltj@@attr@zero')[2]
  local cmod_base_dimen = token.create('ltj@@dimen@zero')[2]
  local cmod_base_skip = token.create('ltj@@skip@zero')[2]

  function const_number(name)
    if name:sub(1, 1) == '\\' then name = name:sub(2) end
    return token.create(name)[2]
  end

  function count_number(name)
    if name:sub(1, 1) == '\\' then name = name:sub(2) end
    return token.create(name)[2] - cmod_base_count
  end

  function attribute_number(name)
    if name:sub(1, 1) == '\\' then name = name:sub(2) end
    return token.create(name)[2] - cmod_base_attr
  end

  function dimen_number(name)
    if name:sub(1, 1) == '\\' then name = name:sub(2) end
    return token.create(name)[2] - cmod_base_dimen
  end

  function skip_number(name)
    if name:sub(1, 1) == '\\' then name = name:sub(2) end
    return token.create(name)[2] - cmod_base_skip
  end

end
-------------------- mock of debug logger

if not _M.debug or _M.debug == _G.debug then
  local function no_op() end
  debug = no_op
  package_debug = no_op
  show_term = no_op
  show_log = no_op
  function debug_logger()
    return no_op
  end
end

-------------------- all done
-- EOF
