code:
/* handle_commands.c -- what to do when a command name is first read */
/* Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016
   Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <stdlib.h>
#include <string.h>

#include "parser.h"
#include "input.h"
#include "text.h"
#include "errors.h"

static int section_level (ELEMENT *section);

/* Return a containing @itemize or @enumerate if inside it. */
// 1847
ELEMENT *
item_container_parent (ELEMENT *current)
{
  if ((current->cmd == CM_item
       || current->type == ET_before_item)
      && current->parent
      && ((current->parent->cmd == CM_itemize
           || current->parent->cmd == CM_enumerate)))
    {
      return current->parent;
    }
  return 0;
}

// 1352
/* Check that there are no text holding environments (currently
   checking only paragraphs and preformatted) in contents. */
int
check_no_text (ELEMENT *current)
{
  int after_paragraph = 0;
  int i, j;
  for (i = 0; i < current->contents.number; i++)
    {
      enum element_type t;
      ELEMENT *f;
      f = current->contents.list[i];
      t = f->type;
      if (t == ET_paragraph)
        {
          after_paragraph = 1;
          break;
        }
      else if (t == ET_preformatted
               || t == ET_rawpreformatted)
        {
          for (j = 0; j < f->contents.number; j++)
            {
              ELEMENT *g = f->contents.list[j];
              if ((g->text.end > 0
                   && g->text.text[strspn (g->text.text, whitespace_chars)])
                  || (g->cmd && g->cmd != CM_c
                      && g->cmd != CM_comment
                      && g->type != ET_index_entry_command))
                {
                  after_paragraph = 1;
                  break;
                }
            }
          if (after_paragraph)
            break;
        }
    }
  return after_paragraph;
}

// 1056
/* Record the information from a command of global effect.
   TODO: Could we scrap the first argument and use CURRENT->cmd? */
int
register_global_command (enum command_id cmd, ELEMENT *current)
{
  // TODO: Why even give @author this flag in the first place?
  if (cmd != CM_author && (command_data(cmd).flags & CF_global))
    {
      if (!current->line_nr.line_nr)
        current->line_nr = line_nr;
      switch (cmd)
        {
#define GLOBAL_CASE(cmx) \
        case CM_##cmx:   \
          add_to_contents_as_array (&global_info.cmx, current); \
          break

        case CM_footnote:
          add_to_contents_as_array (&global_info.footnotes, current);
          break;

        GLOBAL_CASE(hyphenation);
        GLOBAL_CASE(insertcopying);
        GLOBAL_CASE(printindex);
        GLOBAL_CASE(subtitle);
        GLOBAL_CASE(titlefont);
        GLOBAL_CASE(listoffloats);
        GLOBAL_CASE(detailmenu);
        GLOBAL_CASE(part);

        /* from Common.pm %document_settable_at_commands */
        GLOBAL_CASE(allowcodebreaks);
        GLOBAL_CASE(clickstyle);
        GLOBAL_CASE(codequotebacktick);
        GLOBAL_CASE(codequoteundirected);
        GLOBAL_CASE(contents);
        GLOBAL_CASE(deftypefnnewline);
        GLOBAL_CASE(documentencoding);
        GLOBAL_CASE(documentlanguage);
        GLOBAL_CASE(exampleindent);
        GLOBAL_CASE(firstparagraphindent);
        GLOBAL_CASE(frenchspacing);
        GLOBAL_CASE(headings);
        GLOBAL_CASE(kbdinputstyle);
        GLOBAL_CASE(paragraphindent);
        GLOBAL_CASE(shortcontents);
        GLOBAL_CASE(urefbreakstyle);
        GLOBAL_CASE(xrefautomaticsectiontitle);
#undef GLOBAL_CASE
        }
      /* TODO: Check if all of these are necessary. */
      return 1;
    }
  else if ((command_data(cmd).flags & CF_global_unique))
    {
      ELEMENT **where = 0;

      if (cmd == CM_shortcontents)
        cmd = CM_summarycontents;
      if (!current->line_nr.line_nr)
        current->line_nr = line_nr;
      switch (cmd)
        {
          extern int input_number;
        case CM_setfilename:
          /* Check if we are inside an @include, and if so, do nothing. */
          if (input_number > 1)
            break;
          where = &global_info.setfilename;
          break;

#define GLOBAL_UNIQUE_CASE(cmd) \
        case CM_##cmd: \
          where = &global_info.cmd; \
          break

        GLOBAL_UNIQUE_CASE(settitle);
        GLOBAL_UNIQUE_CASE(copying);
        GLOBAL_UNIQUE_CASE(titlepage);
        GLOBAL_UNIQUE_CASE(top);
        GLOBAL_UNIQUE_CASE(documentdescription);
        GLOBAL_UNIQUE_CASE(setcontentsaftertitlepage);
        GLOBAL_UNIQUE_CASE(setshortcontentsaftertitlepage);
        GLOBAL_UNIQUE_CASE(novalidate);
        GLOBAL_UNIQUE_CASE(validatemenus);
        GLOBAL_UNIQUE_CASE(pagesizes);
        GLOBAL_UNIQUE_CASE(fonttextsize);
        GLOBAL_UNIQUE_CASE(footnotestyle);
        GLOBAL_UNIQUE_CASE(setchapternewpage);
        GLOBAL_UNIQUE_CASE(everyheading);
        GLOBAL_UNIQUE_CASE(everyfooting);
        GLOBAL_UNIQUE_CASE(evenheading);
        GLOBAL_UNIQUE_CASE(evenfooting);
        GLOBAL_UNIQUE_CASE(oddheading);
        GLOBAL_UNIQUE_CASE(oddfooting);
        GLOBAL_UNIQUE_CASE(everyheadingmarks);
        GLOBAL_UNIQUE_CASE(everyfootingmarks);
        GLOBAL_UNIQUE_CASE(evenheadingmarks);
        GLOBAL_UNIQUE_CASE(oddheadingmarks);
        GLOBAL_UNIQUE_CASE(evenfootingmarks);
        GLOBAL_UNIQUE_CASE(oddfootingmarks);
        GLOBAL_UNIQUE_CASE(shorttitlepage);
        GLOBAL_UNIQUE_CASE(title);
#undef GLOBAL_UNIQUE_CASE
        /* NOTE: Same list in api.c:build_global_info2. */
        }
      if (where)
        {
          if (*where)
            line_warn ("multiple @%s", command_name(cmd));
          else
            *where = current;
        }
      return 1;
    }

  return 0;
}

/* Line 4289 */
/* STATUS is set to 1 if we should get a new line after this,
   2 if we should stop processing completely. */
ELEMENT *
handle_misc_command (ELEMENT *current, char **line_inout,
                     enum command_id cmd, int *status,
                     enum command_id invalid_parent)
{
  ELEMENT *misc = 0;
  char *line = *line_inout;
  int arg_spec;

  *status = 0;
  /* Root commands (like @node) and @bye 4290 */
  if (command_data(cmd).flags & CF_root || cmd == CM_bye)
    {
      ELEMENT *closed_elt; /* Not used */
      current = close_commands (current, 0, &closed_elt, cmd);
      if (current->type == ET_text_root)
        {
          if (cmd != CM_bye)
            {
              /* Something to do with document_root and text_root. */
              ELEMENT *new_root = new_element (ET_document_root);
              add_to_element_contents (new_root, current);
              current = new_root;
            }
        }
      else
        {
          current = current->parent;
          if (!current)
            abort ();
        }
    }

  /* Look up information about this command ( noarg skipline skipspace text
     line lineraw /^\d$/). */
  arg_spec = command_data(cmd).data;

