/* end_line.c -- what to do at the end of a whole line of input */
/* Copyright 2010-2019 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 . */
#include
#include
#include
#include
#include "parser.h"
#include "text.h"
#include "input.h"
#include "convert.h"
#include "labels.h"
#include "indices.h"
void
check_internal_node (NODE_SPEC_EXTRA *nse)
{
if (nse && nse->manual_content
&& nse->manual_content->contents.number > 0)
{
char *texi = node_extra_to_texi (nse);
line_error ("syntax for an external node used for `%s'", texi);
free (texi);
}
}
int
check_empty_node (NODE_SPEC_EXTRA *nse,
enum command_id cmd)
{
if (!nse || !nse->node_content || nse->node_content->contents.number == 0)
{
line_error ("empty argument in @%s", command_name(cmd));
return 0;
}
else
return 1;
}
int
check_node_label (NODE_SPEC_EXTRA *nse,
enum command_id cmd)
{
check_internal_node (nse);
return check_empty_node (nse, cmd);
}
static int
is_decimal_number (char *string)
{
char *p = string;
char *first_digits = 0;
char *second_digits = 0;
if (string[0] == '\0')
return 0;
if (strchr (digit_chars, *p))
p = first_digits = string + strspn (string, digit_chars);
if (*p == '.')
{
p++;
if (strchr (digit_chars, *p))
p = second_digits = p + strspn (p, digit_chars);
}
if (*p /* Bytes remaining at end of argument. */
|| (!first_digits && !second_digits)) /* Need digits either
before or after the
decimal point. */
{
return 0;
}
return 1;
}
static int
is_whole_number (char *string)
{
if (string[strspn (string, digit_chars)] == '\0')
return 1;
return 0;
}
/* Return end of argument before comment and whitespace. */
char *
skip_comment (char *q, int *has_comment)
{
char *q1;
while (1)
{
q1 = strstr (q, "@c");
if (!q1)
{
q = q + strlen (q);
break;
}
q = q1 + 2;
if (!memcmp (q, "omment", 6))
q += 6;
/* TeX control sequence name ends at an escape character or
whitespace. */
if (*q == '@' || strchr (whitespace_chars, *q))
{
q = q1;
*has_comment = 1;
break;
}
}
/* q is now either at the end of the string, or at the start of a comment.
Find the start of any trailing whitespace. */
while (strchr (whitespace_chars, q[-1]))
q--;
return q;
}
/* Process argument to special line command. */
ELEMENT *
parse_special_misc_command (char *line, enum command_id cmd, int *has_comment)
{
#define ADD_ARG(string, len) do { \
ELEMENT *E = new_element (ET_NONE); \
text_append_n (&E->text, string, len); \
add_to_element_contents (args, E); \
} while (0)
ELEMENT *args = new_element (ET_NONE);
char *p = 0, *q = 0, *r = 0;
char *value = 0, *remaining = 0;;
switch (cmd)
{
case CM_set:
{
p = line;
p += strspn (p, whitespace_chars);
if (!*p)
goto set_no_name;
if (!isalnum (*p) && *p != '-' && *p != '_')
goto set_invalid;
q = strpbrk (p,
" \t\f\r\n" /* whitespace */
"{\\}~^+\"<>|@"); /* other bytes that aren't allowed */
/* see also read_flag_name function in end_line.c */
r = skip_comment (p, has_comment);
if (!strchr (whitespace_chars, *q) && *q != '@')
goto set_invalid;
if (*q == '@')
{
/* Check for a comment, e.g. "@set flag@c comment" */
if (q != r)
goto set_invalid;
}
ADD_ARG(p, q - p); /* name */
p = q + strspn (q, whitespace_chars);
/* Actually, whitespace characters except form feed. */
if (r >= p)
ADD_ARG(p, r - p); /* value */
else
ADD_ARG("", 0);
store_value (args->contents.list[0]->text.text,
args->contents.list[1]->text.text);
break;
set_no_name:
line_error ("@set requires a name");
break;
set_invalid:
line_error ("bad name for @set");
break;
}
case CM_clear:
{
char *flag = 0;
p = line;
p += strspn (p, whitespace_chars);
if (!*p)
goto clear_no_name;
q = p;
flag = read_flag_name (&q);
if (!flag)
goto clear_invalid;
r = q + strspn (q, whitespace_chars);
if (*r)
goto clear_invalid; /* Trailing argument. */
ADD_ARG (p, q - p);
clear_value (flag);
free (flag);
break;
clear_no_name:
line_error ("@clear requires a name");
break;
clear_invalid:
free (flag);
line_error ("bad name for @clear");
break;
}
case CM_unmacro:
p = line;
p += strspn (p, whitespace_chars);
if (!*p)
goto unmacro_noname;
q = p;
value = read_command_name (&q);
if (!value)
goto unmacro_badname;
/* FIXME: Check comment syntax is right */
delete_macro (value);
ADD_ARG(value, q - p);
debug ("UNMACRO %s", value);
free (value);
break;
unmacro_noname:
line_error ("@unmacro requires a name");
break;
unmacro_badname:
line_error ("bad name for @unmacro");
break;
case CM_clickstyle:
p = line;
p += strspn (p, whitespace_chars);
if (*p++ != '@')
goto clickstyle_invalid;
q = p;
value = read_command_name (&q);
if (!value)
goto clickstyle_invalid;
ADD_ARG (p - 1, q - p + 1);
free (global_clickstyle); global_clickstyle = value;
if (!memcmp (q, "{}", 2))
q += 2;
remaining = q;
/* FIXME: check comment */
break;
clickstyle_invalid:
line_error ("@clickstyle should only accept an @-command as argument, "
"not `%s'", line);
free (value);
break;
default:
fatal ("unknown special line command");
}
if (remaining)
{
remaining += strspn (remaining, whitespace_chars);
if (*remaining)
{
line_warn ("remaining argument on @%s line: %s",
command_name(cmd), remaining);
}
}
return args;
#undef ADD_ARG
}
/* Parse the arguments to a line command. Return an element whose contents
is an array of the arguments. For some commands, there is further
processing of the arguments (for example, for an @alias, remember the
alias.) */
ELEMENT *
parse_line_command_args (ELEMENT *line_command)
{
#define ADD_ARG(string) do { \
ELEMENT *E = new_element (ET_NONE); \
text_append (&E->text, string); \
add_to_element_contents (line_args, E); \
} while (0)
ELEMENT *arg = line_command->args.list[0];
ELEMENT *line_args;
enum command_id cmd;
char *line;
cmd = line_command->cmd;
if (arg->contents.number == 0)
{
command_error (line_command, "@%s missing argument", command_name(cmd));
add_extra_integer (line_command, "missing_argument", 1);
return 0;
}
if (arg->contents.number > 1 || arg->contents.list[0]->text.end == 0)
{
line_error ("superfluous argument to @%s", command_name (cmd));
}
if (arg->contents.list[0]->text.end == 0)
return 0;
line_args = new_element (ET_NONE);
line = arg->contents.list[0]->text.text;
switch (cmd)
{
case CM_alias:
{
/* @alias NEW = EXISTING */
char *new = 0, *existing = 0;
enum command_id new_cmd, existing_cmd;
new = read_command_name (&line);
if (!new)
goto alias_invalid;
line += strspn (line, whitespace_chars);
if (*line != '=')
goto alias_invalid;
line++;
line += strspn (line, whitespace_chars);
if (!isalnum (*line))
goto alias_invalid;
existing = read_command_name (&line);
if (!existing)
goto alias_invalid;
if (*line)
goto alias_invalid; /* Trailing argument. */
ADD_ARG(new);
ADD_ARG(existing);
existing_cmd = lookup_command (existing);
if (!existing_cmd)
break; /* TODO: Error message */
else
{
if (command_data(existing_cmd).flags & CF_block)
line_warn ("environment command %s as argument to @alias",
command_name(existing_cmd));
}
/* Remember the alias. */
new_cmd = add_texinfo_command (new);
new_cmd &= ~USER_COMMAND_BIT;
user_defined_command_data[new_cmd].flags |= CF_ALIAS;
user_defined_command_data[new_cmd].data = existing_cmd;
/* Note the data field is an int, existing_cmd is
enum command_id, so would have problems if enum command_id
were wider than an int. */
free (new); free (existing);
break;
alias_invalid:
line_error ("bad argument to @alias");
free (new); free (existing);
break;
}
case CM_definfoenclose:
{
/* @definfoenclose phoo,//,\\ */
char *new_command = 0, *start = 0, *end = 0;
enum command_id new_cmd;
int len;
new_command = read_command_name (&line);
if (!new_command)
goto definfoenclose_invalid;
line += strspn (line, whitespace_chars);
if (*line != ',')
goto definfoenclose_invalid;
line++;
line += strspn (line, whitespace_chars);
/* TODO: Can we have spaces in the delimiters? */
len = strcspn (line, ",");
start = strndup (line, len);
line += len;
if (!*line)
goto definfoenclose_invalid; /* Not enough args. */
line++; /* Past ','. */
line += strspn (line, whitespace_chars);
len = strcspn (line, ",");
end = strndup (line, len);
if (*line == ',')
goto definfoenclose_invalid; /* Too many args. */
/* Remember it. */
new_cmd = add_texinfo_command (new_command);
add_infoenclose (new_cmd, start, end);
new_cmd &= ~USER_COMMAND_BIT;
user_defined_command_data[new_cmd].flags
|= (CF_INFOENCLOSE | CF_brace);
user_defined_command_data[new_cmd].data = BRACE_style;
ADD_ARG(new_command); free (new_command);
ADD_ARG(start); free (start);
ADD_ARG(end); free (end);
break;
definfoenclose_invalid:
line_error ("bad argument to @definfoenclose");
free (new_command); free (start); free (end);
break;
}
case CM_columnfractions:
{
/* @multitable @columnfractions .33 .33 .33 */
ELEMENT *new;
char *p, *q;
if (!*line)
{
line_error ("empty @columnfractions");
break;
}
p = line;
while (1)
{
char *arg;
p += strspn (p, whitespace_chars);
if (!*p)
break;
q = strpbrk (p, whitespace_chars);
if (!q)
q = p + strlen (p);
arg = strndup (p, q - p);
/* Check argument is valid. */
if (!is_decimal_number (arg))
{
line_error ("column fraction not a number: %s", arg);
}
else
{
new = new_element (ET_NONE);
text_append_n (&new->text, p, q - p);
add_to_element_contents (line_args, new);
}
free (arg);
p = q;
}
break;
}
case CM_sp:
{
/* Argument is at least one digit. */
if (strchr (digit_chars, *line)
&& !*(line + 1 + strspn (line + 1, digit_chars)))
{
ADD_ARG(line);
}
else
line_error ("@sp arg must be numeric, not `%s'", line);
break;
}
case CM_defindex:
case CM_defcodeindex:
{
char *name = 0;
char *p = line;
name = read_command_name (&p);
if (*p)
goto defindex_invalid; /* Trailing characters. */
/* Disallow index names NAME where it is likely that for
a source file BASE.texi, there will be other files called
BASE.NAME in the same directory. This is to prevent such
files being overwritten by the files read by texindex. */
{
static char *forbidden_index_names[] = {
"cp", "fn", "ky", "pg", "tp", "vr",
"cps", "fns", "kys", "pgs", "tps", "vrs",
"info", "ps", "pdf", "htm", "html",
"log", "aux", "dvi", "texi", "txi",
"texinfo", "tex", "bib", 0
};
char **ptr;
for (ptr = forbidden_index_names; *ptr; ptr++)
if (!strcmp (name, *ptr))
goto defindex_reserved;
if (index_by_name (name))
{ free (name); break; }
}
add_index (name, cmd == CM_defcodeindex ? 1 : 0);
ADD_ARG(name);
break;
defindex_invalid:
line_error ("bad argument to @%s: %s",
command_name(cmd), line);
free (name);
break;
defindex_reserved:
line_error ("reserved index name %s", name);
free (name);
break;
}
case CM_synindex:
case CM_syncodeindex:
{
/* synindex FROM TO */
char *from = 0, *to = 0;
INDEX *from_index, *to_index;
char *p = line;
if (!isalnum (*p))
goto synindex_invalid;
from = read_command_name (&p);
if (!from)
goto synindex_invalid;
p += strspn (p, whitespace_chars);
if (!isalnum (*p))
goto synindex_invalid;
to = read_command_name (&p);
if (!to)
goto synindex_invalid;
if (*p)
goto synindex_invalid; /* More at end of line. */
from_index = index_by_name (from);
to_index = index_by_name (to);
if (!from_index)
line_error ("unknown source index in @%s: %s",
command_name(cmd), from);
if (!to_index)
line_error ("unknown destination index in @%s: %s",
command_name(cmd), to);
if (from_index && to_index)
{
INDEX *current_to = to_index;
/* Find ultimate index this merges to. */
current_to = ultimate_index (current_to);
if (current_to != from_index)
{
from_index->merged_in = current_to;
from_index->in_code = (cmd == CM_syncodeindex);
ADD_ARG(from);
ADD_ARG(to);
/* Note that 'current_to' may not end up as the index
'from_index' merges into if there are further @synindex
commands. */
}
else
line_warn ("@%s leads to a merging of %s in itself, ignoring",
command_name(cmd), from);
}
free (from);
free (to);
break;
synindex_invalid:
line_error ("bad argument to @%s: %s",
command_name(cmd), line);
free (from); free (to);
break;
}
case CM_printindex:
{
char *arg;
char *p = line;
arg = read_command_name (&p);
if (!arg || *p)
line_error ("bad argument to @printindex: %s", line);
else
{
INDEX *idx = index_by_name (arg);
if (!idx)
line_error ("unknown index `%s' in @printindex", arg);
else
{
if (idx->merged_in)
{
INDEX *i2;
for (i2 = idx; (i2->merged_in); i2 = i2->merged_in)
;
line_warn
("printing an index `%s' merged in another one, `%s'",
arg, i2->name);
}
if (!current_node && !current_section && !current_region ())
{
line_warn ("printindex before document beginning: "
"@printindex %s", arg);
}
ADD_ARG (arg);
}
}
free (arg);
break;
}
case CM_everyheadingmarks:
case CM_everyfootingmarks:
case CM_evenheadingmarks:
case CM_oddheadingmarks:
case CM_evenfootingmarks:
case CM_oddfootingmarks:
{
if (!strcmp (line, "top") || !strcmp (line, "bottom"))
{
ADD_ARG (line);
}
else
line_error ("@%s arg must be `top' or `bottom', not `%s'",
command_name(cmd), line);
break;
}
case CM_fonttextsize:
{
if (!strcmp (line, "10") || !strcmp (line, "11"))
{
ADD_ARG (line);
}
else
line_error ("Only @fonttextsize 10 or 11 is supported, not "
"`%s'", line);
break;
}
case CM_footnotestyle:
{
if (!strcmp (line, "separate") || !strcmp (line, "end"))
{
ADD_ARG(line);
}
else
line_error ("@footnotestyle arg must be "
"`separate' or `end', not `%s'", line);
break;
}
case CM_setchapternewpage:
{
if (!strcmp (line, "on") || !strcmp (line, "off")
|| !strcmp (line, "odd"))
{
ADD_ARG(line);
}
else
line_error ("@setchapternewpage arg must be "
"`on', `off' or `odd', not `%s'", line);
break;
}
case CM_need:
{
/* valid: 2, 2., .2, 2.2 */
if (is_decimal_number (line))
ADD_ARG(line);
else
line_error ("bad argument to @need: %s", line);
break;
}
case CM_paragraphindent:
{
if (!strcmp (line, "none") || !strcmp (line, "asis")
|| is_whole_number (line))
ADD_ARG(line);
else
line_error ("@paragraphindent arg must be "
"numeric/`none'/`asis', not `%s'", line);
break;
}
case CM_firstparagraphindent:
{
if (!strcmp (line, "none") || !strcmp (line, "insert"))
{
ADD_ARG(line);
}
else
line_error ("@firstparagraph arg must be "
"`none' or `insert', not `%s'", line);
break;
}
case CM_exampleindent:
{
if (!strcmp (line, "asis") || is_whole_number (line))
ADD_ARG(line);
else
line_error ("@exampleindent arg must be "
"numeric/`asis', not `%s'", line);
break;
}
case CM_frenchspacing:
case CM_xrefautomaticsectiontitle:
case CM_codequoteundirected:
case CM_codequotebacktick:
case CM_deftypefnnewline:
{
if (!strcmp (line, "on") || !strcmp (line, "off"))
{
ADD_ARG(line);
}
else
line_error ("expected @%s on or off, not `%s'",
command_name(cmd), line);
break;
}
case CM_kbdinputstyle:
{
if (!strcmp (line, "code"))
global_kbdinputstyle = kbd_code;
else if (!strcmp (line, "example"))
global_kbdinputstyle = kbd_example;
else if (!strcmp (line, "distinct"))
global_kbdinputstyle = kbd_distinct;
else goto kdbinputstyle_invalid;
ADD_ARG(line);
if (0)
{
kdbinputstyle_invalid:
line_error ("@kbdinputstyle arg must be "
"`code'/`example'/`distinct', not `%s'", line);
}
break;
}
case CM_allowcodebreaks:
{
if (!strcmp (line, "true") || !strcmp (line, "false"))
{
ADD_ARG(line);
}
else
line_error ("@allowcodebreaks arg must be "
"`true' or `false', not `%s'", line);
break;
}
case CM_urefbreakstyle:
{
if (!strcmp (line, "after") || !strcmp (line, "before")
|| !strcmp (line, "none"))
{
ADD_ARG(line);
}
else
line_error ("@urefbreakstyle arg must be "
"`after'/`before'/`none', not `%s'", line);
break;
}
case CM_headings:
{
if (!strcmp (line, "off") || !strcmp (line, "on")
|| !strcmp (line, "double") || !strcmp (line, "singleafter")
|| !strcmp (line, "doubleafter"))
{
ADD_ARG(line);
}
else
line_error ("bad argument to @headings: %s", line);
break;
}
default:
;
}
if (line_args->contents.number == 0)
{
destroy_element (line_args);
return 0;
}
else
return line_args;
#undef ADD_ARG
}
/* NODE->contents is the Texinfo for the specification of a node. This
function sets three fields on the returned object:
manual_content - Texinfo tree for a manual name extracted from the
node specification.
node_content - Texinfo tree for the node name on its own
normalized - a string with the node name after HTML node name
normalization is applied
Objects returned from this function are used as an 'extra' key in a
few places: the elements of a 'nodes_manuals' array (itself an extra key),
the 'menu_entry_node' key on a 'menu_entry' element (not to be confused
with an ET_menu_entry_node element, which occurs in the args of a
'menu_entry' element), and in the 'node_argument' key of a cross-reference
command (like @xref). */
NODE_SPEC_EXTRA *
parse_node_manual (ELEMENT *node)
{
NODE_SPEC_EXTRA *result;
ELEMENT *new;
int idx = 0; /* index into node->contents */
result = malloc (sizeof (NODE_SPEC_EXTRA));
result->manual_content = result->node_content = 0;
/* If the content starts with a '(', try to get a manual name. */
if (node->contents.number > 0 && node->contents.list[0]->text.end > 0
&& node->contents.list[0]->text.text[0] == '(')
{
ELEMENT *manual, *first;
char *opening_bracket, *closing_bracket;
/* Handle nested parentheses in the manual name, for whatever reason. */
int bracket_count = 1; /* Number of ( seen minus number of ) seen. */
manual = new_element (ET_NONE);
/* If the first contents element is "(" followed by more text, split
the leading "(" into its own element. */
first = node->contents.list[0];
if (first->text.end > 1)
{
memmove (first->text.text, first->text.text + 1, first->text.end);
first->text.end--;
new = new_element (0);
text_append_n (&new->text, "(", 1);
insert_into_contents (node, new, 0);
}
idx++;
for (; idx < node->contents.number; idx++)
{
ELEMENT *e = node->contents.list[idx];
char *p, *q;
if (e->text.end == 0)
{
/* Put this element in the manual contents. */
add_to_contents_as_array (manual, e);
continue;
}
p = e->text.text;
while (p < e->text.text + e->text.end
&& bracket_count > 0)
{
opening_bracket = strchr (p, '(');
closing_bracket = strchr (p, ')');
if (!opening_bracket && !closing_bracket)
{
break;
}
else if (opening_bracket && !closing_bracket)
{
bracket_count++;
p = opening_bracket + 1;
}
else if (!opening_bracket && closing_bracket)
{
bracket_count--;
p = closing_bracket + 1;
}
else if (opening_bracket < closing_bracket)
{
bracket_count++;
p = opening_bracket + 1;
}
else if (opening_bracket > closing_bracket)
{
bracket_count--;
p = closing_bracket + 1;
}
}
if (bracket_count > 0)
add_to_contents_as_array (manual, e);
else /* end of filename component */
{
/* Split the element in two, putting the part before the ")"
in the manual name, leaving the part afterwards for the
node name. */
remove_from_contents (node, idx); /* Remove 'e'. */
p--; /* point at ) */
if (p > e->text.text)
{
/* text before ), part of the manual name */
new = new_element (ET_NONE);
text_append_n (&new->text, e->text.text,
p - e->text.text);
insert_into_contents (node, new, idx++);
add_to_contents_as_array (manual, new);
}
new = new_element (0);
text_append_n (&new->text, ")", 1);
insert_into_contents (node, new, idx++);
/* Skip ')' and any following whitespace.
Note that we don't manage to skip any multibyte
UTF-8 space characters here. */
p++;
q = p + strspn (p, whitespace_chars);
if (q > p)
{
new = new_element (0);
text_append_n (&new->text, p, q - p);
insert_into_contents (node, new, idx++);
}
p = q;
if (*p)
{
/* text after ), part of the node name. */
new = new_element (ET_NONE);
text_append_n (&new->text, p,
e->text.text + e->text.end - p);
insert_into_contents (node, new, idx);
}
destroy_element (e);
break;
}
} /* for */
if (bracket_count == 0)
result->manual_content = manual;
else /* unbalanced */
{
destroy_element (manual);
idx = 0; /* Back to the start, and consider the whole thing
as a node name. */
}
}
/* If anything left, it is the node name. */
if (idx < node->contents.number)
{
new = new_element (0);
insert_slice_into_contents (new, 0, node, idx, node->contents.number);
result->node_content = new;
}
return result;
}
/* Array of recorded @float's. */
FLOAT_RECORD *floats_list = 0;
size_t floats_number = 0;
size_t floats_space = 0;
int
parse_float_type (ELEMENT *current)
{
EXTRA_FLOAT_TYPE *eft;
eft = malloc (sizeof (EXTRA_FLOAT_TYPE));
eft->content = 0;
eft->normalized = 0;
if (current->args.number > 0)
{
if (current->args.list[0]->contents.number > 0)
{
char *normalized;
normalized = convert_to_texinfo (current->args.list[0]);
eft->content = current->args.list[0];
eft->normalized = normalized;
add_extra_float_type (current, "type", eft);
return 1;
}
}
eft->normalized = strdup ("");
add_extra_float_type (current, "type", eft);
return 0;
}
/* Actions to be taken at the end of a line that started a block that
has to be ended with "@end". */
ELEMENT *
end_line_starting_block (ELEMENT *current)
{
enum context c;
c = pop_context ();
if (c != ct_line)
fatal ("line context expected");
if (current->parent->cmd == CM_multitable)
{
/* Parse prototype row for a @multitable. Handling
of @columnfractions is done elsewhere. */
int i;
ELEMENT *prototypes = new_element (ET_NONE);
for (i = 0; i < current->contents.number; i++)
{
ELEMENT *e = contents_child_by_index(current, i);
if (e->type == ET_bracketed)
{
/* Copy and change the type of the element. */
ELEMENT *new;
new = malloc (sizeof (ELEMENT));
memcpy (new, e, sizeof (ELEMENT));
new->type = ET_bracketed_multitable_prototype;
new->parent = 0;
new->extra_number = 0;
add_to_contents_as_array (prototypes, new);
}
else if (e->text.end > 0)
{
/* Split the text up by whitespace. */
char *p, *p2;
p = e->text.text;
while (1)
{
ELEMENT *new;
p2 = p + strspn (p, whitespace_chars);
if (!*p2)
break;
p = p2 + strcspn (p2, whitespace_chars);
new = new_element (ET_row_prototype);
text_append_n (&new->text, p2, p - p2);
add_to_contents_as_array (prototypes, new);
}
}
else
{
if (e->cmd != CM_c && e->cmd != CM_comment)
{
char *texi;
texi = convert_to_texinfo (e);
command_warn (current,
"unexpected argument on @%s line: %s",
command_name(current->parent->cmd),
texi);
free (texi);
}
}
}
{
int max_columns = prototypes->contents.number;
add_extra_integer (current->parent, "max_columns", max_columns);
if (max_columns == 0)
command_warn (current->parent, "empty multitable");
}
add_extra_contents_oot (current->parent, "prototypes", prototypes);
/* See code in destroy_element for how prototypes is deallocated. */
}
isolate_last_space (current);
current = current->parent;
if (counter_value (&count_remaining_args, current) != -1)
counter_pop (&count_remaining_args);
/* Don't consider empty argument of block @-command as argument,
reparent them as contents. */
if (current->args.list[0]->contents.number > 0
&& current->args.list[0]->contents.list[0]->type
== ET_empty_line_after_command)
{
ELEMENT *e = remove_from_contents (current->args.list[0], 0);
insert_into_contents (current, e, 0);
destroy_element (pop_element_from_args (current));
}
if (current->cmd == CM_float)
{
char *type = "";
KEY_PAIR *k;
EXTRA_FLOAT_TYPE *eft;
current->line_nr = line_nr;
if (current->args.number >= 2)
{
NODE_SPEC_EXTRA *float_label;
float_label = parse_node_manual (args_child_by_index (current, 1));
check_internal_node (float_label);
register_label (current, float_label->node_content);
if (float_label->manual_content)
destroy_element (float_label->manual_content);
free (float_label);
}
parse_float_type (current);
k = lookup_extra (current, "type");
if (k)
{
eft = (EXTRA_FLOAT_TYPE *) k->value;
type = eft->normalized;
}
/* add to global 'floats' array */
if (floats_number == floats_space)
{
floats_list = realloc (floats_list,
(floats_space += 5) * sizeof (FLOAT_RECORD));
}
floats_list[floats_number].type = type;
floats_list[floats_number++].element = current;
if (current_section)
add_extra_element (current, "float_section", current_section);
}
if (command_flags(current) & CF_blockitem)
{
if (current->cmd == CM_enumerate)
{
char *spec = "1";
if (current->args.number > 0
&& current->args.list[0]->contents.number > 0)
{
if (current->args.list[0]->contents.number > 1)
command_error (current, "superfluous argument to @%s",
command_name(current->cmd));
ELEMENT *g = current->args.list[0]->contents.list[0];
/* Check if @enumerate specification is either a single
letter or a string of digits. */
if (g->text.end == 1 && isalpha (g->text.text[0])
|| (g->text.end > 0
&& !*(g->text.text
+ strspn (g->text.text, "0123456789"))))
{
spec = g->text.text;
}
else
command_error (current, "bad argument to @%s",
command_name(current->cmd));
}
add_extra_string_dup (current, "enumerate_specification", spec);
}
else if (item_line_command (current->cmd))
{
KEY_PAIR *k;
k = lookup_extra (current, "command_as_argument");
if (!k)
command_error (current,
"%s requires an argument: the formatter for @item",
command_name(current->cmd));
else
{
ELEMENT *e = k->value;
if (!(command_flags(e) & CF_brace)
|| (command_data(e->cmd).data == 0))
{
command_error (current,
"command @%s not accepting argument in brace "
"should not be on @%s line",
command_name(e->cmd),
command_name(current->cmd));
k->key = "";
k->type = extra_deleted;
/* FIXME: Error message for accent commands is done
elsewhere (3040). */
}
}
}
/* check that command_as_argument of the @itemize is alone on the line,
otherwise it is not a command_as_argument */
if (current->cmd == CM_itemize)
{
KEY_PAIR *k;
k = lookup_extra (current, "command_as_argument");
if (k)
{
int i;
ELEMENT *e = args_child_by_index (current, 0);
for (i = 0; i < e->contents.number; i++)
{
if (contents_child_by_index (e, i) == k->value)
{
i++;
break;
}
}
for (; i < e->contents.number; i++)
{
ELEMENT *f = contents_child_by_index (e, i);
if (f->cmd != CM_c
&& f->cmd != CM_comment
&& !(f->text.end > 0
&& !*(f->text.text
+ strspn (f->text.text, whitespace_chars))))
{
k->value->type = ET_NONE;
k->key = "";
k->type = extra_deleted;
break;
}
}
}
}
// Check if command_as_argument isn't an accent command
if (current->cmd == CM_itemize || item_line_command(current->cmd))
{
KEY_PAIR *k = lookup_extra (current, "command_as_argument");
if (k && k->value)
{
enum command_id cmd = k->value->cmd;
if (cmd && (command_data(cmd).flags & CF_accent))
{
command_warn (current, "accent command `@%s' "
"not allowed as @%s argument",
command_name(cmd),
command_name(current->cmd));
k->key = "";
k->value = 0;
k->type = extra_deleted;
}
}
}
/* if no command_as_argument given, default to @bullet for
@itemize, and @asis for @table. */
if (current->cmd == CM_itemize
&& (current->args.number == 0
|| current->args.list[0]->contents.number == 0))
{
ELEMENT *e;
e = new_element (ET_command_as_argument_inserted);
e->cmd = CM_bullet;
insert_into_args (current, e, 0);
add_extra_element (current, "command_as_argument", e);
}
else if (item_line_command (current->cmd)
&& !lookup_extra (current, "command_as_argument"))
{
ELEMENT *e;
e = new_element (ET_command_as_argument_inserted);
e->cmd = CM_asis;
insert_into_args (current, e, 0);
add_extra_element (current, "command_as_argument", e);
}
{
ELEMENT *bi = new_element (ET_before_item);
add_to_element_contents (current, bi);
current = bi;
}
} /* CF_blockitem */
if (command_flags(current) & CF_menu)
{
/* Start reading a menu. Processing will continue in
handle_menu in menus.c. */
ELEMENT *menu_comment = new_element (ET_menu_comment);
add_to_element_contents (current, menu_comment);
current = menu_comment;
debug ("MENU_COMMENT OPEN");
push_context (ct_preformatted);
}
current = begin_preformatted (current);
return current;
}
/* Actions to be taken at the end of an argument to a line command
not starting a block. @end is processed in here. */
static ELEMENT *
end_line_misc_line (ELEMENT *current)
{
enum command_id cmd;
int arg_type;
enum context c;
ELEMENT *misc_cmd;
char *end_command = 0;
enum command_id end_id;
int included_file = 0;
isolate_last_space (current);
current = current->parent;
misc_cmd = current;
cmd = current->cmd;
if (!cmd)
fatal ("command name unknown for @end");
arg_type = command_data(cmd).data;
/* Check 'line' is top of the context stack */
c = pop_context ();
if (c != ct_line)
fatal ("line context expected");
debug ("MISC END %s", command_name(cmd));
if (arg_type > 0)
{
ELEMENT *args = parse_line_command_args (current);
if (args)
add_extra_misc_args (current, "misc_args", args);
}
else if (arg_type == LINE_text)
{
char *text = 0;
int superfluous_arg = 0;
if (current->args.number > 0)
text = convert_to_text (current->args.list[0], &superfluous_arg);
if (!text || !strcmp (text, ""))
{
if (!superfluous_arg)
line_warn ("@%s missing argument", command_name(cmd));
add_extra_integer (current, "missing_argument", 1);
free (text);
}
else
{
add_extra_string (current, "text_arg", text);
if (current->cmd == CM_end)
{
char *line = text;
/* Set end_command - used below. */
end_command = read_command_name (&line);
if (end_command)
{
/* Check if argument is a block Texinfo command. */
end_id = lookup_command (end_command);
if (end_id == 0 || !(command_data(end_id).flags & CF_block))
{
command_warn (current, "unknown @end %s", end_command);
free (end_command); end_command = 0;
}
else
{
debug ("END BLOCK %s", end_command);
/* Handle conditional block commands (e.g. @ifinfo) */
/* If we are in a non-ignored conditional, there is not
an element for the block in the tree; it is recorded
in the conditional stack. Pop it and check it is the
same as the one given in the @end line. */
if (command_data(end_id).data == BLOCK_conditional)
{
enum command_id popped;
if (conditional_number == 0)
goto conditional_stack_fail;
popped = pop_conditional_stack ();
if (popped != end_id)
{
push_conditional_stack (popped);
goto conditional_stack_fail;
}
if (0)
{
conditional_stack_fail:
command_error (current, "unmatched `@end'");
free (end_command); end_command = 0;
}
}
if (end_command)
{
add_extra_string (current, "command_argument",
end_command);
}
if (end_command
&& (superfluous_arg
|| line[strspn (line, whitespace_chars)] != '\0'))
{
char *line, *line2;
line = convert_to_texinfo (current->args.list[0]);
line2 = line;
line2 += strspn (line2, whitespace_chars);
free (read_command_name (&line2));
command_error (current,
"superfluous argument to @end %s: "
"%s", end_command, line2);
superfluous_arg = 0; /* Don't issue another error
message below. */
free (line);
}
}
}
else
{
command_error (current, "bad argument to @end: %s", line);
}
}
else if (superfluous_arg)
{
/* An error message is issued below. */
}
else if (current->cmd == CM_include)
{
int status;
char *fullpath;
debug ("Include %s", text);
fullpath = locate_include_file (text);
if (!fullpath)
{
command_error (current,
"@include: could not find %s", text);
}
else
{
status = input_push_file (fullpath);
if (status)
{
command_error (current,
"@include: could not open %s:",
text,
strerror (status));
}
else
included_file = 1;
free (fullpath);
}
}
else if (current->cmd == CM_verbatiminclude)
{
if (global_info.input_perl_encoding)
add_extra_string_dup (current, "input_perl_encoding",
global_info.input_perl_encoding);
}
else if (current->cmd == CM_documentencoding)
{
int i; char *p, *text2;
char *texinfo_encoding, *perl_encoding, *input_encoding;
/* See tp/Texinfo/Encoding.pm (whole file) */
/* Three concepts of encoding:
texinfo_encoding -- one of the encodings supported as an
argument to @documentencoding, documented
in Texinfo manual
perl_encoding -- used for charset conversion within Perl
input_encoding -- for output within an HTML file */
text2 = strdup (text);
for (p = text2; *p; p++)
*p = tolower (*p);
/* Get texinfo_encoding from what was in the document */
{
static char *canonical_encodings[] = {
"us-ascii", "utf-8", "iso-8859-1",
"iso-8859-15","iso-8859-2","koi8-r", "koi8-u",
0
};
texinfo_encoding = 0;
for (i = 0; (canonical_encodings[i]); i++)
{
if (!strcmp (text2, canonical_encodings[i]))
{
texinfo_encoding = canonical_encodings[i];
break;
}
}
if (!texinfo_encoding)
{
command_warn (current, "encoding `%s' is not a "
"canonical texinfo encoding", text);
}
}
/* Get perl_encoding. */
perl_encoding = 0;
if (texinfo_encoding)
perl_encoding = texinfo_encoding;
else
{
int i;
static char *known_encodings[] = {
"shift_jis",
"latin1",
0
};
for (i = 0; (known_encodings[i]); i++)
{
if (!strcmp (text2, known_encodings[i]))
{
perl_encoding = known_encodings[i];
break;
}
}
}
free (text2);
if (perl_encoding)
{
struct encoding_map {
char *from; char *to;
};
static struct encoding_map map[] = {
"utf-8", "utf-8-strict",
"us-ascii", "ascii",
"shift_jis", "shiftjis",
"latin1", "iso-8859-1"
};
for (i = 0; i < sizeof map / sizeof *map; i++)
{
if (!strcmp (perl_encoding, map[i].from))
{
perl_encoding = map[i].to;
break;
}
}
add_extra_string_dup (current, "input_perl_encoding",
perl_encoding);
free (global_info.input_perl_encoding);
global_info.input_perl_encoding = strdup (perl_encoding);
}
else
{
command_warn (current, "unrecognized encoding name `%s'",
text);
/* Texinfo::Encoding calls Encode::Alias, so knows
about more encodings than what we know about here.
TODO: Check when perl_encoding could be defined when
texinfo_encoding isn't.
Maybe we should check if an iconv conversion is possible
from this encoding to UTF-8. */
}
/* Set input_encoding from perl_encoding */
input_encoding = 0;
if (perl_encoding)
{
struct encoding_map {
char *from; char *to;
};
static struct encoding_map map[] = {
"utf8", "utf-8",
"utf-8-strict","utf-8",
"ascii", "us-ascii",
"shiftjis", "shift_jis",
"latin-1", "iso-8859-1",
"iso-8859-1", "iso-8859-1",
"iso-8859-2", "iso-8859-2",
"iso-8859-15", "iso-8859-15",
"koi8-r", "koi8",
"koi8-u", "koi8",
};
input_encoding = perl_encoding;
for (i = 0; i < sizeof map / sizeof *map; i++)
{
/* Elements in first column map to elements in
second column. Elements in second column map
to themselves. */
if (!strcasecmp (input_encoding, map[i].from)
|| !strcasecmp (input_encoding, map[i].to))
{
input_encoding = map[i].to;
break;
}
}
}
if (input_encoding)
{
add_extra_string_dup (current, "input_encoding_name",
input_encoding);
global_info.input_encoding_name = strdup (input_encoding);
set_input_encoding (input_encoding);
}
}
else if (current->cmd == CM_documentlanguage)
{
char *p, *q;
/* Texinfo::Common::warn_unknown_language checks with
tp/Texinfo/Documentlanguages.pm, which is an automatically
generated list of official IANA language codes. For now,
just check if the language code looks right. */
p = text;
while (isalpha (*p))
p++;
if (*p && *p != '_')
{
/* non-alphabetic char in language code */
command_warn (current, "%s is not a valid language code",
text);
}
else
{
if (p - text > 4)
{
/* looks too long */
char c = *p;
*p = 0;
command_warn (current, "%s is not a valid language code",
text);
*p = c;
}
if (*p == '_')
{
q = p + 1;
p = q;
/* Language code should be of the form LL_CC,
language code followed by country code. */
while (isalpha (*p))
p++;
if (*p || p - q > 4)
{
/* non-alphabetic char in country code or code
is too long. */
command_warn (current,
"%s is not a valid region code", q);
}
}
}
free (global_documentlanguage);
global_documentlanguage = strdup (text);
/* FIXME: check customization variable */
}
}
if (superfluous_arg)
{
char *texi_line, *p, *p1;
p = convert_to_texinfo (args_child_by_index(current, 0));
texi_line = p;
while (isspace (*texi_line))
texi_line++;
/* Trim leading and trailing whitespace. */
p1 = strchr (texi_line, '\0');
if (p1 > texi_line)
{
while (p1 > texi_line && isspace (p1[-1]))
p1--;
c = *p1;
*p1 = '\0';
}
command_error (current, "bad argument to @%s: %s",
command_name(current->cmd), texi_line);
free (p);
}
}
else if (current->cmd == CM_node)
{
int i;
ELEMENT *arg;
NODE_SPEC_EXTRA **nodes_manuals;
/* Construct 'nodes_manuals' array. Maximum of four elements
(node name, up, prev, next). */
nodes_manuals = malloc (sizeof (NODE_SPEC_EXTRA *) * 5);
for (i = 0; i < current->args.number && i < 4; i++)
{
arg = current->args.list[i];
nodes_manuals[i] = parse_node_manual (arg);
}
nodes_manuals[i] = 0;
add_extra_node_spec_array (current, "nodes_manuals", nodes_manuals);
check_internal_node (nodes_manuals[0]);
if (nodes_manuals[0])
{
ELEMENT *label = 0;
if (nodes_manuals[0]->node_content)
{
/* Copy the first 'node_content' array, to avoid the complication
of it being referenced in two different places.
This might be better with a separate function. */
label = new_element (0);
int i;
for (i = 0; inode_content->contents.number;
i++)
{
add_to_contents_as_array (label,
contents_child_by_index(nodes_manuals[0]->node_content, i));
}
}
register_label (current, label);
}
current_node = current;
}
else if (current->cmd == CM_listoffloats)
{
parse_float_type (current);
}
else
{
/* All the other "line" commands. Check they have an argument. Empty
@top is allowed. */
if (current->args.list[0]->contents.number == 0
&& current->cmd != CM_top)
{
command_warn (current, "@%s missing argument",
command_name(current->cmd));
add_extra_integer (current, "missing_argument", 1);
}
else
{
if ((current->parent->cmd == CM_ftable
|| current->parent->cmd == CM_vtable)
&& (current->cmd == CM_item || current->cmd == CM_itemx))
{
enter_index_entry (current->parent->cmd,
current->cmd,
current,
current->args.list[0]);
}
else
{
// 3273 FIXME possibly check for @def... command
}
/* Index commands */
if (command_flags(current) & CF_index_entry_command)
{
enter_index_entry (current->cmd, current->cmd, current,
current->args.list[0]);
current->type = ET_index_entry_command;
}
}
}
current = current->parent;
if (end_command) /* Set above */
{
/* More processing of @end */
ELEMENT *end_elt;
debug ("END COMMAND %s", end_command);
/* Reparent the "@end" element to be a child of the block element. */
end_elt = pop_element_from_contents (current);
/* If not a conditional */
if (command_data(end_id).data != BLOCK_conditional)
{
ELEMENT *closed_command;
/* This closes tree elements (e.g. paragraphs) until we reach
end_command. It can print an error if another block command
is found first. */
current = close_commands (current, end_id, &closed_command, 0);
if (!closed_command)
destroy_element_and_children (end_elt);
else
{
add_extra_element (closed_command, "end_command", end_elt);
close_command_cleanup (closed_command);
add_to_element_contents (closed_command, end_elt);
if (command_flags(closed_command) & CF_menu
&& current_context () == ct_menu)
{
ELEMENT *e;
debug ("CLOSE menu but still in menu context");
e = new_element (ET_menu_comment);
add_to_element_contents (current, e);
current = e;
push_context (ct_preformatted);
}
}
if (close_preformatted_command (end_id))
current = begin_preformatted (current);
}
else
{
/* The "@end" line does not appear in the final tree for a
conditional block. */
destroy_element_and_children (end_elt);
}
}
else
{
if (close_preformatted_command (cmd))
current = begin_preformatted (current);
}
/* If a file was included, remove the include command completely.
Also ignore @setfilename in included file, as said in the manual. */
if (included_file || (cmd == CM_setfilename && top_file_index () > 0))
destroy_element_and_children (pop_element_from_contents (current));
else if (cmd == CM_setfilename && (current_node || current_section))
command_warn (misc_cmd, "@setfilename after the first element");
else if (cmd == CM_columnfractions)
{
ELEMENT *before_item;
KEY_PAIR *misc_args;
/* Check if in multitable. */
if (!current->parent || current->parent->cmd != CM_multitable)
{
command_error (current,
"@columnfractions only meaningful on a @multitable line");
}
else
{
pop_context (); /* ct_line */;
current = current->parent;
if ((misc_args = lookup_extra (misc_cmd, "misc_args")))
{
add_extra_element (current, "columnfractions", misc_cmd);
add_extra_integer (current, "max_columns",
misc_args->value->contents.number);
}
else
add_extra_integer (current, "max_columns", 0);
before_item = new_element (ET_before_item);
add_to_element_contents (current, before_item);
current = before_item;
}
}
else if (command_data(cmd).flags & CF_root)
{
current = last_contents_child (current);
if (cmd == CM_node)
counter_pop (&count_remaining_args);
/* Destroy all contents (TODO: check why do we do this?) */
while (last_contents_child (current))
destroy_element (pop_element_from_contents (current));
/* Set 'associated_section' extra key for a node. */
if (cmd != CM_node && cmd != CM_part)
{
if (current_node)
{
if (!lookup_extra (current_node, "associated_section"))
{
add_extra_element
(current_node, "associated_section", current);
add_extra_element
(current, "associated_node", current_node);
}
}
if (current_part)
{
add_extra_element (current, "associated_part", current_part);
add_extra_element (current_part, "part_associated_section",
current);
if (current->cmd == CM_top)
{
line_error_ext (1, ¤t_part->line_nr,
"@part should not be associated with @top");
}
current_part = 0;
}
current_section = current;
}
else if (cmd == CM_part)
{
current_part = current;
if (current_node
&& !lookup_extra (current_node, "associated_section"))
{
line_warn ("@node precedes @part, but parts may not be "
"associated with nodes");
}
}
}
return current;
}
/* Actions to be taken when a whole line of input has been processed */
ELEMENT *
end_line (ELEMENT *current)
{
ELEMENT *current_old = current; /* Used at very end of function */
/* If empty line, start a new paragraph. */
if (last_contents_child (current)
&& last_contents_child (current)->type == ET_empty_line)
{
debug ("END EMPTY LINE");
if (current->type == ET_paragraph)
{
ELEMENT *e;
/* Remove empty_line element. */
e = pop_element_from_contents (current);
current = end_paragraph (current, 0, 0);
/* Add empty_line to higher-level element. */
add_to_element_contents (current, e);
}
else if (current->type == ET_preformatted
&& current->parent->type == ET_menu_entry_description)
{
ELEMENT *empty_line, *e;
empty_line = pop_element_from_contents (current);
if (current->contents.number == 0)
{
current = current->parent;
destroy_element (pop_element_from_contents (current));
}
else
current = current->parent;
pop_context (); //ct_preformatted
current = current->parent->parent;
e = new_element (ET_menu_comment);
add_to_element_contents (current, e);
current = e;
e = new_element (ET_preformatted);
add_to_element_contents (current, e);
current = e;
e = new_element (ET_after_description_line);
text_append (&e->text, empty_line->text.text);
destroy_element (empty_line);
add_to_element_contents (current, e);
push_context (ct_preformatted);
debug ("MENU: END DESCRIPTION, OPEN COMMENT");
}
else if (in_paragraph_context (current_context ()))
{
current = end_paragraph (current, 0, 0);
}
}
/* The end of the line of a menu. */
else if (current->type == ET_menu_entry_name
|| current->type == ET_menu_entry_node)
{
ELEMENT *end_comment = 0;
int empty_menu_entry_node = 0;
if (current->type == ET_menu_entry_node)
{
ELEMENT *last = last_contents_child (current);
if (current->contents.number > 0
&& (last->cmd == CM_c || last->cmd == CM_comment))
{
end_comment = pop_element_from_contents (current);
}
/* If contents empty or is all whitespace. */
if (current->contents.number == 0
|| (current->contents.number == 1
&& last->text.end > 0
&& !last->text.text[strspn (last->text.text,
whitespace_chars)]))
{
empty_menu_entry_node = 1;
if (end_comment)
add_to_element_contents (current, end_comment);
}
}
/* Abort the menu entry if there is no destination node given. */
if (empty_menu_entry_node || current->type == ET_menu_entry_name)
{
ELEMENT *menu, *menu_entry, *description_or_menu_comment = 0;
debug ("FINALLY NOT MENU ENTRY");
menu = current->parent->parent;
menu_entry = pop_element_from_contents (menu);
if (menu->contents.number > 0
&& last_contents_child(menu)->type == ET_menu_entry)
{
ELEMENT *entry, *description = 0;
int j;
entry = last_contents_child(menu);
for (j = entry->args.number - 1; j >= 0; j--)
{
ELEMENT *e = args_child_by_index (entry, j);
if (e->type == ET_menu_entry_description)
{
description = e;
break;
}
}
if (description)
description_or_menu_comment = description;
else
{
ELEMENT *e;
/* "Normally this cannot happen." */
bug ("no description in menu entry");
e = new_element (ET_menu_entry_description);
add_to_element_args (entry, e);
description_or_menu_comment = e;
}
}
else if (menu->contents.number > 0
&& last_contents_child(menu)->type == ET_menu_comment)
{
description_or_menu_comment = last_contents_child(menu);
}
if (description_or_menu_comment)
{
current = description_or_menu_comment;
if (current->contents.number > 0
&& last_contents_child(current)->type == ET_preformatted)
current = last_contents_child(current);
else
{
/* This should not happen */
bug ("description or menu comment not in preformatted");
ELEMENT *e;
e = new_element (ET_preformatted);
add_to_element_contents (current, e);
current = e;
}
push_context (ct_preformatted);
}
else
{
ELEMENT *e;
e = new_element (ET_menu_comment);
add_to_element_contents (menu, e);
current = e;
e = new_element (ET_preformatted);
add_to_element_contents (current, e);
current = e;
push_context (ct_preformatted);
debug ("THEN MENU_COMMENT OPEN");
}
{
int i, j;
for (i = 0; i < menu_entry->args.number; i++)
{
ELEMENT *arg = args_child_by_index(menu_entry, i);
if (arg->text.end > 0)
current = merge_text (current, arg->text.text);
else
{
ELEMENT *e;
for (j = 0; j < arg->contents.number; j++)
{
e = contents_child_by_index (arg, j);
if (e->text.end > 0)
{
current = merge_text (current, e->text.text);
destroy_element (e);
}
else
{
add_to_element_contents (current, e);
}
}
}
destroy_element (arg);
}
destroy_element (menu_entry);
}
}
else
{
debug ("MENU ENTRY END LINE");
current = current->parent;
current = enter_menu_entry_node (current);
if (end_comment)
add_to_element_contents (current, end_comment);
}
}
/* End of a definition line, like @deffn */
else if (current->parent && current->parent->type == ET_def_line)
{
enum command_id def_command, original_def_command;
DEF_INFO *def_info = 0;
static DEF_INFO zero_def_info; /* always stays zeroed */
KEY_PAIR *k;
if (pop_context () != ct_def)
fatal ("def context expected");
k = lookup_extra (current->parent, "original_def_cmdname");
if (k)
original_def_command = lookup_command ((char *) k->value);
else
original_def_command = current->parent->parent->cmd;
def_command = original_def_command;
/* Strip an trailing x from the command, e.g. @deffnx -> @deffn */
if (command_data(def_command).flags & CF_line)
{
char *stripped = strdup (command_name(def_command));
stripped[strlen (stripped) - 1] = '\0';
def_command = lookup_command (stripped);
free (stripped);
}
def_info = parse_def (def_command, current);
/* Record the index entry if def_info is not empty. */
if (!memcmp(def_info, &zero_def_info, sizeof (DEF_INFO)))
{
free (def_info);
command_warn (current->parent, "missing category for @%s",
command_name (original_def_command));
}
else
{
ELEMENT *index_entry = 0; /* Index entry text. */
add_extra_def_info (current->parent, "def_parsed_hash", def_info);
if (def_info->name)
{
char *t;
/* Set index_entry unless an empty ET_bracketed_def_content. */
if (def_info->name->type == ET_bracketed_def_content
&& (def_info->name->contents.number == 0
|| (def_info->name->contents.number == 1
&& (t = def_info->name->contents.list[0]->text.text)
&& t[strspn (t, whitespace_chars)] == '\0')))
{
}
else
index_entry = def_info->name;
}
if (index_entry)
{
ELEMENT *index_contents = 0;
if (def_info->class &&
(def_command == CM_defop
|| def_command == CM_deftypeop
|| def_command == CM_defmethod
|| def_command == CM_deftypemethod
|| def_command == CM_defivar
|| def_command == CM_deftypeivar
|| def_command == CM_deftypecv))
{
add_extra_string_dup (current->parent, "documentlanguage",
global_documentlanguage);
}
else
{
index_contents = new_element (ET_NONE);
if (index_contents->contents.number == 0)
add_to_contents_as_array (index_contents, index_entry);
}
enter_index_entry (def_command,
original_def_command,
current->parent,
index_contents);
}
else
{
command_warn (current->parent, "missing name for @%s",
command_name (original_def_command));
}
}
current = current->parent->parent;
current = begin_preformatted (current);
}
/* End of a line starting a block. */
else if (current->type == ET_block_line_arg)
{
current = end_line_starting_block (current);
}
/* after an "@end verbatim" */
else if (current->contents.number
&& last_contents_child(current)->type == ET_empty_line_after_command
&& contents_child_by_index(current, -2)
&& contents_child_by_index(current, -2)->cmd == CM_verbatim)
{
/*
if we are after a @end verbatim, we must restart a preformatted if needed,
since there is no @end command explicitly associated to raw commands
it won't be done elsewhere.
*/
current = begin_preformatted (current);
}
else if (current->type == ET_line_arg)
{
current = end_line_misc_line (current);
}
else if (current->contents.number == 1
&& current->contents.list[0]->type == ET_empty_line_after_command
|| current->contents.number == 2
&& current->contents.list[0]->type == ET_empty_line_after_command
&& (current->contents.list[1]->cmd == CM_c
|| current->contents.list[1]->cmd == CM_comment))
{
if (current->type == ET_preformatted
|| current->type == ET_rawpreformatted)
{
/* Empty line after a @menu, or before a preformatted. Reparent
to the menu or other format. */
ELEMENT *parent, *to_reparent;
parent = current->parent;
if (parent->type == ET_menu_comment
&& parent->contents.number == 1)
{
parent = parent->parent;
}
to_reparent = pop_element_from_contents (parent);
debug ("LINE AFTER COMMAND IN PREFORMATTED");
while (current->contents.number > 0)
{
ELEMENT *e;
e = remove_from_contents (current, 0);
add_to_element_contents (parent, e);
}
add_to_element_contents (parent, to_reparent);
}
}
/* 'line' or 'def' at top of "context stack" - this happens when
line commands are nested (always incorrectly?) */
if (current_context () == ct_line || current_context () == ct_def)
{
debug ("Still opened line command");
if (current_context () == ct_def)
{
while (current->parent
&& current->parent->type != ET_def_line)
{
current = close_current (current, 0, 0);
}
}
else
{
while (current->parent
&& current->type != ET_line_arg
&& current->type != ET_block_line_arg)
{
current = close_current (current, 0, 0);
}
}
if (current == current_old)
fatal ("infinite loop when closing commands");
current = end_line (current);
}
return current;
}