/* 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
#include "parser.h"
#include "text.h"
#include "input.h"
const char *whitespace_chars = " \t\f\r\n";
const char *digit_chars = "0123456789";
// [^\S\r\n] in Perl
const char *whitespace_chars_except_newline = " \t\f";
/* Check if the contents of S2 appear at S1). */
int
looking_at (char *s1, char *s2)
{
return !strncmp (s1, s2, strlen (s2));
}
/* Look for a sequence of alphanumeric characters or hyphens, where the
first isn't a hyphen. This is the format of (non-single-character) Texinfo
commands, but is also used elsewhere. Return value to be freed by caller.
*PTR is advanced past the read name. Return 0 if name is invalid. */
char *
read_command_name (char **ptr)
{
char *p = *ptr, *q;
char *ret = 0;
q = p;
if (!isalnum (*q))
return 0; /* Invalid. */
while (isalnum (*q) || *q == '-' || *q == '_')
q++;
ret = strndup (p, q - p);
p = q;
*ptr = p;
return ret;
}
/* Read a name used for @set and @value. */
char *
read_flag_name (char **ptr)
{
char *p = *ptr, *q;
char *ret = 0;
q = p;
if (!isalnum (*q) && *q != '-' && *q != '_')
return 0; /* Invalid. */
while (!strchr (whitespace_chars, *q)
&& !strchr ("{\\}~`^+\"<>|@", *q))
q++;
ret = strndup (p, q - p);
p = q;
*ptr = p;
return ret;
}
char *
element_type_name (ELEMENT *e)
{
return element_type_names[(e)->type];
}
/* Current node, section and part. */
ELEMENT *current_node = 0;
ELEMENT *current_section = 0;
ELEMENT *current_part = 0;
/* Conditional stack. */
enum command_id *conditional_stack;
size_t conditional_number;
size_t conditional_space;
void
push_conditional_stack (enum command_id cond)
{
if (conditional_number == conditional_space)
{
conditional_stack = realloc (conditional_stack,
(conditional_space += 5)
* sizeof (enum command_id));
if (!conditional_stack)
fatal ("realloc failed");
}
conditional_stack[conditional_number++] = cond;
}
enum command_id
pop_conditional_stack (void)
{
if (conditional_number == 0)
return CM_NONE;
return conditional_stack[--conditional_number];
}
/* Counters */
COUNTER count_remaining_args;
COUNTER count_items;
COUNTER count_cells;
/* Information that is not local to where it is set in the Texinfo input,
for example document language and encoding. */
GLOBAL_INFO global_info;
char *global_clickstyle = 0;
char *global_documentlanguage = 0;
enum kbd_enum global_kbdinputstyle = kbd_distinct;
void
set_documentlanguage (char *value)
{
free (global_documentlanguage);
global_documentlanguage = strdup (value);
}
void
set_novalidate (int value)
{
global_info.novalidate = value;
}
/* Record the information from a command of global effect. */
int
register_global_command (ELEMENT *current)
{
enum command_id cmd = current->cmd;
if (cmd == CM_summarycontents)
cmd = CM_shortcontents;
if (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
default:
/* do nothing; just silence -Wswitch about lots of un-covered cases */
break;
}
/* TODO: Check if all of these are necessary. */
return 1;
}
else if ((command_data(cmd).flags & CF_global_unique))
{
ELEMENT **where = 0;
if (!current->line_nr.line_nr)
current->line_nr = line_nr;
switch (cmd)
{
case CM_setfilename:
/* Check if we are inside an @include, and if so, do nothing. */
if (top_file_index () > 0)
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(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 and wipe_global_info. */
default:
/* do nothing; just silence -Wswitch about lots of un-covered cases */
break;
}
if (where)
{
if (*where)
line_warn ("multiple @%s", command_name(cmd));
else
*where = current;
}
return 1;
}
return 0;
}
void
wipe_global_info (void)
{
free (global_clickstyle);
free (global_documentlanguage);
global_clickstyle = strdup ("arrow");
global_documentlanguage = strdup ("");
global_kbdinputstyle = kbd_distinct;
free (global_info.input_perl_encoding);
free (global_info.input_encoding_name);
free (global_info.dircategory_direntry.contents.list);
free (global_info.footnotes.contents.list);
#define GLOBAL_CASE(cmx) \
free (global_info.cmx.contents.list)
GLOBAL_CASE(hyphenation);
GLOBAL_CASE(insertcopying);
GLOBAL_CASE(printindex);
GLOBAL_CASE(subtitle);
GLOBAL_CASE(titlefont);
GLOBAL_CASE(listoffloats);
GLOBAL_CASE(detailmenu);
GLOBAL_CASE(part);
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
memset (&global_info, 0, sizeof (global_info));
global_info.input_perl_encoding = strdup ("utf-8");
global_info.input_encoding_name = strdup ("utf-8");
}
ELEMENT *
parse_texi_file (char *filename)
{
char *p, *q;
char *linep, *line = 0;
ELEMENT *root = new_element (ET_text_root);
ELEMENT *preamble = 0;
char c;
int status;
status = input_push_file (filename);
if (status)
return 0;
/* Strip off a leading directory path, by looking for the last
'/' in filename. */
p = 0;
q = strchr (filename, '/');
while (q)
{
p = q;
q = strchr (q + 1, '/');
}
if (p)
{
c = *p;
*p = '\0';
add_include_directory (filename);
*p = c;
}
/* Put all lines up to a line starting "\input" inside a "preamble"
element. */
while (1)
{
ELEMENT *l;
free (line);
line = next_text ();
if (!line)
break;
linep = line;
linep += strspn (linep, whitespace_chars);
if (*linep && !looking_at (linep, "\\input"))
{
/* This line is not part of the preamble. Shove back
into input stream. */
input_push (line, 0, line_nr.file_name, line_nr.line_nr);
break;
}
if (!preamble)
preamble = new_element (ET_preamble);
l = new_element (ET_preamble_text);
text_append (&l->text, line);
add_to_element_contents (preamble, l);
}
if (preamble)
add_to_element_contents (root, preamble);
return parse_texi (root);
}
int
begin_paragraph_p (ELEMENT *current)
{
return (current->type == ET_NONE /* "True for @-commands" */
|| current->type == ET_before_item
|| current->type == ET_text_root
|| current->type == ET_document_root
|| current->type == ET_brace_command_context)
&& in_paragraph_context (current_context ());
}
/* If in a context where paragraphs are to be started, start a new
paragraph. */
ELEMENT *
begin_paragraph (ELEMENT *current)
{
if (begin_paragraph_p (current))
{
ELEMENT *e;
enum command_id indent = 0;
/* Check if an @indent precedes the paragraph (to record it
in the 'extra' key). */
if (current->contents.number > 0)
{
int i = current->contents.number - 1;
while (i >= 0)
{
ELEMENT *child = contents_child_by_index (current, i);
if (child->type == ET_empty_line
|| child->type == ET_paragraph)
break;
if (close_paragraph_command(child->cmd))
break;
if (child->cmd == CM_indent
|| child->cmd == CM_noindent)
{
indent = child->cmd;
break;
}
i--;
}
}
e = new_element (ET_paragraph);
if (indent)
add_extra_integer (e, indent == CM_indent ? "indent" : "noindent",
1);
add_to_element_contents (current, e);
current = e;
debug ("PARAGRAPH");
}
return current;
}
/* Begin a preformatted element if in a preformatted context. */
ELEMENT *
begin_preformatted (ELEMENT *current)
{
if (current_context() == ct_preformatted
|| current_context() == ct_rawpreformatted)
{
ELEMENT *e;
enum element_type et;
if (current_context() == ct_preformatted)
et = ET_preformatted;
else
et = ET_rawpreformatted;
e = new_element (et);
add_to_element_contents (current, e);
current = e;
debug ("PREFORMATTED %s", et == ET_preformatted ? "preformatted"
: "rawpreformatted");
}
return current;
}
ELEMENT *
end_paragraph (ELEMENT *current,
enum command_id closed_command,
enum command_id interrupting_command)
{
current = close_all_style_commands (current,
closed_command, interrupting_command);
if (current->type == ET_paragraph)
{
debug ("CLOSE PARA");
current = current->parent;
}
return current;
}
ELEMENT *
end_preformatted (ELEMENT *current,
enum command_id closed_command,
enum command_id interrupting_command)
{
current = close_all_style_commands (current,
closed_command, interrupting_command);
if (current->type == ET_preformatted
|| current->type == ET_rawpreformatted)
{
debug ("CLOSE PREFORMATTED %s",
current->type == ET_preformatted ? "preformatted"
: "rawpreformatted");
if (current->contents.number == 0)
{
current = current->parent;
destroy_element (pop_element_from_contents (current));
debug ("popping");
}
else
current = current->parent;
}
return current;
}
/* Add TEXT to the contents of CURRENT, maybe starting a new paragraph. */
ELEMENT *
merge_text (ELEMENT *current, char *text)
{
int no_merge_with_following_text = 0;
int leading_spaces = strspn (text, whitespace_chars);
ELEMENT *last_child = last_contents_child (current);
/* Is there a non-whitespace character in the line? */
if (text[leading_spaces])
{
char *additional = 0;
if (last_child
&& (last_child->type == ET_empty_line_after_command
|| last_child->type == ET_empty_spaces_after_command
|| last_child->type == ET_empty_spaces_before_argument
|| last_child->type == ET_empty_spaces_after_close_brace))
{
no_merge_with_following_text = 1;
}
if (leading_spaces)
{
additional = malloc (leading_spaces + 1);
if (!additional)
fatal ("malloc failed");
memcpy (additional, text, leading_spaces);
additional[leading_spaces] = '\0';
}
if (abort_empty_line (¤t, additional))
text += leading_spaces;
free (additional);
current = begin_paragraph (current);
}
last_child = last_contents_child (current);
if (last_child
/* There is a difference between the text being defined and empty,
and not defined at all. The latter is true for 'brace_command_arg'
elements. We need either to make sure that we initialize all elements
with text_append (&e->text, "") where we want merging with following
text, or treat as a special case here.
Unfortunately we can't make a special case for
ET_empty_spaces_before_argument, because abort_empty_line above
produces such an element that shouldn't be merged with. */
&& (last_child->text.space > 0
&& !strchr (last_child->text.text, '\n')
) /* || last_child->type == ET_empty_spaces_before_argument) */
&& last_child->cmd != CM_value
&& !no_merge_with_following_text)
{
/* Append text to contents */
text_append (&last_child->text, text);
debug ("MERGED TEXT: %s|||", text);
}
else
{
ELEMENT *e = new_element (ET_NONE);
text_append (&e->text, text);
add_to_element_contents (current, e);
debug ("NEW TEXT: %s|||", text);
}
return current;
}
/* If last contents child of CURRENT is an empty line element, remove
or merge text, and return true. */
int
abort_empty_line (ELEMENT **current_inout, char *additional_spaces)
{
ELEMENT *current = *current_inout;
int retval;
ELEMENT *last_child = last_contents_child (current);
if (!additional_spaces)
additional_spaces = "";
if (last_child
&& (last_child->type == ET_empty_line
|| last_child->type == ET_empty_line_after_command
|| last_child->type == ET_empty_spaces_before_argument
|| last_child->type == ET_empty_spaces_after_close_brace))
{
ELEMENT *owning_element = 0, *e;
KEY_PAIR *k;
retval = 1;
k = lookup_extra (last_child, "command");
if (k)
owning_element = (ELEMENT *) k->value;
debug ("ABORT EMPTY %s additional text |%s| "
"current |%s|",
element_type_name(last_child),
additional_spaces,
last_child->text.text);
text_append (&last_child->text, additional_spaces);
/* Remove element altogether if it's empty. */
if (last_child->text.end == 0)
{
e = pop_element_from_contents (current);
destroy_element (e);
}
else if (last_child->type == ET_empty_line)
{
last_child->type = begin_paragraph_p (current)
? ET_empty_spaces_before_paragraph : ET_NONE;
}
else if (last_child->type == ET_empty_line_after_command
|| last_child->type == ET_empty_spaces_before_argument)
{
if (owning_element)
{
/* Remove element from main tree. */
ELEMENT *e = pop_element_from_contents (current);
add_extra_string_dup (owning_element, "spaces_before_argument",
e->text.text);
destroy_element (e);
}
else
{
last_child->type = ET_empty_spaces_after_command;
}
}
}
else
retval = 0;
*current_inout = current;
return retval;
}
static void
isolate_last_space_internal (ELEMENT *current)
{
ELEMENT *last_elt;
last_elt = last_contents_child (current);
char *text = element_text (last_elt);
int text_len = last_elt->text.end;
/* If text all whitespace */
if (text[strspn (text, whitespace_chars)] == '\0')
{
add_extra_string_dup (current, "spaces_after_argument",
last_elt->text.text);
destroy_element (pop_element_from_contents (current));
}
else
{
int i, trailing_spaces;
static TEXT t;
text_reset (&t);
trailing_spaces = 0;
for (i = strlen (text) - 1;
i > 0 && strchr (whitespace_chars, text[i]);
i--)
trailing_spaces++;
text_append_n (&t,
text + text_len - trailing_spaces,
trailing_spaces);
text[text_len - trailing_spaces] = '\0';
last_elt->text.end -= trailing_spaces;
add_extra_string_dup (current, "spaces_after_argument",
t.text);
}
}
static void
isolate_trailing_space (ELEMENT *current, enum element_type spaces_type)
{
ELEMENT *last_elt;
char *text;
int text_len;
last_elt = last_contents_child (current);
text = element_text (last_elt);
text_len = last_elt->text.end;
/* If text all whitespace */
if (text[strspn (text, whitespace_chars)] == '\0')
{
last_elt->type = spaces_type;
}
else
{
ELEMENT *new_spaces;
int i, trailing_spaces;
trailing_spaces = 0;
for (i = strlen (text) - 1;
i > 0 && strchr (whitespace_chars, text[i]);
i--)
trailing_spaces++;
new_spaces = new_element (spaces_type);
text_append_n (&new_spaces->text,
text + text_len - trailing_spaces,
trailing_spaces);
text[text_len - trailing_spaces] = '\0';
last_elt->text.end -= trailing_spaces;
add_to_element_contents (current, new_spaces);
}
}
void
isolate_last_space (ELEMENT *current)
{
char *text;
ELEMENT *last_elt;
int text_len;
if (current->contents.number == 0)
return;
if (last_contents_child(current)->cmd == CM_c
|| last_contents_child(current)->cmd == CM_comment)
{
add_extra_element_oot (current, "comment_at_end",
pop_element_from_contents (current));
}
if (current->contents.number == 0)
return;
last_elt = last_contents_child (current);
text = element_text (last_elt);
if (!text || !*text
|| (last_elt->type && (!current->type
|| current->type != ET_line_arg)))
return;
text_len = last_elt->text.end;
/* Does the text end in whitespace? */
if (!strchr (whitespace_chars, text[text_len - 1]))
return;
if (current->type == ET_menu_entry_node)
isolate_trailing_space (current, ET_space_at_end_menu_node);
else
isolate_last_space_internal (current);
}
/* Add an "ET_empty_line_after_command" element containing the whitespace at
the beginning of the rest of the line. This element can be later changed to
a "ET_empty_spaces_after_command" element in 'abort_empty_line' if more
text follows on the line. Used after line commands or commands starting
a block. */
void
start_empty_line_after_command (ELEMENT *current, char **line_inout,
ELEMENT *command)
{
char *line = *line_inout;
ELEMENT *e;
int len;
len = strspn (line, whitespace_chars_except_newline);
e = new_element (ET_empty_line_after_command);
add_to_element_contents (current, e);
text_append_n (&e->text, line, len);
line += len;
if (command)
add_extra_element (e, "command", command);
*line_inout = line;
}
/* If the parent element takes a command as an argument, like
@itemize @bullet. */
int
command_with_command_as_argument (ELEMENT *current)
{
return current->type == ET_block_line_arg
&& (current->parent->cmd == CM_itemize
|| item_line_command (current->parent->cmd))
&& (current->contents.number == 1);
}
/* Check if line is "@end ..." for current command. If so, advance LINE. */
int
is_end_current_command (ELEMENT *current, char **line,
enum command_id *end_cmd)
{
char *linep;
char *cmdname;
linep = *line;
linep += strspn (linep, whitespace_chars);
if (!looking_at (linep, "@end"))
return 0;
linep += 4;
if (!strchr (whitespace_chars, *linep))
return 0;
linep += strspn (linep, whitespace_chars);
if (!*linep)
return 0;
cmdname = read_command_name (&linep);
if (!cmdname)
return 0;
*end_cmd = lookup_command (cmdname);
free (cmdname);
if (*end_cmd != current->cmd)
return 0;
*line = linep;
return 1;
}
void
check_valid_nesting (ELEMENT *current, enum command_id cmd)
{
enum command_id invalid_parent = 0;
/* Check whether outer command can contain cmd. Commands are
classified according to what commands they can contain:
accents
full text
simple text
full line
full line no refs
*/
int ok = 0; /* Whether nesting is allowed. */
/* Whether command is a "simple text" command. Use a variable
to avoid repeating a complex conditional. */
int simple_text_command = 0;
enum command_id outer = current->parent->cmd;
unsigned long outer_flags = command_data(outer).flags;
unsigned long cmd_flags = command_data(cmd).flags;
// much TODO here.
if ((outer_flags & CF_line
&& (command_data(outer).data >= 0
|| (command_data(outer).data == LINE_line
&& !(outer_flags & (CF_def | CF_sectioning)))
|| command_data(outer).data == LINE_text)
&& outer != CM_center
&& outer != CM_exdent)
|| ((outer_flags & CF_brace)
&& !(outer_flags & CF_inline)
&& command_data(outer).data > 0)
|| outer == CM_shortcaption
|| outer == CM_math
|| (outer_flags & CF_index_entry_command)
|| (outer_flags & CF_block
&& !(outer_flags & CF_def)
&& command_data(outer).data != BLOCK_raw
&& command_data(outer).data != BLOCK_conditional))
{
simple_text_command = 1;
}
if (outer_flags & CF_root && current->type != ET_line_arg)
ok = 1;
else if (outer_flags & CF_block
&& current->type != ET_block_line_arg)
ok = 1;
else if ((outer == CM_item
|| outer == CM_itemx)
&& current->type != ET_line_arg)
ok = 1;
else if (outer_flags & CF_accent)
{
if (cmd_flags & (CF_nobrace | CF_accent))
ok = 1;
else if (cmd_flags & CF_brace
&& command_data(cmd).data == 0)
ok = 1; /* glyph command */
if (cmd == CM_c || cmd == CM_comment)
ok = 1;
}
else if (simple_text_command
/* "full text commands" */
|| (outer_flags & CF_brace)
&& command_data(outer).data == BRACE_style
/* "full line commands" */
|| outer == CM_center
|| outer == CM_exdent
|| outer == CM_item
|| outer == CM_itemx
|| (!current->parent->cmd && current_context () == ct_def)
/* "full line no refs commands" */
|| (outer_flags & (CF_sectioning | CF_def))
|| (!current->parent->cmd && current_context () == ct_def))
{
/* Start by checking if the command is allowed inside a "full text
command" - this is the most permissive. */
if (cmd_flags & CF_nobrace)
ok = 1;
if (cmd_flags & CF_brace && !(cmd_flags & CF_INFOENCLOSE))
ok = 1;
else if (cmd == CM_c
|| cmd == CM_comment
|| cmd == CM_refill
|| cmd == CM_subentry
|| cmd == CM_columnfractions
|| cmd == CM_set
|| cmd == CM_clear
|| cmd == CM_end)
ok = 1;
else if (cmd_flags & CF_format_raw)
ok = 1;
if (cmd == CM_caption || cmd == CM_shortcaption)
ok = 0;
if (cmd_flags & CF_block
&& command_data(cmd).data == BLOCK_conditional)
ok = 1;
/* Now add more restrictions for "full line no refs" commands and "simple
text" commands. */
if (outer_flags & (CF_sectioning | CF_def)
|| (!current->parent->cmd && current_context () == ct_def)
|| simple_text_command)
{
if (cmd == CM_titlefont
|| cmd == CM_anchor
|| cmd == CM_footnote
|| cmd == CM_verb
|| cmd == CM_indent || cmd == CM_noindent)
ok = 0;
}
/* Exceptions for "simple text commands" only. */
if (simple_text_command)
{
if (cmd == CM_xref
|| cmd == CM_ref
|| cmd == CM_pxref
|| cmd == CM_inforef)
ok = 0;
}
}
else
{
/* Default to valid nesting, for example for commands for which
it is not defined which commands can occur within them (e.g.
@tab?). */
ok = 1;
}
if (!ok)
{
invalid_parent = current->parent->cmd;
if (!invalid_parent)
{
/* current_context () == ct_def. Find def block containing
command. */
ELEMENT *d = current;
while (d->parent
&& d->parent->type != ET_def_line)
d = d->parent;
invalid_parent = d->parent->parent->cmd;
}
line_warn ("@%s should not appear in @%s",
command_name(cmd),
command_name(invalid_parent));
}
}
/* *LINEP is a pointer into the line being processed. It is advanced past any
bytes processed. Return 0 when we need to read a new line. */
int
process_remaining_on_line (ELEMENT **current_inout, char **line_inout)
{
ELEMENT *current = *current_inout;
char *line = *line_inout;
char *line_after_command;
int retval = STILL_MORE_TO_PROCESS;
enum command_id end_cmd;
char *p;
enum command_id cmd = CM_NONE;
/********* BLOCK_raw or (ignored) BLOCK_conditional ******************/
/* If in raw block, or ignored conditional block. */
if (command_flags(current) & CF_block
&& (command_data(current->cmd).data == BLOCK_raw
|| command_data(current->cmd).data == BLOCK_conditional))
{
/* Check if we are using a macro within a macro. */
if (current->cmd == CM_macro || current->cmd == CM_rmacro)
{
enum command_id cmd = 0;
char *p = line;
p += strspn (p, whitespace_chars);
if (!strncmp (p, "@macro", strlen ("@macro")))
{
p += strlen ("@macro");
cmd = CM_macro;
}
else if (!strncmp (p, "@rmacro", strlen ("@rmacro")))
{
p += strlen ("@rmacro");
cmd = CM_rmacro;
}
if (cmd)
{
ELEMENT *e = new_element (ET_NONE);
e->cmd = cmd;
line = p;
add_to_element_contents (current, e);
add_extra_string (e, "arg_line", strdup (line));
current = e;
retval = GET_A_NEW_LINE;
goto funexit;
}
}
/* Else check for nested @ifset (so that @end ifset doesn't
end the outermost @ifset). */
if (current->cmd == CM_ifclear || current->cmd == CM_ifset
|| current->cmd == CM_ifcommanddefined
|| current->cmd == CM_ifcommandnotdefined)
{
ELEMENT *e;
char *p = line;
p += strspn (p, whitespace_chars);
if (*p == '@'
&& !strncmp (p + 1, command_name(current->cmd),
strlen (command_name(current->cmd))))
{
line = p + 1;
p += strlen (command_name(current->cmd));
e = new_element (ET_NONE);
e->cmd = current->cmd;
add_extra_string (e, "line", strdup (line));
add_to_element_contents (current, e);
current = e;
retval = GET_A_NEW_LINE;
goto funexit;
}
}
/* Else check if line is "@end ..." for current command. */
p = line;
if (is_end_current_command (current, &line, &end_cmd))
{
ELEMENT *last_child;
char *tmp = 0;
last_child = last_contents_child (current);
if (strchr (whitespace_chars, *p))
{
ELEMENT *e;
int n = strspn (line, whitespace_chars);
e = new_element (ET_raw);
text_append_n (&e->text, line, n);
add_to_element_contents (current, e);
line += n;
line_warn ("@end %s should only appear at the "
"beginning of a line", command_name(end_cmd));
}
else if (last_child
&& last_child->type == ET_raw
&& current->cmd != CM_verbatim)
{
if (last_child->text.end > 0
&& last_child->text.text[last_child->text.end - 1] == '\n')
{
ELEMENT *lrn;
last_child->text.text[--last_child->text.end] = '\0';
lrn = new_element (ET_last_raw_newline);
text_append (&lrn->text, "\n");
add_to_element_contents (current, lrn);
}
}
/* 'line' is now advanced past the "@end ...". Check if
there's anything after it. */
p = line + strspn (line, whitespace_chars);
if (*p && *p != '@')
goto superfluous_arg;
if (*p)
{
p++;
tmp = read_command_name (&p);
if (tmp && (!strcmp (tmp, "c") || !strcmp (tmp, "comment")))
{
}
else if (*p && p[strspn (p, whitespace_chars)])
{
superfluous_arg:
line_warn ("superfluous argument to @end %s: %s",
command_name (current->cmd), line);
}
free (tmp);
}
/* For macros, define a new macro (unless we are in a nested
macro definition). */
if ((end_cmd == CM_macro || end_cmd == CM_rmacro)
&& (!current->parent
|| (current->parent->cmd != CM_macro
&& current->parent->cmd != CM_rmacro)))
{
char *name;
enum command_id existing;
if (current->args.number > 0)
{
name = element_text (args_child_by_index (current, 0));
existing = lookup_command (name);
if (existing)
{
MACRO *macro;
macro = lookup_macro (existing);
if (macro)
{
line_error_ext (1, ¤t->line_nr,
"macro `%s' previously defined", name);
line_error_ext (1, ¯o->element->line_nr,
"here is the previous definition of `%s'", name);
}
else if (!(existing & USER_COMMAND_BIT))
{
line_error_ext (1, ¤t->line_nr,
"redefining Texinfo language command: @%s",
name);
}
}
if (!lookup_extra (current, "invalid_syntax"))
{
new_macro (name, current);
}
}
}
current = current->parent;
/* Check for conditionals. */
if (command_data(end_cmd).flags & CF_block
&& command_data(end_cmd).data == BLOCK_conditional)
{
/* Remove an ignored block. */
ELEMENT *popped;
popped = pop_element_from_contents (current);
if (popped->cmd != end_cmd)
fatal ("command mismatch for ignored block");
/* Ignore until end of line */
if (!strchr (line, '\n'))
{
line = new_line ();
debug ("IGNORE CLOSE LINE");
}
destroy_element_and_children (popped);
debug ("CLOSED conditional %s", command_name(end_cmd));
retval = GET_A_NEW_LINE;
goto funexit;
}
else
{
ELEMENT *e;
int n;
debug ("CLOSED raw %s", command_name(end_cmd));
e = new_element (ET_empty_line_after_command);
n = strspn (line, whitespace_chars_except_newline);
text_append_n (&e->text, line, n);
line += n;
add_to_element_contents (current, e);
}
}
else /* save the line verbatim */
{
ELEMENT *last = last_contents_child (current);
/* Append to existing element only if the text is all
whitespace. */
if (last && last->type == ET_empty_line_after_command
&& line[strspn (line, whitespace_chars)] == '\0'
&& !strchr (last->text.text, '\n'))
{
text_append (&last->text, line);
}
else
{
ELEMENT *e;
e = new_element (ET_raw);
text_append (&e->text, line);
add_to_element_contents (current, e);
}
retval = GET_A_NEW_LINE;
goto funexit;
}
} /********* BLOCK_raw or (ignored) BLOCK_conditional *************/
/* Check if parent element is 'verb' */
else if (current->parent && current->parent->cmd == CM_verb)
{
char c;
char *q;
KEY_PAIR *k;
k = lookup_extra (current->parent, "delimiter");
c = *(char *)k->value;
if (c)
{
/* Look forward for the delimiter character followed by a close
brace. */
q = line;
while (1)
{
q = strchr (q, c);
if (!q || q[1] == '}')
break;
q++;
}
}
else
{
/* Look forward for a close brace. */
q = strchr (line, '}');
}
if (q)
{
/* Save up to the delimiter character. */
if (q != line)
{
ELEMENT *e = new_element (ET_raw);
text_append_n (&e->text, line, q - line);
add_to_element_contents (current, e);
}
debug ("END VERB");
if (c)
line = q + 1;
else
line = q;
/* The '}' will close the @verb command in handle_separator below. */
}
else
{
/* Save the rest of line. */
ELEMENT *e = new_element (ET_raw);
text_append (&e->text, line);
add_to_element_contents (current, e);
debug_nonl ("LINE VERB: %s", line);
retval = GET_A_NEW_LINE; goto funexit; /* Get next line. */
}
} /* CM_verb */
/* Skip empty lines. If we reach the end of input, continue in case there
is an @include. */
/* There are cases when we need more input, but we don't want to
get it in the top-level loop in parse_texi - this is mostly
(always?) when we don't want to start a new, empty line, and
need to get more from the current, incomplete line of input. */
while (*line == '\0')
{
static char *allocated_text;
debug ("EMPTY TEXT");
/* Each place we supply Texinfo input we store the supplied
input in a static variable like allocated_text, to prevent
memory leaks. */
free (allocated_text);
line = allocated_text = next_text ();
if (!line)
{
/* TODO: Can this only happen at end of file? */
current = end_line (current);
retval = GET_A_NEW_LINE;
goto funexit;
}
}
if (*line == '@')
{
line_after_command = line + 1;
/* List of single character Texinfo commands. */
if (strchr ("([\"'~@&}{,.!?"
" \f\n\r\t"
"*-^`=:|/\\",
*line_after_command))
{
char single_char[2];
single_char[0] = *line_after_command++;
single_char[1] = '\0';
cmd = lookup_command (single_char);
}
else
{
char *command = read_command_name (&line_after_command);
cmd = 0;
if (command)
{
ELEMENT *paragraph;
cmd = lookup_command (command);
if (!cmd)
{
line_error ("unknown command `%s'", command);
debug ("COMMAND (UNKNOWN) %s", command);
free (command);
abort_empty_line (¤t, 0);
paragraph = begin_paragraph (current);
if (paragraph)
current = paragraph;
line = line_after_command;
retval = STILL_MORE_TO_PROCESS;
goto funexit;
}
free (command);
}
else
{
/* @ was followed by gibberish. "unexpected @" is printed
below. */
}
}
if (cmd && (command_data(cmd).flags & CF_ALIAS))
cmd = command_data(cmd).data;
}
/* Handle user-defined macros before anything else because their expansion
may lead to changes in the line. */
if (cmd && (command_data(cmd).flags & CF_MACRO))
{
static char *allocated_line;
line = line_after_command;
current = handle_macro (current, &line, cmd);
free (allocated_line);
allocated_line = next_text ();
line = allocated_line;
}
/* Cases that may "lead to command closing": brace commands that don't
need a brace: accent commands.
@definfoenclose. */
/* This condition is only checked immediately after the command opening,
otherwise the current element is in the 'args' and not right in the
command container. */
else if (command_flags(current) & CF_brace && *line != '{')
{
if (command_with_command_as_argument (current->parent))
{
debug ("FOR PARENT @%s command_as_argument @%s",
command_name(current->parent->parent->cmd),
command_name(current->cmd));
if (!current->type)
current->type = ET_command_as_argument;
add_extra_element (current->parent->parent,
"command_as_argument", current);
current = current->parent;
}
else if (command_flags(current) & CF_accent)
{
if (strchr (whitespace_chars_except_newline, *line))
{
if (isalpha (command_name(current->cmd)[0]))
/* e.g. @dotaccent */
{
char *p; char *s;
KEY_PAIR *k;
p = line + strspn (line, whitespace_chars_except_newline);
k = lookup_extra (current, "spaces");
if (!k)
{
asprintf (&s, "%.*s", (int) (p - line), line);
add_extra_string (current, "spaces", s);
}
else
{
asprintf (&s, "%s%.*s",
(char *) k->value,
(int) (p - line), p);
free (k->value);
k->value = (ELEMENT *) s;
}
line = p;
}
else
{
line_warn ("accent command `@%s' must not be followed "
"by whitespace", command_name(current->cmd));
current = current->parent;
}
}
else if (*line == '@')
{
line_error ("use braces to give a command as an argument "
"to @%s", command_name(current->cmd));
current = current->parent;
}
else if (*line != '\0' && *line != '\n' && *line != '\r')
{
ELEMENT *e, *e2;
debug ("ACCENT");
e = new_element (ET_following_arg);
add_to_element_args (current, e);
e2 = new_element (ET_NONE);
text_append_n (&e2->text, line, 1);
add_to_element_contents (e, e2);
if (current->cmd == CM_dotless
&& *line != 'i' && *line != 'j')
{
line_error ("@dotless expects `i' or `j' as argument, "
"not `%c'", *line);
}
if (isalpha (command_name(current->cmd)[0]))
e->type = ET_space_command_arg;
while (current->contents.number > 0)
destroy_element (pop_element_from_contents (current));
line++;
current = current->parent;
}
else
{
debug ("STRANGE ACC");
line_warn ("accent command `@%s' must not be followed by "
"new line", command_name(current->cmd));
current = current->parent;
}
goto funexit;
}
else
{
if (conf.ignore_space_after_braced_command_name)
{
char *p;
p = line + strspn (line, whitespace_chars);
if (p != line)
{
line = p;
goto funexit;
}
}
line_error ("@%s expected braces",
command_name(current->cmd));
current = current->parent;
}
}
else if (handle_menu (¤t, &line))
{
; /* Nothing - everything was done in handle_menu. */
}
/* Any other @-command. */
else if (cmd)
{
int def_line_continuation;
line = line_after_command;
debug ("COMMAND %s", command_name(cmd));
/* @value */
if (cmd == CM_value)
{
char *arg_start;
char *flag;
line += strspn (line, whitespace_chars);
if (*line != '{')
goto value_invalid;
line++;
arg_start = line;
flag = read_flag_name (&line);
if (!flag)
goto value_invalid;
if (*line != '}')
{
line = arg_start - 1;
goto value_invalid;
}
if (1) /* @value syntax is valid */
{
char *value;
value_valid:
value = fetch_value (flag);
if (!value)
{
/* Add element for unexpanded @value.
This is not necessarily an error - in
Texinfo::Report::gdt we deliberately pass
in undefined values. */
ELEMENT *value_elt;
line_warn ("undefined flag: %s", flag);
/* Note: In the Perl code, this warning is conditional on
in_gdt setting, but the only effect that this possibly has
is on speed, as these warnings would not be printed to the
user. */
abort_empty_line (¤t, NULL);
value_elt = new_element (ET_NONE);
value_elt->cmd = CM_value;
text_append (&value_elt->text, flag);
/* In the Perl code, the name of the flag is stored in
the "type" field. We need to store in 'text' instead
and then output it as the type in
dump_perl.c / api.c. */
add_to_element_contents (current, value_elt);
line++; /* past '}' */
retval = STILL_MORE_TO_PROCESS;
}
else
{
line++; /* past '}' */
input_push_text (strdup (line), line_nr.macro);
input_push_text (strdup (value), line_nr.macro);
line += strlen (line);
retval = STILL_MORE_TO_PROCESS;
}
free (flag);
goto funexit;
}
else
{
value_invalid:
line_error ("bad syntax for @value");
retval = STILL_MORE_TO_PROCESS;
goto funexit;
}
}
/* Warn on deprecated command */
if (command_data(cmd).flags & CF_deprecated)
{
char *msg = 0;
switch (cmd)
{
/* messages for commands could go here. */
default:
break;
}
if (!msg)
line_warn ("@%s is obsolete.", command_name(cmd));
else
line_warn ("@%s is obsolete; %s", command_name(cmd), msg);
/* note: will have to translate msg if string translation with
gettext is implemented */
}
def_line_continuation = (current_context() == ct_def
&& cmd == CM_NEWLINE);
/* warn on not appearing at line beginning */
/* TODO maybe have a command flag for "begin line commands" */
if (!def_line_continuation
&& !abort_empty_line (¤t, NULL)
&& ((cmd == CM_node || cmd == CM_bye)
|| (command_data(cmd).flags & CF_block)
|| ((command_data(cmd).flags & CF_line)
&& cmd != CM_comment
&& cmd != CM_c
&& cmd != CM_sp
&& cmd != CM_columnfractions
&& cmd != CM_item
&& cmd != CM_verbatiminclude
&& cmd != CM_set
&& cmd != CM_clear
&& cmd != CM_vskip)
&& cmd != CM_subentry))
{
line_warn ("@%s should only appear at the beginning of a line",
command_name(cmd));
}
if (current->parent)
check_valid_nesting (current, cmd);
if (def_line_continuation)
{
retval = GET_A_NEW_LINE;
goto funexit;
}
/* check command doesn't start a paragraph */
/* TODO store this in cmd->flags. */
if (!(command_data(cmd).flags & (CF_line | CF_other | CF_block)
|| cmd == CM_titlefont
|| cmd == CM_caption
|| cmd == CM_shortcaption
|| cmd == CM_image
|| cmd == CM_ASTERISK /* @* */
|| cmd == CM_hyphenation
|| cmd == CM_anchor
|| cmd == CM_errormsg
|| (command_data(cmd).flags & CF_index_entry_command)))
{
ELEMENT *paragraph;
paragraph = begin_paragraph (current);
if (paragraph)
current = paragraph;
}
if (cmd)
{
if (close_paragraph_command (cmd))
current = end_paragraph (current, 0, 0);
if (close_preformatted_command (cmd))
current = end_preformatted (current, 0, 0);
}
if ((cmd == CM_sortas
|| cmd == CM_seeentry
|| cmd == CM_seealso
|| cmd == CM_subentry)
&& current->contents.number > 0
&& last_contents_child(current)->text.end > 0)
{
isolate_trailing_space (current, ET_empty_spaces_before_argument);
}
if (cmd == CM_item && item_line_parent (current))
cmd = CM_item_LINE;
/* We could possibly have done this before check_valid_nesting. */
if (command_data(cmd).flags & CF_other)
{
int status;
current = handle_other_command (current, &line, cmd, &status);
if (status == GET_A_NEW_LINE || status == FINISHED_TOTALLY)
{
retval = status;
goto funexit;
}
}
else if (command_data(cmd).flags & CF_line)
{
int status;
current = handle_line_command (current, &line, cmd, &status);
if (status == GET_A_NEW_LINE || status == FINISHED_TOTALLY)
{
retval = status;
goto funexit;
}
}
else if (command_data(cmd).flags & CF_block)
{
int new_line = 0;
current = handle_block_command (current, &line, cmd, &new_line);
if (new_line)
{
/* For @macro, to get a new line. This is done instead of
doing the EMPTY TEXT code on the next time round. */
retval = GET_A_NEW_LINE;
goto funexit;
}
}
else if (command_data(cmd).flags & (CF_brace | CF_accent))
{
current = handle_brace_command (current, &line, cmd);
}
/* No-brace command */
else if (command_data(cmd).flags & CF_nobrace)
{
ELEMENT *nobrace;
nobrace = new_element (ET_NONE);
nobrace->cmd = cmd;
add_to_element_contents (current, nobrace);
if (cmd == CM_BACKSLASH && current_context () != ct_math)
{
line_warn ("@\\ should only appear in math context");
}
if (cmd == CM_NEWLINE)
{
current = end_line (current);
retval = GET_A_NEW_LINE;
goto funexit;
}
}
}
/* "Separator" character */
else if (*line != '\0' && strchr ("{}@,:\t.\f", *line))
{
char separator = *line++;
debug ("SEPARATOR: %c", separator);
if (separator == '@')
line_error ("unexpected @");
else
current = handle_separator (current, separator, &line);
}
/* "Misc text except end of line." */
else if (*line && *line != '\n')
{
size_t len;
/* Output until next command, separator or newline. */
{
char saved; /* TODO: Have a length argument to merge_text? */
len = strcspn (line, "{}@,:\t.\n\f");
saved = line[len];
line[len] = '\0';
current = merge_text (current, line);
line += len;
*line = saved;
}
retval = STILL_MORE_TO_PROCESS;
goto funexit;
}
else /* End of line */
{
if (current->type)
debug ("END LINE (%s)", element_type_names[current->type]);
else if (current->cmd)
debug ("END LINE (@%s)", command_name(current->cmd));
else
debug ("END LINE");
if (current->parent)
{
debug_nonl (" <- ");
if (current->parent->cmd)
debug_nonl("@%s", command_name(current->parent->cmd));
if (current->parent->type)
debug_nonl("(%s)", element_type_names[current->parent->type]);
debug ("");
debug ("");
}
if (*line == '\n')
{
current = merge_text (current, "\n");
line++;
}
else
{
if (input_number > 0)
bug_message ("Text remaining without normal text but `%s'", line);
}
/* '@end' is processed in here. */
current = end_line (current);
retval = GET_A_NEW_LINE;
}
funexit:
*current_inout = current;
*line_inout = line;
return retval;
}
/* Check for a #line directive. */
static int
check_line_directive (char *line)
{
char *p = line, *q;
int line_no = 0;
char *filename = 0;
if (!conf.cpp_line_directives)
return 0;
/* Check input is coming directly from a file. */
if (!line_nr.file_name || !line_nr.file_name
|| (line_nr.macro && *line_nr.macro))
return 0;
p += strspn (p, " \t");
if (*p != '#')
return 0;
p++;
q = p + strspn (p, " \t");
if (!memcmp (q, "line", strlen ("line")))
p = q + strlen ("line");
if (!strchr (" \t", *p))
return 0;
p += strspn (p, " \t");
/* p should now be at the line number */
if (!strchr ("0123456789", *p))
return 0;
line_no = strtoul (p, &p, 10);
p += strspn (p, " \t");
if (*p == '"')
{
char c;
p++;
q = strchr (p, '"');
if (!q)
return 0;
c = *q;
*q = 0;
filename = save_string (p);
*q = c;
p = q + 1;
p += strspn (p, " \t");
p += strspn (p, "0123456789");
p += strspn (p, " \t");
}
if (*p && *p != '\n')
return 0; /* trailing text on line */
save_line_directive (line_no, filename);
return 1;
}
/* Pass in and return root of a "Texinfo tree". */
ELEMENT *
parse_texi (ELEMENT *root_elt)
{
ELEMENT *current = root_elt;
static char *allocated_line;
char *line;
/* Read input file line-by-line. */
while (1)
{
free (allocated_line);
line = allocated_line = next_text ();
if (!allocated_line)
break; /* Out of input. */
debug_nonl ("NEW LINE %s", line);
/* If not in 'raw' or 'conditional' and parent isn't a 'verb', collect
leading whitespace and save as an "ET_empty_line" element. This
element type can be changed in 'abort_empty_line' when more text is
read. */
if (!((command_flags(current) & CF_block)
&& (command_data(current->cmd).data == BLOCK_raw
|| command_data(current->cmd).data == BLOCK_conditional)
|| current->parent && current->parent->cmd == CM_verb)
&& current_context () != ct_def)
{
ELEMENT *e;
int n;
if (check_line_directive (line))
continue;
debug ("BEGIN LINE");
if (current->contents.number > 0
&& last_contents_child(current)->type
== ET_empty_spaces_before_argument)
{
/* Remove this element and update 'extra' values. */
abort_empty_line (¤t, 0);
}
e = new_element (ET_empty_line);
add_to_element_contents (current, e);
n = strspn (line, whitespace_chars_except_newline);
text_append_n (&e->text, line, n);
line += n;
}
/* Process from start of remaining line, advancing it until we run out
of line. */
while (1)
{
int status = process_remaining_on_line (¤t, &line);
if (status == GET_A_NEW_LINE)
break;
if (status == FINISHED_TOTALLY)
goto finished_totally;
if (!line)
break;
}
}
finished_totally:
/* Check for unclosed conditionals */
while (conditional_number > 0)
{
line_error ("expected @end %s",
command_name(conditional_stack[conditional_number - 1]));
conditional_number--;
}
{
ELEMENT *dummy;
current = close_commands (current, CM_NONE, &dummy, CM_NONE);
/* Make sure we are at the very top - we could have stopped at the "top"
element, with "document_root" still to go. (This happens if the file
didn't end with "@bye".) */
while (current->parent)
current = current->parent;
}
input_reset_input_stack (); /* to avoid a memory leak if @bye is given */
/* TODO: Check for "unclosed stacks". */
return current;
}