  /* noarg 4312 */
  if (arg_spec == MISC_noarg)
    {
      int ignored = 0;
      int only_in_headings = 0;
      if (cmd == CM_insertcopying)
        {
          ELEMENT *p = current;
          while (p)
            {
              if (p->cmd == CM_copying)
                {
                  line_error ("@%s not allowed inside `@copying' block",
                              command_name(cmd));
                  ignored = 1;
                  break;
                }
              p = p->parent;
            }
        }
      else if (command_data(cmd).flags & CF_in_heading)
        {
          line_error ("@%s should only appear in heading or footing",
                      command_name(cmd));
          only_in_headings = 1;
        }

      if (!ignored)
        {
          misc = new_element (ET_NONE);
          misc->cmd = cmd;
          add_to_element_contents (current, misc);
          if (only_in_headings)
            add_extra_string (misc, "invalid_nesting", "1");
          register_global_command (cmd, misc);
        }
      mark_and_warn_invalid (cmd, invalid_parent, misc);
      if (close_preformatted_command(cmd))
        current = begin_preformatted (current);
    }
  /* All the cases using the raw line.
     I don't understand what the difference is between these. */
  else if (arg_spec == MISC_skipline /* 4347 */
           || arg_spec == MISC_lineraw
           || arg_spec == MISC_special)
    {
      ELEMENT *args = 0;
      enum command_id equivalent_cmd = 0;
      int has_comment = 0;

      /* 4350 If the current input is the result of a macro expansion,
         it may not be a complete line.  Check for this and acquire the rest
         of the line if necessary. */
      if (!strchr (line, '\n'))
        {
          char *line2;
          LINE_NR save_ln;

          input_push_text (strdup (line), 0);

          save_ln = line_nr;
          line2 = new_line ();
          if (line2)
            {
              line = line2;
              line_nr = save_ln;
            }
        }

      misc = new_element (ET_NONE);
      misc->cmd = cmd;

      if (arg_spec == MISC_skipline || arg_spec == MISC_lineraw)
        {
          ELEMENT *arg;
          args = new_element (ET_NONE);
          arg = new_element (ET_NONE);
          add_to_element_contents (args, arg);
          text_append (&arg->text, line);
        }
      else /* arg_spec == MISC_special */
        {
          args = parse_special_misc_command (line, cmd, &has_comment); //4362
          add_extra_string (misc, "arg_line", strdup (line));
        }

      /* Handle @set txicodequoteundirected as an
         obsolete alternative to @codequoteundirected. */
      if (cmd == CM_set || cmd == CM_clear)
        {
          if (args->contents.number > 0
              && args->contents.list[0]->text.end > 0)
            {
              if (!strcmp (args->contents.list[0]->text.text,
                           "txicodequoteundirected"))
                equivalent_cmd = CM_codequoteundirected;
              else if (!strcmp (args->contents.list[0]->text.text,
                                "txicodequotebacktick"))
                equivalent_cmd = CM_codequotebacktick;
            }
        }
      if (equivalent_cmd)
        {
          char *arg = 0;
          ELEMENT *misc_line_args;
          ELEMENT *spaces_after_command;
          ELEMENT *e;

          if (cmd == CM_set)
            arg = "on";
          else
            arg = "off";

          /* Now manufacture the parse tree for the equivalent
             command and add it to the tree. */

          destroy_element (args);
          args = new_element (ET_NONE);
          e = new_element (ET_NONE);
          text_append (&e->text, arg);
          add_to_element_contents (args, e);

          destroy_element (misc);
          misc = new_element (ET_NONE);
          misc->cmd = equivalent_cmd;
          misc->line_nr = line_nr;

          misc_line_args = new_element (ET_misc_line_arg);
          add_to_element_args (misc, misc_line_args);
          add_extra_misc_args (misc, "misc_args", args);

          spaces_after_command = new_element
            (ET_empty_spaces_after_command);
          text_append_n (&spaces_after_command->text, " ", 1);
          add_extra_element (misc, "spaces_after_command",
                             spaces_after_command);
          add_extra_element (spaces_after_command, "command", misc);

          add_to_element_contents (misc_line_args, spaces_after_command);

          e = new_element (ET_NONE);
          text_append (&e->text, arg);
          add_to_element_contents (misc_line_args, e);

          e = new_element (ET_spaces_at_end);
          text_append_n (&e->text, "\n", 1);
          add_to_element_contents (misc_line_args, e);

          add_to_element_contents (current, misc);
        }
      else // 4402
        {
          int i;
          add_to_element_contents (current, misc);

          for (i = 0; i < args->contents.number; i++)
            {
              ELEMENT *misc_arg = new_element (ET_misc_arg);
              text_append_n (&misc_arg->text,
                             args->contents.list[i]->text.text,
                             args->contents.list[i]->text.end);
              add_to_element_args (misc, misc_arg);
            }
          /* TODO: Could we have just set misc->args directly as args? */

          if (args->contents.number > 0 && arg_spec != MISC_skipline)
            add_extra_misc_args (misc, "misc_args", args);
          else
            {
              for (i = 0; i < args->contents.number; i++)
                {
                  destroy_element (args->contents.list[i]);
                }
              destroy_element (args);
            }
        }

      if (0 || cmd == CM_raisesections)
        {
        }
      else if (0 || cmd == CM_raisesections)
        {
        }
      else if (cmd == CM_novalidate)
        {
        }

      mark_and_warn_invalid (cmd, invalid_parent, misc);
      register_global_command (cmd, misc); // 4423

      if (arg_spec != MISC_special || !has_comment)
        current = end_line (current);

      // 4429
      if (cmd == CM_bye)
        {
          *status = 2; /* Finish processing completely. */
          goto funexit;
        }

      if (close_preformatted_command(cmd))
        current = begin_preformatted (current);

      *status = 1; /* Get a new line */
      goto funexit;
    }
  else
    {
      /* line 4435 - text, line, skipspace or a number.
         (This includes handling of "@end", which is MISC_text.) */

      int line_arg = 0;

      if (arg_spec != MISC_skipspace)
        line_arg = 1;

      /* 4439 */
      /*************************************************************/
      /* Special handling of @item because it can appear
         in several contents: in an @itemize, a @table, or
         a @multitable. */
      if (cmd == CM_item || cmd == CM_itemx
          || cmd == CM_headitem || cmd == CM_tab)
        {
          ELEMENT *parent;

          /* @itemize or @enumerate */ // 4443
          if ((parent = item_container_parent (current)))
            {
              if (cmd == CM_item)
                {
                  char *s;
                  debug ("ITEM CONTAINER");
                  counter_inc (&count_items);
                  misc = new_element (ET_NONE);
                  misc->cmd = CM_item;

                  asprintf (&s, "%d", counter_value (&count_items, parent));
                  add_extra_string (misc, "item_number", s);

                  add_to_element_contents (parent, misc);
                  current = misc;
                }
              else
                {
                  line_error ("@%s not meaningful inside `@%s' block",
                              command_name(cmd),
                              command_name(parent->cmd));
                }
              current = begin_preformatted (current);
            }
          /* @table, @vtable, @ftable */
          else if ((parent = item_line_parent (current)))
            {
              if (cmd == CM_item || cmd == CM_itemx)
                {
                  debug ("ITEM_LINE");
                  current = parent;
                  gather_previous_item (current, cmd);
                  misc = new_element (ET_NONE);
                  misc->cmd = cmd;
                  add_to_element_contents (current, misc);
                  line_arg = 1;
                }
              else
                {
                  line_error ("@%s not meaningful inside `@%s' block",
                              command_name(cmd),
                              command_name(parent->cmd));
                  current = begin_preformatted (current);
                }
            }
          /* In a @multitable */
          else if ((parent = item_multitable_parent (current))) // 4477
            {
              if (cmd != CM_item && cmd != CM_headitem
                  && cmd != CM_tab)
                {
                  line_error ("@%s not meaningful inside @%s block",
                              command_name(cmd),
                              command_name(parent->cmd)); // 4521
                }
              else
                { /* 4480 */
                  int max_columns = 0;
                  KEY_PAIR *prototypes;

                  prototypes = lookup_extra_key  (parent, "prototypes");
                  if (prototypes)
                    max_columns = prototypes->value->contents.number;
                  else
                    {
                      prototypes = lookup_extra_key(parent, "columnfractions");
                      if (prototypes)
                        max_columns = prototypes->value->contents.number;
                    }

                  if (max_columns == 0)
                    {
                      line_warn ("@%s in empty multitable",
                                 command_name(cmd));
                    }
                  else if (cmd == CM_tab)
                    { // 4484
                      ELEMENT *row;
                      row = last_contents_child (parent);
                      if (row->type == ET_before_item)
                        line_error ("@tab before @item");
                      // 4489
                      else if (counter_value (&count_cells, row)
                               >= max_columns)
                        {
                          line_error ("too many columns in multitable item"
                                      " (max %d)", max_columns);
                        }
                      else // 4493
                        {
                          char *s;
                          counter_inc (&count_cells);
                          misc = new_element (ET_NONE);
                          misc->cmd = cmd;
                          add_to_element_contents (row, misc);
                          current = misc;
                          debug ("TAB");

                          asprintf (&s, "%d",
                                    counter_value (&count_cells, row));
                          add_extra_string (current, "cell_number", s);
                        }
                    }
                  else /* 4505 @item or @headitem */
                    {
                      ELEMENT *row; char *s;

                      debug ("ROW");
                      row = new_element (ET_row);
                      add_to_element_contents (parent, row);

                      /* FIXME:The "row_number" extra value,
                         isn't actually used anywhere. */
                      asprintf (&s, "%d", parent->contents.number-1);
                      add_extra_string (row, "row_number", s);

                      misc = new_element (ET_NONE);
                      misc->cmd = cmd;
                      add_to_element_contents (row, misc);
                      current = misc;

                      if (counter_value (&count_cells, parent) != -1)
                        counter_pop (&count_cells);
                      counter_push (&count_cells, row, 1);
                      asprintf (&s, "%d",
                                counter_value (&count_cells, row));
                      add_extra_string (current, "cell_number", s);
                    }
                }
              current = begin_preformatted (current);
            } /* In @multitable */
          else if (cmd == CM_tab) // 4526
            {
              line_error ("ignoring @tab outside of multitable");
              current = begin_preformatted (current);
            }
          else
            {
              line_error ("@%s outside of table or list",
                          command_name(cmd));
              current = begin_preformatted (current);
            }
          if (misc)
            misc->line_nr = line_nr; // 4535
        }
      /*************************************************************/
      else /* Not @item, @itemx, @headitem, nor @tab 4536 */
        {
          /* Add to contents */
          misc = new_element (ET_NONE);
          misc->cmd = cmd;
          misc->line_nr = line_nr;
          add_to_element_contents (current, misc);

          if (command_data(cmd).flags & CF_sectioning)
            {
              /* Store section level in 'extra' key. */
              /* TODO: @part? */
              /*add_extra_string (last_contents_child (current),
                "sections_level", "1"); */
              add_extra_string (misc, "level",
                          &("0\0" "0\0" "1\0" "2\0" "3\0" "4\0"
                                  "5\0" "6\0" "7\0" "8\0" "9\0" + 2)
                                  [section_level (misc) * 2]);
            }

          /* 4546 - def*x */
          if (command_data(cmd).flags & CF_def)
            {
              enum command_id base_command;
              char *base_name;
              int base_len;
              int after_paragraph;

              /* Find the command with "x" stripped from the end, e.g.
                 deffnx -> deffn. */
              base_name = command_name(cmd);
              add_extra_string (misc, "original_def_cmdname", base_name);

              base_name = strdup (base_name);
              base_len = strlen (base_name);
              if (base_name[base_len - 1] != 'x')
                abort ();
              base_name[base_len - 1] = '\0';
              base_command = lookup_command (base_name);
              if (base_command == CM_NONE)
                abort ();
              add_extra_string (misc, "def_command", base_name);

              after_paragraph = check_no_text (current);
              push_context (ct_def);
              misc->type = ET_def_line; // 4553
              if (current->cmd == base_command)
                {
                  ELEMENT *e = pop_element_from_contents (current);
                  /* e should be the same as misc */
                  /* Gather an "inter_def_item" element. */
                  gather_def_item (current, cmd);
                  add_to_element_contents (current, e);
                }
              if (current->cmd != base_command || after_paragraph)
                {
                  // error - deffnx not after deffn
                  line_error ("must be after `@%s' to use `@%s'",
                               command_name(base_command),
                               command_name(cmd));
                  add_extra_string (misc, "not_after_command", "1");
                }
            }
        } /* 4571 */

      // Rest of the line is the argument - true unless is MISC_skipspace. */
      if (line_arg)
        {
          ELEMENT *arg;
          /* 4576 - change 'current' to its last child.  This is ELEMENT *misc
             above.  */
          current = last_contents_child (current);
          arg = new_element (ET_misc_line_arg);
          add_to_element_args (current, arg);

          if (cmd == CM_node) // 4584
            {
              /* At most three comma-separated arguments to @node.  This
                 is the only (non-block) line command taking comma-separated
                 arguments.  Its arguments will be gathered the same as
                 those of some block line commands and brace commands. */
              counter_push (&count_remaining_args, current, 3);
            }
          else if (cmd == CM_author)
            {
              ELEMENT *parent = current;
              int found = 0;
              while (parent->parent)
                {
                  parent = parent->parent;
                  if (parent->type == ET_brace_command_context)
                    break;
                  if (parent->cmd == CM_titlepage)
                    {
                      // TODO 4595 global author
                      add_extra_element (current, "titlepage", parent);
                      found = 1; break;
                    }
                  else if (parent->cmd == CM_quotation
                           || parent->cmd == CM_smallquotation)
                    {
                      KEY_PAIR *k; ELEMENT *e;
                      k = lookup_extra_key (parent, "authors");
                      if (k)
                        e = k->value;
                      else
                        {
                          e = new_element (ET_NONE);
                          add_extra_contents (parent, "authors", e);
                        }
                      add_to_contents_as_array (e, current);
                      add_extra_element (current, "quotation", parent);
                      found = 1; break;
                    }
                }
              if (!found)
                line_warn ("@author not meaningful outside "
                           "`@titlepage' and `@quotation' environments");
            }
          else if (cmd == CM_dircategory && current_node)
            line_warn ("@dircategory after first node");

          current = last_args_child (current); /* arg */

          /* add 'line' to context_stack (Parser.pm:141).  This will be the
             case while we read the argument on this line. */
          if (!(command_data(cmd).flags & CF_def))
            push_context (ct_line);
        }
      start_empty_line_after_command (current, &line, misc); //4621

      if (cmd == CM_indent || cmd == CM_noindent)
        {
          /* Start a new paragraph if not in one already. */
          int spaces;
          enum element_type t;
          ELEMENT *paragraph;

          /* Check if if we should change an ET_empty_line_after_command
             element to ET_empty_spaces_after_command by looking ahead
             to see what comes next. */
          if (!strchr (line, '\n'))
            {
              char *line2;
              input_push_text (strdup (line), 0);
              line2 = new_line ();
              if (line2)
                line = line2;
            }
          spaces = strspn (line, whitespace_chars);
          if (spaces > 0)
            {
              char saved = line[spaces];
              line[spaces] = '\0';
              current = merge_text (current, line);
              line[spaces] = saved;
              line += spaces;
            }
          if (*line
              && last_contents_child(current)->type
                == ET_empty_line_after_command)
            {
              last_contents_child(current)->type
                                              = ET_empty_spaces_after_command;
            }
          paragraph = begin_paragraph (current);
          if (paragraph)
            current = paragraph;
          if (!*line)
            {
              *status = 1; /* Get a new line. */
              goto funexit;
            }
        }
    }

