--
-- This is file `lua-list-hyphen.lua',
-- generated with the docstrip utility.
--
-- The original source files were:
--
-- lua-list-hyphen.dtx  (with options: `lua')
-- 
-- This is a generated file.
-- 
-- Copyright (C) 2026 Alan J. Cain
-- 
-- This file may be distributed and/or modified under the
-- conditions of the LaTeX Project Public License, either
-- version 1.3c of this license or (at your option) any later
-- version. The latest version of this license is in:
-- 
-- http://www.latex-project.org/lppl.txt
-- 
-- and version 1.3c or later is part of all distributions of
-- LaTeX version 2008-05-04 or later.
-- 
local module_name = 'lua-list-hyphen'
local lualisthyphen_module = {
  name        = module_name,
  version     = '0.43.2',
  date        = '2026-08-23',
  description = 'lua-list-hyphen',
  author      = 'Alan J. Cain',
  copyright   = 'Alan J. Cain',
  license     = 'LPPL v1.3c'
}
luatexbase.provides_module(lualisthyphen_module)
local function info(s,...)
  luatexbase.module_info(module_name,s:format(...))
end
local function warning(s,...)
  luatexbase.module_warning(module_name,s:format(...))
end
local function error_(s,...)
  luatexbase.module_error(module_name,s:format(...))
end
local function debug_dummy(s,...)
end

local function debug_real(s,...)
  print(module_name .. ' DEBUG: ' .. s:format(...))
end

local debug = debug_dummy
local debug_bool = false
local function set_debug(a)
  if a then
    debug = debug_real
  else
    debug = debug_dummy
  end
  debug_bool = a
end
local NODE_ID_HLIST = node.id('hlist')
local NODE_ID_DISC = node.id('disc')
local NODE_ID_GLUE = node.id('glue')
local NODE_ID_KERN = node.id('kern')
local NODE_ID_MARGIN_KERN = node.id('margin_kern')
local NODE_ID_GLYPH = node.id('glyph')
local NODE_ID_MATH = node.id('math')
local NODE_ID_PENALTY = node.id('penalty')
local NODE_KERN_SUBTYPE_FONTKERN
local NODE_KERN_SUBTYPE_USERKERN
for k,v in pairs(node.subtypes('kern')) do
  if v == 'fontkern' then
    NODE_KERN_SUBTYPE_FONTKERN = k
  elseif v == 'userkern' then
    NODE_KERN_SUBTYPE_USERKERN = k
  end
end
local NODE_MATH_SUBTYPE_BEGIN
local NODE_MATH_SUBTYPE_END
for k,v in pairs(node.subtypes('math')) do
  if v == 'beginmath' then
    NODE_MATH_SUBTYPE_BEGIN = k
  elseif v == 'endmath' then
    NODE_MATH_SUBTYPE_END = k
  end
end
local function list_filter(t, f)
  local j = 1
  local n = #t

  for i=1,n do
    if (f(t[i])) then
      if (i ~= j) then
        t[j] = t[i]
        t[i] = nil
      end
      j = j + 1
    else
      t[i] = nil
    end
  end

end
local function list_uniq(t, f)
  local j = 1
  local n = #t

  for i=2,n do
    if (f(t[i],t[j])) then
      t[i] = false
    else
      j = i
    end
  end

  list_filter(
    t,
    function(a) return a end
  )
end
local STR_MATH = '[MATH]'
local STR_SPACE = ' '
local STR_SPACE_TWO = '  '
local STR_ARROW = ' -> '
local STR_PAGE_PREFIX = 'p.'
local STR_PAGE_NONE = '<none>'
local STR_QUOTE_OPEN = '"'
local STR_QUOTE_CLOSE = '"'
local STR_VALID = 'Valid'
local STR_INVALID = 'Inva.'
local STR_AMBIGUOUS = 'Ambi.'
local STR_UNKNOWN = 'Unkn.'
local function trim_whitespace_both(s)

  return unicode.utf8.match(s,'^%s*(.-)%s*$')

end
local function trim_whitespace_left(s)

  return unicode.utf8.match(s,'^%s*(.*)$')

end
local function trim_nonlettershyphens_both(s)

  return unicode.utf8.match(s,'^[^%a-]*(.-)[^%a-]*$')

end
local function trim_nonlettershyphens_start(s)

  return unicode.utf8.match(s,'^[^%a-]*(.-)$')

end
local function trim_nonlettershyphens_end(s)

  return unicode.utf8.match(s,'^(.-)[^%a-]*$')

end
local function rpad(s,n)

   return s .. unicode.utf8.rep(STR_SPACE,n - unicode.utf8.len(s))

end
local function lpad(s,n)

   return unicode.utf8.rep(STR_SPACE,n - unicode.utf8.len(s)) .. s

end
local function parenthesize(s)

  return '(' .. s .. ')'

end
local function delete_hyphens(s)

  return unicode.utf8.gsub(s,'-','')

end
local function uniformize_hyphens(s)

  return unicode.utf8.gsub(unicode.utf8.gsub(s,'|','-'),'¦','-')

