Category : Miscellaneous Language Source Code
Archive   : MKINF10.ZIP
Filename : MAKEINFO.D

 
Output of file : MAKEINFO.D contained in archive : MKINF10.ZIP
/* Makeinfo -- convert texinfo format files into info files

Copyright (C) 1987 Free Software Foundation, Inc.

This file is part of GNU Info.

Makeinfo is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY. No author or distributor accepts
responsibility to anyone for the consequences of using it or for
whether it serves any particular purpose or works at all, unless he
says so in writing. Refer to the GNU Emacs General Public License
for full details.

Everyone is granted permission to copy, modify and redistribute
Makeinfo, but only under the conditions described in the GNU Emacs
General Public License. A copy of this license is supposed to
have been given to you along with GNU Emacs so you can know your
rights and responsibilities. It should be in a file named COPYING.
Among other things, the copyright notice and this notice must be
preserved on all copies. */

/* MS-DOS port (c) 1990 by Thorsten Ohl, [email protected]
This port is also distributed under the terms of the
GNU General Public License as published by the
Free Software Foundation.

Please note that this file is not identical to the
original GNU release, you should have received this
code as patch to the official release.

$Header: e:/gnu/info/RCS/makeinfo.d 0.1.1.1 90/10/05 11:25:31 tho Exp $
*/

/* cont'd from makeinfo.c */

/*===(cut here)===*/

/* **************************************************************** */
/* */
/* Converting the File */
/* */
/* **************************************************************** */

/* Convert the file named by NAME. The output is saved on the file
named as the argument to the @setfilename command. */
VOID
convert (name)
char *name;
{
char *real_output_filename, *expand_filename (), *filename_part ();
init_tag_table ();
init_indices ();
init_internals ();
init_paragraph ();

if (!find_and_load (name))
{
/* For some reason, the file couldn't be loaded. Print a message
to that affect, and split. */
fs_error (name);
return;
}
else
input_filename = savestring (name);

/* Search this file looking for the special string which starts conversion.
Once found, we may truly begin. */

input_text_offset = search_forward ("@setfilename", 0);
if (input_text_offset < 0)
{
error ("No `@setfilename' found in `%s'", name);
goto finished;
}
else
input_text_offset += strlen ("@setfilename");

get_until ("\n", &output_filename); /* no braces expected. */
canon_white (output_filename);

printf ("Making info file `%s' from `%s'.\n", output_filename, name);
real_output_filename = expand_filename (output_filename, name);
output_stream = fopen (real_output_filename, "w");
if (output_stream == NULL)
{
fs_error (real_output_filename);
goto finished;
}

/* Make the displayable filename from output_filename. Only the root
portion of the filename need be displayed. */
pretty_output_filename = filename_part (output_filename);

/* For this file only, count the number of newlines from the top of
the file to here. This way, we keep track of line numbers for
error reporting. Line_number starts at 1, since the user isn't
zero-based. */
{
LONG temp = 0;
line_number = 1;
while (temp != input_text_offset)
if (input_text[temp++] == '\n')
line_number++;
}

add_word_args ("Info file %s, produced by Makeinfo, -*- Text -*-\n\
from input file %s.\n", output_filename, input_filename);
close_paragraph ();

reader_loop ();

finished:
close_paragraph ();
flush_file_stack ();
if (output_stream != NULL)
{
output_pending_notes ();
free_pending_notes ();
if (tag_table != NULL)
{
tag_table = (TAG_ENTRY *) reverse_list (tag_table);
write_tag_table ();
}

fclose (output_stream);

/* If validating, then validate the entire file right now. */
if (validating)
validate_file (real_output_filename, tag_table);

/* This used to test && !errors_printed.
But some files might have legit warnings. So split anyway. */
if (splitting)
split_file (real_output_filename, 0);
}
}

VOID
free_and_clear (pointer)
char **pointer;
{
if ((*pointer) != (char *) NULL)
{
free (*pointer);
*pointer = (char *) NULL;
}
}

/* Initialize some state. */
VOID
init_internals ()
{
free_and_clear (¤t_node);
free_and_clear (&output_filename);
free_and_clear (&command);
free_and_clear (&input_filename);
free_node_references ();
init_insertion_stack ();
init_brace_stack ();
command_index = 0;
in_menu = 0;
}

VOID
init_paragraph ()
{
free_and_clear (&output_paragraph);
output_paragraph = xmalloc (paragraph_buffer_len);
output_position = 0;
output_paragraph[0] = '\0';
output_paragraph_offset = 0;
output_column = 0;
paragraph_is_open = false;
current_indent = 0;
}

/* Okay, we are ready to start the conversion. Call the reader on
some text, and fill the text as it is output. Handle commands by
remembering things like open braces and the current file position on a
stack, and when the corresponding close brace is found, you can call
the function with the proper arguments. */
VOID
reader_loop ()
{
int character;
boolean done = false;
int dash_count = 0;

while (!done)
{
if (input_text_offset >= size_of_input_text)
{
if (filestack)
{
free (input_filename);
free (input_text);
popfile ();
}
else
break;
}
character = curchar ();

if (character == '-')
{
dash_count++;
if (dash_count == 3)
{
input_text_offset++;
continue;
}
}
else
{
dash_count = 0;
}

if (character == '\n')
{
line_number++;
if (in_menu && input_text_offset + 1 < size_of_input_text)
{
glean_node_from_menu ();
}
}

switch (character)
{
case COMMAND_PREFIX:
read_command ();
if (strcmp (command, "bye") == 0)
{
done = true;
continue;
}
break;

case '{':

/* Special case. I'm not supposed to see this character by itself.
If I do, it means there is a syntax error in the input text.
Report the error here, but remember this brace on the stack so
you can ignore its partner. */

line_error ("Misplaced `{'");
remember_brace (misplaced_brace);

/* Don't advance input_text_offset since this happens in
remember_brace ().
input_text_offset++;
*/
break;

case '}':
pop_and_call_brace ();
input_text_offset++;
break;

default:
add_char (character);
input_text_offset++;
}
}
}

/* Find the command corresponding to STRING. If the command
is found, return a pointer to the data structure. Otherwise
return (-1). */
COMMAND *
get_command_entry (string)
char *string;
{
register int i;

for (i = 0; CommandTable[i].name; i++)
if (strcmp (CommandTable[i].name, string) == 0)
return (&CommandTable[i]);

/* This command is not in our predefined command table. Perhaps
it is a user defined command. */
for (i = 0; i < user_command_array_len; i++)
if (user_command_array[i] &&
(strcmp (user_command_array[i]->name, string) == 0))
return (user_command_array[i]);

/* Nope, we never heard of this command. */
return ((COMMAND *) - 1);
}