  // 4622
  mark_and_warn_invalid (cmd, invalid_parent, misc);

  if (misc)
    register_global_command (cmd, misc);

funexit:
  *line_inout = line;
  return current;
}

/* Return numbered level of an element */
static int
section_level (ELEMENT *section)
{
  int level;
int min_level = 0, max_level = 5;

  switch (section->cmd)
    {
    case CM_top: level = 0; break;
    case CM_chapter: level = 1; break;
    case CM_unnumbered: level = 1; break;
    case CM_chapheading: level = 1; break;
    case CM_appendix: level = 1; break;
    case CM_section: level = 2; break;
    case CM_unnumberedsec: level = 2; break;
    case CM_heading: level = 2; break;
    case CM_appendixsec: level = 2; break;
    case CM_subsection: level = 3; break;
    case CM_unnumberedsubsec: level = 3; break;
    case CM_subheading: level = 3; break;
    case CM_appendixsubsec: level = 3; break;
    case CM_subsubsection: level = 4; break;
    case CM_unnumberedsubsubsec: level = 4; break;
    case CM_subsubheading: level = 4; break;
    case CM_appendixsubsubsec: level = 4; break;
    case CM_part: level = 0; break;
    case CM_appendixsection: level = 2; break;
    case CM_majorheading: level = 1; break;
    case CM_centerchap: level = 1; break;
    default: level = -1; break;
    }
  return level;
  /* then adjust according to raise-/lowersections. */
}

          /* TODO: Allow user to change which formats are true. */
struct expanded_format {
    char *format;
    int expandedp;
};
static struct expanded_format expanded_formats[] = {
    "html", 0,
    "docbook", 0,
    "plaintext", 1,
    "tex", 0,
    "xml", 0,
    "info", 1,
};

void
clear_expanded_formats (void)
{
  int i;
  for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
       i++)
    {
      expanded_formats[i].expandedp = 0;
    }
}

void
add_expanded_format (char *format)
{
  int i;
  for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
       i++)
    {
      if (!strcmp (format, expanded_formats[i].format))
        {
          expanded_formats[i].expandedp = 1;
          break;
        }
    }
  if (!strcmp (format, "plaintext"))
    add_expanded_format ("info");
}

int
format_expanded_p (char *format)
{
  int i;
  for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
       i++)
    {
      if (!strcmp (format, expanded_formats[i].format))
        return expanded_formats[i].expandedp;
    }
  return 0;
}

/* line 4632 */
/* A command name has been read that starts a multiline block, which should
   end in @end <command name>.  The block will be processed until
   "end_line_misc_line" in end_line.c processes the @end command. */
ELEMENT *
handle_block_command (ELEMENT *current, char **line_inout,
                      enum command_id cmd, int *get_new_line,
                      enum command_id invalid_parent)
{
  char *line = *line_inout;
  unsigned long flags = command_data(cmd).flags;

  /* New macro being defined. */
  if (cmd == CM_macro || cmd == CM_rmacro)
    {
      ELEMENT *macro;
      macro = parse_macro_command_line (cmd, &line, current);
      add_to_element_contents (current, macro);
      mark_and_warn_invalid (cmd, invalid_parent,
                             last_contents_child(current));
      current = macro;

      /* 4640 */
      /* A new line should be read immediately after this.  */
      line = strchr (line, '\0');
      *get_new_line = 1;
      goto funexit;
    }
  else if (command_data(cmd).data == BLOCK_conditional) //4641
    {
      int iftrue = 0; /* Whether the conditional is true. */
      if (cmd == CM_ifclear || cmd == CM_ifset
          || cmd == CM_ifcommanddefined || cmd == CM_ifcommandnotdefined)
        {
          char *flag;
          char *p = line;
          p = line + strspn (line, whitespace_chars);
          if (!*p)
            line_error ("@%s requires a name", command_name(cmd));
          else
            {
              flag = read_command_name (&p);
              if (!flag)
                goto bad_value;
              else
                {
                  p += strspn (p, whitespace_chars);
                  /* Check for a comment at the end of the line. */
                  if (memcmp (p, "@c", 2) == 0)
                    {
                      p += 2;
                      if (memcmp (p, "omment", 6) == 0)
                        p += 7;
                      if (*p && *p != '@' && !strchr (whitespace_chars, *p))
                        goto bad_value; /* @c or @comment not terminated. */
                    }
                  else if (*p)
                    goto bad_value; /* Trailing characters on line. */
                }
              if (1)
                {
                  // 4652
                  if (cmd == CM_ifclear || cmd == CM_ifset)
                    {
                      char *val = fetch_value (flag, strlen (flag));
                      if (val)
                        iftrue = 1;
                      if (cmd == CM_ifclear)
                        iftrue = !iftrue;
                    }
                  else /* cmd == CM_ifcommanddefined
                          || cmd == CM_ifcommandnotdefined */
                    {
                      enum command_id c = lookup_command (flag);
                      if (c)
                        iftrue = 1;
                      if (cmd == CM_ifcommandnotdefined)
                        iftrue = !iftrue;
                    }
                }
              else if (0)
                {
              bad_value:
                  line_error ("bad name for @%s", command_name(cmd));
                }

            }
        }
      else if (!memcmp (command_name(cmd), "if", 2)) //4687
        {
          int i; char *p;
          /* Handle @if* and @ifnot* */
          /* FIXME: Check @if and @ifnot* a nicer way, without memcmp. */

          p = command_name(cmd) + 2; /* After "if". */
          if (!memcmp (p, "not", 3))
            p += 3; /* After "not". */
          for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
               i++)
            {
              if (!strcmp (p, expanded_formats[i].format))
                {
                  iftrue = expanded_formats[i].expandedp;
                  break;
                }
            }
          if (!memcmp (command_name(cmd), "ifnot", 5))
            iftrue = !iftrue;
        }
      else
        abort (); // BUG

      // 4699 - If conditional true, push onto conditional stack.  Otherwise
      // open a new element (which we shall later remove, in
      // process_remaining_on_line ("CLOSED conditional").

      debug ("CONDITIONAL %s %d", command_name(cmd), iftrue);
      if (iftrue)
        push_conditional_stack (cmd);
      else
        {
          // Ignored.
          ELEMENT *e;
          e = new_element (ET_NONE);
          e->cmd = cmd;
          add_to_element_contents (current, e);
          current = e;
        }
      // 4709 ("last;")
      line = strchr (line, '\0');
      *get_new_line = 1;
      goto funexit;
    }
  else /* line 4710 */
    {
      ELEMENT *block = 0;
      // 4715
      if (flags & CF_menu
          && (current->type == ET_menu_comment
              || current->type == ET_menu_entry_description))
        {
          /* This is for @detailmenu within @menu */
          ELEMENT *menu = current->parent;
          if (current->contents.number == 0)
            destroy_element (pop_element_from_contents (menu));

          if (pop_context () != ct_preformatted)
            abort ();
          if (menu->type == ET_menu_entry)
            menu = menu->parent;
          current = menu;
        }

      // 4740
      if (flags & CF_def)
        {
          ELEMENT *def_line;
          push_context (ct_def);
          block = new_element (ET_NONE);
          block->cmd = cmd;
          block->line_nr = line_nr;
          add_to_element_contents (current, block);
          current = block;
          def_line = new_element (ET_def_line);
          def_line->line_nr = line_nr;
          add_to_element_contents (current, def_line);
          current = def_line;
          add_extra_string (current, "def_command", command_name(cmd));
          add_extra_string (current, "original_def_cmdname",
                            command_name(cmd));
        }
      else
        {
          /*  line 4756 */
          block = new_element (ET_NONE);

          block->cmd = cmd;
          add_to_element_contents (current, block);
          current = block;
        }

      /* 4763 Check if 'block args command' */
      if (command_data(cmd).data != BLOCK_raw)
        {
          if (command_data(cmd).flags & CF_preformatted)
            push_context (ct_preformatted);
          else if (command_data(cmd).flags & CF_format_raw)
            {
              push_context (ct_rawpreformatted);
              if (!format_expanded_p (command_name(cmd)))
                {
                  ELEMENT *e;
                  enum command_id dummy;
                  char *line_dummy;

                  e = new_element (ET_elided_block);
                  add_to_element_contents (current, e);
                  line_dummy = line;
                  while (!is_end_current_command (current,
                                                  &line_dummy, &dummy))
                    {
                      line = new_line ();
                      if (!line)
                        abort (); // TODO
                      line_dummy = line;
                    }
                  e = new_element (ET_empty_line_after_command);
                  text_append_n (&e->text, "\n", 1);
                  add_to_element_contents (current, e);

                  e = new_element (ET_empty_line);
                  add_to_element_contents (current, e);
                  goto funexit;
                }
            }

          // 4775
          if (command_data(cmd).flags & CF_region)
            {
              if (current_region_cmd ())
                {
                  line_error ("region %s inside region %s is not allowed",
                              command_name(cmd),
                              command_name(current_region_cmd ()));
                }
              push_region (block);
            }

          // 4784 menu commands
          if (command_data(cmd).flags & CF_menu)
            {
              if (current_context () == ct_preformatted)
                push_context (ct_preformatted);
              else
                push_context (ct_menu);

              // Record dir entry here

              if (current_node) // 4793
                {
                  if (cmd == CM_direntry && conf.show_menu)
                    {
                      line_warn ("@direntry after first node");
                    }
                  else if (cmd == CM_menu)
                    {
                      if (!(command_flags(current->parent) & CF_root))
                        line_warn ("@menu in invalid context");
                      /* Add to array of menus for current node.  Currently
                         done in Perl code. */
                    }
                }
              else if (cmd != CM_direntry)
                {
                  if (conf.show_menu)
                    {
                      line_error ("@%s seen before first @node",
                                  command_name(cmd));
                      line_error ("perhaps your @top node should be "
                                  "wrapped in @ifnottex rather than @ifinfo?");
                    }
                  // 4810 unassociated menus
                }
            }

          if (cmd == CM_itemize || cmd == CM_enumerate)
            counter_push (&count_items, current, 0);
          /* Note that no equivalent thing is done in the Perl code, because
             'item_count' is assumed to start at 0. */

          // 4816
          {
            ELEMENT *bla = new_element (ET_block_line_arg);
            add_to_element_args (current, bla);

            if (command_data (current->cmd).data > 1)
              {
                counter_push (&count_remaining_args,
                              current,
                              command_data (current->cmd).data - 1);
              }

            current = bla;
            if (!(command_data(cmd).flags & CF_def))
              push_context (ct_line);

            /* Note that an ET_empty_line_after_command gets reparented in the
               contents in 'end_line'. */

          }
        } /* 4827 */
      block->line_nr = line_nr;
      mark_and_warn_invalid (cmd, invalid_parent, block);
      register_global_command (cmd, block);
      start_empty_line_after_command (current, &line, block);
    }

