--[[----------------------------------------------------------------

            REIMPLEMENTATION OF NATBIB STYLES FOR NBIBTEX

This file implements the three 'natbib' bibliography styles: plainnat,
abbrvnat, and unsrtnat.  It also implements the 'improved' style
plainnatx.  One advantage of using Lua as opposed to the original
Bibtex language is that we can easily use higher-order functions to
reuse code across styles.

The styles generated by this file have two modes.  In 'compatibility'
mode, every effort is made to produce output that is bit-for-bit
identical with the original 'natbib' styles.  In non-compatibility
mode, I have felt free to change the original styles to reduce clutter
and otherwise make the bibliography easier to read.

If you are reading this source code to change the way a particular
entry looks, search for 'style.' followed by the type of that entry. For
example, if you want to change the formatting for a conference paper,
search for 'style.inproceedings'.

--]]----------------------------------------------------------------

local plain_compatible = true

local plain_compatible = plain_compatible
if plain_compatible == nil then plain_compatible = false end
                                       --- default : plain not compatible

local suppress = { doi = true, url = false } 

  -- In non-compatibility mode, certain fields are suppressed from
  -- (almost) every bibtex entry.  By default I suppress 'doi' and
  -- 'url', but you can suppress others

if not bibtex then
  require 'bibtex'
end
local bst = bibtex.bst -- used to implement style