/* input_text_offset is right at the command prefix character.
Read the next token to determine what to do. */
VOID
read_command ()
{
COMMAND *entry;
input_text_offset++;
free_and_clear (&command);
command = read_token ();

entry = get_command_entry (command);
if ((LONG) entry < 0)
{
line_error ("Unknown info command `%s'", command);
return;
}

if (entry->argument_in_braces)
remember_brace (entry->proc);

(*(entry->proc)) (START);
}

/* Return the string which invokes PROC; a pointer to a function. */
char *
find_proc_name (proc)
FUNCTION *proc;
{
register int i;

for (i = 0; CommandTable[i].name; i++)
if (proc == CommandTable[i].proc)
return (CommandTable[i].name);
return ("NO_NAME!");
}

VOID
init_brace_stack ()
{
brace_stack = (BRACE_ELEMENT *) NULL;
}

VOID
remember_brace (proc)
FUNCTION *proc;
{
if (curchar () != '{')
line_error ("@%s expected `{..}'", command);
else
input_text_offset++;
remember_brace_1 (proc, output_paragraph_offset);
}

/* Remember the current output position here. Save PROC
along with it so you can call it later. */
VOID
remember_brace_1 (proc, position)
FUNCTION *proc;
LONG position;
{
BRACE_ELEMENT *new = (BRACE_ELEMENT *) xmalloc (sizeof (BRACE_ELEMENT));
new->next = brace_stack;
new->proc = proc;
new->pos = position;
new->line = line_number;
brace_stack = new;
}

/* Pop the top of the brace stack, and call the associated function
with the args END and POS. */
pop_and_call_brace ()
{
BRACE_ELEMENT *temp;
FUNCTION *proc;
LONG pos;

if (brace_stack == (BRACE_ELEMENT *) NULL)
return (line_error ("Unmatched close bracket"));

pos = brace_stack->pos;
proc = brace_stack->proc;
temp = brace_stack->next;
free (brace_stack);
brace_stack = temp;

return ((*proc) (END, pos, output_paragraph_offset));
}

/* You call discard_braces () when you shouldn't have any braces on the stack.
I used to think that this happens for commands that don't take arguments
in braces, but that was wrong because of things like @code{foo @@}. So now
I only detect it at the beginning of nodes. */
VOID
discard_braces ()
{
int temp_line_number = line_number;
char *proc_name;

if (!brace_stack)
return;

while (brace_stack)
{
line_number = brace_stack->line;
proc_name = find_proc_name (brace_stack->proc);
line_error ("@%s missing close brace", proc_name);
line_number = temp_line_number;
pop_and_call_brace ();
}
}

get_char_len (character)
int character;
{
/* Return the printed length of the character. */
int len;

switch (character)
{
case '\t':
len = (output_column + 8) & 0xf7;
if (len > fill_column)
len = fill_column - output_column;
else
len = len - output_column;
break;

case '\n':
len = fill_column - output_column;
break;

default:
if (character < ' ')
len = 2;
else
len = 1;
}
return (len);
}


#ifdef MSDOS

VOID CDECL
add_word_args (char *format, ...)
{
char buffer[1000];
va_list arg_ptr;
va_start (arg_ptr, format);

vsprintf (buffer, format, arg_ptr);
add_word (buffer);
}

#else /* not MSDOS */

VOID
add_word_args (format, arg1, arg2, arg3, arg4, arg5)
char *format;
{
char buffer[1000];
sprintf (buffer, format, arg1, arg2, arg3, arg4, arg5);
add_word (buffer);
}

#endif /* not MSDOS */


/* Add STRING to output_paragraph. */
VOID
add_word (string)
char *string;
{
while (*string)
add_char (*string++);
}

boolean last_char_was_newline = true;
int last_inserted_character = 0;

/* Add the character to the current paragraph. If filling_enabled is
true, then do filling as well. */
VOID
add_char (character)
int character;
{
extern int must_start_paragraph;

/* If we are adding a character now, then we don't have to
ignore close_paragraph () calls any more. */
if (must_start_paragraph)
{
must_start_paragraph = 0;
if (current_indent > output_column)
{
indent (current_indent - output_column);
output_column = current_indent;
}
}

if (non_splitting_words && member (character, " \t\n"))
character = ' ' | 0x80;

switch (character)
{

case '\n':
if (!filling_enabled)
{
insert ('\n');

/* Should I be flushing output here? * /
flush_output (); */

output_column = 0;
if (!no_indent)
indent (output_column = current_indent);
break;
}
else
{
if (sentence_ender (last_inserted_character))
{
insert (' ');
output_column++;
last_inserted_character = character;
}
}

if (last_char_was_newline)
{
close_paragraph ();
pending_indent = 0;
}
else
{
last_char_was_newline = true;
insert (' ');
output_column++;
}
break;

default:
{
int len = get_char_len (character);
if ((character == ' ') && (last_char_was_newline))
{
if (!paragraph_is_open)
{
pending_indent++;
return;
}
}
if (!paragraph_is_open)
{
start_paragraph ();

/* If the paragraph is supposed to be indented a certain way,
then discard all of the pending whitespace. Otherwise, we
let the whitespace stay. */
if (!paragraph_start_indent)
indent (pending_indent);
pending_indent = 0;
}
if ((output_column += len) >= fill_column)
{
if (filling_enabled)
{
int temp = output_paragraph_offset - 1;
while (temp > 0 && output_paragraph[--temp] != '\n')
{
if (output_paragraph[temp] == ' ')
{
output_paragraph[temp++] = '\n';

/* We have correctly broken the line where we want
to. What we don't want is spaces following where
we have decided to break the line. We get rid of
them. */
{
int t1 = temp;
while (t1 < output_paragraph_offset
&& whitespace (output_paragraph[t1]))
t1++;

if (t1 != temp)
{
strncpy (&output_paragraph[temp],
&output_paragraph[t1],
(output_paragraph_offset - t1));
output_paragraph_offset -= (t1 - temp);
}
}

/* Filled, but now indent if that is right. */
if (indented_fill && current_indent)
{
int buffer_len = ((output_paragraph_offset - temp)
+ current_indent);
char *temp_buffer = xmalloc (buffer_len);
int indentation = 0;

/* We have to shift any markers that are in
front of the wrap point. */
{
register BRACE_ELEMENT *stack = brace_stack;

while (stack)
{
if (stack->pos > temp)
stack->pos += current_indent;
stack = stack->next;
}
}

while (indentation != current_indent)
temp_buffer[indentation++] = ' ';

strncpy (&temp_buffer[current_indent],
&output_paragraph[temp],
buffer_len - current_indent);

if (output_paragraph_offset + buffer_len
>= paragraph_buffer_len)
{
char *tt =
(char *) xrealloc (output_paragraph,
(paragraph_buffer_len += buffer_len));
output_paragraph = tt;
}
strncpy (&output_paragraph[temp], temp_buffer, buffer_len);
output_paragraph_offset += current_indent;
free (temp_buffer);
}
output_column = 0;
while (temp != output_paragraph_offset)
output_column += get_char_len (output_paragraph[temp++]);
output_column += len;
break;
}
}
}
}
insert (character);
last_char_was_newline = false;
last_inserted_character = character;
}
}
}