end
local function hyphenation_matches_pattern(hyphenation,pattern)

  debug('    Testing "%s" against "%s"',hyphenation,pattern)
  local i = 1
  for j=1,#pattern do

    local s
    local t

    s = string.sub(hyphenation,i,i)
    t = string.sub(pattern,j,j)

    if s == t then
      i = i + 1
    elseif s == '-' or t ~= '-' then
      debug('      Fails at i=%d,s=%s,j=%d,t=%s',i,s,j,t)
      return false
    end

  end

  debug('      Matches')
  return true

end
local lang_name_cname_table = {}
local lang_cname_name_set_table = {}
local LANGUAGE_DAT_LUA = 'language.dat.lua'

local function lang_load_language_dat_lua()
  debug('  Loading names from %s',LANGUAGE_DAT_LUA)
  for k,v in pairs(require(LANGUAGE_DAT_LUA)) do
    local t = { [k] = true }
    lang_cname_name_set_table[k] = t
    lang_name_cname_table[k] = k
    for _,vv in pairs(v.synonyms) do
      lang_name_cname_table[vv] = k
      t[vv] = true
    end
  end
end
local LANGUAGE_DAT = 'language.dat'

local function lang_load_language_dat()
  debug('  Loading names from %s',LANGUAGE_DAT)

  local p = kpse.find_file(LANGUAGE_DAT)
  if p == nil then
    warning('Could not find "%s"',LANGUAGE_DAT)
    return
  end

  local f = io.open(p,'r')
  if f == nil then
    warning('Could not read "%s"',p)
    return nil
  end

  io.input(f)

  local cname
  local cname_name_set
  for line in io.lines() do
    if #line == 0 or string.sub(line,1,1) == '%' then
      goto continue
    end
    local name = string.match(line,'^=([^%s]+)')
    if name then
      if cname then
        if cname_name_set[name] then
          debug('    Known synonym "%s" found for "%s"',name,cname)
        else
          debug('    New synonym "%s" found for "%s"',name,cname)
          cname_name_set[name] = true
        end
      else
        warning('Problem in parsing "%s": the name "%s" is a synonym but no canonical name was found',LANGUAGE_DAT,name)
      end

      goto continue
    end
    cname = string.match(line,'^([^%s]+)%s')
    cname_name_set = lang_cname_name_set_table[cname]
    if cname_name_set == nil then
      debug('    New cname "%s" found',cname)
      cname_name_set = { [cname] = true }
      lang_cname_name_set_table[cname] = cname_name_set
      lang_name_cname_table[cname] = cname
    else
      debug('    Known cname "%s" found',cname)
    end

    ::continue::
  end

  io.close(f)

end
local function lang_load_names()
  debug('Loading language names')
  lang_load_language_dat_lua()
  lang_load_language_dat()
  if true then
    debug('Language name -> cname:')
    for k,v in pairs(lang_name_cname_table) do
      debug('  %s -> %s',k,v)
    end
    debug('Language cname -> name list:')
    for k,v in pairs(lang_cname_name_set_table) do
      local s = ''
      for kk,_ in pairs(v) do
        s = s .. kk .. ','
      end
      debug('  %s -> %s ',k,s)
    end
  end
end
local function get_cname(name)

  local cname = lang_name_cname_table[name]
  if cname == nil then
    cname = name
    lang_name_cname_table[name] = cname
    lang_cname_name_set_table[cname] = { [name] = true }
  end

  return cname

end
local function lang_options_check_name_known(name)

  if lang_name_cname_table[name] == nil then
    warning('Option(s) set for unknown language "%s"',name)
  end

end
local lang_id_cname_table = {}
local lang_cname_id_table = {}
local function store_lang_id_name(lang_id,name)

  local cname = get_cname(name)

  local lang_id_cname = lang_id_cname_table[lang_id]
  if lang_id_cname == cname then
    debug(
      'Language name "%s" is a synonym for cname "%s" (lang. ID %s)',
      name,cname,lang_id
    )
    return
  elseif lang_id_cname ~= nil then
    error_(
      'Languages with canonical names "%s" and "%s" both have ID %s',
      cname,lang_id_cname,lang_id
    )
    return
  end

  lang_id_cname_table[lang_id] = cname
  lang_cname_id_table[cname] = lang_id

  debug('Language ID %s has cname "%s"',lang_id,cname)

end
local function polyglossia_store_lang_id_name(lang_id)

  if not polyglossia then
    return
  end

  for name,language in pairs(polyglossia.newloader_loaded_languages) do
    local this_lang_id = lang.id(language)
    if this_lang_id == lang_id then
      store_lang_id_name(lang_id,name)
    end
  end

end
local lang_cname_options_table = {}
local lang_id_options_table = {}
local LANG_DEFAULT = -1
local function get_lang_option(lang_id,key)
  local options = lang_id_options_table[lang_id]

  if not options then
    local cname = lang_id_cname_table[lang_id]
    if cname then
      options = lang_cname_options_table[cname]
    end
    if not options then
      options = {}
      for k,v in pairs(lang_id_options_table[LANG_DEFAULT]) do
        options[k] = v
      end
    end
    lang_id_options_table[lang_id] = options
  end

  return options[key]