-- create a bibtex style
local function make_style(style_base, min_etal, chicago, compatible)
  -- style_base is 'abbrv', 'plain', or 'unsrt'
  -- min_etal is minimum number of authors require to force 'First et al'
  -- chicago == chicago-style dates
  -- compatible == output should be identical to existing natbib styles
  min_etal = min_etal or 4
  if chicago    == nil then chicago    = (style_base == 'plain') end
  if compatible == nil then compatible = (style_base ~= 'plain') end

  local suppress = compatible and { } or suppress
    -- compatible styles suppress no fields

  local abbrv = style_base == 'abbrv' -- drives several short/long formats



  --[=[----------------------------------------------------------------
  The original Bibtex is written in a PostScript-like language that has
  no function parameters; all values are passed on a stack.  In this
  language, each entry in turn has its fields placed in global
  variables, after which the engine applies a function named in an
  ITERATE directive.

  To produce output identical to the original, it is easiest to mimic
  this style.  We therefore arrange to take a citation and place its
  information into variables that are shared by all the functions
  defined below.  The full citation is in [[citation]], the key is in
  [[key]], and a table of field values is in [[fields]].  Because with
  nbibtex, the original entry key may be different from the actual
  citation key, we provide the original as [[warning_key]].  All these
  variables are set by function [[with_entry]], which then applies a
  function.  The ITERATE directive is replaced by a loop that calls
  [[with_entry]] with each entry and a suitable function.
  --]=]----------------------------------------------------------------

  local citation, key, warning_key, fields --- set to current entry
  local function with_entry(c, f, ...)
    citation, key, fields = c, c.key, c.fields
    warning_key = citation.orig_key or key
    f(...)
  end


  ----------------------------------------------------------------------
  ----------------------------- INTERFACES -----------------------------
  ----------------------------------------------------------------------

  --[[----------------------------------------------------------------
  Below see sections on
    * Abbreviations
    * Utilities
    * Error handling
    * Output
    * Field infrastructure
    * Formatting support
    * Sorting
  
  --]]----------------------------------------------------------------
  

  ----- ABBREVIATIONS -----
  local find = string.find
  local gsub = string.gsub
  local stringf = string.format
  local function eprintf(...) return io.stderr:write(stringf(...)) end
  local empty = bst.empty
  -------------------------

  ----- UTILITIES -----
  local defaulting -- apply to table to make 'default' field the default
  local nonempty   -- return argument if not empty; otherwise return nil
  ---------------------

  ----- ERROR HANDLING -----
  local dump     -- dump(when)      -- dump current entry, with optional tag 'when'
  local warnf    -- warnf(fmt,...)  -- issue warning for current citation
  local check    -- check(k, s/f)   -- if s or f() is empty, warning of missing k
  --------------------------

  ----- OUTPUT IN SENTENCE STRUCTURE -----
  -- Output is to a given file; it can be raw with writef or structured with output().

  local output_to      -- output_to(file) --- emit entries to this file
  local writef         -- writef(fmt,...) --- raw write to file

  -- As written to the output, an entry has a prolog, an epilog, and contents.
  -- The contents is a sequence of blocks.  A block is a sequence of sentences,
  -- each terminated by a period.  A sentence is a sequence of fragments separated
  -- by commas.  A single fragment is written by output().

  local new_block      -- new_block()     --- start a new block
  local new_sentence   -- new_sentence()  --- start a new sentence
  local output         -- output(...)     --- emit each argument as one fragment
  local output_bibitem -- emit an entry's prolog
  local fin_entry      -- emit an entry's epilog

  local output_caps    -- output_caps()   --- returns true iff next fragment needs caps

  -- The actual story about output() is more complicated.
  -- It emits zero or more items, where an item is
  --    * a string (emitted as fragment)
  --    * nil (ignored)
  --    * a function returning zero or more items (run and emitted)
  --    * a list of items in a Lua table
  -- Useful effects can be obtained by including new_block or new_sentence
  -- among the arguments to output()
  
  -- The mismatched names for prolog and epilog are inherited from bibtex.
  ----------------------------------------


  ----- FIELD INFRASTRUCTURE -----

  -- Support for formatting names
  local make_full_names   -- make_full_names()
          -- return names by which entry is identified in author-date styles
  local format_names      -- format_names(s[, fmt])
          -- return argument, formatting using given format or default for style
  local format_full_names -- format_full_names(s)
          -- return argument formatted as full names
  

  -- Here follow several higher-order functions to manipulate fields.
  -- Function [[nonempty_field(fieldname, f)]] returns a nullary function
  -- that returns [[f]] applied to the field, or if the field is empty, 
  -- returns the empty string.
  -- One source of useful functions [[f]] is function [[fieldf]],
  -- which is a curried form of [[string.format]]. It can usefully be
  -- partially applied and passed to [[nonempty_field]].  Applying
  -- [[nb]] to a function [[f]] produces a new function that first
  -- creates a new block, then applies [[f]].
  local nonempty_field
  local fieldf
  local nb
  --------------------------------

  ----- FORMATTING SUPPORT -----
  local n_dashify -- return argument with each single dash converted to a TeX en-dash
  local emphasize -- return argument wrapped in \emph{...}
  local box_small -- if argument is short, wrap in mbox
  local tie_or_space_connect --
          -- connect(s, t): join s and t with tie if t is short and space if long
  ------------------------------

  ----- SORTING -----
  local bibsort -- sort list of citations and assign 'extra_label' fields
  -------------------





  --[[----------------------------------------------------------------

                              IMPLEMENTATIONS

  --]]----------------------------------------------------------------

  do
    local default_meta = { __index = function(t) return t.default end }
    function defaulting(t) setmetatable(t, default_meta); return t end
  end

  function nonempty(s) if not bst.empty(s) then return s end end

  ----------------------------------------------------------------------
  --------------------------- ERROR HANDLING ---------------------------
  ----------------------------------------------------------------------

  function dump(when)
    eprintf('%s========= %s ========\n', when and 'at ' .. when .. ': ' or '', key)
    for k, v in pairs(fields) do
      eprintf('  %s = %q\n', k, type(v) == 'string' and v or tostring(v))
    end
    eprintf('  purify(fields.label) == %q\n', bst.purify(fields.label or ''))
    eprintf '\n'
  end


  function warnf(...)
    io.stderr:write('Warning--')
    io.stderr:write(string.format(...))
    io.stderr:write(string.format('\n--line %d of file %s\n',
                      citation.line, string.gsub(citation.file, '.*[/\\]', '')))
  end

  function check(k, s)
    if type(s) == 'function' then s = s() end --- be tolerant
    if empty(s) then
      warnf('empty %s in %s', k, warning_key)
    else
      return s
    end
  end


  ----------------------------------------------------------------------
  -------------------- OUTPUT IN SENTENCE STRUCTURE --------------------
  ----------------------------------------------------------------------

  do
    local bblfile -- open file for bbl file
    function output_to(bbl) bblfile = bst.writer(bbl) end
    function writef(...) return bblfile:write(string.format(...)) end

    -- output is done with a delay of one fragment, so that state changes
    -- may be used to add a period to the preceding fragment, for example.
    local pending_out = '' --- delayed fragment awaiting output

    local output_state --- tracks where we are in sentence and block
    local before_all, mid_sentence, after_sentence, after_block =
      'bef', 'mids', 'afters', 'afterbl'

    function output_caps()
      -- anything not in the middle of a sentence should be capitalized
      return output_state ~= mid_sentence
    end


    local function output_one(s)
      -- emit a single fragment, or if a fragment is empty, do nothing
      -- (replaces both 'output' and 'output.nonnull' from original Bibtex)
      if empty(s) then return end
    --  writef('[outputting %s:]', string.len(s) > 10 and string.sub(s, 1, 10) .. '...' or s)
      if output_state == mid_sentence then
        writef('%s, ', pending_out)
      elseif output_state == after_block then
        writef('%s\n\\newblock ', bst.add_period(pending_out))
      elseif output_state == before_all then
        writef('%s', pending_out)
      elseif output_state == after_sentence then
        writef('%s ', bst.add_period(pending_out))
      else
        assert(false, 'Bad output state ' .. (output_state or 'nil'))
      end
      output_state = mid_sentence
      pending_out = s
    --  writef('[:done]')
    end

    do
      -- The general output() is table-driven
      local output_funs = { }
      output_funs['string']   = output_one
      output_funs['nil']      = function() end
      output_funs['table']    = function(t) return output(unpack(t)) end
      output_funs['function'] = function(f) return output(f()) end
      function output(...)
        local n = select('#', ...)
        local arg = { ... }
        for i = 1, n do --- can't use ipairs b/c arg might be nil
          local v = arg[i]
          assert(output_funs[type(v)], "Bad value to output()") (v)
        end
      end
    end

    -- prolog
    function output_bibitem()
      local full = make_full_names()
      if full == fields.short_list then full = '' end
      writef('\n\\bibitem[%s)%s]{%s}\n', fields.label, full, key)
      if false and style_base == 'abbrv' then -- for diagnostics
        writef('\\sortkey{%s}\n', fields.sort_key)
        writef('\\sortlabel{%s}\n', fields.sort_label)
      end
      output_state = before_all
      pending_out = ''
    end

    -- epilog
    function fin_entry()
      writef('%s\n', bst.add_period(pending_out))
      pending_out = ''
      if citation.warningmsg then writef('%s\n', citation.warningmsg) end
      for _, a in ipairs(citation.also_cited_as or { }) do
        writef("\\letbib{%s}{%s}\n", a, key)
      end
    end

    -- state changers
    function new_block()
      if output_state ~= before_all then
        output_state = after_block
      end
    end

    function new_sentence()
      if output_state ~= after_block and output_state ~= before_all then
        output_state = after_sentence
      end
    end
  end
  ----------------------------------------



  ----------------------------------------------------------------------
  ------------------------- FORMATTING SUPPORT -------------------------
  ----------------------------------------------------------------------

  function emphasize(s) return empty(s) and '' or "\\emph{" .. s .. '}' end

  function n_dashify(s)
    return (string.gsub(s, '%-+', function(s) return s == '-' and '--' or s end))
  end

  function box_small(s)  -- box small page ranges to avoid splitting
    if bst.text_length(s) < 7 then
      return stringf("\\mbox{%s}", s)
    else
      return s
    end
  end

  function tie_or_space_connect(s, t)
    if bst.text_length(t) < 3 then
      return stringf('%s~%s', s, t)
    else
      return stringf('%s %s', s, t)
    end
  end



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

  local function key_if_empty(s) -- return [[key]] field if [[s]] is empty
    if empty(s) then
      return fields.key or ''
    else
      return ''
    end
  end
  local format_key = key_if_empty --- name compatible with original bibtex


  ----------------------------------------------------------------------
  ------------------------ FIELD INFRASTRUCTURE ------------------------
  ----------------------------------------------------------------------

  local function nonempty_field(k, f)
    -- takes a nonempty s = fields[k] and returns f(s)
    assert(k, 'missing field name in nonempty_field')
    if suppress[k] then
      return function () end
    else
      return function()
               local s = fields[k]
               if empty(s) then return '' else return f(s) end
             end
    end
  end
  -- a useful f to pass to nonempty_field is 'fieldf' (curried stringf)
  function fieldf(fmt) return function(s) return stringf(fmt, s) end end

  -- a wrapper for f puts output after a new block
  function nb(f) return function(s) new_block() return f(s) end end



  do ---- NAME FORMATTING
    local default_name_format =
      abbrv and "{f.~}{vv~}{ll}{, jj}" or "{ff~}{vv~}{ll}{, jj}"

    function format_names(s, fmt)
      fmt = fmt or default_name_format
      local s = bst.commafy(bst.format_names(fmt, bst.namesplit(s)))
      return (gsub(s, ' and others$', ' et~al.'))
    end
    function format_full_names(s) return format_names(s, "{vv~}{ll}") end
  end

  ----- BASIC FORMATTING UNITS -----
  -- 
  -- The general idea here is that each field has its own characteristic
  -- formatting.  The details are a little more complicated:  Names can be
  -- formatted either as full names or using the default formatting;
  -- a book title is italicized where other titles are not.  But many things
  -- are clean.

  local format_isbn   = nonempty_field('isbn', nb(fieldf 'ISBN %s'))
  local format_issn   = nonempty_field('issn', nb(fieldf 'ISSN %s'))
  local format_url    = nonempty_field('url',  nb(fieldf 'URL \\url{%s}'))
  local format_doi    = nonempty_field('doi',  nb(fieldf '\\doi{%s}'))
  local format_eid =
    nonempty_field('eid', function (s) return tie_or_space_connect("art.", s) end)

  local format_title  = nonempty_field('title', bst.title)
  local format_btitle = nonempty_field('title', emphasize) -- book title

  local author_full = nonempty_field('author', format_full_names)
  local editor_full = nonempty_field('editor', format_full_names)

  local function author_editor_full()
    return nonempty(author_full()) or nonempty(editor_full()) or ''
  end

  local format_authors = nonempty_field('author', format_names)
  local format_editors = nonempty_field('editor',
                                        function(e)
                                          if table.getn(bst.namesplit(e)) > 1 then
                                            return format_names(e) .. ', editors'
                                          else
                                            return format_names(e) .. ', editor'
                                          end
                                        end)

  function make_full_names()
    if citation.type == 'book' or citation.type == 'inbook' then
      return author_editor_full()
    elseif citation.type == 'proceedings' then
      return editor_full()
    else
      return author_full()
    end
  end

  ----- FORMATTING DATES -----

  local function format_date()
    local year = fields.year or ''
    if empty(year) then
      warnf('empty year in %s', warning_key)
      return fields.extra_label or ''
    elseif not empty(fields.month) then
      if chicago then
        return stringf('%s (%s)%s', year, fields.month, fields.extra_label or '')
      else
        return stringf('%s %s%s', fields.month, year, fields.extra_label or '')
      end
    else
      return year .. (fields.extra_label or '')
    end
  end
    
  local chicago_date, nonchicago_date
   -- each entry emits both dates, one of which is nil
   -- according to which convention is used

  if chicago then
    function chicago_date() return { check('year', format_date()), new_block } end
    function nonchicago_date() end
  else
    function chicago_date() end
    function nonchicago_date() return check('year', format_date()) end
  end
  

  local function either_or_check(fnames, f)
    if not empty(f) then
      warnf("can't use both %s in %s", fnames, warning_key)
    end
  end


  ----------------------------------------------------------------------
  ---------------- FORMATTING COMPLICATED HAIRY THINGS -----------------
  ----------------------------------------------------------------------

  --[[ These are more complicated formatted fragments, typically
       composed of a group of one to a few fields                    ]]--

  local format_bvolume =
    nonempty_field('volume',
                   function(s)
                     local v = tie_or_space_connect("volume", s)
                     either_or_check("volume and number", fields.number)
                     local series = fields.series
                     if not empty(series) then
                       if not compatible
                       and (series == "Lecture Notes in Computer Science"
                            or series == "LNCS") then
                         return emphasize 'LNCS' .. ' ' .. v
                       else
                         return v .. ' of ' .. emphasize(series)
                       end
                     else
                       return v
                     end
                   end)

  local function format_number_series()
    if empty(fields.volume) then
      if empty(fields.number) then
        return fields.series or ''
      else
        local num = output_caps() and 'Number' or 'number'
        num = tie_or_space_connect(num, fields.number)
        if empty(fields.series) then
          warnf("there's a number but no series in %s", warning_key)
          return num
        else
          return stringf('%s in %s', num, fields.series)
        end
      end
    else
      return ''
    end
  end

  local format_edition =
    nonempty_field('edition',
                   function(s)
                     local f = output_caps() and bst.title or bst.lower
                     return stringf('%s edition', f(s))
                   end)

  local format_pages =
    nonempty_field('pages',
                   compatible and
                     function (s)
                       if string.find(s, '[%-%,%+]') then
                         return tie_or_space_connect('pages', n_dashify(s))
                       else
                         return tie_or_space_connect('page', s)
                       end
                     end
                   or
                     function (s)
                       if string.find(s, '[%-%,%+]') then
                         return stringf('pages~%s', box_small(n_dashify(s)))
                       else
                         return stringf('page~%s', s)
                       end
                     end
                 )

  local function format_vol_num_other(format_other, raw_other)
    return function()
             local v = fields.volume or ''

             if not empty(fields.number) then
               v = stringf('%s\\penalty0 (%s)', v, fields.number)
               if empty(fields.volume) then
                 warnf("there's a number but no volume in ", warning_key)
               end
             end

             if not empty(fields.pages) then
               if empty(v) then
                 v = format_other()
               else
                 v = stringf('%s:\\penalty0 %s', v, raw_other())
               end
             end

             return v
           end
  end

  local format_vol_num_pages =
    format_vol_num_other(format_pages, function() return n_dashify(fields.pages) end)

  local format_vol_num_eid =
    format_vol_num_other(format_eid, function() return fields.eid end)

  local function format_chapter_pages()
    if empty(fields.chapter) then
      return format_pages()
    else
      local ty = bst.lower(nonempty(fields.type) or 'chapter')
      local c = tie_or_space_connect(ty, fields.chapter)
      if nonempty(fields.pages) then
        c = stringf('%s, %s', c, format_pages())
      end
      return c
    end
  end

  local format_in_ed_booktitle =
    nonempty_field('booktitle',
                   function (bt)
                     if empty(fields.editor) then
                       return 'In ' .. emphasize(bt)
                     else
                       return stringf('In %s, %s', format_editors(), emphasize(bt))
                     end
                   end)

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

  local function format_tr_number()
    local ty = nonempty(fields.type) or 'Technical Report'
    if empty(fields.number) then
      return bst.title(ty)
    else
      return tie_or_space_connect(ty, fields.number)
    end
  end

  local function format_article_crossref()
    local pfx
    if empty(fields.key) then
      if empty(fields.journal) then
        warnf("need key or journal for %s to crossref %s ",
              warning_key, fields.crossref)
      else
        pfx = stringf("In \\emph{%s}", fields.journal)
      end
    else
      pfx = "In " .. fields.key
    end
    return stringf('%s \\citep{%s}', pfx, fields.crossref)
  end

  local function format_book_crossref()
    local pfx
    if empty(fields.volume) then
      warnf("empty volume in %s's crossref of %s", warning_key, fields.crossref)
      pfx = "In "
    else
      pfx = tie_or_space_connect("Volume", fields.volume) .. ' of '
    end
    if empty(fields.editor) or (fields.editor or '') == (fields.author or '') then
      if not empty(fields.key) then
        pfx = pfx .. fields.key
      elseif not empty(fields.series) then
        pfx = stringf("%s\\emph{%s}", pfx, fields.series)
      else
        warnf("need editor, key, or series for %s to crossref %s",
              warning_key, fields.crossref)
      end
    end
    return stringf("%s, \\citet{%s}", pfx, fields.crossref)
  end


  local function format_incoll_inproc_crossref()
    local pfx
    if empty(fields.editor) or (fields.editor or '') == (fields.author or '') then
      if not empty(fields.key) then
        pfx = "In " .. fields.key
      elseif not empty(fields.booktitle) then
        pfx = "In \\emph{" .. fields.booktitle .. "}"
      else
        warnf("need editor, key, or booktitle for %s to crossref %s",
              warning_key, fields.crossref)
        pfx = ""
      end
    else
      pfx = "In "
    end
    return stringf("%s \\citet{%s}", pfx, fields.crossref)
  end


  ----------------------------------------------------------------
  -----------   FORMATTING FUNCTION FOR EACH ENTRY TYPE ----------
  ----------------------------------------------------------------

  local style = defaulting { }
    --- contains function for each entry type, plus out field for writing

  function style.article()
    output_bibitem()
    output(check('author', format_authors()),
           format_key(fields.author),
           new_block,
           chicago_date,
           check('title', format_title()),
           new_block)
    if not fields.crossref then
      output(check('journal', emphasize(fields.journal)),
             format_vol_num_pages,
             nonchicago_date)
    else
      output(format_article_crossref, format_pages)
    end
    output(format_issn, format_doi, format_url, new_block, fields.note)
    fin_entry()
  end

  function style.book()
    output_bibitem()
    if empty(fields.author) then
      output(check("author and editor", format_editors()), format_key(fields.editor))
    else
      output(format_authors)
      if not fields.crossref then
        either_or_check('author and editor', fields.editor)
      end
    end
    output(new_block, chicago_date(), check('title', format_btitle()))
    if not fields.crossref then
      output(format_bvolume, new_block, format_number_series, new_sentence,
             check("publisher", fields.publisher), fields.address)
    else
      output(new_block, format_book_crossref)
    end
    output(format_edition, nonchicago_date, format_isbn, format_doi, format_url,
           new_block, fields.note)
    fin_entry()
  end

  function style.booklet()
    output_bibitem()
    output(format_authors, format_key(fields.author),
           new_block, check("title", format_title()))
    if not (empty(fields.howpublished) and empty(fields.address)) then new_block() end
    output(fields.howpublished, fields.address, format_date, format_isbn, format_doi,
           format_url, new_block, fields.note)
    fin_entry()
  end

  function style.inbook()
    output_bibitem()
    if empty(fields.author) then
      output(check("author and editor", format_editors()), format_key(fields.editor))
    else
      output(format_authors)
      if not fields.crossref then
        either_or_check("author and editor", fields.editor)
      end
    end
    output(new_block, chicago_date, check("title", format_btitle()))
    if not fields.crossref then
      output(format_bvolume, check("chapter and pages", format_chapter_pages),
             new_block, format_number_series, new_sentence, 
             check("publisher", fields.publisher), fields.address)
    else
      output(check("chapter and pages", format_chapter_pages), 
             new_block, format_book_crossref)
    end
    output(format_edition, nonchicago_date, format_isbn, format_doi, format_url,
           new_block, fields.note)
    fin_entry()
  end

  function style.incollection()
    output_bibitem()
    output(check("author", format_authors()), format_key(fields.author),
           new_block, chicago_date, check("title", format_title()), new_block)
    if not fields.crossref then
      output(check("booktitle", format_in_ed_booktitle()),
             format_bvolume, format_number_series, format_chapter_pages,
             new_sentence, check("publisher", fields.publisher),
             fields.address, format_edition, nonchicago_date)
    else
      output(format_incoll_inproc_crossref, format_chapter_pages)
    end
    output(format_isbn, format_doi, format_url, new_block, fields.note)
    fin_entry()
  end

  function style.inproceedings()
    output_bibitem()
    output(check("author", format_authors()), format_key(fields.author),
           new_block, chicago_date, check("title", format_title()), new_block)
    if not fields.crossref then
      output(check("booktitle", format_in_ed_booktitle()),
             format_bvolume, format_number_series, format_pages)
      if empty(fields.address) then
        if not (empty(fields.organization) and empty(fields.publisher)) then
          output(new_sentence, fields.organization, fields.publisher)
        end
        output(nonchicago_date)
      else
        output(fields.address, nonchicago_date,
               new_sentence, fields.organization, fields.publisher)
      end
    else
      output(format_incoll_inproc_crossref, format_pages)
    end
    output(format_isbn, format_doi, format_url, new_block, fields.note)
    fin_entry()
  end

  style.conference = style.inproceedings

  function style.manual()
    output_bibitem()
    output(format_authors, format_key(fields.author), new_block, chicago_date,
           check("title", format_btitle()))
    if not (empty(fields.organization) and empty(fields.address)) then
      output(new_block, fields.organization, fields.address)
    end
    output(format_edition, nonchicago_date, format_url, new_block, fields.note)
    fin_entry()
  end

  local function thesis(default_type, format_title)
    return
      function()
        local type = empty(fields.type) and default_type or bst.title(fields.type)
        output_bibitem()
        output(check("author", format_authors()), format_key(fields.author),
               new_block, chicago_date, check("title", format_title()),
               new_block, type, check("school", fields.school), fields.address,
               nonchicago_date, format_url, new_block, fields.note)
        fin_entry()
      end
    end

  style.mastersthesis = thesis("Master's thesis", format_title)

  function style.misc()
    -- misc cannot be refactored to use chicago_date
    output_bibitem()
    output(format_authors, format_key(fields.author))
    if chicago then
      output(new_block, check("year", format_date()))
    end
    if not (empty(fields.title) and empty(fields.howpublished)) then
      output(new_block, format_title)
      if not empty(fields.howpublished) then
        output(new_block, fields.howpublished)
      end
    end
    if not chicago then output(format_date()) end
    output(format_url, new_block, fields.note)
    do 
      -- if everything interesting is empty, complain
      local found
      for _, f in
        ipairs { 'author', 'title', 'howpublished', 'month', 'year', 'note', 'key' }
      do
        if not empty(fields[f]) then found = true; break end
      end    
      if not found then warnf("all relevant fields are empty in %s", warning_key) end
    end
    fin_entry()
  end

  style.phdthesis = thesis("PhD thesis", format_btitle)

  function style.proceedings()
    output_bibitem()
    output(format_editors, format_key(fields.editor), new_block, 
           chicago_date, check("title", format_btitle()), format_bvolume,
           format_number_series, fields.address, nonchicago_date,
           new_sentence, fields.organization, fields.publisher, 
           format_isbn, format_doi, format_url, new_block, fields.note)
    fin_entry()
  end

  function style.techreport()
    output_bibitem()
    output(check("author", format_authors()), format_key(fields.author),
           new_block, chicago_date, check("title", format_title()),
           new_block, format_tr_number, check("institution", fields.institution),
           fields.address, nonchicago_date, format_url, new_block, fields.note)
    fin_entry()
  end

  function style.unpublished()
    output_bibitem()
    output(check("author", format_authors()), format_key(fields.author),
           new_block, chicago_date, check("title", format_title()),
           format_url, new_block, check("note", fields.note), nonchicago_date)
    fin_entry()
  end

  style.default = assert(style.misc, 'empty default function')



  ----------------------------------------------------------------
  ---------------------- MACROS ----------------------------------
  ----------------------------------------------------------------
  local macros

  if abbrv then

    function macros()
      local macros = bst.months(3)
      for k, v in pairs(macros) do macros[k] = v .. "." end
      macros.may = "May"
      macros.jun = "June"
      macros.jul = "July"
      macros.sep = "Sept."

      macros.acmcs = "ACM Comput. Surv."
      macros.acta = "Acta Inf."
      macros.cacm = "Commun. ACM"
      macros.ibmjrd = "IBM J. Res. Dev."
      macros.ibmsj = "IBM Syst.~J."
      macros.ieeese = "IEEE Trans. Softw. Eng."
      macros.ieeetc = "IEEE Trans. Comput."
      macros.ieeetcad = "IEEE Trans. Comput.-Aided Design Integrated Circuits"
      macros.ipl = "Inf. Process. Lett."
      macros.jacm = "J.~ACM"
      macros.jcss = "J.~Comput. Syst. Sci."
      macros.scp = "Sci. Comput. Programming"
      macros.sicomp = "SIAM J. Comput."
      macros.tocs = "ACM Trans. Comput. Syst."
      macros.tods = "ACM Trans. Database Syst."
      macros.tog = "ACM Trans. Gr."
      macros.toms = "ACM Trans. Math. Softw."
      macros.toois = "ACM Trans. Office Inf. Syst."
      macros.toplas = "ACM Trans. Prog. Lang. Syst."
      macros.tcs = "Theoretical Comput. Sci."
      return macros
    end

  else --- not abbreviated

    function macros()
      local macros = bst.months()

      macros.acmcs = "ACM Computing Surveys"
      macros.acta = "Acta Informatica"
      macros.cacm = "Communications of the ACM"
      macros.ibmjrd = "IBM Journal of Research and Development"
      macros.ibmsj = "IBM Systems Journal"
      macros.ieeese = "IEEE Transactions on Software Engineering"
      macros.ieeetc = "IEEE Transactions on Computers"
      macros.ieeetcad = "IEEE Transactions on Computer-Aided Design of Integrated Circuits"
      macros.ipl = "Information Processing Letters"
      macros.jacm = "Journal of the ACM"
      macros.jcss = "Journal of Computer and System Sciences"
      macros.scp = "Science of Computer Programming"
      macros.sicomp = "SIAM Journal on Computing"
      macros.tocs = "ACM Transactions on Computer Systems"
      macros.tods = "ACM Transactions on Database Systems"
      macros.tog = "ACM Transactions on Graphics"
      macros.toms = "ACM Transactions on Mathematical Software"
      macros.toois = "ACM Transactions on Office Information Systems"
      macros.toplas = "ACM Transactions on Programming Languages and Systems"
      macros.tcs = "Theoretical Computer Science"
      return macros
    end

  end


  ----------------------------------------------------------------
  ----------------------------- LABELS ---------------------------
  ----------------------------------------------------------------

  -- Make the label used to refer to citation (normally author or editor, but...)
  local label_from -- take list of fields; use first nonempty one to form label, e.g.
                   -- label_from { 'author', 'editor' }
  do
    local lab_name_format = compatible and '{vv~}{ll}' or '{vv }{ll}'

    function format_lab_names(s) -- names as used in a label
      -- if too many names, use 'et~al.'
      local t = bst.namesplit(s)
      if table.getn(t) >= min_etal then
        return bst.format_name(lab_name_format, t[1]) .. ' et~al.'
      else
        return format_names(s, lab_name_format)
      end
    end

    local labformat = defaulting {
      -- how to form label text from each kind of field used
      default = format_lab_names,
      key = function(s) return s end,
      organization = function(s) return bst.text_prefix(gsub(s, '^The ',''), 3) end,
    }
    function label_from(fs)
      return function()
               for _, f in ipairs(fs) do
                 if nonempty(fields[f]) then
                   return labformat[f](fields[f])
                 end
               end
               return nonempty(fields.key) or string.sub(key, 1, 3)
                                 -- label of last restort
             end
    end
  end


  ----------------------------------------------------------------
  ---------------------------- SORTING ---------------------------
  ----------------------------------------------------------------

  do
    --[[--------------------------------------------------------------
    Sorting works as follows: 
       1. Assign labels (called 'sort_key' and 'sort_label').
       2. Sort by 'sort_key'.
       3. Make a forward-backward pass to assign an 'extra' part to
          each label.  Thus, if two papers by the same author are both
          published in 2005, they become '2005a' and '2005b'.  But if
          only one is, it stays '2005', not '2005a'.
       4. Sort again by 'sort_label'.
    All sorts are stable
    --]]--------------------------------------------------------------

    --- Internal sorting interfaces
    local presort = { }
       -- Pass 1: presort[style_base](c, i) sets labels in entry c, which is number i
    local adjust_labels 
       -- Pass 3: forward and backward label adjustment

    function bibsort(citations)
      for i = 1, table.getn(citations) do
        with_entry(citations[i], presort[style_base], i)
      end
      local function cmp(field)
        return function (e1, e2) return e1.fields[field] < e2.fields[field] end
      end
      bst.sort(citations, cmp 'sort_key') -- use a stable sort
      adjust_labels(citations)
      bst.sort(citations, cmp 'sort_label')
    end

    function adjust_labels(citations)
      -- duplicate labels in sequence get extra 'a', 'b', 'c', etc
      local i, n = 1, table.getn(citations)
      while i <= n do
        local c = citations[i]
        local next = citations[i+1]
        if next and c.fields.label == next.fields.label then
          -- do a run of identical labels
          local label = c.fields.label
          local tag = string.byte 'a' --- if next label gets a tag, this is it
          local j = i
          while j <= n and citations[j].fields.label == label do
            local fields = citations[j].fields
            fields.extra_label = stringf("{\\natexlab{%c}}", tag + j - i)
            fields.label = fields.label .. fields.extra_label
            j = j + 1
          end
          i = j
        else
          c.fields.extra_label = ''
          i = i + 1
        end
      end
    end

    -- all the rest of the complexity lies in computing sort keys for each entry


    -- Make a key used in sorting (much as is done with labels)
    local sort_from   -- take list of fields; use first nonempty one to form sort key, 
                      -- e.g., sort_from { 'author', 'editor' }
    local sort_format -- table of sort-format functions indexed by field
    local sortify     -- make string suitable for sorting (lower case, no specials)
    do
      function sortify(s) return bst.lower(bst.purify(s)) end
        -- for sorting, ignore case and special characters

      local function strip(pfx, f) -- return function to strip prefix word and apply f
        local pat = '^' .. pfx .. '%s'
        return function(s) return f(gsub(s, pat, '')) end
      end

      -- to assist sorting, names are formatted without punctuation
      local sort_name_format =
        -- if abbreviating, use first initials; otherwise use first names
        abbrv and "{vv{ } }{ll{ }}{  f{ }}{  jj{ }}" or "{vv{ } }{ll{ }}{  ff{ }}{  jj{ }}"
      local function sort_format_names(s)
        local names = bst.namesplit(s)
        local f = ""
        local n = table.getn(names)
        for i = 1, n do
          if i > 1 then f = f .. "   " end
          local t = bst.format_name(sort_name_format, names[i])
          if i == n and t == "others" then
            f = f .. (compatible and "zzzzz" or "et al")
          else
            if compatible and n >= min_etal and i == min_etal - 1 then
              f = f .. "zz" .. (fields.year or '') .. "   "
            end
            f = f .. sortify(t)
          end
        end
        return f
      end

      --- sort functions by field type

      sort_format = defaulting { -- how to form sort key from each kind of field used
        default = sort_format_names,
        title   = strip('A', strip('An', strip('The', sortify))),
        key     = sortify,
        organization = strip('The', sortify),
      }
      function sort_from(fs)
        return function()
                 for _, f in ipairs(fs) do
                   if nonempty(fields[f]) then
                     return sort_format[f](fields[f])
                   end
                 end
                 warnf("to sort, need %s in %s", bst.commafy(fs, 'or'), warning_key)
                 return ''
               end
      end

      local entry_max = 250 --- maximum number of characters used in sort key

      local short_label_tab = defaulting { --- fields used to compute label
        default = label_from { 'author' },
        book    = label_from { 'author', 'editor' },
        inbook  = label_from { 'author', 'editor' },
        manual  = label_from { 'author', 'key', 'organization' },
        proceedings = label_from { 'editor', 'key', 'organization' },
      }

      local sort_types = defaulting { -- fields used to compute sort key
        default     = sort_from { 'author', 'key' },
        book        = sort_from { 'author', 'editor', 'key' },
        inbook      = sort_from { 'author', 'editor', 'key' },
        manual      = sort_from { 'author', 'organization', 'key' },
        proceedings = sort_from { 'editor', 'organization', 'key' },
      }

      local function lab_year()
        if empty(fields.year) or fields.short_list == (fields.key or '') then
          return ''
        else
          return fields.year
        end
      end

      function presort.plain()
        local type = citation.type
        fields.short_list = short_label_tab[type]()
        if compatible then
          fields.label = stringf('%s(%s', fields.short_list, lab_year())
          fields.sort_label = stringf("%s    %s", sort_types[type](), key)
        else
          fields.label = stringf('%s(%s', fields.short_list, fields.year or '')
          fields.sort_label = stringf("%s    %s    %s", sort_types[type](),
                               sortify(fields.year or ''),
                               sort_format.title(fields.title or ''))
        end
        fields.sort_key   = sortify(fields.label) .. "    " .. fields.sort_label
        fields.sort_key   = string.sub(fields.sort_key,   1, entry_max)
        fields.sort_label = string.sub(fields.sort_label, 1, entry_max)
      end
      presort.abbrv = presort.plain

      function presort.unsrt(i)
        fields.short_list = short_label_tab[citation.type]()
        if compatible then
          fields.label = stringf('%s(%s', fields.short_list, lab_year())
        else
          fields.label = stringf('%s(%s', fields.short_list, fields.year or '')
        end
        fields.sort_label = stringf("%010d", i)
        fields.sort_key = sortify(fields.label) .. "    " .. fields.sort_label
        fields.sort_key   = string.sub(fields.sort_key,   1, entry_max)
        fields.sort_label = string.sub(fields.sort_label, 1, entry_max)
      end
    end
  end


  ----------------------------------------------------------------
  ---------------------- EMIT THE BBL FILE -----------------------
  ----------------------------------------------------------------

  local function multikeys(citations)
    for _, c in ipairs(citations) do
      if table.getn(c.also_cited_as or { }) > 0 then
        return true
      end
    end
    return false
  end

  local function emitbbl(bbl, preamble, citations)
    output_to(bbl)
    if multikeys(citations) then -- for compatibility, do only on demand
      writef('%s\n',
[[\def\letbib#1#2{%
  \def\tempb##1##2##3##4##5\ends{\csname NAT@wrout\endcsname{##1}{##2}{##3}{##4}}%
  \def\tempc##1{\expandafter\tempb##1{??}{??}{??}{??}{??}\ends}%
  \csname @ifundefined\endcsname{b@#2\csname @extra@b@citeb\endcsname}%
     {}%
     {\expandafter\tempc\csname b@#2\csname @extra@b@citeb\endcsname\endcsname{#1}}}]])
    end
    for _, l in ipairs(preamble) do
      writef('%s', l)
    end
    if preamble[1] then writef('\n') end
    if chicago then
      writef([[
\begin{thebibliography}{%d}
\expandafter\ifx\csname natexlab\endcsname\relax\def\natexlab#1{#1}\fi
\expandafter\ifx\csname url\endcsname\relax
  \def\url#1{{\tt #1}}\fi
]],
             table.getn(citations))
    else
      writef([[
\begin{thebibliography}{%d}
\providecommand{\natexlab}[1]{#1}
\providecommand{\url}[1]{\texttt{#1}}
\expandafter\ifx\csname urlstyle\endcsname\relax
  \providecommand{\doi}[1]{doi: #1}\else
  \providecommand{\doi}{doi: \begingroup \urlstyle{rm}\Url}\fi
]],
             table.getn(citations))
    end

    bibsort(citations)
    for _, c in ipairs(citations) do
      with_entry(c, style[c.type])
    end
    writef("\n\\end{thebibliography}\n")
  end

  return { emit = emitbbl, style = style, macros = assert(macros) }
end


------------------------------------------------------------------------
----------------- BUILD AND REGISTER THE NATBIB STYLES -----------------
------------------------------------------------------------------------

local plain  = make_style 'plain'
local abbrv  = make_style ('abbrv', 3)
local unsrt  = make_style ('unsrt', 3)
local plainc = make_style ('plain', 3, false, true)

bibtex.register_style('plainnat',  plain_compatible and plainc or plain)
bibtex.register_style('plainnat2', make_style('plain', 3))
bibtex.register_style('plainnatx', plain)
bibtex.register_style('plainnatc', plainc)
bibtex.register_style('abbrvnat',  abbrv)
bibtex.register_style('unsrtnat',  unsrt)