/* Insert CHARACTER into OUTPUT_PARAGRAPH. */
VOID
insert (character)
int character;
{
output_paragraph[output_paragraph_offset++] = character;
if (output_paragraph_offset == paragraph_buffer_len)
{
output_paragraph =
(char *) xrealloc (output_paragraph,
(paragraph_buffer_len += 100));
}
}

/* Remove upto COUNT characters of whitespace from the
the current output line. If COUNT is less than zero,
then remove until none left. */
VOID
kill_self_indent (count)
int count;
{
/* Handle infinite case first. */
if (count < 0)
{
output_column = 0;
while (output_paragraph_offset)
{
if (whitespace (output_paragraph[output_paragraph_offset - 1]))
output_paragraph_offset--;
else
break;
}
}
else
{
while (output_paragraph_offset && count--)
if (whitespace (output_paragraph[output_paragraph_offset - 1]))
output_paragraph_offset--;
else
break;
}
}

VOID
flush_output ()
{
register int i;

if (!output_paragraph_offset)
return;
for (i = 0; i < output_paragraph_offset; i++)
output_paragraph[i] &= 0x7f;

fwrite (output_paragraph, 1, output_paragraph_offset, output_stream);
output_position += output_paragraph_offset;
output_paragraph_offset = 0;
}

/* How to close a paragraph controlling the number of lines between
this one and the last one. */

/* Paragraph spacing is controlled by this variable. It is the number of
blank lines that you wish to appear between paragraphs. A value of
1 creates a single blank line between paragraphs. */
int paragraph_spacing = 1;


/* Close the current paragraph, leaving no blank lines between them. */
VOID
close_single_paragraph ()
{
close_paragraph_with_lines (0);
}

VOID
close_paragraph_with_lines (lines)
int lines;
{
int old_spacing = paragraph_spacing;
paragraph_spacing = lines;
close_paragraph ();
paragraph_spacing = old_spacing;
}

/* Non-zero means that start_paragraph () MUST be called before we pay
any attention to close_paragraph () calls. */
int must_start_paragraph = 0;

/* Close the currently open paragraph. */
VOID
close_paragraph ()
{
if (paragraph_is_open && !must_start_paragraph)
{
/* Gobble up blank lines that are extra... */
register int tindex = output_paragraph_offset;
register int c;
while (tindex && ((c = output_paragraph[tindex - 1]) == ' ' || c == '\n'))
output_paragraph[--tindex] = '\n';

output_paragraph_offset = tindex;

insert ('\n');
{
register int i;
for (i = 0; i < paragraph_spacing; i++)
insert ('\n');
}
flush_output ();
paragraph_is_open = false;
no_indent = false;
}
last_char_was_newline = true;
}

/* Begin a new paragraph. */
VOID
start_paragraph ()
{
close_paragraph (); /* First close existing one. */

paragraph_is_open = true;

if (!must_start_paragraph)
{
output_column = 0;

/* If doing indentation, then insert the appropriate amount. */
if (!no_indent)
{
if (inhibit_paragraph_indentation || paragraph_start_indent < 0)
output_column = current_indent;
else
output_column = current_indent + paragraph_start_indent;

indent (output_column);
}
}
else
must_start_paragraph = 0;
}

/* Insert the indentation specified by AMOUNT. */
VOID
indent (amount)
int amount;
{
while (--amount >= 0)
insert (' ');
}

/* Search forward for STRING in input_text.
FROM says where where to start. */
LONG
search_forward (string, from)
char *string;
LONG from;
{
int len = strlen (string);

while (from < size_of_input_text)
{
if (strnicmp (input_text + from, string, len) == 0)
return (from);
from++;
}
return ((LONG) -1);
}


#ifndef MSDOS
/* Whoops, Unix doesn't have stricmp, or strnicmp. */

/* Case independent string compare. */
stricmp (string1, string2)
char *string1, *string2;
{
char ch1, ch2;

for (;;)
{
ch1 = *string1++;
ch2 = *string2++;
if (!(ch1 | ch2))
return (0);

ch1 = coerce_to_upper (ch1);
ch2 = coerce_to_upper (ch2);

if (ch1 != ch2)
return (1);
}
}

/* Compare at most COUNT characters from string1 to string2. Case
doesn't matter. */
strnicmp (string1, string2, count)
char *string1, *string2;
{
char ch1, ch2;

while (count)
{
ch1 = *string1++;
ch2 = *string2++;
if (coerce_to_upper (ch1) == coerce_to_upper (ch2))
count--;
else
break;
}
return (count);
}
#endif /* not MSDOS */

enum insertion_type
{
menu, quotation, lisp, example, smallexample, display,
itemize, format, enumerate, table, group, ifinfo,
defun, defvar, defopt, deffn, defspec, defmac,
bad_type
};

char *insertion_type_names[] = {
"menu", "quotation", "lisp", "example", "smallexample", "display",
"itemize", "format", "enumerate", "table", "group", "ifinfo",
"defun", "defvar", "defopt", "deffn", "defspec", "defmac",
};

int insertion_level = 0;
typedef struct istack_elt
{
struct istack_elt *next;
char *item_function;
int line_number;
int filling_enabled;
int indented_fill;
enum insertion_type insertion;
int inhibited;
} INSERTION_ELT;

INSERTION_ELT *insertion_stack = (INSERTION_ELT *) NULL;

VOID
init_insertion_stack ()
{
insertion_stack = (INSERTION_ELT *) NULL;
}

/* Return the type of the current insertion. */
enum insertion_type
current_insertion_type ()
{
if (!insertion_level)
return (bad_type);
else
return (insertion_stack->insertion);
}

/* Return a pointer to the string which is the function
to wrap around items. */
char *
current_item_function ()
{
if (!insertion_level)
return ((char *) NULL);
else
return (insertion_stack->item_function);
}

char *
get_item_function ()
{
char *item_function;
get_until ("\n", &item_function);
canon_white (item_function);
return (item_function);
}

/* Push the state of the current insertion on the stack. */
VOID
push_insertion (type, item_function)
enum insertion_type type;
char *item_function;
{
INSERTION_ELT *new = (INSERTION_ELT *) xmalloc (sizeof (INSERTION_ELT));

new->item_function = item_function;
new->filling_enabled = filling_enabled;
new->indented_fill = indented_fill;
new->insertion = type;
new->line_number = line_number;
new->inhibited = inhibit_paragraph_indentation;
new->next = insertion_stack;
insertion_stack = new;
insertion_level++;
}