end
local function set_global_option(key,value)

  local options = lang_id_options_table[LANG_DEFAULT]
  if not options then
    options = {}
    lang_id_options_table[LANG_DEFAULT] = options
  end

  options[key] = value
  debug('Set global option %s="%s"',key,value)

end
local function set_lang_option(name,key,value)

  local cname = get_cname(name)

  local options = lang_cname_options_table[cname]
  if not options then
    options = {}
    lang_cname_options_table[cname] = options
  end

  options[key] = value
  debug('Set lang. "%s" option %s="%s"',cname,key,value)

end
local lang_id_division_dict_table = {}
local lang_id_division_dict_read_table = {}
local lang_id_division_dict_path_table = {}
local function load_division_dict(path,case_sensitive)

  local count = 0

  local f = io.open(path,'r')
  if f == nil then
    return nil
  end

  debug('  Loading divisions from "%s"',path)

  local division_dict = {}

  io.input(f)

  for line in io.lines() do

    line = trim_whitespace_left(line)
    if #line == 0 or string.sub(line,1,1) == '%' then
      goto continue
    end

    count = count + 1
    local pattern = unicode.utf8.match(line,'^(.-)%s') or line

    pattern = uniformize_hyphens(pattern)

    if not case_sensitive then
      pattern = unicode.utf8.lower(pattern)
    end

    local word = delete_hyphens(pattern)

    local divisions = division_dict[word]
    if divisions == nil then
      divisions = {}
      division_dict[word] = divisions
    end

    table.insert(divisions,pattern)

    ::continue::
  end

  io.close(f)

  debug('Read %s divisions from "%s"',count,path)

  return division_dict

end
local lang_id_validation_preprocessor_table = {}
local preprocessors = {}
local function identity(s)
  return s
end
preprocessors.identity = identity
local function english_strip_possessives(s)
  return string.gsub(s,'’s$','')
end
preprocessors.english_strip_possessives = english_strip_possessives
local function get_lang_division_dict_validation_preprocessor(lang_id)
  local division_dict = lang_id_division_dict_table[lang_id]
  if division_dict ~= nil then
    return division_dict,lang_id_validation_preprocessor_table[lang_id]
  end
  polyglossia_store_lang_id_name(lang_id)
  local cname = lang_id_cname_table[lang_id]
  local preprocessor_name = get_lang_option(lang_id,'validation-preprocessor')
  local preprocessor
  if not preprocessor_name then
    debug(
      'No preprocessor specified for "%s"',cname
    )
    preprocessor = identity
  else
    preprocessor = preprocessors[preprocessor_name]
    if not preprocessor then
       preprocessor = _G[preprocessor_name]
    end
    if not preprocessor then
      debug(
        'Preprocessor specified for "%s" was not found',cname
      )
      preprocessor = identity
    else
      debug(
        'Using preprocessor specified for "%s"',cname
      )
    end
  end
  lang_id_validation_preprocessor_table[lang_id] = preprocessor
  debug(
    'Attempting to load division dictionary for "%s" (lang. ID %s)',
    cname,lang_id
  )
  local file_name = get_lang_option(lang_id,'division-dict-path')
  if file_name then
    debug('  Filename: ' .. file_name)
    local p = kpse.find_file(file_name)
    if p then
      debug('  Path (after kpathsea lookup): %s',p)
      lang_id_division_dict_path_table[lang_id] = p

      local case_sensitive = get_lang_option(lang_id,'validation-case-sensitive')

      division_dict = load_division_dict(p,case_sensitive)
      if division_dict ~= nil then
        info(
          '  Loaded division dict for "%s" (lang. ID %s) from "%s"',
          cname,lang_id,path
        )
        lang_id_division_dict_read_table[lang_id] = true
        lang_id_division_dict_table[lang_id] = division_dict
        return division_dict,preprocessor
      end

    else
      warning(
        '  Specified division dict "%s" not found for "%s" (lang. ID %s)',
        file_name,cname,lang_id
      )
    end
  else
    debug('  No division dict path specified')
  end

  lang_id_division_dict_read_table[lang_id] = false
  division_dict = {}
  lang_id_division_dict_table[lang_id] = division_dict

  return division_dict,preprocessor

end
local STATUS_VALID = 1
local STATUS_INVALID = 2
local STATUS_AMBIGUOUS = 3
local STATUS_UNKNOWN = 4
local function validate_division(lang_id,word,divided)

  debug(
    '    Validating "%s" for language ID %s',divided,lang_id
  )
  local division_dict,preprocessor
    = get_lang_division_dict_validation_preprocessor(lang_id)
  word = preprocessor(word)
  divided = preprocessor(divided)

  word = delete_hyphens(word)

  if not get_lang_option(lang_id,'validation-case-sensitive') then
    word = unicode.utf8.lower(word)
    divided = unicode.utf8.lower(divided)
  end

  local divisions = division_dict[word]
  if not divisions then
    return STATUS_UNKNOWN
  end

  local count = 0

  for _,division in ipairs(divisions) do
    if hyphenation_matches_pattern(divided,division) then
      count = count + 1
    end
  end

  if count == 0 then
    return STATUS_INVALID
  elseif count == #divisions then
    return STATUS_VALID
  else
    return STATUS_AMBIGUOUS
  end