funexit:
  *line_inout = line;
  return current;
}

/* 4835 */
ELEMENT *
handle_brace_command (ELEMENT *current, char **line_inout,
                      enum command_id cmd,
                      enum command_id invalid_parent)
{
  char *line = *line_inout;
  ELEMENT *e;

  e = new_element (ET_NONE);
  e->cmd = cmd;

  // 4841
  // 258 keep_line_nr_brace_commands
  // also 4989 sets line_nr.
  /* The line number information is only ever used for brace commands
     if the command is given with braces, but it's easier just to always
     store the information. */
  e->line_nr = line_nr;

  add_to_element_contents (current, e);
  current = e;

  mark_and_warn_invalid (cmd, invalid_parent, e);
  if (cmd == CM_click)
    {
      add_extra_string (e, "clickstyle", global_clickstyle);
    }
  else if (cmd == CM_kbd)
    {
      if (current_context () == ct_preformatted
          && global_kbdinputstyle != kbd_distinct
          || global_kbdinputstyle == kbd_code)
        {
          add_extra_string (e, "code", "1");
        }
      else if (global_kbdinputstyle == kbd_example)
        {
          // _in_code line 1277
          // TODO: Understand what is going on here.

          ELEMENT *tmp = current->parent;
          while (tmp->parent
                 && (command_flags(tmp->parent) & CF_brace)
                 && command_data(tmp->parent->cmd).data != BRACE_context)
            {
              if (command_flags(tmp->parent) & CF_code_style)
                {
                  add_extra_string (e, "code", "1");
                  break;
                }
              tmp = tmp->parent->parent;
            }
        }
    }
  else if (command_data(cmd).flags & CF_INFOENCLOSE)
    {
      INFO_ENCLOSE *ie = lookup_infoenclose (cmd);
      if (ie)
        {
          add_extra_string (e, "begin", ie->begin);
          add_extra_string (e, "end", ie->end);
        }
      e->type = ET_definfoenclose_command;
    }

  *line_inout = line;
  return current;
}


code:
ELEMENT *handle_misc_command (ELEMENT *current, char **line_inout,
                     enum command_id cmd_id, int *status,
                     enum command_id invalid_parent);
ELEMENT *handle_block_command (ELEMENT *current, char **line_inout,
                      enum command_id cmd_id, int *new_line,
                      enum command_id invalid_parent);
ELEMENT *handle_brace_command (ELEMENT *current, char **line_inout,
                               enum command_id cmd_id,
                               enum command_id invalid_parent);
int check_no_text (ELEMENT *current);
int register_global_command (enum command_id cmd, ELEMENT *current);

void clear_expanded_formats (void);
void add_expanded_format (char *format);

repo_name: mthl/texinfo

license: mit


code:
/* Copyright 2010, 2011, 2012, 2013, 2014, 2015
   Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include <string.h>
#include <stdio.h>

#include "parser.h"
#include "indices.h"

INDEX **index_names;
int number_of_indices;
int space_for_indices;

typedef struct {
    enum command_id cmd;
    INDEX *idx;
} CMD_TO_IDX;

/* Array mapping Texinfo commands to index data structures. */
static CMD_TO_IDX *cmd_to_idx = 0;
static size_t num_index_commands = 0;
static size_t cmd_to_idx_space = 0;

static void
associate_command_to_index (enum command_id cmd, INDEX *idx)
{
  if (num_index_commands == cmd_to_idx_space)
    {
      cmd_to_idx = realloc (cmd_to_idx,
                            sizeof (CMD_TO_IDX) * (cmd_to_idx_space += 10));
      if (!cmd_to_idx)
        abort ();
    }

  cmd_to_idx[num_index_commands].cmd = cmd;
  cmd_to_idx[num_index_commands++].idx = idx;
}

/* Get the index associated with CMD. */
INDEX *
index_of_command (enum command_id cmd)
{
  int i;

  for (i = 0; i < num_index_commands; i++)
    {
      if (cmd_to_idx[i].cmd == cmd)
        return cmd_to_idx[i].idx;
    }
  return 0;
}

/* Save a new Texinfo command with the name CMDNAME and record that it
   creates index entries in IDX. */
static void
add_index_command (char *cmdname, INDEX *idx)
{
  enum command_id new = add_texinfo_command (cmdname);
  user_defined_command_data[new & ~USER_COMMAND_BIT].flags
    = CF_misc | CF_index_entry_command;
  user_defined_command_data[new & ~USER_COMMAND_BIT].data = MISC_line;
  associate_command_to_index (new, idx);
}

static INDEX *
add_index_internal (char *name, int in_code)
{
  INDEX *idx = malloc (sizeof (INDEX));

  memset (idx, 0, sizeof *idx);
  idx->name = name;
  idx->prefix = name;
  idx->in_code = in_code;
  if (number_of_indices == space_for_indices)
    {
      space_for_indices += 5;
      index_names = realloc (index_names, (space_for_indices + 1)
                             * sizeof (INDEX *));
    }
  index_names[number_of_indices++] = idx;
  index_names[number_of_indices] = 0;
  return idx;
}

/* NAME is the name of an index, e.g. "cp" */
INDEX *
index_by_name (char *name)
{
  int i;

  for (i = 0; i < number_of_indices; i++)
    {
      if (!strcmp (index_names[i]->name, name))
        return index_names[i];
    }
  return 0;
}

/* Add a user defined index with the name NAME */
void
add_index (char *name, int in_code)
{
  INDEX *idx;
  char *cmdname;

  idx = add_index_internal (name, in_code);

  /* For example, "rq" -> "rqindex". */
  asprintf (&cmdname, "%s%s", name, "index");
  add_index_command (cmdname, idx);
  free (cmdname);
}

void
init_index_commands (void)
{
  INDEX *idx;

  struct def { char *name; int in_code; }
  *p, default_indices[] = {
    "cp", 0, /* concepts */
    "fn", 1, /* functions */
    "vr", 1, /* variables */
    "ky", 1, /* keystrokes */
    "pg", 1, /* programs */
    "tp", 1, /* types */
    0, 0
  };
  int i, j;

  char name[] = "?index";
  char name2[] = "??index";

#define MAX (10 * 2)

#define X(command) CM_##command, CM_##command##x
  struct def_cmds { char *name; enum command_id id[MAX]; }
    def_command_indices[] = {
      "fn",

      {X(deffn),
       X(deftypefn),
       X(deftypeop),
       X(defop),
       X(defun),
       X(defmac),
       X(defspec),
       X(deftypefun),
       X(defmethod),
       X(deftypemethod),
      },

      "vr",

      {X(defvr),
       X(deftypevr),
       X(defcv),
       X(deftypecv),
       X(defvar),
       X(defivar),
       X(defopt),
       X(deftypevar),
       X(deftypeivar),
      },

      "tp",

      {X(deftp),}
    };
#undef X

  number_of_indices = 0;
  num_index_commands = 0;

  for (p = default_indices; p->name; p++)
    {
      /* Both @cindex and @cpindex are added. */
      idx = add_index_internal (p->name, p->in_code);

      *name = p->name[0];
      add_index_command (name, idx); /* @cindex */

      name2[0] = p->name[0];
      name2[1] = p->name[1];
      add_index_command (name2, idx); /* @cpindex */
    }

  associate_command_to_index (CM_vtable, index_by_name ("vr"));
  associate_command_to_index (CM_ftable, index_by_name ("fn"));

  for (i = 0;
       i < sizeof (def_command_indices) / sizeof (def_command_indices[0]);
       i++)
    {
      enum command_id cmd;
      idx = index_by_name (def_command_indices[i].name);
      if (idx)
        {
          for (j = 0; j < MAX; j++)
            {
              cmd = def_command_indices[i].id[j];
              if (cmd)
                associate_command_to_index (cmd, idx);
            }
        }
    }
#undef MAX
}

// 2530
/* INDEX_TYPE_COMMAND is used to determine which index to enter the entry in.
   INDEX_AT_COMMAND is the Texinfo @-command defining the index entry.
   CONTENT is an element whose contents represent the text of the
   index entry.  CURRENT is the element in the main body of the manual that
   the index entry refers to.

   CONTENT_NORMALIZED would be "the index entry content, independent
   of the current language." */
void
enter_index_entry (enum command_id index_type_command,
                   enum command_id index_at_command,
                   ELEMENT *current,
                   ELEMENT *content /*, content_normalized */ )
{
  INDEX *idx;
  INDEX_ENTRY *entry;
  INDEX_ENTRY_REF *ier;

  idx = index_of_command (index_type_command);
  if (idx->index_number == idx->index_space)
    {
      idx->index_entries = realloc (idx->index_entries,
                             sizeof (INDEX_ENTRY) * (idx->index_space += 20));
      if (!idx->index_entries)
        abort ();
    }
  entry = &idx->index_entries[idx->index_number++];
  memset (entry, 0, sizeof (INDEX_ENTRY));

  entry->index_name = idx->name;
  entry->index_at_command = index_at_command;
  entry->index_type_command = index_type_command;
  entry->index_prefix = idx->prefix;
  entry->content = content;
  //entry->content_normalized = ... ;
  entry->command = current;
  entry->number = idx->index_number;

  if (current_region ())
    entry->region = current_region ();
  entry->node = current_node;

  entry->number = idx->index_number;

  ier = malloc (sizeof (INDEX_ENTRY_REF));
  ier->index = idx;
  ier->entry = idx->index_number - 1;

  add_extra_index_entry (current, "index_entry", ier);

  if (!current_region () && !current_node && !current_section)
    line_warn ("entry for index `%s' outside of any node", idx->name);
}

INDEX *
ultimate_index (INDEX *index)
{
  while (index->merged_in)
    index = index->merged_in;
  return index;
}


code:
extern INDEX **index_names;