/* Pop the value on top of the insertion stack into the
global variables. */
VOID
pop_insertion ()
{
INSERTION_ELT *temp = insertion_stack;
if (temp == (INSERTION_ELT *) NULL)
return;
inhibit_paragraph_indentation = temp->inhibited;
filling_enabled = insertion_stack->filling_enabled;
indented_fill = insertion_stack->indented_fill;
free_and_clear (&(temp->item_function));
insertion_stack = insertion_stack->next;
free (temp);
insertion_level--;
}

/* Return a pointer to the print name of this
enumerated type. */
char *
insertion_type_pname (type)
enum insertion_type type;
{
if ((int) type < (int) bad_type)
return (insertion_type_names[(int) type]);
else
return ("Broken-Type in insertion_type_pname");
}

/* Return the insertion_type associated with NAME.
If the type is not one of the known ones, return BAD_TYPE. */
enum insertion_type
find_type_from_name (name)
char *name;
{
int index = 0;
while (index < (int) bad_type)
{
if (stricmp (name, insertion_type_names[index]) == 0)
return (enum insertion_type) index;
index++;
}
return (bad_type);
}

VOID
do_nothing ()
{
}

int
defun_insertion (type)
enum insertion_type type;
{
return (type == defun ||
type == defvar ||
type == defopt ||
type == deffn ||
type == defspec ||
type == defmac);
}

/* This is where the work for all the "insertion" style
commands is done. A huge switch statement handles the
various setups, and generic code is on both sides. */
VOID
begin_insertion (type)
enum insertion_type type;
{
int no_discard = 0;

close_paragraph ();

if (defun_insertion (type))
{
push_insertion (type, savestring (""));
no_discard = 1;
}
else
push_insertion (type, get_item_function ());

filling_enabled = false; /* the general case for insertions. */
inhibit_paragraph_indentation = 1;
no_indent = false;

switch (type)
{
case menu:
add_word ("* Menu:\n");
in_menu++;
discard_until ("\n");
input_text_offset--;
/* discard_until () has already counted the newline. Discount it. */
line_number--;
return;

/* I think @quotation is meant to do filling.
If you don't want filling, then use @example. */
case quotation:
last_char_was_newline = 0;
indented_fill = filling_enabled = true;
current_indent += default_indentation_increment;
break;

/* Just like @example, but no indentation. */
case format:
break;

case display:
case example:
case smallexample:
case lisp:
last_char_was_newline = 0;
current_indent += default_indentation_increment;
break;

case table:
case itemize:
current_indent += default_indentation_increment;
filling_enabled = indented_fill = true;

/* Make things work for losers who forget the itemize syntax. */
if (type == itemize)
{
if (!(*insertion_stack->item_function))
{
free (insertion_stack->item_function);
insertion_stack->item_function = savestring ("*");
}
}
break;

case enumerate:
inhibit_paragraph_indentation = 0;
current_indent += default_indentation_increment;
start_numbering (1);
filling_enabled = indented_fill = true;
break;

case group:
inhibit_paragraph_indentation = 0;
break;

case ifinfo:
/* Undo whatever we just did. This is a no-op. */
inhibit_paragraph_indentation = 0;
filling_enabled = insertion_stack->filling_enabled;
indented_fill = insertion_stack->indented_fill;
break;

case defun:
case defvar:
case defopt:
case deffn:
case defspec:
case defmac:
inhibit_paragraph_indentation = 0;
filling_enabled = indented_fill = true;
current_indent += default_indentation_increment;
break;
}
if (!no_discard)
discard_until ("\n");
}

/* Try to end the quotation with the specified type.
Like begin_insertion (), this uses a giant switch statement as
well. A big difference is that you *must* pass a valid type to
this function, and a value of bad_type gets translated to match
the value currently on top of the stack. If, however, the value
passed is a valid type, and it doesn't match the top of the
stack, then we produce an error. Should fix this, somewhat
unclean. */
VOID
end_insertion (type)
enum insertion_type type;
{
enum insertion_type temp_type;

if (!insertion_level)
return;

temp_type = current_insertion_type ();
if (type == bad_type)
type = temp_type;

if (type != temp_type)
{
line_error ("Expected `%s', but saw `%s'. Token unread",
insertion_type_pname (temp_type), insertion_type_pname (type));
return;
}
pop_insertion ();

switch (type)
{

case menu:
in_menu--; /* no longer hacking menus. */
break;

case enumerate:
stop_numbering ();
current_indent -= default_indentation_increment;
break;

case group:
case ifinfo:
case format:
break;

default:
current_indent -= default_indentation_increment;
break;
}
close_paragraph ();
}

/* Insertions cannot cross certain boundaries, such as node beginnings. In
code that creates such boundaries, you should call discard_insertions ()
before doing anything else. It prints the errors for you, and cleans up
the insertion stack. */
VOID
discard_insertions ()
{
int real_line_number = line_number;
while (insertion_stack)
{
if (insertion_stack->insertion == ifinfo)
break;
else
{
char *offender = (char *) insertion_type_pname (insertion_stack->insertion);

line_number = insertion_stack->line_number;
line_error ("This `%s' doesn't have a matching `%cend %s'", offender,
COMMAND_PREFIX, offender);
pop_insertion ();
}
}
line_number = real_line_number;
}

/* MAX_NS is the maximum nesting level for enumerations. I picked 100
which seemed reasonable. This doesn't control the number of items,
just the number of nested lists. */
#define max_ns 100
int number_stack[max_ns];
int number_offset = 0;
int the_current_number = 0;

VOID
start_numbering (at_number)
int at_number;
{
if (number_offset + 1 == max_ns)
{
line_error ("Enumeration stack overflow");
return;
}
number_stack[number_offset++] = the_current_number;
the_current_number = at_number;
}

VOID
stop_numbering ()
{
the_current_number = number_stack[--number_offset];
if (number_offset < 0)
number_offset = 0;
}

/* Place a number into the output stream. */
VOID
number_item ()
{
char temp[10];
sprintf (temp, "%d. ", the_current_number);
indent (output_column += (current_indent - strlen (temp)));
add_word (temp);
the_current_number++;
}

/* The actual commands themselves. */

/* Commands which insert themselves. */
VOID
insert_self ()
{
add_word (command);
}

/* Force line break */
VOID
cm_asterisk ()
{
/* Force a line break in the output. */
insert ('\n');
indent (output_column = current_indent);
}

/* Insert ellipsis. */
VOID
cm_dots (arg)
int arg;
{
if (arg == START)
add_word ("...");
}

VOID
cm_bullet (arg)
int arg;
{
if (arg == START)
add_char ('*');
}

VOID
cm_minus (arg)
int arg;
{
if (arg == START)
add_char ('-');
}