end
local LIGATURE_TEXT = {
  [0xfb00] = 'ff',
  [0xfb01] = 'fi',
  [0xfb02] = 'fl',
  [0xfb03] = 'ffi',
  [0xfb04] = 'ffl',
  [0xfb05] = 'ſt',
  [0xfb06] = 'st',
}
local font_characters = {}
local get_node_text
local get_nodelist_text
get_node_text = function(n)

  if n.id == NODE_ID_GLYPH then

    local ligature_text = LIGATURE_TEXT[n.char]
    if ligature_text ~= nil then
      return ligature_text
    elseif n.components then
      return get_nodelist_text(n.components)
    else
      -- See [https://tug.org/pipermail/luatex/2018-March/006786.html]
      local characters = font_characters[n.font]
      if not characters then
        characters = fonts.hashes.identifiers[n.font].characters
        font_characters[n.font] = characters
      end
      local ok,retval = pcall(
        function ()
          return utf8.char(tonumber(characters[n.char].tounicode,16))
        end
      )
      if ok then
        return retval
      else
        return ''
      end
    end

  elseif n.id == NODE_ID_DISC then

    if n.replace then
      return get_nodelist_text(n.replace)
    else
      return ''
    end

  else
    return ''
  end

end
get_nodelist_text = function (head,last)

  local text = ''

  for item in node.traverse(head) do

    text = text .. get_node_text(item)

    if item == last then
      break
    end
  end

  return text

end
local function is_possible_word_node(n)

  return (
    n.id == NODE_ID_GLYPH
    or
    n.id == NODE_ID_DISC
    or
    (n.id == NODE_ID_KERN and n.subtype == NODE_KERN_SUBTYPE_FONTKERN)
    or
    n.id == NODE_ID_MARGIN_KERN
    or
    n.id == NODE_ID_PENALTY
    or
    (n.id == NODE_ID_GLUE and n.width == 0)
  )

end
local function is_possible_space_node(n)

  return (
    (n.id == NODE_ID_GLUE and n.width ~= 0)
    or
    (n.id == NODE_ID_KERN and n.subtype == NODE_KERN_SUBTYPE_USERKERN and n.kern ~= 0)
  )

end
  -- local function get_first_glyph_lang(n)

  --   local item = n
  --   while item and is_possible_word_node(item) do
  --     if item.id == NODE_ID_GLYPH then
  --       return item.lang
  --     end
  --     item = item.next
  --   end

  --   return nil

  -- end
local function get_disc_lang(n)

  -- lang = get_first_glyph_lang(n.replace)
  -- if lang then
  --   return lang
  -- end

  -- lang = get_first_glyph_lang(n.pre)
  -- if lang then
  --   return lang
  -- end

  -- lang = get_first_glyph_lang(n.post)
  -- if lang then
  --   return lang
  -- end

  local item
  item = n
  while item and is_possible_word_node(item) do
    if item.id == NODE_ID_GLYPH then
      return item.lang
    end
    item = item.prev
  end
  item = n
  while item and is_possible_word_node(item) do
    if item.id == NODE_ID_GLYPH then
      return item.lang
    end
    item = item.next
  end

  return 0

end
local hyphen_attr = luatexbase.new_attribute('hyphen_attr')
local hlist_segment_list = {}
local SEGMENT_WORD = 0
local SEGMENT_SPACE = 1
local SEGMENT_MATH = 2
local function pre_linebreak(hlist_head,groupcode)

  local word_start_node = nil
  local segment_count = 0
  local lang = nil

  debug('Pre-linebreak processing start')

  node.slide(hlist_head)

  local item = hlist_head
  while item do
    if item.id == NODE_ID_MATH then
      assert(item.subtype == NODE_MATH_SUBTYPE_BEGIN)
      debug('  Math')
      while not (
        item.id == NODE_ID_MATH and item.subtype == NODE_MATH_SUBTYPE_END
      ) do
        item = item.next
      end
      item = item.next

      segment_count = segment_count + 1
      hlist_segment_list[segment_count] = {
        type = SEGMENT_MATH
      }

      goto continue
    end
    if is_possible_word_node(item) then
      word_start_node = item
      segment_count = segment_count + 1
      while item and is_possible_word_node(item) do

        if item.id == NODE_ID_DISC then
          if not lang then
            lang = get_disc_lang(item)
          end
          node.set_attribute(item,hyphen_attr,segment_count)
        end

        item = item.next
      end
      local word_end_node
      if item then
        word_end_node = item.prev
      else
        word_end_node = node.tail(word_start_node)
      end

      local word = get_nodelist_text(word_start_node,word_end_node)
      hlist_segment_list[segment_count] = {
        type = SEGMENT_WORD,
        word = word,
        lang = lang,
      }

      debug('  Word "%s"',word)

      word_start_node = nil
      lang = nil

      goto continue
    end
    if is_possible_space_node(item) then
      debug('  Space')
      segment_count = segment_count + 1

      while item and is_possible_space_node(item) do
        item = item.next
      end

      hlist_segment_list[segment_count] = {
        type = SEGMENT_SPACE
      }

      goto continue
    end
    item = item.next

    ::continue::
  end

  debug('Pre-linebreak processing finish')

  return true
end
local USED_DISC_SEARCH_LIMIT = 20

local function get_used_disc(hlist_head)

  debug('  Looking for disc node at end of line')

  local item = node.tail(hlist_head)

  local count = 0
  while item and item.id ~= NODE_ID_GLYPH and count < USED_DISC_SEARCH_LIMIT do
    if item.id == NODE_ID_DISC then
      return item
    end
    item = item.prev
    count = count + 1
  end

  return nil

end
local function get_disc_word_start(hlist_head,n)

  local item = n

  while item do
    local prev = item.prev

    if not (prev and is_possible_word_node(prev)) then
      return item
    end

    item = prev
  end

  return nil
end
local function get_next_hlist(n)

  local item = n.next

  while item do
    if item.id == NODE_ID_HLIST then
      return item
    end
    item = item.next
  end

  return nil

end
local function get_line_first_word(hlist_head)
  local word_start_node = nil

  for item in node.traverse(hlist_head) do

    if item.id == NODE_ID_GLYPH then
      if not word_start_node then
        word_start_node = item
      end
    end

    if not is_possible_word_node(item) then
      if word_start_node then
        return get_nodelist_text(word_start_node,item.prev)
      end
    end

  end
  if word_start_node then
    return get_nodelist_text(word_start_node,node.tail(hlist_head))
  else
    return nil
  end
end
local function get_context(index,incr,target_word_count)

  local result = ''
  local word_count = 0

  local i = index + incr
  while (
    i > 0 and i <= #hlist_segment_list and word_count < target_word_count
  ) do
    local t = hlist_segment_list[i]

    local item

    if t.type == SEGMENT_WORD then
      item = t.word
      word_count = word_count + 1
    elseif t.type == SEGMENT_SPACE then
      item = STR_SPACE
    elseif t.type == SEGMENT_MATH then
      item = STR_MATH
    end

    if incr > 0 then
      result = result .. item
    else
      result = item .. result
    end

    i = i + incr
  end

  return result

end
local function make_flag_node(code,context)
  if context then
    code = context .. ' ' .. code
  end

  local n = node.new('whatsit','pdf_literal')
  n.data = 'q ' .. code .. ' Q'

  return n

end
local FLAG_MARGIN_SHIFT = '1 0 0 1 4 -1 cm'
local FLAG_WIDTH = tex.sp('6pt')
local FLAG_VALID_CODE
  = '0 0 6 10 re .1 .7 .1 rg f ' -- Green background
  .. '1 w 1 G ' -- Line width 1pt, colour white
  .. '1 5.5 m 2.33333 3 l 5 8 l S' -- "Tick"
local FLAG_INVALID_CODE
  = '0 0 6 10 re .9 0 0 rg f ' -- Red background
  .. '1 w 1 G ' -- Line width 1pt, colour white
  .. '1 3 m 5 7 l 1 7 m 5 3 l S'
local FLAG_AMBIGUOUS_CODE = (
  '0 0 6 10 re .1 .7 .7 rg f ' -- Cyan background
  .. '1 0 0 1 2.85 6.25 cm ' -- Shift
  .. '1 w 1 G ' -- Line width 1pt, colour white
  .. '-1.55885 .9 m -1 1.4 -.65 2 0 2 c 1.3 2 1.8 1.3 1.8 .5 c '
  .. '1.8 -.75 0 -1.25 0 -2.5 c 0 -2.75 l S ' -- Question mark body
  .. '1.4 w 1 J ' -- Line width 1.4pt, line cap round
  .. '0 -4.25 m 0 -4.25 l S' -- Question mark dot
)
local FLAG_UNKNOWN_CODE = (
  '0 0 6 10 re .9 .9 0 rg f ' -- Yellow background
  .. '1 0 0 1 3 5 cm ' -- Shift
  .. '.8 w 1 G ' -- Linec with .8pt, colour white
  .. '-1.41421 -1.41421 m 1.41421 1.41421 l S ' -- Stroke of 0
  .. '2 0 m 2 1.2 1.2 3 0 3 c -1.2 3 -2 1.2 -2 0 c -2 -1.2 -1.2 -3 0 -3 c '
  .. '1.2 -3 2 -1.2 2 0 c h S' -- Outside of 0
)
local FLAG_VALID_NODE = make_flag_node(FLAG_VALID_CODE,FLAG_MARGIN_SHIFT)
local FLAG_INVALID_NODE = make_flag_node(FLAG_INVALID_CODE,FLAG_MARGIN_SHIFT)
local FLAG_AMBIGUOUS_NODE = make_flag_node(FLAG_AMBIGUOUS_CODE,FLAG_MARGIN_SHIFT)
local FLAG_UNKNOWN_NODE = make_flag_node(FLAG_UNKNOWN_CODE,FLAG_MARGIN_SHIFT)
local function insert_flag(head,current,status)

  if status == STATUS_VALID then
    node.insert_after(head,current,node.copy(FLAG_VALID_NODE))
  elseif status == STATUS_UNKNOWN then
    node.insert_after(head,current,node.copy(FLAG_UNKNOWN_NODE))
  elseif  status == STATUS_INVALID then
    node.insert_after(head,current,node.copy(FLAG_INVALID_NODE))
  elseif  status == STATUS_AMBIGUOUS then
    node.insert_after(head,current,node.copy(FLAG_AMBIGUOUS_NODE))
  end

end
local function write_flag(code)

  node.write(make_flag_node(code,'1 0 0 1 0 -1 cm'))

  local kern_n = node.new('kern','userkern')
  kern_n.kern=FLAG_WIDTH
  node.write(kern_n)

end
local function write_flag_valid()

  write_flag(FLAG_VALID_CODE)

end
local function write_flag_invalid()

  write_flag(FLAG_INVALID_CODE)

end
local function write_flag_ambiguous()

  write_flag(FLAG_AMBIGUOUS_CODE)

end
local function write_flag_unknown()

  write_flag(FLAG_UNKNOWN_CODE)

end
local hyphenation_list = {}
local hyphenation_count = 0
local function check_line_hyphenation(hlist)
  local last_disc = get_used_disc(hlist.head)
  if not last_disc then
    debug('  No disc node found at end of line')
    return
  end
  debug('  Disc node found at end of line')
  local hyphenation_index = node.has_attribute(last_disc,hyphen_attr)
  local t = hlist_segment_list[hyphenation_index]
  assert(t)
  assert(t.type == SEGMENT_WORD)
  local word = t.word
  local lang = t.lang
  if not unicode.utf8.match(word,'%a') then
    debug('  Divided "word" contains no letters')
    return
  end
  local next_line = get_next_hlist(hlist)

  if not next_line then
    debug('  No following line found (which should not happen)')
    return
  end
  local pre = get_nodelist_text(get_disc_word_start(hlist.head,last_disc))
  local pre_temp = trim_nonlettershyphens_start(pre)
  if pre_temp ~= '' then
    pre = pre_temp
  end
  local post = trim_nonlettershyphens_end(get_line_first_word(next_line.head))
  local word_orig = word
  word = trim_nonlettershyphens_both(word)

  local divided = pre .. post
  if string.sub(post,1,1) == '-' and string.sub(pre,-1,-1) == '-' then
    divided = pre .. string.sub(post,2,-1)
    debug('  Hyphenated word with split hyphen found: %s -> %s (%s/%s)',word,divided,pre,post)
  else
    debug('  Hyphenated word found: %s -> %s (%s/%s)',word,divided,pre,post)
  end

  local status = validate_division(lang,word,divided)
  local flag_mode = get_lang_option(lang,'flag-mode')
  if flag_mode == 1 or (flag_mode == 2 and status ~= STATUS_VALID) then
    insert_flag(hlist.head,last_disc,status)
  end
   local context =
    get_context(
      hyphenation_index,-1,get_lang_option(lang,'context-before')
    )
    .. word_orig ..
    get_context(
      hyphenation_index,1,get_lang_option(lang,'context-after')
    )
  hyphenation_count = hyphenation_count + 1
  hyphenation_list[hyphenation_count] = {
    lang = lang,
    word = word,
    divided = divided,
    index = hyphenation_count,
    context = context,
    status = status,
  }
  late_lua_n = node.new('whatsit','late_lua')
  late_lua_n.data =
    'lualisthyphen.set_hyphenation_page('
    .. hyphenation_count .. ',tex.count["c@page"])'

  node.insert_after(hlist.head,last_disc,late_lua_n)

end
local function set_hyphenation_page(index,page)

  hyphenation_list[index].page = page

end
local function post_linebreak(vlist_head,groupcode)

  debug('Post-linebreak processing start')
  for item in node.traverse(vlist_head) do
    if item.id == NODE_ID_HLIST then
      node.slide(item.head)
    end
  end
  local line_no = 0

  for item in node.traverse(vlist_head) do

    if item.id == NODE_ID_HLIST then
      line_no = line_no + 1
      debug('  Line no. %d',line_no)
      check_line_hyphenation(item)
    end

  end

  hlist_segment_list = {}

  debug('Post-linebreak processing end')

  return true

end
local LUA_LIST_HYPHEN_PRE_LINEBREAK = 'LUA_LIST_HYPHEN_PRE_LINEBREAK'
luatexbase.add_to_callback(
  'pre_linebreak_filter',
  pre_linebreak,
  LUA_LIST_HYPHEN_PRE_LINEBREAK
)

local LUA_LIST_HYPHEN_POST_LINEBREAK = 'LUA_LIST_HYPHEN_POST_LINEBREAK'
luatexbase.add_to_callback(
  'post_linebreak_filter',
  post_linebreak,
  LUA_LIST_HYPHEN_POST_LINEBREAK
)
local function equal_hyphenation_case_sensitive(a,b)
  return (
    a.word == b.word
    and
    a.divided == b.divided
  )
end
local function equal_hyphenation_case_insensitive(a,b)
  return (
    unicode.utf8.lower(a.word) == unicode.utf8.lower(b.word)
    and
    unicode.utf8.lower(a.divided) == unicode.utf8.lower(b.divided)
  )
end
local function lessthan_hyphenation_case_sensitive(a,b)
  return (
    a.word < b.word
    or
    (
      a.word == b.word
      and
      a.divided < b.divided
    )
    or
    (
      a.word == b.word
      and
      a.divided == b.divided
      and
      a.index < b.index
    )
  )
end
local function lessthan_hyphenation_case_insensitive(a,b)
  return (
    unicode.utf8.lower(a.word) < unicode.utf8.lower(b.word)
    or
    (
      unicode.utf8.lower(a.word) == unicode.utf8.lower(b.word)
      and
      unicode.utf8.lower(a.divided) < unicode.utf8.lower(b.divided)
    )
    or
    (
      unicode.utf8.lower(a.word) == unicode.utf8.lower(b.word)
      and
      unicode.utf8.lower(a.divided) < unicode.utf8.lower(b.divided)
      and
      a.index < b.index
    )
  )
end
local function sort_hyphenation_list_none(hyphenation_list)
  table.sort(
    hyphenation_list,
    function(a,b)
      return a.index < b.index
    end
  )
end
local function sort_hyphenation_list_case(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_sensitive
  )
end
local function sort_hyphenation_list_nocase(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_insensitive
  )
end
local function deduplicate_hyphenation_list_case(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_sensitive
  )
  list_uniq(
    hyphenation_list,
    equal_hyphenation_case_sensitive
  )
end
local function deduplicate_hyphenation_list_nocase(hyphenation_list)
  table.sort(
    hyphenation_list,
    lessthan_hyphenation_case_insensitive
  )
  list_uniq(
    hyphenation_list,
    equal_hyphenation_case_insensitive
  )
end
local function process_lang_hyphenation_list(lang_id,hyphenation_list)

  local output_non_typeset = get_lang_option(lang_id,'output-non-typeset')
  local include_valid = (get_lang_option(lang_id,'output-mode') == 0)

  list_filter(
    hyphenation_list,
    function (a)
      return (
        (a.status ~= STATUS_VALID or include_valid)
        and
        (a.page or output_non_typeset)
      )
    end
  )
  local unique = get_lang_option(lang_id,'unique')
  if unique == 1 then
    deduplicate_hyphenation_list_case(hyphenation_list)
  elseif unique == 2 then
    deduplicate_hyphenation_list_nocase(hyphenation_list)
  end
  local sort_mode = get_lang_option(lang_id,'sort')
  if sort_mode == 1 then
    sort_hyphenation_list_case(hyphenation_list)
  elseif sort_mode == 2 then
    sort_hyphenation_list_nocase(hyphenation_list)
  else
    sort_hyphenation_list_none(hyphenation_list)
  end

end
local function write_lang_hyphenation_list_standard(f,hyphenation_list,widths)

  for i,v in ipairs(hyphenation_list) do

    if v then
      f:write(v.divided .. '\n')
    end

  end

end
local function write_lang_hyphenation_list_verbose(f,hyphenation_list,show_status)
  local cols_word = 0
  local cols_divided = 0
  local cols_page = 0

  for _,h in pairs(hyphenation_list) do

    cols_word = math.max(
      cols_word,
      unicode.utf8.len(h.word)
    )
    cols_divided = math.max(
      cols_divided,
      unicode.utf8.len(h.divided)
    )
    cols_page = math.max(
      cols_page,
      unicode.utf8.len(tostring(h.page))
    )

  end
  cols_page = math.max(
    cols_page + unicode.utf8.len(STR_PAGE_PREFIX),
    unicode.utf8.len(STR_PAGE_NONE)
  )

  local cols_status = math.max(
    unicode.utf8.len(STR_VALID),
    unicode.utf8.len(STR_INVALID),
    unicode.utf8.len(STR_AMBIGUOUS),
    unicode.utf8.len(STR_UNKNOWN)
  ) + 2 -- Add for parentheses

  for i,v in ipairs(hyphenation_list) do

    if v then
      local page = v.page
      if page then
        page = STR_PAGE_PREFIX .. page
      else
        page = STR_PAGE_NONE
      end
      local status
      if show_status then
        status = v.status
        if status == STATUS_VALID then
          status = STR_VALID
        elseif status == STATUS_AMBIGUOUS then
          status = STR_AMBIGUOUS
        elseif status == STATUS_INVALID then
          status = STR_INVALID
        else
          status = STR_UNKNOWN
        end
        status = rpad(parenthesize(status),cols_status) .. STR_SPACE_TWO
      else
        status = ''
      end
      f:write(
        rpad(v.word,cols_word)
        .. STR_ARROW
        .. rpad(v.divided,cols_divided)
        .. STR_SPACE_TWO
        .. status
        .. lpad(page,cols_page)
        .. STR_SPACE
        .. STR_QUOTE_OPEN
        .. v.context
        .. STR_QUOTE_CLOSE
        .. '\n'
      )
    end

  end

end
local function get_settings_desc(lang_id)

  local settings_desc

  if get_lang_option(lang_id,'output-verbose') then
    settings_desc = string.format(
      'verbose=true,context-before=%d,context-after=%s',
      get_lang_option(lang_id,'context-before'),
      get_lang_option(lang_id,'context-after')
    )
  else
    settings_desc = 'verbose=false'
  end

  local ALL_NONVALID = {
    [0] = 'all',
    [1] = 'non-valid',
  }
  settings_desc = settings_desc .. string.format(
    ',output-mode=%s',
    ALL_NONVALID[get_lang_option(lang_id,'output-mode')]
  )
  settings_desc = settings_desc .. string.format(
    ',include-non-typeset=%s',
    get_lang_option(lang_id,'output-non-typeset')
  )
  local NONE_CASE_NOCASE = {
    [0] = 'none',
    [1] = 'case',
    [2] = 'nocase'
  }
  settings_desc = settings_desc .. string.format(
    ',sort=%s,unique=%s',
    NONE_CASE_NOCASE[get_lang_option(lang_id,'sort')],
    NONE_CASE_NOCASE[get_lang_option(lang_id,'unique')]
  )

  return settings_desc

end
local function prefix_output_directory(path)

  local output_directory = status.output_directory
  if not output_directory then
    return path
  end

  if string.sub(output_directory,-1,-1) == '/' then
    return output_directory .. path
  else
    return output_directory .. '/' .. path
  end

end
local function process_write_lang_hyphenation_list(
    lang_id,hyphenation_list
)
  process_lang_hyphenation_list(lang_id,hyphenation_list)

  local output_path
  local lang_desc

  local cname = lang_id_cname_table[lang_id]

  debug('Processing and writing hyphenation lists for "%s" (lang. ID %d)',cname,lang_id)

  if cname then
    output_path = get_lang_option(lang_id,'output-path')
    if not output_path then
      output_path = get_lang_option(LANG_DEFAULT,'output-prefix')
        .. cname .. get_lang_option(LANG_DEFAULT,'output-extension')
    end
    lang_desc = string.format('language "%s" (ID %s)',cname,lang_id)
  else
    output_path = get_lang_option(LANG_DEFAULT,'output-prefix')
      .. lang_id .. get_lang_option(LANG_DEFAULT,'output-extension')
    lang_desc = 'language with ID ' .. lang_id
  end

  output_path = prefix_output_directory(output_path)
  debug('Output path: "%s"',output_path)

  local output_header = get_lang_option(lang_id,'output-header')
  local output_verbose = get_lang_option(lang_id,'output-verbose')

  local division_dict_read = lang_id_division_dict_read_table[lang_id]

  local f = io.open(output_path,'w')

  if output_header then
    f:write('% This file was generated by lua-list-hyphen\n')
    f:write('% Hyphenations for ' ..  lang_desc .. '\n')
    f:write('% General settings: ' .. get_settings_desc(lang_id) .. '\n')
    local division_dict_path = lang_id_division_dict_path_table[lang_id]
    if division_dict_path and division_dict_read then
      f:write(
        '% Division dictionary read from: "' .. division_dict_path .. '"\n'
      )
    end
  end

  if output_verbose then
    write_lang_hyphenation_list_verbose(f,hyphenation_list,division_dict_read)
  else
    write_lang_hyphenation_list_standard(f,hyphenation_list,division_dict_read)
  end

  f:close()

end
local function process_write_hyphenation_lists()

  local lang_hyphenation_table = {}
  local lang_widths_table = {}
  for _,h in pairs(hyphenation_list) do

    local lang = h.lang

    local t = lang_hyphenation_table[lang]
    if not t then
      lang_hyphenation_table[lang] = {}
      t = lang_hyphenation_table[lang]
    end

    table.insert(t,h)

  end
  for lang_id,hyphenations in pairs(lang_hyphenation_table) do
    process_write_lang_hyphenation_list(lang_id,hyphenations)
  end

end
lualisthyphen = lualisthyphen or {}

lualisthyphen.lang_load_names = lang_load_names
lualisthyphen.store_lang_id_name = store_lang_id_name

lualisthyphen.set_debug = set_debug
lualisthyphen.set_global_option = set_global_option
lualisthyphen.set_lang_option = set_lang_option
lualisthyphen.lang_options_check_name_known = lang_options_check_name_known

lualisthyphen.preprocessors = preprocessors

lualisthyphen.set_hyphenation_page = set_hyphenation_page

lualisthyphen.process_write_hyphenation_lists = process_write_hyphenation_lists
lualisthyphen.write_flag_valid = write_flag_valid
lualisthyphen.write_flag_invalid = write_flag_invalid
lualisthyphen.write_flag_ambiguous = write_flag_ambiguous
lualisthyphen.write_flag_unknown = write_flag_unknown