INDEX *index_by_name (char *name);
void add_index (char *name, int in_code);
INDEX *index_of_command (enum command_id cmd);
void enter_index_entry (enum command_id index_type_command,
                   enum command_id index_at_command, ELEMENT *current,
                   ELEMENT *content);
INDEX *ultimate_index (INDEX *index);


code:
/* Copyright 2010, 2011, 2012, 2013, 2014, 2015
   Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <iconv.h>
#include <errno.h>
#include <sys/stat.h>

#include "tree_types.h"
#include "input.h"
#include "text.h"
#include "api.h"

enum input_type { IN_file, IN_text };

enum character_encoding {
    ce_latin1,
    ce_latin2,
    ce_utf8,
    ce_shiftjis
};

typedef struct {
    enum input_type type;

    FILE *file;
    char *input_encoding;
    LINE_NR line_nr;

    char *text;  /* Input text to be parsed as Texinfo. */
    char *ptext; /* How far we are through 'text'.  Used to split 'text'
                    into lines. */
} INPUT;

static INPUT *input_stack = 0;
int input_number = 0;
int input_space = 0;

/* Current filename and line number.  Used for reporting. */
LINE_NR line_nr;

// 1961
/* Collect text from the input sources until a newline is found.  This is used
   instead of next_text when we need to be sure we get an entire line of
   Texinfo input (for example as a line argument to a command), which might not
   be the case if the input is the result of a macro expansion.

   Return value should not be freed by caller, and becomes invalid after
   a subsequent call. */
char *
new_line (void)
{
  static TEXT t;
  char *new = 0;

  t.end = 0;

  while (1)
    {
      new = next_text ();
      if (!new)
        break;
      text_append (&t, new);
      free (new);

      if (t.text[t.end - 1] == '\n')
        break;
    }

  if (t.end > 0)
    return t.text;
  else
    return 0;
}

/* TODO: integrate with gnulib */
#define ICONV_CONST

static iconv_t iconv_from_latin1 = (iconv_t) 0;
static iconv_t iconv_from_latin2;
static iconv_t iconv_from_shiftjis;

/* Run iconv using text buffer as output buffer. */
size_t
text_buffer_iconv (TEXT *buf, iconv_t iconv_state,
                   ICONV_CONST char **inbuf, size_t *inbytesleft)
{
  size_t out_bytes_left;
  char *outptr;
  size_t iconv_ret;

  outptr = buf->text + buf->end;
  if (buf->end == buf->space - 1)
    {
      errno = E2BIG;
      return (size_t) -1;
    }
  out_bytes_left = buf->space - buf->end - 1;
  iconv_ret = iconv (iconv_state, inbuf, inbytesleft,
                     &outptr, &out_bytes_left);

  buf->end = outptr - buf->text;

  return iconv_ret;
}

/* Return conversion of S according to ENC.  This function frees S. */
static char *
convert_to_utf8 (char *s, char *input_encoding)
{
  iconv_t our_iconv;
  static TEXT t;
  char *inptr; size_t bytes_left;
  size_t iconv_ret;
  enum character_encoding enc;

  /* Convert from @documentencoding to UTF-8.
       It might be possible not to convert to UTF-8 and use an 8-bit encoding
     throughout, but then we'd have to not set the UTF-8 flag on the Perl
     strings in api.c.  If multiple character encodings were used in a single
     file, then we'd have to keep track of which strings needed the UTF-8 flag
     and which didn't. */

  /* Could and check for malformed input: see
     <http://savannah.gnu.org/bugs/?42896>. */

  if (iconv_from_latin1 == (iconv_t) 0)
    {
      /* Initialize the conversion for the first time. */
      iconv_from_latin1 = iconv_open ("UTF-8", "ISO-8859-1");
      if (iconv_from_latin1 == (iconv_t) -1)
        {
          abort ();

          /* big trouble.  if we do return it unconverted, we will have to
             remember not to set the UTF-8 flags on the Perl strings, otherwise
             Perl will choke. */
          return s;
        }
    }
  if (iconv_from_latin2 == (iconv_t) 0)
    {
      /* Initialize the conversion for the first time. */
      iconv_from_latin2 = iconv_open ("UTF-8", "ISO-8859-2");
      if (iconv_from_latin2 == (iconv_t) -1)
        iconv_from_latin2 = iconv_from_latin1;
    }
  if (iconv_from_shiftjis == (iconv_t) 0)
    {
      /* Initialize the conversion for the first time. */
      iconv_from_shiftjis = iconv_open ("UTF-8", "SHIFT-JIS");
      if (iconv_from_shiftjis == (iconv_t) -1)
        iconv_from_shiftjis = iconv_from_latin1;
    }

  enc = ce_latin1;
  if (!input_encoding)
    ;
  else if (!strcmp (input_encoding, "utf-8"))
    enc = ce_utf8;
  else if (!strcmp (input_encoding, "iso-8859-2"))
    enc = ce_latin2;
  else if (!strcmp (input_encoding, "shift_jis"))
    enc = ce_shiftjis;

  switch (enc)
    {
    case ce_utf8:
      return s; /* no conversion required. */
      break;
    case ce_latin1:
      our_iconv = iconv_from_latin1;
      break;
    case ce_latin2:
      our_iconv = iconv_from_latin2;
      break;
    case ce_shiftjis:
      our_iconv = iconv_from_shiftjis;
      break;
    }

  t.end = 0;
  inptr = s;
  bytes_left = strlen (s);
  text_alloc (&t, 10);

  while (1)
    {
      iconv_ret = text_buffer_iconv (&t, our_iconv,
                                     &inptr, &bytes_left);

      /* Make sure libiconv flushes out the last converted character.
         This is required when the conversion is stateful, in which
         case libiconv might not output the last character, waiting to
         see whether it should be combined with the next one.  */
      if (iconv_ret != (size_t) -1
          && text_buffer_iconv (&t, our_iconv, 0, 0) != (size_t) -1)
        /* Success: all of input converted. */
        break;

      switch (errno)
        {
        case E2BIG:
          text_alloc (&t, t.space + 20);
          break;
        default:
          abort ();
          break;
        }
    }

  free (s);
  t.text[t.end] = '\0';
  //fprintf (stderr, "CONVERTED STRING IS <<%s>>", t.text);
  return strdup (t.text);
}

int
expanding_macro (char *macro)
{
  int i;
  for (i = 0; i < input_number; i++)
    {
      if (input_stack[i].line_nr.macro
          && !strcmp (input_stack[i].line_nr.macro, macro))
        {
          return 1;
        }
    }
  return 0;
}

/* Return value to be freed by caller.  Return null if we are out of input. */
char *
next_text (void)
{
  ssize_t status;
  char *line = 0;
  size_t n;
  FILE *input_file;

  while (input_number > 0)
    {
      /* Check for pending input. */
      INPUT *i = &input_stack[input_number - 1];

      switch (i->type)
        {
          char *p, *new;
        case IN_text:
          if (!*i->ptext)
            {
              /* End of text reached. */
              free (i->text);
              break;
            }
          /* Split off a line of input. */
          p = strchrnul (i->ptext, '\n');
          new = strndup (i->ptext, p - i->ptext + 1);
          if (*p)
            i->ptext = p + 1;
          else
            i->ptext = p; /* The next time, we will pop the input source. */

          if (!i->line_nr.macro)
            i->line_nr.line_nr++;

          line_nr = i->line_nr;

          return convert_to_utf8 (new, 0); // i->input_encoding);

          break;
        case IN_file: // 1911
          input_file = input_stack[input_number - 1].file;
          status = getline (&line, &n, input_file);
          while (status != -1)
            {
              char *comment;
              if (feof (input_file))
                {
                  /* Add a newline at the end of the file if one is missing. */
                  char *line2;
                  asprintf (&line2, "%s\n", line);
                  free (line);
                  line = line2;
                }

              /* Strip off a comment. */
              comment = strchr (line, '\x7F');
              if (comment)
                *comment = '\0';

              // 1920 CPP_LINE_DIRECTIVES

              i->line_nr.line_nr++;
              line_nr = i->line_nr;

              return convert_to_utf8 (line, i->input_encoding);
            }
          free (line); line = 0;
          break;
        default:
          abort ();
        }

      /* Top input source failed.  Pop it and try the next one. */

      if (input_stack[input_number - 1].type == IN_file)
        {
          FILE *file = input_stack[input_number - 1].file;

          if (file != stdin)
            {
              if (fclose (input_stack[input_number - 1].file) == EOF)
                abort (); // error
            }
        }
      input_number--;
    }
  return 0;
}

void
input_push (char *text, char *macro, char *filename, int line_number)
{
  if (input_number == input_space)
    {
      input_space++; input_space *= 1.5;
      input_stack = realloc (input_stack, input_space * sizeof (INPUT));
      if (!input_stack)
        abort ();
    }

  input_stack[input_number].type = IN_text;
  input_stack[input_number].file = 0;
  input_stack[input_number].text = text;
  input_stack[input_number].ptext = text;
  input_stack[input_number].input_encoding = 0;

  if (!macro)
    line_number--;
  input_stack[input_number].line_nr.line_nr = line_number;
  input_stack[input_number].line_nr.file_name = filename;
  input_stack[input_number].line_nr.macro = macro;
  input_number++;
}

/* Store TEXT as a source for Texinfo content.  TEXT will be later free'd
   and must be allocated on the heap.  MACRO is the name of a macro that
   the text came from. */
void
input_push_text (char *text, char *macro)
{
  if (text)
    input_push (text, macro, 0, line_nr.line_nr);
}

/* Used in tests - like input_push_text, but the lines from the text have
   line numbers. */
void
input_push_text_with_line_nos (char *text, int starting)
{
  input_push (text, 0, 0, starting);
  input_stack[input_number - 1].type = IN_text;
}

void
input_reset_input_stack (void)
{
  input_number = 0;
  /* TODO: free the memory */
}

int
top_file_index (void)
{
  int i = input_number - 1;
  while (i >= 0 && input_stack[i].type != IN_file)
    i--;
  return i;
}

void
set_input_encoding (char *encoding)
{
  int i;

  /* Set encoding of top file in stack. */
  i = top_file_index ();
  if (i >= 0)
    input_stack[i].input_encoding = encoding;
}

static char **include_dirs;
static size_t include_dirs_number;
static size_t include_dirs_space;

void
add_include_directory (char *filename)
{
  int len;
  if (include_dirs_number == include_dirs_space)
    {
      include_dirs = realloc (include_dirs,
                              sizeof (char *) * (include_dirs_space += 5));
    }
  filename = strdup (filename);
  include_dirs[include_dirs_number++] = filename;
  len = strlen (filename);
  if (len > 0 && filename[len - 1] == '/')
    filename[len - 1] = '\0';
}