/* Insert "TeX". */
VOID
cm_TeX (arg)
int arg;
{
if (arg == START)
add_word ("TeX");
}

VOID
cm_copyright (arg)
int arg;
{
if (arg == START)
add_word ("(C)");
}

VOID
cm_code (arg)
int arg;
{
if (arg == START)
add_char ('`');
else
add_word ("'");
}

VOID
cm_samp (arg)
int arg;
{
cm_code (arg);
}

VOID
cm_file (arg)
int arg;
{
cm_code (arg);
}

VOID
cm_kbd (arg)
int arg;
{
cm_code (arg);
}

VOID

cm_key (arg)
int arg;
{
}

/* Convert the character at position-1 into CTL. */
VOID
cm_ctrl (arg, position)
int arg, position;
{
if (arg == END)
output_paragraph[position - 1] = CTL (output_paragraph[position - 1]);
}

/* Small Caps in makeinfo just does all caps. */
VOID
cm_sc (arg, start_pos, end_pos)
int arg, start_pos, end_pos;
{
if (arg == END)
{
while (start_pos < end_pos)
{
output_paragraph[start_pos] =
coerce_to_upper (output_paragraph[start_pos]);
start_pos++;
}
}
}

/* @var in makeinfo just uppercases the text. */
VOID
cm_var (arg, start_pos, end_pos)
int arg, start_pos, end_pos;
{
if (arg == END)
{
while (start_pos < end_pos)
{
output_paragraph[start_pos] =
coerce_to_upper (output_paragraph[start_pos]);
start_pos++;
}
}
}

VOID
cm_dfn (arg, position)
int arg, position;
{
add_char ('"');
}

VOID
cm_emph (arg)
int arg;
{
add_char ('*');
}

VOID
cm_strong (arg, position)
int arg, position;
{
cm_emph (arg);
}

VOID
cm_cite (arg, position)
int arg, position;
{
if (arg == START)
add_word ("``");
else
add_word ("''");
}

VOID
cm_italic (arg)
int arg;
{
}

VOID
cm_bold (arg)
int arg;
{
cm_italic (arg);
}

VOID
cm_roman (arg)
int arg;
{
}

VOID
cm_title (arg)
int arg;
{
cm_italic (arg);
}

VOID
cm_refill ()
{
}

/* Prevent the argument from being split across two lines. */
VOID
cm_w (arg)
int arg;
{
if (arg == START)
non_splitting_words++;
else
non_splitting_words--;
}


/* Explain that this command is obsolete, thus the user shouldn't
do anything with it. */
VOID
cm_obsolete (arg)
int arg;
{
if (arg == START)
warning ("The command `@%s' is obsolete", command);
}

/* Insert the text following input_text_offset up to the end of the line
in a new, separate paragraph. Directly underneath it, insert a
line of WITH_CHAR, the same length of the inserted text. */
VOID
insert_and_underscore (with_char)
int with_char;
{
LONG len;
int i, old_no_indent;
char *temp;

close_paragraph ();
filling_enabled = indented_fill = false;
old_no_indent = no_indent;
no_indent = true;
get_rest_of_line (&temp);

len = output_position;
execute_string ("%s\n", temp);
free (temp);

len = ((output_position + output_paragraph_offset) - 1) - len;
#ifdef MSDOS
assert (len < (1L<<16));
for (i = 0; i < (int) len; i++)
add_char (with_char);
#else /* not MSDOS */
for (i = 0; i < len; i++)
add_char (with_char);
#endif /* not MSDOS */
insert ('\n');
close_paragraph ();
filling_enabled = true;
no_indent = old_no_indent;
}

VOID
cm_chapter ()
{
insert_and_underscore ('*');
}

VOID
cm_section ()
{
insert_and_underscore ('=');
}

VOID
cm_subsection ()
{
insert_and_underscore ('-');
}

VOID
cm_subsubsection ()
{
insert_and_underscore ('.');
}

VOID
cm_unnumbered ()
{
cm_chapter ();
}

VOID
cm_unnumberedsec ()
{
cm_section ();
}

VOID
cm_unnumberedsubsec ()
{
cm_subsection ();
}

VOID
cm_unnumberedsubsubsec ()
{
cm_subsubsection ();
}

VOID
cm_appendix ()
{
cm_chapter ();
}

VOID
cm_appendixsec ()
{
cm_section ();
}

VOID
cm_appendixsubsec ()
{
cm_subsection ();
}

VOID
cm_appendixsubsubsec ()
{
cm_subsubsection ();
}

/* **************************************************************** */
/* */
/* Adding nodes, and making tags */
/* */
/* **************************************************************** */

/* Start a new tag table. */
VOID
init_tag_table ()
{
while (tag_table != (TAG_ENTRY *) NULL)
{
TAG_ENTRY *temp = tag_table;
free (temp->node);
free (temp->prev);
free (temp->next);
free (temp->up);
tag_table = tag_table->next_ent;
free (temp);
}
}

write_tag_table ()

{
return (write_tag_table_internal (false)); /* Not indirect. */
}

write_tag_table_indirect ()
{
return (write_tag_table_internal (true));
}

/* Write out the contents of the existing tag table.
INDIRECT_P says how to format the output. */
VOID
write_tag_table_internal (indirect_p)
boolean indirect_p;
{
TAG_ENTRY *node = tag_table;

close_paragraph ();
filling_enabled = false;
add_word_args ("\037\nTag Table:\n%s", indirect_p ? "(Indirect)\n" : "");

while (node != (TAG_ENTRY *) NULL)
{
#ifdef MSDOS
add_word_args ("Node: %s\177%ld\n", node->node, node->position);
#else /* not MSDOS */
add_word_args ("Node: %s\177%d\n", node->node, node->position);
#endif /* not MSDOS */
node = node->next_ent;
}
add_word ("\037\nEnd Tag Table\n");
flush_output ();
}

char *
get_node_token ()
{
char *string;

get_until_in_line (",", &string);

if (curchar () == ',')
input_text_offset++;

canon_white (string);

/* Allow things like @@nodename. */
normalize_node_name (string);

return (string);
}

/* Given a node name in STRING, remove double @ signs, replacing them
with just one. */
VOID
normalize_node_name (string)
char *string;
{
register int i, l = strlen (string);

for (i = 0; i < l; i++)
{
if (string[i] == '@' && string[i + 1] == '@')
{
strncpy (string + i, string + i + 1, l - i);
l--;
}
}
}

/* Look up NAME in the tag table, and return the associated
tag_entry. If the node is not in the table return NULL. */
TAG_ENTRY *
find_node (name)
char *name;
{
TAG_ENTRY *tag = tag_table;

while (tag != (TAG_ENTRY *) NULL)
{
if (stricmp (tag->node, name) == 0)
return (tag);
tag = tag->next_ent;
}
return ((TAG_ENTRY *) NULL);
}