char *
locate_include_file (char *filename)
{
  char *fullpath;
  struct stat dummy;
  int i, status;

  /* Checks if filename is absolute or relative to current directory.
     TODO: Could use macros in top-level config.h for this. */
  /* TODO: The Perl code (in Common.pm, 'locate_include_file') handles
     a volume in a path (like "A:"), possibly more general treatment
     with File::Spec module. */
  if (!memcmp (filename, "/", 1)
      || !memcmp (filename, "../", 3)
      || !memcmp (filename, "./", 2))
    {
      status = stat (filename, &dummy);
      if (status == 0)
        return filename;
    }
  else
    {
      for (i = 0; i < include_dirs_number; i++)
        {
          asprintf (&fullpath, "%s/%s", include_dirs[i], filename);
          status = stat (fullpath, &dummy);
          if (status == 0)
            return fullpath;
          free (fullpath);
        }
    }
  return 0;
}

/* Try to open a file called FILENAME, looking for it in the list of include
   directories. */
int
input_push_file (char *filename)
{
  FILE *stream;

  stream = fopen (filename, "r");
  if (!stream)
    return errno;

  if (input_number == input_space)
    {
      input_stack = realloc (input_stack, (input_space += 5) * sizeof (INPUT));
      if (!input_stack)
        abort ();
    }

  /* Strip off a leading directory path. */
  char *p, *q;
  p = 0;
  q = strchr (filename, '/');
  while (q)
    {
      p = q;
      q = strchr (q + 1, '/');
    }
  if (p)
    filename = strdup (p+1);

  input_stack[input_number].type = IN_file;
  input_stack[input_number].file = stream;
  input_stack[input_number].line_nr.file_name = filename;
  input_stack[input_number].line_nr.line_nr = 0;
  input_stack[input_number].line_nr.macro = 0;
  input_stack[input_number].text = 0;
  input_stack[input_number].ptext = 0;
  input_stack[input_number].input_encoding = 0;
  input_number++;

  return 0;
}


code:
#include <stdio.h>

char *new_line (void);
char *next_text (void);

void input_push (char *text, char *macro, char *filename, int line_number);
void input_push_text (char *line, char *macro);
void input_push_text_with_line_nos (char *text, int starting);
int input_push_file (char *filename);
void input_reset_input_stack (void);
int expanding_macro (char *macro);
int top_file_index (void);
char *locate_include_file (char *filename);

extern LINE_NR line_nr;

extern int input_number;


code:
#!/bin/sh
# install - install a program, script, or datafile

scriptversion=2013-12-25.23; # UTC

# This originates from X11R5 (mit/util/scripts/install.sh), which was
# later released in X11R6 (xc/config/util/install.sh) with the
# following copyright and license.
#
# Copyright (C) 1994 X Consortium
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Except as contained in this notice, the name of the X Consortium shall not
# be used in advertising or otherwise to promote the sale, use or other deal-
# ings in this Software without prior written authorization from the X Consor-
# tium.
#
#
# FSF changes to this file are in the public domain.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# 'make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.

tab='	'
nl='
'
IFS=" $tab$nl"

# Set DOITPROG to "echo" to test this script.

doit=${DOITPROG-}
doit_exec=${doit:-exec}

# Put in absolute file names if you don't have them in your path;
# or use environment vars.

chgrpprog=${CHGRPPROG-chgrp}
chmodprog=${CHMODPROG-chmod}
chownprog=${CHOWNPROG-chown}
cmpprog=${CMPPROG-cmp}
cpprog=${CPPROG-cp}
mkdirprog=${MKDIRPROG-mkdir}
mvprog=${MVPROG-mv}
rmprog=${RMPROG-rm}
stripprog=${STRIPPROG-strip}

posix_mkdir=

# Desired mode of installed file.
mode=0755

chgrpcmd=
chmodcmd=$chmodprog
chowncmd=
mvcmd=$mvprog
rmcmd="$rmprog -f"
stripcmd=

src=
dst=
dir_arg=
dst_arg=

copy_on_change=false
is_target_a_directory=possibly

usage="\
Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
   or: $0 [OPTION]... SRCFILES... DIRECTORY
   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
   or: $0 [OPTION]... -d DIRECTORIES...

In the 1st form, copy SRCFILE to DSTFILE.
In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
In the 4th, create DIRECTORIES.

Options:
     --help     display this help and exit.
     --version  display version info and exit.

  -c            (ignored)
  -C            install only if different (preserve the last data modification time)
  -d            create directories instead of installing files.
  -g GROUP      $chgrpprog installed files to GROUP.
  -m MODE       $chmodprog installed files to MODE.
  -o USER       $chownprog installed files to USER.
  -s            $stripprog installed files.
  -t DIRECTORY  install into DIRECTORY.
  -T            report an error if DSTFILE is a directory.

Environment variables override the default commands:
  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
  RMPROG STRIPPROG
"

while test $# -ne 0; do
  case $1 in
    -c) ;;

    -C) copy_on_change=true;;

    -d) dir_arg=true;;

    -g) chgrpcmd="$chgrpprog $2"
        shift;;

    --help) echo "$usage"; exit $?;;

    -m) mode=$2
        case $mode in
          *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*)
            echo "$0: invalid mode: $mode" >&2
            exit 1;;
        esac
        shift;;

    -o) chowncmd="$chownprog $2"
        shift;;

    -s) stripcmd=$stripprog;;

    -t)
        is_target_a_directory=always
        dst_arg=$2
        # Protect names problematic for 'test' and other utilities.
        case $dst_arg in
          -* | [=\(\)!]) dst_arg=./$dst_arg;;
        esac
        shift;;

    -T) is_target_a_directory=never;;

    --version) echo "$0 $scriptversion"; exit $?;;

    --) shift
        break;;

    -*) echo "$0: invalid option: $1" >&2
        exit 1;;

    *)  break;;
  esac
  shift
done

# We allow the use of options -d and -T together, by making -d
# take the precedence; this is for compatibility with GNU install.

if test -n "$dir_arg"; then
  if test -n "$dst_arg"; then
    echo "$0: target directory not allowed when installing a directory." >&2
    exit 1
  fi
fi

if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
  # When -d is used, all remaining arguments are directories to create.
  # When -t is used, the destination is already specified.
  # Otherwise, the last argument is the destination.  Remove it from $@.
  for arg
  do
    if test -n "$dst_arg"; then
      # $@ is not empty: it contains at least $arg.
      set fnord "$@" "$dst_arg"
      shift # fnord
    fi
    shift # arg
    dst_arg=$arg
    # Protect names problematic for 'test' and other utilities.
    case $dst_arg in
      -* | [=\(\)!]) dst_arg=./$dst_arg;;
    esac
  done
fi

if test $# -eq 0; then
  if test -z "$dir_arg"; then
    echo "$0: no input file specified." >&2
    exit 1
  fi
  # It's OK to call 'install-sh -d' without argument.
  # This can happen when creating conditional directories.
  exit 0
fi

if test -z "$dir_arg"; then
  if test $# -gt 1 || test "$is_target_a_directory" = always; then
    if test ! -d "$dst_arg"; then
      echo "$0: $dst_arg: Is not a directory." >&2
      exit 1
    fi
  fi
fi

if test -z "$dir_arg"; then
  do_exit='(exit $ret); exit $ret'
  trap "ret=129; $do_exit" 1
  trap "ret=130; $do_exit" 2
  trap "ret=141; $do_exit" 13
  trap "ret=143; $do_exit" 15

  # Set umask so as not to create temps with too-generous modes.
  # However, 'strip' requires both read and write access to temps.
  case $mode in
    # Optimize common cases.
    *644) cp_umask=133;;
    *755) cp_umask=22;;

    *[0-7])
      if test -z "$stripcmd"; then
        u_plus_rw=
      else
        u_plus_rw='% 200'
      fi
      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
    *)
      if test -z "$stripcmd"; then
        u_plus_rw=
      else
        u_plus_rw=,u+rw
      fi
      cp_umask=$mode$u_plus_rw;;
  esac
fi

for src
do
  # Protect names problematic for 'test' and other utilities.
  case $src in
    -* | [=\(\)!]) src=./$src;;
  esac

  if test -n "$dir_arg"; then
    dst=$src
    dstdir=$dst
    test -d "$dstdir"
    dstdir_status=$?
  else

    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
    # might cause directories to be created, which would be especially bad
    # if $src (and thus $dsttmp) contains '*'.
    if test ! -f "$src" && test ! -d "$src"; then
      echo "$0: $src does not exist." >&2
      exit 1
    fi

    if test -z "$dst_arg"; then
      echo "$0: no destination specified." >&2
      exit 1
    fi
    dst=$dst_arg

    # If destination is a directory, append the input filename; won't work
    # if double slashes aren't ignored.
    if test -d "$dst"; then
      if test "$is_target_a_directory" = never; then
        echo "$0: $dst_arg: Is a directory" >&2
        exit 1
      fi
      dstdir=$dst
      dst=$dstdir/`basename "$src"`
      dstdir_status=0
    else
      dstdir=`dirname "$dst"`
      test -d "$dstdir"
      dstdir_status=$?
    fi
  fi

  obsolete_mkdir_used=false

  if test $dstdir_status != 0; then
    case $posix_mkdir in
      '')
        # Create intermediate dirs using mode 755 as modified by the umask.
        # This is like FreeBSD 'install' as of 1997-10-28.
        umask=`umask`
        case $stripcmd.$umask in
          # Optimize common cases.
          *[2367][2367]) mkdir_umask=$umask;;
          .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;

          *[0-7])
            mkdir_umask=`expr $umask + 22 \
              - $umask % 100 % 40 + $umask % 20 \
              - $umask % 10 % 4 + $umask % 2
            `;;
          *) mkdir_umask=$umask,go-w;;
        esac

        # With -d, create the new directory with the user-specified mode.
        # Otherwise, rely on $mkdir_umask.
        if test -n "$dir_arg"; then
          mkdir_mode=-m$mode
        else
          mkdir_mode=
        fi

        posix_mkdir=false
        case $umask in
          *[123567][0-7][0-7])
            # POSIX mkdir -p sets u+wx bits regardless of umask, which
            # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
            ;;
          *)
            tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
            trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0

            if (umask $mkdir_umask &&
                exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
            then
              if test -z "$dir_arg" || {
                   # Check for POSIX incompatibilities with -m.
                   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
                   # other-writable bit of parent directory when it shouldn't.
                   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
                   ls_ld_tmpdir=`ls -ld "$tmpdir"`
                   case $ls_ld_tmpdir in
                     d????-?r-*) different_mode=700;;
                     d????-?--*) different_mode=755;;
                     *) false;;
                   esac &&
                   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
                     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
                     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
                   }
                 }
              then posix_mkdir=:
              fi
              rmdir "$tmpdir/d" "$tmpdir"
            else
              # Remove any dirs left behind by ancient mkdir implementations.
              rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
            fi
            trap '' 0;;
        esac;;
    esac

    if
      $posix_mkdir && (
        umask $mkdir_umask &&
        $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
      )
    then :
    else

      # The umask is ridiculous, or mkdir does not conform to POSIX,
      # or it failed possibly due to a race condition.  Create the
      # directory the slow way, step by step, checking for races as we go.

      case $dstdir in
        /*) prefix='/';;
        [-=\(\)!]*) prefix='./';;
        *)  prefix='';;
      esac

      oIFS=$IFS
      IFS=/
      set -f
      set fnord $dstdir
      shift
      set +f
      IFS=$oIFS

      prefixes=

      for d
      do
        test X"$d" = X && continue

        prefix=$prefix$d
        if test -d "$prefix"; then
          prefixes=
        else
          if $posix_mkdir; then
            (umask=$mkdir_umask &&
             $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
            # Don't fail if two instances are running concurrently.
            test -d "$prefix" || exit 1
          else
            case $prefix in
              *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
              *) qprefix=$prefix;;
            esac
            prefixes="$prefixes '$qprefix'"
          fi
        fi
        prefix=$prefix/
      done

      if test -n "$prefixes"; then
        # Don't fail if two instances are running concurrently.
        (umask $mkdir_umask &&
         eval "\$doit_exec \$mkdirprog $prefixes") ||
          test -d "$dstdir" || exit 1
        obsolete_mkdir_used=true
      fi
    fi
  fi

  if test -n "$dir_arg"; then
    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
  else

    # Make a couple of temp file names in the proper directory.
    dsttmp=$dstdir/_inst.$$_
    rmtmp=$dstdir/_rm.$$_

    # Trap to clean up those temp files at exit.
    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0

    # Copy the file name to the temp name.
    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&

    # and set any options; do chmod last to preserve setuid bits.
    #
    # If any of these fail, we abort the whole thing.  If we want to
    # ignore errors from any of these, just make sure not to ignore
    # errors from the above "$doit $cpprog $src $dsttmp" command.
    #
    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&

    # If -C, don't bother to copy if it wouldn't change the file.
    if $copy_on_change &&
       old=`LC_ALL=C ls -dlL "$dst"     2>/dev/null` &&
       new=`LC_ALL=C ls -dlL "$dsttmp"  2>/dev/null` &&
       set -f &&
       set X $old && old=:$2:$4:$5:$6 &&
       set X $new && new=:$2:$4:$5:$6 &&
       set +f &&
       test "$old" = "$new" &&
       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
    then
      rm -f "$dsttmp"
    else
      # Rename the file to the real destination.
      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||

      # The rename failed, perhaps because mv can't rename something else
      # to itself, or perhaps because mv is so ancient that it does not
      # support -f.
      {
        # Now remove or move aside any old file at destination location.
        # We try this two ways since rm can't unlink itself on some
        # systems and the destination file might be busy for other
        # reasons.  In this case, the final cleanup might fail but the new
        # file should still install successfully.
        {
          test ! -f "$dst" ||
          $doit $rmcmd -f "$dst" 2>/dev/null ||
          { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
            { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
          } ||
          { echo "$0: cannot unlink or rename $dst" >&2
            (exit 1); exit 1
          }
        } &&

        # Now rename the file to the real destination.
        $doit $mvcmd "$dsttmp" "$dst"
      }
    fi || exit 1

    trap '' 0
  fi
done

# Local variables:
# eval: (add-hook 'write-file-hooks 'time-stamp)
# time-stamp-start: "scriptversion="
# time-stamp-format: "%:y-%02m-%02d.%02H"
# time-stamp-time-zone: "UTC"
# time-stamp-end: "; # UTC"
# End:

path: tp/parsetexi/install-sh


code:
/* Copyright 2010, 2011, 2012, 2013, 2014, 2015
   Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include "parser.h"
#include "convert.h"
#include "labels.h"

/* Array of recorded labels. */
/* If looking through this array turns out to be slow, we might have to replace
   it with some kind of hash table implementation. */
LABEL *labels_list = 0;
size_t labels_number = 0;
size_t labels_space = 0;

/* Register a label, that is something that may be the target of a reference
   and must be unique in the document.  Corresponds to @node, @anchor, and
   second arg of @float. */
void
register_label (ELEMENT *current, NODE_SPEC_EXTRA *label)
{
  if (labels_number == labels_space)
    {
      labels_space += 1;
      labels_space *= 1.5;
      labels_list = realloc (labels_list, labels_space * sizeof (LABEL));
      if (!labels_list)
        abort ();
    }
  labels_list[labels_number++].target = current;

  // 2504
  add_extra_contents (current, "node_content", label->node_content);
}

void
reset_labels (void)
{
  labels_number = 0;
}

ELEMENT **internal_xref_list = 0;
size_t internal_xref_number = 0;
size_t internal_xref_space = 0;

void
remember_internal_xref (ELEMENT *element)
{
  if (internal_xref_number == internal_xref_space)
    {
      internal_xref_list = realloc (internal_xref_list,
                             (internal_xref_space += 2)
                             * sizeof (*internal_xref_list));
    }
  internal_xref_list[internal_xref_number++] = element;
}

void
reset_internal_xrefs (void)
{
  internal_xref_number = 0;
}


code:
/* Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016
   Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

/* Information about a possible target of a cross-reference, often a node. */
typedef struct {
    /* Pointer to the element for the command defining this label, usually a
       node element.  FIXME: I'm not sure if we actualy need to get to the
       target - much of the use of the labels_information is to check that
       references are to real places. */
    ELEMENT *target;
} LABEL;

extern LABEL *labels_list;
extern size_t labels_number;
void register_label (ELEMENT *current, NODE_SPEC_EXTRA *label);
void reset_labels (void);

extern ELEMENT **internal_xref_list;
extern size_t internal_xref_number;
extern size_t internal_xref_space;

void remember_internal_xref (ELEMENT *element);
void reset_internal_xrefs (void);


code:
/* Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016
   Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#define _GNU_SOURCE

#include <stdlib.h>
#include <string.h>

#include "parser.h"
#include "tree.h"
#include "text.h"
#include "input.h"
#include "errors.h"
#include "convert.h"

static MACRO *macro_list;
static size_t macro_number;
static size_t macro_space;

/* Macro definition. */

void
new_macro (char *name, ELEMENT *macro)
{
  enum command_id new;
  MACRO *m = 0;
  ELEMENT tmp;

  /* Check for an existing definition first for us to overwrite. */
  new = lookup_command (name);
  if (new)
    m = lookup_macro (new);
  if (!m)
    {
      if (macro_number == macro_space)
        {
          macro_list = realloc (macro_list,
                                (macro_space += 5) * sizeof (MACRO));
          if (!macro_list)
            abort ();
        }
      new = add_texinfo_command (name);
      m = &macro_list[macro_number];
      m->cmd = new;
      macro_number++;
      new &= ~USER_COMMAND_BIT;
      user_defined_command_data[new].flags |= CF_MACRO;
    }

  m->macro_name = strdup (name);
  m->element = macro;

  memset (&tmp, 0, sizeof (ELEMENT));
  tmp.contents = macro->contents;
  m->macrobody = convert_to_texinfo (&tmp);
}

// 1088
/* CMD will be either CM_macro or CM_rmacro.  Read the line defining a macro's
   name and the arguments it takes, and return this information in a new
   ELEMENT. */
ELEMENT *
parse_macro_command_line (enum command_id cmd, char **line_inout,
                          ELEMENT *parent)
{
  char *line = *line_inout;
  ELEMENT *macro, *macro_name;
  char *name, *args_ptr;
  int index;

  macro = new_element (ET_NONE);
  macro->cmd = cmd;
  macro->line_nr = line_nr;

  add_extra_string (macro, "arg_line", strdup (line));
  /* FIXME: This extra value isn't used much, so is a candidate for
     simplification. */

  line += strspn (line, whitespace_chars);
  name = read_command_name (&line);

  if (*line && *line != '{' && !strchr (whitespace_chars, *line))
    {
      line_error ("bad name for @%s", command_name (cmd));
      add_extra_string (macro, "invalid_syntax", "1");
      return macro;
    }
  else if (!name)
    {
      line_error ("@%s requires a name", command_name (cmd));
      add_extra_string (macro, "invalid_syntax", "1");
      return macro;
    }

  macro_name = new_element (ET_macro_name);
  text_append (&macro_name->text, name);
  free (name);
  add_to_element_args (macro, macro_name);

  args_ptr = line;
  args_ptr += strspn (args_ptr, whitespace_chars);

  if (*args_ptr != '{')
    {
      /* Either error or no args. */
      goto check_trailing;
    }
  args_ptr++;

  index = 0;
  while (1)
    {
      /* args_ptr is after a '{' or ','.  INDEX holds the number of
         the macro argument */

      char *q, *q2;
      ELEMENT *arg;

      args_ptr += strspn (args_ptr, whitespace_chars);

      /* Find end of current argument. */
      q = args_ptr;
      while (*q != '\0' && *q != ',' && *q != '}')
        q++;

      if (!*q)
        {
          /* End of string reached before closing brace. */
          abort ();
        }

      /* Disregard trailing whitespace. */
      q2 = q;
      while (q2 > args_ptr && strchr (whitespace_chars, q2[-1]))
        q2--;

      if (q2 == args_ptr)
        {
          // 1126 - argument is completely whitespace
          if (*q == ',')
            {
              line_error ("bad or empty @%s formal argument: ",
                          command_name(cmd));
              arg = new_element (ET_macro_arg);
              add_to_element_args (macro, arg);
              text_append_n (&arg->text, "", 0);
              add_extra_string (macro, "invalid_syntax", "1");
            }
        }
      else
        {
          arg = new_element (ET_macro_arg);
          text_append_n (&arg->text, args_ptr, q2 - args_ptr);
          add_to_element_args (macro, arg);

          /* Check the argument name. */
            {
              char *p;
              for (p = args_ptr; p < q2; p++)
                {
                  if (!isalnum (*p) && *p != '_' && *p != '-')
                    {
                      char c = *q2; *q2 = 0;
                      line_error ("bad or empty @%s formal argument: %s",
                                  command_name(cmd), args_ptr);
                      *q2 = c;
                      add_extra_string (macro, "invalid_syntax", "1");
                      break;
                    }
                }
            }
        }

      args_ptr = q + 1;

      if (*q == '}')
        break;

      index++;
    }

check_trailing:
  line = args_ptr;
  line += strspn (line, whitespace_chars);
  if (*line && *line != '@')
    {
      line_error ("bad syntax for @%s argument: %s",
                  command_name(cmd), line);
      add_extra_string (macro, "invalid_syntax", "1");
    }
  //line += strlen (line); /* Discard rest of line. */

funexit:
  *line_inout = line;
  return macro;
}

/* Macro use. */

/* Return index into given arguments to look for the value of NAME.
   Return -1 if not found. */

int
lookup_macro_parameter (char *name, ELEMENT *macro)
{
  int i, pos;
  ELEMENT **args;

  /* Find 'arg' in MACRO parameters. */
  args = macro->args.list;
  pos = 0;
  for (i = 0; i < macro->args.number; i++)
    {
      if (args[i]->type == ET_macro_arg)
        {
          if (!strcmp (args[i]->text.text, name))
            return pos;
          pos++;
        }
    }
  return -1;
}