/* Remember NODE and associates. */
VOID
remember_node (node, prev, next, up, position, line_no, no_warn)
char *node, *prev, *next, *up;
LONG position;
int line_no, no_warn;
{
/* Check for existence of this tag already. */
if (validating)
{
register TAG_ENTRY *tag = find_node (node);
if (tag)
{
line_error ("Node `%s' multiply defined (%d is first definition)",
node, tag->line_no);
return;
}
}

/* First, make this the current node. */
current_node = node;

/* Now add it to the list. */
{
TAG_ENTRY *new = (TAG_ENTRY *) xmalloc (sizeof (TAG_ENTRY));
new->node = node;
new->prev = prev;
new->next = next;
new->up = up;
new->position = position;
new->line_no = line_no;
new->filename = node_filename;
new->touched = 0; /* not yet referenced. */
new->flags = 0;
if (no_warn)
new->flags |= NO_WARN;
new->next_ent = tag_table;
tag_table = new;
}
}

/* Here is a structure which associates sectioning commands with
an integer, hopefully to reflect the `depth' of the current
section. */
struct {
char *name;
int level;
} section_alist[] = {
{ "chapter", 1 },
{ "section", 2},
{ "subsec", 3},
{ "subsubsec", 4},
{ "unnumbered", 1},
{ "unnumberedsec", 2},
{ "unnumberedsubsec", 3},
{ "unnumberedsubsubsec", 4},
{ "appendix", 1},
{ "appendixsec", 2},
{ "appendixsubsec", 3},
{ "appendixsubsubsec", 4},
{ (char *)NULL, 0 }
};

/* Return an integer which identifies the type section present in TEXT. */
int
what_section (text)
char *text;
{
int i, j;
char *t;

for (j = 0; text[j] && whitespace (text[j]) || text[j] == '\n'; j++);
if (text[j] != '@')
return (-1);

text = text + j + 1;

/* Handle italicized sectioning commands. */
if (*text == 'i')
text++;

for (i = 0; t = section_alist[i].name; i++)
{
if (strncmp (t, text, strlen (t)) == 0)
return (section_alist[i].level);
}
return (-1);
}

/* The order is: nodename, nextnode, prevnode, upnode.
The next, prev, and up fields can be defaulted.
You must follow a node command which has those fields
defaulted with a sectioning command (e.g. @chapter) giving
the "level" of that node. It is an error not to do so.
The defaults come from the menu in this nodes parent. */
VOID
cm_node ()
{
char *node, *prev, *next, *up;
LONG new_node_pos;
int defaulting, this_section, no_warn = 0;
extern int already_outputting_pending_notes;

if (strcmp (command, "nwnode") == 0)
no_warn = 1;

/* Get rid of unmatched brace arguments from previous commands. */
discard_braces ();

/* There also might be insertions left lying around that haven't been
ended yet. Do that also. */
discard_insertions ();

if (!already_outputting_pending_notes)
{
close_paragraph ();
output_pending_notes ();
free_pending_notes ();
}

filling_enabled = indented_fill = false;
new_node_pos = output_position + 1;

node = get_node_token ();
next = get_node_token ();
prev = get_node_token ();
up = get_node_token ();

this_section = what_section (input_text + input_text_offset);

/* ??? The first \n in the following string shouldn't be there, but I have
to revamp the @example & @group things so that they always leave a \n
as the last character output. Until that time, this is the only way
I can produce reliable output. */
no_indent = true;
add_word_args ("\n\037\nFile: %s, Node: %s", pretty_output_filename, node);

/* Check for defaulting of this node's next, prev, and up fields. */
defaulting = ((strlen (next) == 0) &&
(strlen (prev) == 0) &&
(strlen (up) == 0));

/* If we are defaulting, then look at the immediately following
sectioning command (error if none) to determine the node's
level. Find the node that contains the menu mentioning this node
that is one level up (error if not found). That node is the "Up"
of this node. Default the "Next" and "Prev" from the menu. */
if (defaulting)
{
NODE_REF *last_ref = (NODE_REF *)NULL;
NODE_REF *ref = node_references;

if (this_section < 0)
{
char *polite_section_name = "chapter";
int i;

for (i = 0; section_alist[i].name; i++)
if (section_alist[i].level == current_section + 1)
{
polite_section_name = section_alist[i].name;
break;
}

line_error
("Node `%s' requires a sectioning command (e.g. @%s)",
node, polite_section_name);
}
else
{
while (ref)
{
if (ref->section == (this_section - 1) &&
ref->type == menu_reference &&
stricmp (ref->node, node) == 0)
{
free (up);
up = savestring (ref->containing_node);

if (last_ref &&
strcmp
(last_ref->containing_node, ref->containing_node) == 0)
{
free (next);
next = savestring (last_ref->node);
}

if (ref->next &&
strcmp
(ref->next->containing_node, ref->containing_node) == 0)
{
free (prev);
prev = savestring (ref->next->node);
}
break;
}
last_ref = ref;
ref = ref->next;
}
}
}

if (*next)
add_word_args (", Next: %s", next);

if (*prev)
add_word_args (", Prev: %s", prev);

if (*up)
add_word_args (", Up: %s", up);

insert ('\n');
close_paragraph ();
no_indent = false;

if (!*node)
{
line_error ("No node name specified for `@%s' command", command);
free (node);
free (next);
free (prev);
free (up);
}
else
{
if (!*next) { free (next); next = (char *)NULL; }
if (!*prev) { free (prev); prev = (char *)NULL; }
if (!*up) { free (up); up = (char *)NULL; }
remember_node (node, prev, next, up, new_node_pos, line_number, no_warn);
}

/* Change the section only if there was a sectioning command. */
if (this_section >= 0)
current_section = this_section;

filling_enabled = true;
}

/* Validation of an info file.
Scan through the list of tag entrys touching the Prev, Next, and Up
elements of each. It is an error not to be able to touch one of them,
except in the case of external node references, such as "(DIR)".

If the Prev is different from the Up,
then the Prev node must have a Next pointing at this node.

Every node except Top must have an Up.
The Up node must contain some sort of reference, other than a Next,
to this node.

If the Next is different from the Next of the Up,
then the Next node must have a Prev pointing at this node. */
VOID
validate_file (filename, tag_table)
char *filename;
TAG_ENTRY *tag_table;
{
char *old_input_filename = input_filename;
TAG_ENTRY *tags = tag_table;

while (tags != (TAG_ENTRY *) NULL)
{
register TAG_ENTRY *temp_tag;

input_filename = tags->filename;
line_number = tags->line_no;

/* If this node has a Next, then make sure that the Next exists. */
if (tags->next)
{
validate (tags->next, tags->line_no, "Next");

/* If the Next node exists, and there is no Up, then make
sure that the Prev of the Next points back. */
if (temp_tag = find_node (tags->next))
{
char *prev = temp_tag->prev;
if (!prev || (stricmp (prev, tags->node) != 0))
{
line_error
("Node `%s''s Next field not pointed back to", tags->node);
line_number = temp_tag->line_no;
input_filename = temp_tag->filename;
line_error
("This node (`%s') is the one with the bad `Prev'",
temp_tag->node);
input_filename = tags->filename;
line_number = tags->line_no;
temp_tag->flags |= PREV_ERROR;
}
}
}

/* Validate the Prev field if there is one, and we haven't already
complained about it in some way. You don't have to have a Prev
field at this stage. */
if (!(tags->flags & PREV_ERROR) && tags->prev)
{
int valid = validate (tags->prev, tags->line_no, "Prev");

if (!valid)
tags->flags |= PREV_ERROR;
else
{
/* If the Prev field is not the same as the Up field,
then the node pointed to by the Prev field must have
a Next field which points to this node. */
if (tags->up && (stricmp (tags->prev, tags->up) != 0))
{
temp_tag = find_node (tags->prev);
if (!temp_tag->next ||
(stricmp (temp_tag->next, tags->node) != 0))
{
line_error ("Node `%s''s Prev field not pointed back to",
tags->node);
line_number = temp_tag->line_no;
input_filename = temp_tag->filename;
line_error
("This node (`%s') is the one with the bad `Next'",
temp_tag->node);
input_filename = tags->filename;
line_number = tags->line_no;
temp_tag->flags |= NEXT_ERROR;
}
}
}
}

if (!tags->up && (stricmp (tags->node, "Top") != 0))
line_error ("Node `%s' is missing an \"Up\" field", tags->node);
else if (tags->up)
{
int valid = validate (tags->up, tags->line_no, "Up");

/* If node X has Up: Y, then warn if Y fails to have a menu item
or note pointing at X, if Y isn't of the form "(Y)". */
if (valid && *tags->up != '(')
{
NODE_REF *nref, *tref, *list;
NODE_REF *find_node_reference ();

tref = (NODE_REF *) NULL;
list = node_references;

for (;;)
{
if (!(nref = find_node_reference (tags->node, list)))
break;

if (stricmp (nref->containing_node, tags->up) == 0)
{
if (nref->type != menu_reference)
{
tref = nref;
list = nref->next;
}
else
break;
}
list = nref->next;
}

if (!nref)
{
temp_tag = find_node (tags->up);
line_number = temp_tag->line_no;
filename = temp_tag->filename;
if (!tref)
line_error ("`%s' has an Up field of `%s', but `%s' has no menu item for `%s'",
tags->node, tags->up, tags->up, tags->node);
line_number = tags->line_no;
filename = tags->filename;
}
}
}
tags = tags->next_ent;
}

validate_other_references (node_references);
/* We have told the user about the references which didn't exist.
Now tell him about the nodes which aren't referenced. */

tags = tag_table;
while (tags != (TAG_ENTRY *) NULL)
{
/* Special hack. If the node in question appears to have
been referenced more than REFERENCE_WARNING_LIMIT times,
give a warning. */
if (tags->touched > reference_warning_limit)
{
input_filename = tags->filename;
line_number = tags->line_no;
warning ("Node `%s' has been referenced %d times",
tags->node, tags->touched);
}

if (tags->touched == 0)
{
input_filename = tags->filename;
line_number = tags->line_no;

/* Notice that the node "Top" is special, and doesn't have to
be referenced. */
if (stricmp (tags->node, "Top") != 0)
warning ("Unreferenced node `%s'", tags->node);
}
tags = tags->next_ent;
}
input_filename = old_input_filename;
}

/* Return 1 if tag correctly validated, or 0 if not. */
validate (tag, line, label)
char *tag;
int line;
char *label;
{
TAG_ENTRY *result;

/* If there isn't a tag to verify, or if the tag is in another file,
then it must be okay. */
if (!tag || !*tag || *tag == '(')
return (1);

/* Otherwise, the tag must exist. */
result = find_node (tag);

if (!result)
{
line_number = line;
line_error ("Validation error. `%s' field points to node `%s', which doesn't exist",
label, tag);
return (0);
}
result->touched++;
return (1);
}