/* LINE points to after the opening brace in a macro invocation.  CMD is the
   command identifier of the macro command.  Return array of the arguments.
   Return value to be freed by caller.  */
// 1984
char **
expand_macro_arguments (ELEMENT *macro, char **line_inout, enum command_id cmd)
{
  char *line = *line_inout;
  char *pline = line;
  TEXT arg;
  int braces_level = 1;
  int args_total;

  char **arg_list = 0;
  size_t arg_number = 0;
  size_t arg_space = 0;

  arg_list = malloc (sizeof (char *));
  args_total = macro->args.number - 1;

  text_init (&arg);

  while (braces_level > 0)
    {
      /* At the beginning of this loop pline is at the start
         of an argument. */
      char *sep;

      sep = pline + strcspn (pline, "\\,{}");
      if (!*sep)
        {
          debug ("MACRO ARG end of line");
          text_append (&arg, pline);
          line = new_line ();
          if (!line)
            {
              line_error ("@%s missing closing brace", command_name(cmd));
              line = "\n";
              goto funexit;
            }
          pline = line;
          continue;
        }

      text_append_n (&arg, pline, sep - pline);

      // 2002
      switch (*sep)
        {
        case '\\':
          if (!strchr ("\\{},", sep[1]))
            text_append_n (&arg, sep, 1);
          if (sep[1])
            {
              text_append_n (&arg, &sep[1], 1);
              pline = sep + 2;
            }
          else
            pline = sep + 1;
          break;
        case '{':
          braces_level++;
          text_append_n (&arg, sep, 1);
          pline = sep + 1;
          break;
        case '}':
          braces_level--;
          if (braces_level > 0)
            {
              text_append_n (&arg, sep, 1);
              pline = sep + 1;
              break;
            }

          /* Fall through to add argument. */
        case ',':
          if (braces_level > 1)
            {
              text_append_n (&arg, sep, 1);
              pline = sep + 1;
              break;
            }

          // 2021 check for too many args
          if (*sep == '}' || arg_number < args_total - 1)
            {
              /* Add the last argument read to the list. */
              if (arg_number == arg_space)
                {
                  arg_list = realloc (arg_list,
                                      (1+(arg_space += 5)) * sizeof (char *));
                  /* Include space for terminating null element. */
                  if (!arg_list)
                    abort ();
                }
              if (arg.space > 0)
                arg_list[arg_number++] = arg.text;
              else
                arg_list[arg_number++] = strdup ("");
              text_init (&arg);
              // TODO: is "@m {     }" one empty argument or none?

              debug ("MACRO NEW ARG");
              pline = sep + 1;

              if (*sep == ',')
                pline += strspn (pline, whitespace_chars);
            }
          else
            {
              if (args_total != 1)
                line_error ("macro `%s' called with too many args",
                            command_name(cmd));
              text_append_n (&arg, ",", 1);
              pline = sep + 1;
            }
          break;
        }
    }

  debug ("END MACRO ARGS EXPANSION");
  line = pline;

funexit:
  *line_inout = line;
  arg_list[arg_number] = 0;
  return arg_list;
}

// 2063
/* ARGUMENTS are the arguments used in the macro invocation.  EXPANDED gets the
   result of the expansion. */
static void
expand_macro_body (MACRO *macro_record, char *arguments[], TEXT *expanded)
{
  char *arg;
  int pos; /* Index into arguments. */
  int i; /* Index into macro contents. */
  ELEMENT *macro;
  char *macrobody;
  char *ptext;

  macro = macro_record->element;

  macrobody = macro_record->macrobody;

  /* Initialize TEXT object. */
  expanded->end = 0;

  if (!macrobody)
    return;

  ptext = macrobody;
  while (1)
    {
      /* At the start of this loop ptext is at the beginning or
         just after the last backslash sequence. */

      char *bs; /* Pointer to next backslash. */

      bs = strchrnul (ptext, '\\');
      text_append_n (expanded, ptext, bs - ptext);
      if (!*bs)
        break; /* End of line. */

      ptext = bs + 1;
      if (*ptext == '\\')
        {
          text_append_n (expanded, "\\", 1); /* Escaped backslash (\\). */
          ptext++;
        }
      else
        {
          bs = strchr (ptext, '\\');
          if (!bs)
            {
              // error - malformed
              return;
              abort ();
            }

          *bs = '\0';
          pos = lookup_macro_parameter (ptext, macro);
          if (pos == -1)
            {
              line_error ("\\ in @%s expansion followed `%s' instead of "
                          "parameter name or \\",
                          macro->args.list[0]->text.text,
                          ptext);
              text_append (expanded, "\\");
              text_append (expanded, ptext);
            }
          else
            {
              if (arguments && arguments[pos])
                text_append (expanded, arguments[pos]);
            }
          *bs = '\\';
          ptext = bs + 1;
        }
    }
}

MACRO *
lookup_macro (enum command_id cmd)
{
  int i;

  for (i = 0; i < macro_number; i++)
    {
      if (macro_list[i].cmd == cmd)
        return &macro_list[i];
    }
  return 0;
}

void
delete_macro (char *name)
{
  enum command_id cmd;
  MACRO *m;
  cmd = lookup_command (name);
  if (!cmd)
    return;
  m = lookup_macro (cmd);
  if (!m)
    return;
  m->cmd = 0;
  m->macro_name = "";
  m->element = 0;
  remove_texinfo_command (cmd);
}

void
wipe_macros (void)
{
  macro_number = 0;
}

// 3898
/* CMD is the macro command. */
ELEMENT *
handle_macro (ELEMENT *current, char **line_inout, enum command_id cmd)
{
  char *line, *p;
  MACRO *macro_record;
  ELEMENT *macro;
  TEXT expanded;
  char **arguments = 0;
  int args_number;

  line = *line_inout;
  text_init (&expanded);

  macro_record = lookup_macro (cmd);
  if (!macro_record)
    abort ();
  macro = macro_record->element;

  // 3907 Get number of args. - 1 for the macro name.
  args_number = macro->args.number - 1;

  p = line + strspn (line, whitespace_chars);
  if (*p == '{')
    {
      line = p;
      line++;
      /* In the Perl version formfeed is excluded for some reason. */
      line += strspn (line, whitespace_chars);
      arguments = expand_macro_arguments (macro, &line, cmd);
    }
  /* Warning depending on the number of arguments this macro
     is supposed to take. */
  else if (args_number != 1)
    {
      if (args_number > 1)
        line_warn ("@%s defined with zero or more than one argument should "
                   "be invoked with {}", command_name(cmd));
      /* As agreed on the bug-texinfo mailing list, no warn when zero
         arg and not called with {}. */
    }
  else
    {
      char *p;
      /* If it takes a single line of input, and we don't have a full line of
         input already, call new_line. */
      if (!strchr (line, '\n'))
        {
          line = new_line ();
          if (!line)
            line = "";
        }
      line += strspn (line, whitespace_chars);

      arguments = malloc (sizeof (char *) * 2);
      arguments[0] = strdup (line);
      arguments[1] = 0;

      p = strchr (arguments[0], '\n');
      if (p)
        {
          *p = '\0';
          line = "\n";
        }
    }

  expand_macro_body (macro_record, arguments, &expanded);
  debug ("MACROBODY: %s||||||", expanded.text);

  if (expanded.end > 0 && expanded.text[expanded.end - 1] == '\n')
    expanded.text[--expanded.end] = '\0';

  if (input_number >= 1000)
    {
      line_warn (
         "macro call nested too deeply "
         "(set MAX_NESTED_MACROS to override; current value %d)", 1000);
      goto funexit;
    }

  if (macro->cmd == CM_macro)
    {
      if (expanding_macro (command_name(cmd)))
        {
          line_error ("recursive call of macro %s is not allowed; "
                      "use @rmacro if needed", command_name(cmd));
          goto funexit;
        }
    }

  /* Free arguments. */
  if (arguments)
    {
      char **s = arguments;
      while (*s)
        {
          free (*s);
          s++;
        }
      free (arguments);
    }

  // 3958 Pop macro stack

  // 3961
  /* Put expansion in front of the current line. */
  input_push_text (strdup (line), 0);
  line = strchr (line, '\0');
  input_push_text (expanded.text, command_name(cmd));

funexit:
  *line_inout = line;
  return current;
}

/* @set and @value */

typedef struct {
    char *name;
    char *value;
} VALUE;

static VALUE *value_list;
static size_t value_number;
static size_t value_space;

void
wipe_values (void)
{
  size_t i;
  for (i = 0; i < value_number; i++)
    {
      free (value_list[i].name);
      free (value_list[i].value);
    }
  value_number = 0;
}

void
store_value (char *name, char *value)
{
  int i;
  VALUE *v = 0;
  int len;

  len = strlen (name);

  /* Check if already defined. */
  for (i = 0; i < value_number; i++)
    {
      if (!memcmp (value_list[i].name, name, len) && !value_list[i].name[len])
        {
          v = &value_list[i];
          free (v->name); free (v->value);
          break;
        }
    }

  if (!v)
    {
      if (value_number == value_space)
        {
          value_list = realloc (value_list, (value_space += 5) * sizeof (VALUE));
        }
      v = &value_list[value_number++];
    }

  v->name = strdup (name);
  v->value = strdup (value);
}

void
clear_value (char *name, int len)
{
  int i;
  for (i = 0; i < value_number; i++)
    {
      if (!memcmp (value_list[i].name, name, len) && !value_list[i].name[len])
        {
          value_list[i].name[0] = '\0';
          value_list[i].value[0] = '\0';
        }
    }
}

char *
fetch_value (char *name, int len)
{
  int i;
  for (i = 0; i < value_number; i++)
    {
      if (!memcmp (value_list[i].name, name, len) && !value_list[i].name[len])
        return value_list[i].value;
    }

  if (!strcmp (name, "txicommandconditionals"))
    return "1";
  return 0;
}

static INFO_ENCLOSE *infoencl_list;
static size_t infoencl_number;
static size_t infoencl_space;

INFO_ENCLOSE *
lookup_infoenclose (enum command_id cmd)
{
  int i;
  for (i = 0; i < infoencl_number; i++)
    {
      if (infoencl_list[i].cmd == cmd)
        return &infoencl_list[i];
    }
  return 0;
}

void
add_infoenclose (enum command_id cmd, char *begin, char *end)
{
  int i;
  INFO_ENCLOSE *ie = 0;

  /* Check if already defined. */
  for (i = 0; i < infoencl_number; i++)
    {
      if (infoencl_list[i].cmd == cmd)
        {
          ie = &infoencl_list[i];
          free (ie->begin);
          free (ie->end);
          break;
        }
    }

  if (!ie)
    {
      if (infoencl_number == infoencl_space)
        {
          infoencl_list = realloc (infoencl_list,
                                   (infoencl_space += 5)
                                   * sizeof (INFO_ENCLOSE));
        }
      ie = &infoencl_list[infoencl_number++];
    }

  ie->cmd = cmd;
  ie->begin = strdup (begin);
  ie->end = strdup (end);
}