/* Split large output files into a series of smaller files. Each file
is pointed to in the tag table, which then gets written out as the
original file. The new files have the same name as the original file
with a "-num" attached. SIZE is the largest number of bytes to allow
in any single split file. */
VOID
split_file (filename, size)
char *filename;
LONG size;
{
char *root_filename, *root_pathname;
char HUGE *the_file;
char *filename_part ();
struct stat fileinfo;
char *the_header;
SIZE_T header_size;

/* Can only do this to files with tag tables. */
if (!tag_table)
return;

if (size == 0)
size = DEFAULT_SPLIT_SIZE;

if ((stat (filename, &fileinfo) != 0) ||
(fileinfo.st_size < SPLIT_SIZE_THRESHOLD))
return;

the_file = find_and_load (filename);
if (!the_file)
return;

root_filename = filename_part (filename);
root_pathname = pathname_part (filename);

if (!root_pathname)
root_pathname = savestring ("");

/* Start splitting the file. Walk along the tag table
outputting sections of the file. When we have written
all of the nodes in the tag table, make the top-level
pointer file, which contains indirect pointers and
tags for the nodes. */
{
int which_file = 1;
TAG_ENTRY *tags = tag_table;
char HUGE *indirect_info = (char HUGE *) 0x00;

/* Remember the `header' of this file. The first tag in the file is
the bottom of the header; the top of the file is the start. */
#ifdef MSDOS
assert (tags->position < (1L<<16));
the_header = xmalloc (1 + (header_size = (size_t) (tags->position - 2)));
#else /* not MSDOS */
the_header = xmalloc (1 + (header_size = (tags->position - 2)));
#endif /* not MSDOS */
bcopy (the_file, the_header, header_size);

while (tags)
{
LONG file_top, file_bot, limit;

/* Have to include the Control-_. */
file_top = file_bot = tags->position - 2;
limit = file_top + size;

/* If the rest of this file is only one node, then
that is the entire subfile. */
if (!tags->next_ent)
{
LONG i = tags->position + 1;
char last_char = the_file[i];

while (i < fileinfo.st_size)
{
if ((the_file[i] == '\037') &&
((last_char == '\n') ||
(last_char == '\014')))
break;
else
last_char = the_file[i];
i++;
}
file_bot = i;
tags = tags->next_ent;
goto write_region;
}

/* Otherwise, find the largest number of nodes that can fit in
this subfile. */
for (; tags; tags = tags->next_ent)
{
if (!tags->next_ent)
{
/* This entry is the last node. Search forward for the end
of this node, and that is the end of this file. */
LONG i = tags->position + 1;
char last_char = the_file[i];

while (i < fileinfo.st_size)
{
if ((the_file[i] == '\037') &&
((last_char == '\n') ||
(last_char == '\014')))
break;
else
last_char = the_file[i];
i++;
}
file_bot = i;

if (file_bot < limit)
{
tags = tags->next_ent;
goto write_region;
}
else
{
/* Here we want to write out everything before the last
node, and then write the last node out in a file
by itself. */
file_bot = tags->position;
goto write_region;
}
}

if (tags->next_ent->position > limit)
{
if ((tags->position) - 2 == file_top)
tags = tags->next_ent;
file_bot = tags->position;
write_region:
{
int fd;
char *split_file = xmalloc (10 + strlen (root_pathname)
+ strlen (root_filename));
#ifdef MSDOS
sprintf (split_file,
"%s%s.%d", root_pathname, root_filename, which_file);
assert (file_bot - file_top < (1L<<16));

if (((fd = open (split_file, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
|| (write (fd, the_header, header_size) != header_size)
|| (write (fd, the_file + file_top,
(size_t) (file_bot - file_top))
!= (size_t) (file_bot - file_top))
|| ((close (fd)) < 0))
#else /* not MSDOS */
sprintf (split_file,
"%s%s-%d", root_pathname, root_filename, which_file);

if (((fd = open (split_file, O_WRONLY | O_TRUNC | O_CREAT, 0666)) < 0)
|| (write (fd, the_header, header_size) != header_size)
|| (write (fd, the_file + file_top, file_bot - file_top)
!= (file_bot - file_top))
|| ((close (fd)) < 0))
#endif /* not MSDOS */
{
perror (split_file);
close (fd);
exit (2);
}

if (!indirect_info)
{
indirect_info = the_file + file_top;
sprintf (indirect_info, "\037\nIndirect:\n");
indirect_info += strlen (indirect_info);
}

#ifdef MSDOS
sprintf (indirect_info, "%s.%d: %ld\n",
root_filename, which_file, file_top);
#else /* not MSDOS */
sprintf (indirect_info, "%s-%d: %d\n",
root_filename, which_file, file_top);
#endif /* not MSDOS */

free (split_file);
indirect_info += strlen (indirect_info);
which_file++;
break;
}
}
}
}

/* We have sucessfully created the subfiles. Now write out the
original again. We must use `output_stream', or
write_tag_table_indirect () won't know where to place the output. */
output_stream = fopen (filename, "w");
if (!output_stream)
{
perror (filename);
exit (2);
}

{
#ifdef MSDOS
size_t distance;
assert ((long) (indirect_info - the_file) < (1L << 16));
distance = (size_t) (long) (indirect_info - the_file);
#else /* not MSDOS */
int distance = indirect_info - the_file;
#endif /* not MSDOS */
fwrite (the_file, 1, distance, output_stream);

/* Inhibit newlines. */
paragraph_is_open = false;

write_tag_table_indirect ();
fclose (output_stream);
free (the_header);
free (the_file);
return;
}
}
}

/* Some menu hacking. This is used to remember menu references while
reading the input file. After the output file has been written, if
validation is on, then we use the contents of NODE_REFERENCES as a
list of nodes to validate. */
char *
reftype_type_string (type)
enum reftype type;
{
switch (type)
{
case menu_reference:
return ("Menu");
case followed_reference:
return ("Followed-Reference");
default:
return ("Internal-bad-reference-type");
}
}

/* Remember this node name for later validation use. */
VOID
remember_node_reference (node, line, type)
char *node;
int line;
enum reftype type;
{
NODE_REF *temp = (NODE_REF *) xmalloc (sizeof (NODE_REF));

temp->next = node_references;
temp->node = savestring (node);
temp->line_no = line;
temp->section = current_section;
temp->type = type;
temp->containing_node = savestring (current_node);
temp->filename = node_filename;

node_references = temp;
}

VOID
validate_other_references (ref_list)
register NODE_REF *ref_list;
{
char *old_input_filename = input_filename;

while (ref_list != (NODE_REF *) NULL)
{
input_filename = ref_list->filename;
validate (ref_list->node, ref_list->line_no,
reftype_type_string (ref_list->type));
ref_list = ref_list->next;
}
input_filename = old_input_filename;
}

/* Find NODE in REF_LIST. */
NODE_REF *
find_node_reference (node, ref_list)
char *node;
register NODE_REF *ref_list;
{
while (ref_list)
{
if (stricmp (node, ref_list->node) == 0)
break;
ref_list = ref_list->next;
}
return (ref_list);
}

VOID
free_node_references ()
{
register NODE_REF *list, *temp;

list = node_references;

while (list)
{
temp = list;
free (list->node);
free (list->containing_node);
list = list->next;
free (temp);
}
node_references = (NODE_REF *) NULL;
}

#define menu_starter "* "
VOID
glean_node_from_menu ()
{
/* This function gets called at the start of every line while inside of
a menu. It checks to see if the line starts with "* ", and if so,
remembers the node reference that this menu refers to.

input_text_offset is at the \n just before the line start. */

SIZE_T i;
LONG orig_offset = input_text_offset;
char *nodename;

if (strncmp (&input_text[input_text_offset + 1],
menu_starter,
strlen (menu_starter)) != 0)
return;
else
input_text_offset += strlen (menu_starter) + 1;

get_until_in_line (":", &nodename);
if (curchar () == ':')
input_text_offset++;
canon_white (nodename);

if (curchar () == ':')
goto save_node;
free (nodename);
get_rest_of_line (&nodename);

/* Special hack: If the nodename follows the menu item name,
then we have to read the rest of the line in order to find
out what the nodename is. But we still have to read the
line later, in order to process any formatting commands that
might be present. So un-count the carriage return that has just
been counted. */
line_number--;

canon_white (nodename);
for (i = 0; i < strlen (nodename); i++)
{
if (nodename[i] == '\t' ||
nodename[i] == '.' ||
nodename[i] == ',')
{
nodename[i] = '\0';
break;
}
}
save_node:
normalize_node_name (nodename);
i = strlen (nodename);
if (i && nodename[i - 1] == ':')
nodename[i - 1] = '\0';

remember_node_reference (nodename, line_number, menu_reference);
free (nodename);
input_text_offset = orig_offset;
}

VOID
cm_menu ()
{
begin_insertion (menu);
}

/* cont'd in makeinfo.e */

/*===(cut here)===*/


/*
* Local Variables:
* mode:C
* ChangeLog:ChangeLog
* End:
*/



  3 Responses to “Category : Miscellaneous Language Source Code
Archive   : MKINF10.ZIP
Filename : MAKEINFO.D

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/