summaryrefslogtreecommitdiff
path: root/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi
Initial commit
Diffstat (limited to 'macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi')
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parser-5556.pm7078
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.c664
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.pm445
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.xs103
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.c940
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.h21
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/close.c419
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.awk159
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.c360
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.txt497
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_ids.h390
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.c177
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.h113
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.c50
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.h29
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.c121
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.h51
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.c226
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.h4
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.c72
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.h28
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/debug.c46
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.c409
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.h3
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.awk48
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.c70
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.h72
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.txt98
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/end_line.c2277
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.c232
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.h14
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/extra.c164
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.c1098
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.h14
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.c334
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.h14
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.c586
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.h24
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.c76
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.h37
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.c747
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.h44
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/menus.c252
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/multitable.c136
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.c1921
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.h211
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/separator.c791
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.c87
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.h24
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.c361
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.h24
-rw-r--r--macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree_types.h133
52 files changed, 22294 insertions, 0 deletions
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parser-5556.pm b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parser-5556.pm
new file mode 100644
index 0000000000..0b1f091bcd
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parser-5556.pm
@@ -0,0 +1,7078 @@
+# Parser.pm: parse texinfo code into a tree.
+#
+# Copyright 2010, 2011, 2012 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License,
+# or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# Original author: Patrice Dumas <pertusus@free.fr>
+# Parts (also from Patrice Dumas) come from texi2html.pl or texi2html.init.
+
+# The organization of the file is the following:
+# module definitions.
+# default parser state. With explanation of the internal structures.
+# initializations, determination of command types.
+# user visible subroutines.
+# internal subroutines, doing the parsing.
+
+package Texinfo::Parser;
+
+# We need the unicode stuff.
+use 5.006;
+use strict;
+
+# debug
+use Carp qw(cluck);
+
+use Data::Dumper;
+
+# to detect if an encoding may be used to open the files
+use Encode;
+
+# for fileparse
+use File::Basename;
+
+#use POSIX qw(setlocale LC_ALL LC_CTYPE LC_MESSAGES);
+
+# commands definitions
+use Texinfo::Common;
+# Error reporting and counting, translation of strings.
+use Texinfo::Report;
+# encoding_alias
+use Texinfo::Encoding;
+
+# to expand file names in @include and similar @-commands
+use Texinfo::Convert::Text;
+# to normalize node name, anchor, float arg, listoffloats and first *ref argument.
+use Texinfo::Convert::NodeNameNormalization;
+# in error messages, and for macro body expansion
+use Texinfo::Convert::Texinfo;
+
+require Exporter;
+use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+@ISA = qw(Exporter Texinfo::Report);
+
+# Items to export into callers namespace by default. Note: do not export
+# names by default without a very good reason. Use EXPORT_OK instead.
+# Do not simply export all your public functions/methods/constants.
+
+# This allows declaration use Texinfo::Parser ':all';
+# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
+# will save memory.
+%EXPORT_TAGS = ( 'all' => [ qw(
+ parser
+ parse_texi_text
+ parse_texi_line
+ parse_texi_file
+ indices_information
+ floats_information
+ internal_references_information
+ labels_information
+ global_commands_information
+ global_informations
+) ] );
+
+@EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+@EXPORT = qw(
+);
+
+$VERSION = '6.6';
+
+sub N__($)
+{
+ return $_[0];
+}
+
+#sub __($$)
+#{
+# my $self = shift;
+# return &{$self->{'gettext'}}(@_);
+#}
+
+# Customization variables obeyed by the Parser, and the default values.
+our %default_customization_values = (
+ 'TEST' => 0,
+ 'DEBUG' => 0, # if >= 10, tree is printed in texi2any.pl after parsing.
+ # If >= 100 tree is printed every line.
+ 'SHOW_MENU' => 1, # if false no menu error related.
+ 'INLINE_INSERTCOPYING' => 0,
+ 'IGNORE_BEFORE_SETFILENAME' => 1,
+ 'MACRO_BODY_IGNORES_LEADING_SPACE' => 0,
+ 'IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME' => 1,
+ 'INPUT_PERL_ENCODING' => undef, # input perl encoding name, set from
+ # @documentencoding in the default case
+ 'INPUT_ENCODING_NAME' => undef, # encoding name normalized as preferred
+ # IANA, set from @documentencoding in the default
+ # case
+ 'CPP_LINE_DIRECTIVES' => 1, # handle cpp like synchronization lines
+ 'MAX_MACRO_CALL_NESTING' => 100000, # max number of nested macro calls
+ 'GLOBAL_COMMANDS' => [], # list of commands registered
+ # This is not used directly, but passed to Convert::Text through
+ # Texinfo::Common::_convert_text_options
+ 'ENABLE_ENCODING' => 1, # output accented and special characters
+ # based on @documentencoding
+ # following are used in Texinfo::Structuring
+ 'TOP_NODE_UP' => '(dir)', # up node of Top node
+ 'SIMPLE_MENU' => 0, # not used in the parser but in structuring
+ 'USE_UP_NODE_FOR_ELEMENT_UP' => 0, # Use node up for Up if there is no
+ # section up.
+);
+
+my %parser_default_configuration = (%Texinfo::Common::default_parser_state_configuration,
+ %default_customization_values);
+
+# the other possible keys for the parser state are:
+#
+# expanded_formats_hash each key comes from expanded_formats value is 1
+# index_names a structure holding the link between index
+# names, prefixes, merged indices,
+# initial value is %index_names in Texinfo::Common.
+# context_stack stack of the contexts, more recent on top.
+# 'line' is added when on a line or
+# block @-command line,
+# 'def' is added instead if on a definition line.
+# 'preformatted' is added in block commands
+# where there is no paragraphs and spaces are kept
+# (format, example, display...)
+# 'rawpreformatted' is added in raw block commands
+# (html, xml, docbook...)
+# 'menu' is added in menu commands
+# 'math', 'footnote', 'caption', 'shortcaption',
+# 'inlineraw' are also added when in those commands
+# conditionals_stack a stack of conditional commands that are expanded.
+# raw_formats_stack a stack of 1 or 0 for raw formats (@html... or
+# @inlineraw), is 0 if within a raw format that is
+# not expanded.
+# macro_stack stack of macros being expanded (more recent first)
+# definfoenclose an hash, key is the command name, value is an array
+# reference with 2 values, beginning and ending.
+# input a stack, with last at bottom. Holds the opened files
+# or text. Pending macro expansion or text expansion
+# is also in that structure.
+# misc_commands the same than %misc_commands in Texinfo::Common,
+# but with index entry commands dynamically added
+# close_paragraph_commands same than %close_paragraph_commands, but with
+# insertcopying removed if INLINE_INSERTCOPYING
+# close_preformatted_commands same than %close_preformatted_commands, but with
+# insertcopying removed if INLINE_INSERTCOPYING
+# no_paragraph_commands the same than %default_no_paragraph_commands
+# below, with index
+# entry commands dynamically added
+# simple_text_commands the same than %simple_text_commands below, but
+# with index entry commands dynamically added
+# current_node last seen node.
+# current_section last seen section.
+# nodes list of nodes.
+# command_index_prefix associate a command name to an index prefix.
+# prefix_to_index_name associate an index prefix to the index name.
+# floats key is the normalized float type, value is an array
+# reference holding all the floats.
+# internal_references an array holding all the internal references.
+
+# set points to the value set when initializing, for
+# configuration items that are not to be overriden
+# by @-commands. For example documentlanguage.
+
+# A line information is an hash reference with the keys:
+# line_nr the line number
+# file_name the file name
+# macro if in a macro expansion, the name of the macro
+#
+# A text fragment information is a 2 element array reference, the first is the
+# text fragment, the second is the line information.
+
+# The input structure is an array, the first is the most recently included
+# file. The last element may be a file if the parsing is done on a file,
+# with parse_texi_file, or simply pending text, if called as parse_texi_text.
+# each element of the array is a hash reference. The key are:
+# pending an array reference containing pending text fragments, either the
+# text given as parse_texi_text or macro expansion text.
+# name file name
+# line_nr current line number in the file
+# fh filehandle for the file
+
+# content is not copied but reference is copied when duplicating a parser.
+my %tree_informations;
+foreach my $tree_information ('values', 'macros', 'explained_commands', 'labels') {
+ $tree_informations{$tree_information} = 1;
+}
+
+# The commands in initialization_overrides are not set in the document if
+# set at the parser initialization.
+my %initialization_overrides = (
+ 'INPUT_ENCODING_NAME' => 1,
+ 'documentlanguage' => 1,
+);
+
+my %no_brace_commands = %Texinfo::Common::no_brace_commands;
+my %misc_commands = %Texinfo::Common::misc_commands;
+my %brace_commands = %Texinfo::Common::brace_commands;
+my %accent_commands = %Texinfo::Common::accent_commands;
+my %context_brace_commands = %Texinfo::Common::context_brace_commands;
+my %block_commands = %Texinfo::Common::block_commands;
+my %block_item_commands = %Texinfo::Common::block_item_commands;
+my %close_paragraph_commands = %Texinfo::Common::close_paragraph_commands;
+my %def_map = %Texinfo::Common::def_map;
+my %def_commands = %Texinfo::Common::def_commands;
+my %def_aliases = %Texinfo::Common::def_aliases;
+my %menu_commands = %Texinfo::Common::menu_commands;
+my %preformatted_commands = %Texinfo::Common::preformatted_commands;
+my %format_raw_commands = %Texinfo::Common::format_raw_commands;
+my %item_container_commands = %Texinfo::Common::item_container_commands;
+my %item_line_commands = %Texinfo::Common::item_line_commands;
+my %deprecated_commands = %Texinfo::Common::deprecated_commands;
+my %root_commands = %Texinfo::Common::root_commands;
+my %sectioning_commands = %Texinfo::Common::sectioning_commands;
+my %command_index_prefix = %Texinfo::Common::command_index_prefix;
+my %command_structuring_level = %Texinfo::Common::command_structuring_level;
+my %ref_commands = %Texinfo::Common::ref_commands;
+my %region_commands = %Texinfo::Common::region_commands;
+my %code_style_commands = %Texinfo::Common::code_style_commands;
+my %in_heading_commands = %Texinfo::Common::in_heading_commands;
+my %explained_commands = %Texinfo::Common::explained_commands;
+my %inline_format_commands = %Texinfo::Common::inline_format_commands;
+my %inline_commands = %Texinfo::Common::inline_commands;
+my %inline_conditional_commands = %Texinfo::Common::inline_conditional_commands;
+my %all_commands = %Texinfo::Common::all_commands;
+
+# equivalence between a @set flag and an @@-command
+my %set_flag_command_equivalent = (
+ 'txicodequoteundirected' => 'codequoteundirected',
+ 'txicodequotebacktick' => 'codequotebacktick',
+# 'txideftypefnnl' => 'deftypefnnewline',
+);
+
+
+# keep line information for those commands.
+my %keep_line_nr_brace_commands = %context_brace_commands;
+foreach my $keep_line_nr_brace_command ('titlefont', 'anchor') {
+ $keep_line_nr_brace_commands{$keep_line_nr_brace_command} = 1;
+}
+foreach my $brace_command (keys (%brace_commands)) {
+ $keep_line_nr_brace_commands{$brace_command} = 1
+ if ($brace_commands{$brace_command} > 1);
+}
+
+my %type_with_paragraph;
+foreach my $type ('before_item', 'text_root', 'document_root',
+ 'brace_command_context') {
+ $type_with_paragraph{$type} = 1;
+}
+
+my %command_ignore_space_after;
+foreach my $command ('anchor', 'hyphenation', 'caption', 'shortcaption') {
+ $command_ignore_space_after{$command} = 1;
+}
+
+my %global_multiple_commands;
+foreach my $global_multiple_command (
+ 'author', 'footnote', 'hyphenation', 'insertcopying', 'printindex',
+ 'subtitle','titlefont', 'listoffloats', 'detailmenu', 'part',
+ keys(%Texinfo::Common::document_settable_at_commands), ) {
+ $global_multiple_commands{$global_multiple_command} = 1;
+}
+
+my %global_unique_commands;
+foreach my $global_unique_command (
+ 'copying', 'settitle',
+ 'shorttitlepage', 'title', 'titlepage', 'top',
+ keys(%Texinfo::Common::document_settable_unique_at_commands), ) {
+ $global_unique_commands{$global_unique_command} = 1;
+}
+
+my %index_names = %Texinfo::Common::index_names;
+
+# index names that cannot be set by the user.
+my %forbidden_index_name = ();
+
+foreach my $name(keys(%index_names)) {
+ foreach my $prefix (@{$index_names{$name}->{'prefix'}}) {
+ $forbidden_index_name{$prefix} = 1;
+ }
+}
+
+foreach my $other_forbidden_index_name ('info','ps','pdf','htm',
+ 'html', 'log','aux','dvi','texi','txi','texinfo','tex','bib') {
+ $forbidden_index_name{$other_forbidden_index_name} = 1;
+}
+
+# @-commands that do not start a paragraph
+my %default_no_paragraph_commands;
+# @-commands that should be at a line beginning
+my %begin_line_commands;
+
+foreach my $command ('node', 'end') {
+ $begin_line_commands{$command} = $command;
+}
+
+foreach my $no_paragraph_command ('titlefont', 'caption', 'shortcaption',
+ 'image', '*', 'hyphenation', 'anchor', 'errormsg') {
+ $default_no_paragraph_commands{$no_paragraph_command} = 1;
+}
+
+foreach my $no_paragraph_command (keys(%misc_commands)) {
+ $default_no_paragraph_commands{$no_paragraph_command} = 1;
+ $begin_line_commands{$no_paragraph_command} = 1;
+}
+
+# verbatiminclude is not said to begin at the beginning of the line
+# in the manual
+foreach my $misc_not_begin_line ('comment', 'c', 'sp', 'refill',
+ 'noindent', 'indent', 'columnfractions',
+ 'tab', 'item', 'headitem', 'verbatiminclude',
+ 'set', 'clear',
+ 'vskip', keys(%in_heading_commands)) {
+ delete $begin_line_commands{$misc_not_begin_line};
+}
+
+my %block_arg_commands;
+foreach my $block_command (keys(%block_commands)) {
+ $begin_line_commands{$block_command} = 1;
+ $default_no_paragraph_commands{$block_command} = 1;
+ $block_arg_commands{$block_command} = 1
+ if ($block_commands{$block_command} ne 'raw');
+# and ! $format_raw_commands{$block_command});
+}
+
+my %close_preformatted_commands = %close_paragraph_commands;
+foreach my $no_close_preformatted('sp') {
+ delete $close_preformatted_commands{$no_close_preformatted};
+}
+# FIXME to close preformated or not to close?
+#foreach my $format_raw_command(keys(%format_raw_commands)) {
+# $close_preformatted_commands{$format_raw_command} = 1;
+#}
+
+# commands that may appear in accents
+my %in_accent_commands = %accent_commands;
+foreach my $brace_command(keys(%brace_commands)) {
+ $in_accent_commands{$brace_command} = 1 if (!$brace_commands{$brace_command});
+}
+foreach my $no_brace_command (keys(%no_brace_commands)) {
+ $in_accent_commands{$no_brace_command} = 1;
+}
+$in_accent_commands{'c'} = 1;
+$in_accent_commands{'comment'} = 1;
+
+# commands that may appear in texts arguments
+my %in_full_text_commands;
+foreach my $command (keys(%brace_commands), keys(%no_brace_commands)) {
+ $in_full_text_commands{$command} = 1;
+}
+foreach my $misc_command_in_full_text('c', 'comment', 'refill', 'noindent',
+ 'indent', 'columnfractions', 'set', 'clear', 'end') {
+ $in_full_text_commands{$misc_command_in_full_text} = 1;
+}
+
+foreach my $out_format (keys(%format_raw_commands)) {
+ $in_full_text_commands{$out_format} = 1;
+}
+delete $in_full_text_commands{'caption'};
+delete $in_full_text_commands{'shortcaption'};
+foreach my $block_command (keys(%block_commands)) {
+ $in_full_text_commands{$block_command} = 1
+ if ($block_commands{$block_command} eq 'conditional');
+}
+
+# commands that may happen on lines where everything is
+# permitted
+my %in_full_line_commands = %in_full_text_commands;
+foreach my $not_in_full_line_commands('noindent', 'indent') {
+ delete $in_full_line_commands{$not_in_full_line_commands};
+}
+
+# commands that may happen on sectioning commands
+my %in_full_line_commands_no_refs = %in_full_line_commands;
+foreach my $not_in_full_line_commands_no_refs ('titlefont',
+ 'anchor', 'footnote', 'verb') {
+ delete $in_full_line_commands_no_refs{$not_in_full_line_commands_no_refs};
+}
+
+# commands that may happen in simple text arguments
+my %in_simple_text_commands = %in_full_line_commands_no_refs;
+foreach my $not_in_simple_text_command('xref', 'ref', 'pxref', 'inforef') {
+ delete $in_simple_text_commands{$not_in_simple_text_command};
+}
+
+# commands that only accept simple text as argument in any context.
+my %simple_text_commands;
+foreach my $misc_command(keys(%misc_commands)) {
+ if ($misc_commands{$misc_command} =~ /^\d+$/
+ or ($misc_commands{$misc_command} eq 'line'
+ and !($sectioning_commands{$misc_command}
+ or $def_commands{$misc_command}))
+ or $misc_commands{$misc_command} eq 'text') {
+ $simple_text_commands{$misc_command} = 1;
+ }
+}
+
+my %full_line_commands_no_refs = (%sectioning_commands,
+ %def_commands);
+
+delete $simple_text_commands{'center'};
+delete $simple_text_commands{'exdent'};
+foreach my $command ('titlefont', 'anchor', 'xref','ref','pxref',
+ 'inforef', 'shortcaption', 'math', 'indicateurl',
+ 'email', 'uref', 'url', 'image', 'abbr', 'acronym',
+ 'dmn', 'ctrl', 'errormsg') {
+ $simple_text_commands{$command} = 1;
+}
+
+# commands that accept full text, but no block or top-level commands
+my %full_text_commands;
+foreach my $brace_command (keys (%brace_commands)) {
+ if ($brace_commands{$brace_command} == 1
+ and !$simple_text_commands{$brace_command}
+ and !$context_brace_commands{$brace_command}
+ and !$accent_commands{$brace_command}) {
+ $full_text_commands{$brace_command} = 1;
+ }
+}
+
+# commands that accept almost the same than in full text, except
+# what do not make sense on a line.
+my %full_line_commands;
+$full_line_commands{'center'} = 1;
+$full_line_commands{'exdent'} = 1;
+$full_line_commands{'item'} = 1;
+$full_line_commands{'itemx'} = 1;
+
+# Fill the valid nestings hash. All commands not in that hash
+# are considered to accept anything within. There are additional
+# context tests, to make sure, for instance that we are testing
+# @-commands on the block, misc or node @-command line and not
+# in the content.
+# index entry commands are dynamically set as in_simple_text_commands
+my %default_valid_nestings;
+
+foreach my $command (keys(%accent_commands)) {
+ $default_valid_nestings{$command} = \%in_accent_commands;
+}
+foreach my $command (keys(%full_text_commands)) {
+ $default_valid_nestings{$command} = \%in_full_text_commands;
+}
+foreach my $command (keys(%simple_text_commands)) {
+ $default_valid_nestings{$command} = \%in_simple_text_commands;
+}
+foreach my $command (keys(%full_line_commands)) {
+ $default_valid_nestings{$command} = \%in_full_line_commands;
+}
+foreach my $command (keys(%full_line_commands_no_refs)) {
+ $default_valid_nestings{$command} = \%in_full_line_commands_no_refs;
+}
+# Only for block commands with line arguments
+foreach my $command (keys(%block_commands)) {
+ if ($block_commands{$command} and $block_commands{$command} ne 'raw'
+ and $block_commands{$command} ne 'conditional'
+ and !$def_commands{$command}) {
+ $default_valid_nestings{$command} = \%in_simple_text_commands;
+ }
+}
+
+
+my @preformatted_contexts = ('preformatted', 'rawpreformatted');
+my %preformatted_contexts;
+foreach my $preformatted_context (@preformatted_contexts) {
+ $preformatted_contexts{$preformatted_context} = 1;
+}
+
+# contexts on the context_stack stack where empty line don't trigger
+# paragraph
+my %no_paragraph_contexts;
+foreach my $no_paragraph_context ('math', 'menu', @preformatted_contexts,
+ 'def', 'inlineraw') {
+ $no_paragraph_contexts{$no_paragraph_context} = 1;
+};
+
+
+
+# Format a bug message
+sub _bug_message($$;$$)
+{
+ my $self = shift;
+ my $message = shift;
+ my $line_number = shift;
+ my $current = shift;
+
+ my $line_message = '';
+ if ($line_number) {
+ my $file = $line_number->{'file_name'};
+ $line_message
+ = "last location: $line_number->{'file_name'}:$line_number->{'line_nr'}";
+ if ($line_number->{'macro'} ne '') {
+ $line_message .= " (possibly involving $line_number->{'macro'})";
+ }
+ $line_message .= "\n";
+ }
+ my $message_context_stack = "context_stack: (@{$self->{'context_stack'}})\n";
+ my $current_element_message = '';
+ if ($current) {
+ $current_element_message = "current: ". _print_current($current);
+ }
+ warn "You found a bug: $message\n\n".
+ "Additional informations:\n".
+ $line_message.$message_context_stack.$current_element_message;
+
+}
+
+# simple deep copy of a structure
+sub _deep_copy($)
+{
+ my $struct = shift;
+ my $string = Data::Dumper->Dump([$struct], ['struct']);
+ eval $string;
+ return $struct;
+}
+
+# return true if effect of global commands should be ignored.
+sub _ignore_global_commands($)
+{
+ my $self = shift;
+ return !$self->{'expanded_formats_stack'}->[-1];
+}
+
+# enter all the commands associated with an index name using the prefix
+# list
+sub _register_index_commands($$)
+{
+ my $self = shift;
+ my $index_name = shift;
+ if (!$self->{'index_names'}->{$index_name}->{'prefix'}) {
+ $self->{'index_names'}->{$index_name}->{'prefix'} = [$index_name];
+ }
+ if (!exists($self->{'index_names'}->{$index_name}->{'name'})) {
+ $self->{'index_names'}->{$index_name}->{'name'} = $index_name;
+ }
+ if (!exists($self->{'index_names'}->{$index_name}->{'contained_indices'})) {
+ $self->{'index_names'}->{$index_name}->{'contained_indices'}->{$index_name} = 1;
+ }
+ foreach my $prefix (@{$self->{'index_names'}->{$index_name}->{'prefix'}}) {
+ $self->{'misc_commands'}->{$prefix.'index'} = 'line';
+ $self->{'no_paragraph_commands'}->{$prefix.'index'} = 1;
+ $self->{'valid_nestings'}->{$prefix.'index'} = \%in_simple_text_commands;
+ $self->{'command_index_prefix'}->{$prefix.'index'} = $prefix;
+ $self->{'prefix_to_index_name'}->{$prefix} = $index_name;
+ }
+}
+
+# initialization entry point. Set up a parser.
+# The last argument, optional, is a hash provided by the user to change
+# the default values for what is present in %parser_default_configuration.
+# The exact arguments of the function depend on how it was called,
+# in a object oriented way or not.
+sub parser(;$$)
+{
+ my $class = shift;
+ my $conf;
+
+ my $parser = _deep_copy(\%parser_default_configuration);
+ # _deep_copy doesn't handle subs
+ $parser->{'gettext'} = $parser_default_configuration{'gettext'};
+ $parser->{'pgettext'} = $parser_default_configuration{'pgettext'};
+
+ # called not object-oriented
+ if (ref($class) eq 'HASH') {
+ #print STDERR "Not oo\n"
+ $conf = $class;
+ bless $parser;
+
+ } elsif (ref($class)) {
+ # called on an existing parser, interpreted as a duplication
+ my $old_parser = $class;
+ $class = ref($class);
+ foreach my $key (keys(%parser_default_configuration)) {
+ if ($tree_informations{$key}) {
+ if (defined($old_parser->{$key})) {
+ foreach my $info_key (keys(%{$old_parser->{$key}})) {
+ $parser->{$key}->{$info_key}
+ = $old_parser->{$key}->{$info_key};
+ }
+ }
+ } else {
+ $parser->{$key} = _deep_copy($old_parser->{$key});
+ }
+ }
+ #$parser = _deep_copy($old_parser);
+ $parser->{'gettext'} = $old_parser->{'gettext'};
+ $parser->{'pgettext'} = $old_parser->{'pgettext'};
+ bless $parser, $class;
+ $conf = shift;
+
+ } elsif (defined($class)) {
+ bless $parser, $class;
+ $conf = shift;
+ } else {
+ bless $parser;
+ $conf = shift;
+ }
+
+ if (defined($conf)) {
+ foreach my $key (keys(%$conf)) {
+ if (exists($parser_default_configuration{$key})) {
+ if (ref($conf->{$key}) ne 'CODE' and $key ne 'values') {
+ $parser->{$key} = _deep_copy($conf->{$key});
+ } else {
+ $parser->{$key} = $conf->{$key};
+ }
+ if ($initialization_overrides{$key}) {
+ $parser->{'set'}->{$key} = $parser->{$key};
+ }
+ } else {
+ warn "$key not a possible customization in Texinfo::Parser::parser\n";
+ }
+ }
+ }
+ #foreach my $value (keys %{$parser->{'values'}}) {
+ # print STDERR " -> $value $parser->{'values'}->{$value}\n";
+ #}
+ # Now initialize command hash that are dynamically modified, notably
+ # those for index commands, and lists, based on defaults and user provided.
+ $parser->{'misc_commands'} = _deep_copy (\%misc_commands);
+ $parser->{'valid_nestings'} = _deep_copy (\%default_valid_nestings);
+ $parser->{'no_paragraph_commands'} = { %default_no_paragraph_commands };
+ $parser->{'index_names'} = _deep_copy (\%index_names);
+ $parser->{'command_index_prefix'} = {%command_index_prefix};
+ $parser->{'close_paragraph_commands'} = {%close_paragraph_commands};
+ $parser->{'close_preformatted_commands'} = {%close_preformatted_commands};
+ if ($parser->{'INLINE_INSERTCOPYING'}) {
+ delete $parser->{'close_paragraph_commands'}->{'insercopying'};
+ delete $parser->{'close_preformatted_commands'}->{'insercopying'};
+ }
+ # a hash is simply concatenated. It should be like %index_names.
+ if (ref($parser->{'indices'}) eq 'HASH') {
+ %{$parser->{'index_names'}} = (%{$parser->{'index_names'}},
+ %{$parser->{'indices'}});
+ } else { # an array holds index names defined with @defindex
+ foreach my $name (@{$parser->{'indices'}}) {
+ $parser->{'index_names'}->{$name} = {'in_code' => 0};
+ }
+ }
+ foreach my $index (keys (%{$parser->{'index_names'}})) {
+ $parser->_register_index_commands($index);
+ }
+ if ($parser->{'merged_indices'}) {
+ foreach my $index_from (keys (%{$parser->{'merged_indices'}})) {
+ my $index_to = $parser->{'merged_indices'}->{$index_from};
+ if (defined($parser->{'index_names'}->{$index_from})
+ and defined($parser->{'index_names'}->{$index_to})) {
+ $parser->{'index_names'}->{$index_from}->{'merged_in'} = $index_to;
+ $parser->{'index_names'}->{$index_to}->{'contained_indices'}->{$index_from} = 1;
+ }
+ }
+ }
+ foreach my $explained_command(keys(%explained_commands)) {
+ if (!defined($parser->{'explained_commands'}->{$explained_command})) {
+ $parser->{'explained_commands'}->{$explained_command} = {};
+ }
+ }
+ $parser->{'context_stack'} = [ $parser->{'context'} ];
+ $parser->{'regions_stack'} = [];
+ $parser->{'macro_stack'} = [];
+ $parser->{'conditionals_stack'} = [];
+ $parser->{'expanded_formats_stack'} = [1];
+
+ # turn the array to a hash for speed. Not sure it really matters for such
+ # a small array.
+ foreach my $expanded_format(@{$parser->{'expanded_formats'}}) {
+ $parser->{'expanded_formats_hash'}->{$expanded_format} = 1;
+ }
+
+ %{$parser->{'global_commands'}} = %global_multiple_commands;
+
+ foreach my $global_command (@{$parser->{'GLOBAL_COMMANDS'}}) {
+ $parser->{'global_commands'}->{$global_command} = 1;
+ }
+
+ $parser->Texinfo::Report::new;
+
+ return $parser;
+}
+
+sub get_conf($$)
+{
+ my $self = shift;
+ my $var = shift;
+ return $self->{$var};
+}
+
+# split a scalar text in an array lines.
+sub _text_to_lines($)
+{
+ my $text = shift;
+ die if (!defined($text));
+ my $had_final_end_line = chomp($text);
+ my $lines = [ map {$_."\n"} split (/\n/, $text, -1) ];
+ $lines = [''] if (!@$lines);
+ chomp($lines->[-1]) unless ($had_final_end_line);
+ return $lines;
+}
+
+# construct a text fragments array matching a lines array, based on information
+# supplied.
+# If $fixed_line_number is set the line number is not increased, otherwise
+# it is increased, beginning at $first_line.
+sub _complete_line_nr($$;$$$)
+{
+ my $lines = shift;
+ my $first_line = shift;
+ my $file = shift;
+ my $macro = shift;
+ my $fixed_line_number = shift;
+
+ $macro = '' if (!defined($macro));
+ $file = '' if (!defined($file));
+ my $new_lines = [];
+
+ if (defined($first_line)) {
+ my $line_index = $first_line;
+ foreach my $index(0..scalar(@$lines)-1) {
+ $line_index = $index+$first_line if (!$fixed_line_number);
+ $new_lines->[$index] = [ $lines->[$index],
+ { 'line_nr' => $line_index,
+ 'file_name' => $file, 'macro' => $macro } ];
+ }
+ } else {
+ foreach my $line (@$lines) {
+ push @$new_lines, [ $line ];
+ }
+ }
+ return $new_lines;
+}
+
+# entry point for text fragments.
+# Used in tests.
+# Note that it has no associated root type a opposed to parse_texi_line
+# and parse_texi_file.
+sub parse_texi_text($$;$$$$)
+{
+ my $self = shift;
+ my $text = shift;
+ my $lines_nr = shift;
+ my $file = shift;
+ my $macro = shift;
+ my $fixed_line_number = shift;
+
+ return undef if (!defined($text));
+
+ my $lines_array = [];
+ if (!ref($text)) {
+ $text = _text_to_lines($text);
+ }
+ $lines_nr = [] if (!defined($lines_nr));
+ if (!ref($lines_nr)) {
+ #$file =~ s/^.*\/// if (defined($file) and $self->{'TEST'});
+ $lines_array = _complete_line_nr($text, $lines_nr, $file,
+ $macro, $fixed_line_number);
+ } else {
+ while (@$text) {
+ my $line_nr = shift @$lines_nr;
+ my $line = shift @$text;
+ push @$lines_array, [$line, $line_nr];
+ }
+ }
+
+ $self = parser() if (!defined($self));
+ $self->{'input'} = [{'pending' => $lines_array}];
+ my $tree = $self->_parse_texi();
+ return $tree;
+}
+
+# Not used for now, as a @contents after the first sectioning command
+# is correct if not using TeX.
+sub _check_contents_location($$)
+{
+ my $self = shift;
+ my $tree = shift;
+
+ my $commands = $self->global_commands_information();
+ return unless ($commands);
+ # Find the last sectioning command
+ my $index = -1;
+ my %ending_root_commands;
+ my $found = 0;
+ while ($tree->{'contents'}->[$index]) {
+ if (defined($tree->{'contents'}->[$index]->{'cmdname'})) {
+ $ending_root_commands{$tree->{'contents'}->[$index]} = 1;
+ if ($sectioning_commands{$tree->{'contents'}->[$index]->{'cmdname'}}) {
+ $found = 1;
+ last;
+ }
+ }
+ $index--;
+ }
+ return if (!$found);
+
+ #print STDERR "ending_root_commands ".join('|',keys(%ending_root_commands))."\n";
+ #print STDERR "tree contents: ".join('|', @{$tree->{'contents'}})."\n";
+ foreach my $command ('contents', 'shortcontents', 'summarycontents') {
+ if ($commands->{$command}) {
+ foreach my $current (@{$commands->{$command}}) {
+ my $root_command = $self->Texinfo::Common::find_parent_root_command($current);
+ #print STDERR "root_command for $current->{'cmdname'}: $root_command\n";
+ if (defined($root_command)
+ and !$ending_root_commands{$root_command}) {
+ $self->line_warn(sprintf($self->__(
+ "\@%s should only appear at beginning or end of document"),
+ $current->{'cmdname'}), $current->{'line_nr'});
+ }
+ }
+ }
+ }
+}
+
+# parse a texi file
+sub parse_texi_file($$)
+{
+ my $self = shift;
+ my $file_name = shift;
+
+ my $filehandle = do { local *FH };
+ if (! open($filehandle, $file_name)) {
+ $self->document_error(sprintf($self->__("could not open %s: %s"),
+ $file_name, $!));
+ return undef;
+ }
+ my $line_nr = 0;
+ my $line;
+ my @first_lines;
+
+ my $pending_first_texi_line;
+ # the first line not empty and not with \input is kept in
+ # $pending_first_texi_line and put in the pending lines just below
+ while ($line = <$filehandle>) {
+ $line_nr++;
+ if ($line =~ /^ *\\input/ or $line =~ /^\s*$/) {
+ $line =~ s/\x{7F}.*\s*//;
+ push @first_lines, $line;
+ } else {
+ $pending_first_texi_line = $line;
+ last;
+ }
+ }
+ my $root = { 'contents' => [], 'type' => 'text_root' };
+ if (@first_lines) {
+ push @{$root->{'contents'}}, { 'type' => 'preamble', 'contents' => [] };
+ foreach my $line (@first_lines) {
+ push @{$root->{'contents'}->[-1]->{'contents'}},
+ { 'text' => $line,
+ 'type' => 'preamble_text' };
+ }
+ }
+ my ($directories, $suffix);
+ ($file_name, $directories, $suffix) = fileparse($file_name)
+ if ($self->{'TEST'});
+ $self = parser() if (!defined($self));
+ $self->{'input'} = [{
+ 'pending' => [[$pending_first_texi_line, {'line_nr' => $line_nr,
+ 'macro' => '', 'file_name' => $file_name}]],
+ 'name' => $file_name,
+ 'line_nr' => $line_nr,
+ 'fh' => $filehandle
+ }];
+ $self->{'info'}->{'input_file_name'} = $file_name;
+ my $tree = $self->_parse_texi($root);
+
+ # Find 'text_root', which contains everything before first node/section.
+ # if there are elements, 'text_root' is the first content, otherwise it
+ # is the root.
+ my $text_root;
+ if ($tree->{'type'} eq 'text_root') {
+ $text_root = $tree;
+ } elsif ($tree->{'contents'} and $tree->{'contents'}->[0]
+ and $tree->{'contents'}->[0]->{'type'} eq 'text_root') {
+ $text_root = $tree->{'contents'}->[0];
+ }
+
+ # Put everything before @setfilename in a special type. This allows to
+ # ignore everything before @setfilename.
+ if ($self->{'IGNORE_BEFORE_SETFILENAME'} and $text_root and
+ $self->{'extra'} and $self->{'extra'}->{'setfilename'}
+ and $self->{'extra'}->{'setfilename'}->{'parent'} eq $text_root) {
+ my $before_setfilename = {'type' => 'preamble_before_setfilename',
+ 'parent' => $text_root,
+ 'contents' => []};
+ while ($text_root->{'contents'}->[0] ne $self->{'extra'}->{'setfilename'}) {
+ my $content = shift @{$text_root->{'contents'}};
+ $content->{'parent'} = $before_setfilename;
+ push @{$before_setfilename->{'contents'}}, $content;
+ }
+ unshift (@{$text_root->{'contents'}}, $before_setfilename)
+ if (@{$before_setfilename->{'contents'}});
+ }
+ #$self->_check_contents_location($tree);
+
+ return $tree;
+}
+
+sub parse_texi_line($$;$$$$)
+{
+ my $self = shift;
+ my $text = shift;
+ my $lines_nr = shift;
+ my $file = shift;
+ my $macro = shift;
+ my $fixed_line_number = shift;
+
+ return undef if (!defined($text));
+
+ if (!ref($text)) {
+ $text = _text_to_lines($text);
+ }
+ #$file =~ s/^.*\/// if (defined($file) and $self->{'TEST'});
+ my $lines_array = _complete_line_nr($text, $lines_nr, $file,
+ $macro, $fixed_line_number);
+
+ $self = parser() if (!defined($self));
+ $self->{'input'} = [{'pending' => $lines_array}];
+ my $tree = $self->_parse_texi({'contents' => [], 'type' => 'root_line'});
+ return $tree;
+}
+
+# return indices informations
+sub indices_information($)
+{
+ my $self = shift;
+ return ($self->{'index_names'}, $self->{'merged_indices'});
+ #return ($self->{'index_names'}, $self->{'merged_indices'}, $self->{'index_entries'});
+}
+
+sub floats_information($)
+{
+ my $self = shift;
+ return $self->{'floats'};
+}
+
+sub internal_references_information($)
+{
+ my $self = shift;
+ return $self->{'internal_references'};
+}
+
+sub global_commands_information($)
+{
+ my $self = shift;
+ return $self->{'extra'};
+}
+
+# @ dircategory_direntry
+# @ unassociated_menus
+# perl_encoding
+# input_encoding_name
+# input_file_name
+sub global_informations($)
+{
+ my $self = shift;
+ return $self->{'info'};
+}
+
+sub labels_information($)
+{
+ my $self = shift;
+ return $self->{'labels'};
+}
+
+# Following are the internal subroutines. The most important are
+# _parse_texi: the main parser loop.
+# _end_line: called at an end of line. Handling of @include lines is
+# done here.
+# _next_text: present the next text fragment, from pending text or line,
+# as described above.
+
+# for debugging
+sub _print_current($)
+{
+ my $current = shift;
+ return Texinfo::Common::_print_current($current);
+}
+
+# for debugging
+sub _print_command_args_texi($)
+{
+ my $current = shift;
+ return '' if (!$current->{'cmdname'});
+ my $args = '';
+ my $with_brace;
+ if ($current->{'args'} and @{$current->{'args'}}) {
+ $with_brace
+ = ($current->{'args'}->[0]->{'type'} eq 'brace_command_arg'
+ or $current->{'args'}->[0]->{'type'} eq 'brace_command_context');
+ $args .= '{' if ($with_brace);
+ foreach my $arg (@{$current->{'args'}}) {
+ $args .= Texinfo::Convert::Texinfo::convert($arg).', ';
+ }
+ $args =~ s/, $//;
+ }
+ chomp($args);
+ if ($with_brace) {
+ $args .= '}';
+ }
+ return '@'.$current->{'cmdname'} .$args."\n";
+}
+
+sub _print_current_keys($)
+{
+ my $current = shift;
+ my $string = _print_current($current);
+ foreach my $key (keys (%$current)) {
+ $string .= " $key: $current->{$key}\n";
+ }
+ if ($current->{'extra'}) {
+ $string .= " EXTRA\n";
+ foreach my $key (keys (%{$current->{'extra'}})) {
+ $string .= " $key: $current->{'extra'}->{$key}\n";
+ }
+ }
+ return $string;
+}
+
+# For debugging
+my @kept_keys = ('contents', 'cmdname', 'type', 'text', 'args');
+my %kept_keys;
+foreach my $key (@kept_keys) {
+ $kept_keys{$key} = 1;
+}
+sub _filter_print_keys { [grep {$kept_keys{$_}} ( sort keys %{$_[0]} )] };
+sub _print_tree($)
+{
+ my $tree = shift;
+ local $Data::Dumper::Sortkeys = \&_filter_print_keys;
+ local $Data::Dumper::Purity = 1;
+ local $Data::Dumper::Indent = 1;
+
+ return Data::Dumper->Dump([$tree]);
+}
+
+sub _register_global_command($$$$)
+{
+ my $self = shift;
+ my $command = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ if ($command eq 'summarycontents' and !$self->{'global_commands'}->{$command}) {
+ $command = 'shortcontents';
+ }
+ if ($self->{'global_commands'}->{$command} and $command ne 'author') {
+ push @{$self->{'extra'}->{$command}}, $current
+ unless (_ignore_global_commands($self));
+ $current->{'line_nr'} = $line_nr if (!$current->{'line_nr'});
+ return 1;
+ } elsif ($global_unique_commands{$command}) {
+ # setfilename ignored in an included file
+ $current->{'line_nr'} = $line_nr if (!$current->{'line_nr'});
+ if ($command eq 'setfilename'
+ and scalar(@{$self->{'input'}}) > 1) {
+ } elsif (exists ($self->{'extra'}->{$current->{'cmdname'}})) {
+ $self->line_warn(sprintf($self->__('multiple @%s'),
+ $current->{'cmdname'}), $line_nr);
+ } else {
+ $self->{'extra'}->{$current->{'cmdname'}} = $current
+ unless (_ignore_global_commands($self));
+ }
+ return 1;
+ }
+ return 0;
+}
+
+# parse a @macro line
+sub _parse_macro_command_line($$$$$;$)
+{
+ my $self = shift;
+ my $command = shift;
+ my $line = shift;
+ my $parent = shift;
+ my $line_nr = shift;
+ my $macro = { 'cmdname' => $command, 'parent' => $parent, 'contents' => [],
+ 'extra' => {'arg_line' => $line}, 'line_nr' => $line_nr };
+ # REMACRO
+ if ($line =~ /^\s+([[:alnum:]][[:alnum:]-]*)\s*(.*)/) {
+ my $macro_name = $1;
+ my $args_def = $2;
+ my @args;
+
+ if ($args_def =~ s/^\s*{\s*(.*?)\s*}\s*//) {
+ @args = split(/\s*,\s*/, $1);
+ }
+
+ # accept an @-command after the arguments in case there is a @c or
+ # @comment
+ if ($args_def =~ /^\s*[^\@]/) {
+ $self->line_error(sprintf($self->__("bad syntax for \@%s argument: %s"),
+ $command, $args_def),
+ $line_nr);
+ $macro->{'extra'}->{'invalid_syntax'} = 1;
+ }
+ print STDERR "MACRO \@$command $macro_name\n" if ($self->{'DEBUG'});
+
+ $macro->{'args'} = [
+ { 'type' => 'macro_name', 'text' => $macro_name,
+ 'parent' => $macro } ];
+ my $index = 0;
+ foreach my $formal_arg (@args) {
+ push @{$macro->{'args'}},
+ { 'type' => 'macro_arg', 'text' => $formal_arg,
+ 'parent' => $macro};
+ if ($formal_arg !~ /^[\w\-]+$/) {
+ $self->line_error(sprintf($self->__("bad or empty \@%s formal argument: %s"),
+ $command, $formal_arg), $line_nr);
+ $macro->{'extra'}->{'invalid_syntax'} = 1;
+ }
+ $macro->{'extra'}->{'args_index'}->{$formal_arg} = $index;
+ $index++;
+ }
+ } elsif ($line !~ /\S/) {
+ $self->line_error(sprintf($self->
+ __("%c%s requires a name"), ord('@'), $command), $line_nr);
+ $macro->{'extra'}->{'invalid_syntax'} = 1;
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad name for \@%s"), $command), $line_nr);
+ $macro->{'extra'}->{'invalid_syntax'} = 1;
+ }
+ return $macro;
+}
+
+# start a paragraph if in a context where paragraphs are to be started.
+sub _begin_paragraph($$;$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+
+ # !$current->{'type'} is true for @-commands. In fact it is unclear
+ # that there may be cases of !$current->{'type'} and !$current->{'cmdname'}
+ if ((!$current->{'type'} or $type_with_paragraph{$current->{'type'}})
+ and !$no_paragraph_contexts{$self->{'context_stack'}->[-1]}) {
+ if (!defined($current->{'contents'})) {
+ $self->_bug_message("contents undef", $line_nr, $current);
+ die;
+ }
+
+ # find whether an @indent precedes the paragraph
+ my $indent;
+ if (scalar(@{$current->{'contents'}})) {
+ my $index = scalar(@{$current->{'contents'}}) -1;
+ while ($index >= 0
+ and !($current->{'contents'}->[$index]->{'type'}
+ and ($current->{'contents'}->[$index]->{'type'} eq 'empty_line'
+ or $current->{'contents'}->[$index]->{'type'} eq 'paragraph'))
+ and !($current->{'contents'}->[$index]->{'cmdname'}
+ and $self->{'close_paragraph_commands'}->{$current->{'contents'}->[$index]->{'cmdname'}})) {
+ if ($current->{'contents'}->[$index]->{'cmdname'}
+ and ($current->{'contents'}->[$index]->{'cmdname'} eq 'indent'
+ or $current->{'contents'}->[$index]->{'cmdname'} eq 'noindent')) {
+ $indent = $current->{'contents'}->[$index]->{'cmdname'};
+ last;
+ }
+ $index--;
+ }
+ }
+ push @{$current->{'contents'}},
+ { 'type' => 'paragraph', 'parent' => $current, 'contents' => [] };
+ $current->{'contents'}->[-1]->{'extra'}->{$indent} = 1 if ($indent);
+ $current = $current->{'contents'}->[-1];
+ print STDERR "PARAGRAPH\n" if ($self->{'DEBUG'});
+ return $current;
+ }
+ return 0;
+}
+
+sub _begin_preformatted($)
+{
+ my $self = shift;
+ my $current = shift;
+ if ($preformatted_contexts{$self->{'context_stack'}->[-1]}) {
+ push @{$current->{'contents'}},
+ { 'type' => $self->{'context_stack'}->[-1],
+ 'parent' => $current, 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ print STDERR "PREFORMATTED $self->{'context_stack'}->[-1]\n" if ($self->{'DEBUG'});
+ }
+ return $current;
+}
+
+# wrapper around line_warn. Set line_nr to be the line_nr of the command,
+# corresponding to the opening of the command. Call line_warn with
+# sprintf if needed.
+sub _command_warn($$$$;@)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $message = shift;
+
+ if ($current->{'line_nr'}) {
+ $line_nr = $current->{'line_nr'};
+ }
+ if (@_) {
+ $self->line_warn(sprintf($message, @_), $line_nr);
+ } else {
+ $self->line_warn($message, $line_nr);
+ }
+}
+
+sub _command_error($$$$;@)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $message = shift;
+
+ # use the beginning of the @-command for the error message
+ # line number if available.
+ # FIXME line_nr currently not registered for regular brace commands
+ if ($current->{'line_nr'}) {
+ $line_nr = $current->{'line_nr'};
+ }
+ if (@_) {
+ $self->line_error(sprintf($message, @_), $line_nr);
+ } else {
+ $self->line_error($message, $line_nr);
+ }
+}
+
+# currently doesn't do much more than
+# return $_[1]->{'parent'}
+sub _close_brace_command($$$;$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $closed_command = shift;
+ my $interrupting_command = shift;
+
+ if ($current->{'cmdname'} ne 'verb' or $current->{'type'} eq '') {
+ if (defined($closed_command)) {
+ $self->_command_error($current, $line_nr,
+ $self->__("\@end %s seen before \@%s closing brace"),
+ $closed_command, $current->{'cmdname'});
+ } elsif (defined($interrupting_command)) {
+ $self->_command_error($current, $line_nr,
+ $self->__("\@%s seen before \@%s closing brace"),
+ $interrupting_command, $current->{'cmdname'});
+
+ } else {
+ $self->_command_error($current, $line_nr,
+ $self->__("%c%s missing close brace"), ord('@'), $current->{'cmdname'});
+ }
+ } else {
+ $self->_command_error($current, $line_nr,
+ $self->__("\@%s missing closing delimiter sequence: %s}"),
+ $current->{'cmdname'}, $current->{'type'});
+ }
+ $current = $current->{'parent'};
+ return $current;
+}
+
+sub _in_code($$)
+{
+ my $self = shift;
+ my $current = shift;
+
+ while ($current->{'parent'} and $current->{'parent'}->{'cmdname'}
+ and exists $brace_commands{$current->{'parent'}->{'cmdname'}}
+ and !exists $context_brace_commands{$current->{'parent'}->{'cmdname'}}) {
+ return 1 if ($code_style_commands{$current->{'parent'}->{'cmdname'}});
+ $current = $current->{'parent'}->{'parent'};
+ }
+ return 0;
+}
+
+# close brace commands, that don't set a new context (ie @caption, @footnote)
+sub _close_all_style_commands($$$;$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $closed_command = shift;
+ my $interrupting_command = shift;
+
+ while ($current->{'parent'} and $current->{'parent'}->{'cmdname'}
+ and exists $brace_commands{$current->{'parent'}->{'cmdname'}}
+ and !exists $context_brace_commands{$current->{'parent'}->{'cmdname'}}) {
+ $current = _close_brace_command($self, $current->{'parent'}, $line_nr,
+ $closed_command, $interrupting_command);
+ }
+ return $current;
+}
+
+# close brace commands except for @caption, @footnote then the paragraph
+sub _end_paragraph($$$;$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $closed_command = shift;
+ my $interrupting_command = shift;
+
+ $current = _close_all_style_commands($self, $current, $line_nr,
+ $closed_command, $interrupting_command);
+ if ($current->{'type'} and $current->{'type'} eq 'paragraph') {
+ print STDERR "CLOSE PARA\n" if ($self->{'DEBUG'});
+ $current = $current->{'parent'};
+ }
+ return $current;
+}
+
+# close brace commands except for @caption, @footnote then the preformatted
+sub _end_preformatted($$$;$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $closed_command = shift;
+ my $interrupting_command = shift;
+
+ $current = _close_all_style_commands($self, $current, $line_nr,
+ $closed_command, $interrupting_command);
+ if ($current->{'type'} and $preformatted_contexts{$current->{'type'}}) {
+ print STDERR "CLOSE PREFORMATTED $current->{'type'}\n" if ($self->{'DEBUG'});
+ # completly remove void preformatted contexts
+ if (!@{$current->{'contents'}}) {
+ my $removed = pop @{$current->{'parent'}->{'contents'}};
+ print STDERR "popping $removed->{'type'}\n" if ($self->{'DEBUG'});
+ }
+ $current = $current->{'parent'};
+ }
+ return $current;
+}
+
+# check that there are no text holding environment (currently
+# checking only paragraphs and preformatted) in contents
+sub _check_no_text($)
+{
+ my $current = shift;
+ my $after_paragraph = 0;
+ foreach my $content (@{$current->{'contents'}}) {
+ if ($content->{'type'} and $content->{'type'} eq 'paragraph') {
+ $after_paragraph = 1;
+ last;
+ } elsif ($content->{'type'} and $preformatted_contexts{$content->{'type'}}) {
+ foreach my $preformatted_content (@{$content->{'contents'}}) {
+ if ((defined($preformatted_content->{'text'})
+ and $preformatted_content->{'text'} =~ /\S/)
+ or ($preformatted_content->{'cmdname'}
+ and ($preformatted_content->{'cmdname'} ne 'c'
+ and $preformatted_content->{'cmdname'} ne 'comment')
+ and !($preformatted_content->{'type'}
+ and $preformatted_content->{'type'} eq 'index_entry_command'))) {
+ $after_paragraph = 1;
+ last;
+ }
+ }
+ last if ($after_paragraph);
+ }
+ }
+ return $after_paragraph;
+}
+
+# put everything after the last @item/@itemx in an item_table type container
+# and distinguish table_term and table_entry.
+sub _gather_previous_item($$;$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $next_command = shift;
+ my $line_nr = shift;
+
+ # nothing to do in that case.
+ if ($current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'before_item') {
+ if ($next_command and $next_command eq 'itemx') {
+ $self->line_warn(sprintf($self->__("\@itemx should not begin \@%s"),
+ $current->{'cmdname'}), $line_nr);
+ }
+ return;
+ }
+ #print STDERR "GATHER "._print_current($current)."\n";
+ my $type;
+ # if before an itemx, the type is different since there should not be
+ # real content, so it may be treated differently
+ if ($next_command and $next_command eq 'itemx') {
+ $type = 'inter_item';
+ } else {
+ $type = 'table_item';
+ }
+ my $table_gathered = {'type' => $type,
+ 'contents' => []};
+ # remove everything that is not an @item/@items or before_item to
+ # put it in the table_item, starting from the end.
+ my $contents_count = scalar(@{$current->{'contents'}});
+ for (my $i = 0; $i < $contents_count; $i++) {
+ #print STDERR "_gather_previous_item $i on $contents_count: "._print_current($current->{'contents'}->[-1])."\n";
+ if ($current->{'contents'}->[-1]->{'cmdname'}
+ and ($current->{'contents'}->[-1]->{'cmdname'} eq 'item'
+ or ($current->{'contents'}->[-1]->{'cmdname'} eq 'itemx'))) {
+ last;
+ } else {
+ my $item_content = pop @{$current->{'contents'}};
+ $item_content->{'parent'} = $table_gathered;
+ unshift @{$table_gathered->{'contents'}}, $item_content;
+ }
+ }
+ if ($type eq 'table_item') {
+ my $table_entry = {'type' => 'table_entry',
+ 'parent' => $current,
+ 'contents' => []};
+ my $table_term = {'type' => 'table_term',
+ 'parent' => $table_entry,
+ 'contents' => []};
+ push @{$table_entry->{'contents'}}, $table_term;
+ my $contents_count = scalar(@{$current->{'contents'}});
+ for (my $i = 0; $i < $contents_count; $i++) {
+ if ($current->{'contents'}->[-1]->{'type'}
+ and ($current->{'contents'}->[-1]->{'type'} eq 'before_item'
+ or $current->{'contents'}->[-1]->{'type'} eq 'table_entry')) {
+ last;
+ } else {
+ my $item_content = pop @{$current->{'contents'}};
+ $item_content->{'parent'} = $table_term;
+ unshift @{$table_term->{'contents'}}, $item_content;
+ # debug
+ if (! (($item_content->{'cmdname'}
+ and ($item_content->{'cmdname'} eq 'itemx'
+ or $item_content->{'cmdname'} eq 'item'))
+ or ($item_content->{'type'}
+ and $item_content->{'type'} eq 'inter_item'))) {
+ $self->_bug_message("wrong element in table term", $line_nr,
+ $item_content);
+ }
+ }
+ }
+ push @{$current->{'contents'}}, $table_entry;
+ if (scalar(@{$table_gathered->{'contents'}})) {
+ push @{$table_entry->{'contents'}}, $table_gathered;
+ $table_gathered->{'parent'} = $table_entry;
+ }
+ } else {
+ my $after_paragraph = _check_no_text($table_gathered);
+ if ($after_paragraph) {
+ $self->line_error($self->__("\@itemx must follow \@item"), $line_nr);
+ }
+ if (scalar(@{$table_gathered->{'contents'}})) {
+ push @{$current->{'contents'}}, $table_gathered;
+ $table_gathered->{'parent'} = $current;
+ }
+ }
+}
+
+# Starting from the end, gather everything util the def_line to put in
+# a def_item
+sub _gather_def_item($;$)
+{
+ my $current = shift;
+ my $next_command = shift;
+ my $type;
+ # means that we are between a @def*x and a @def
+ if ($next_command) {
+ $type = 'inter_def_item';
+ } else {
+ $type = 'def_item';
+ }
+
+ # This may happen for a construct like
+ # @deffnx a b @section
+ # but otherwise the end of line will lead to the command closing
+ return if (!$current->{'cmdname'} or $current->{'cmdname'} =~ /x$/);
+ #print STDERR "_gather_def_item($type) in "._print_current($current)."\n";
+ my $def_item = {'type' => $type,
+ 'parent' => $current,
+ 'contents' => []};
+ # remove everything that is not a def_line to put it in the def_item,
+ # starting from the end.
+ my $contents_count = scalar(@{$current->{'contents'}});
+ for (my $i = 0; $i < $contents_count; $i++) {
+ #print STDERR "_gather_def_item $type ($i on $contents_count) "._print_current($current->{'contents'}->[-1])."\n";
+ if ($current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'def_line') {
+ # and !$current->{'contents'}->[-1]->{'extra'}->{'not_after_command'}) {
+ last;
+ } else {
+ my $item_content = pop @{$current->{'contents'}};
+ $item_content->{'parent'} = $def_item;
+ unshift @{$def_item->{'contents'}}, $item_content;
+ }
+ }
+ if (scalar(@{$def_item->{'contents'}})) {
+ push @{$current->{'contents'}}, $def_item;
+ }
+}
+
+# close formats
+sub _close_command_cleanup($$$) {
+ my $self = shift;
+ my $current = shift;
+
+ return unless ($current->{'cmdname'});
+ # remove the dynamic counters in multitable, they are not of use in the final
+ # tree. Also determine the multitable_body and multitable_head with
+ # @item or @headitem rows.
+ if ($current->{'cmdname'} eq 'multitable') {
+ my $in_head_or_rows;
+ my @contents = @{$current->{'contents'}};
+ $current->{'contents'} = [];
+ foreach my $row (@contents) {
+ if ($row->{'type'} and $row->{'type'} eq 'row') {
+ delete $row->{'cells_count'};
+ if ($row->{'contents'}->[0]->{'cmdname'} eq 'headitem') {
+ if (!$in_head_or_rows) {
+ push @{$current->{'contents'}}, {'type' => 'multitable_head',
+ 'parent' => $current};
+ $in_head_or_rows = 1;
+ }
+ } elsif ($row->{'contents'}->[0]->{'cmdname'} eq 'item') {
+ if (!defined($in_head_or_rows) or $in_head_or_rows) {
+ push @{$current->{'contents'}}, {'type' => 'multitable_body',
+ 'parent' => $current};
+ $in_head_or_rows = 0;
+ }
+ }
+ push @{$current->{'contents'}->[-1]->{'contents'}}, $row;
+ $row->{'parent'} = $current->{'contents'}->[-1];
+ } else {
+ push @{$current->{'contents'}}, $row;
+ $in_head_or_rows = undef;
+ }
+ }
+ delete $current->{'rows_count'};
+ } elsif ($item_container_commands{$current->{'cmdname'}}) {
+ delete $current->{'items_count'};
+ }
+
+ # put everything after the last @def*x command in a def_item type container.
+ if ($def_commands{$current->{'cmdname'}}) {
+ # At this point the end command hasn't been added to the command contents.
+ # so checks cannot be done at this point.
+ _gather_def_item($current);
+ }
+
+ if ($item_line_commands{$current->{'cmdname'}}) {
+ # At this point the end command hasn't been added to the command contents.
+ # so checks cannot be done at this point.
+ if (@{$current->{'contents'}}) {
+ $self->_gather_previous_item($current);
+ }
+ }
+
+ # put end out of before_item, and replace it at the end of the parent.
+ # remove empty before_item.
+ # warn if not empty before_item, but format is empty
+ if ($block_item_commands{$current->{'cmdname'}}) {
+ if (@{$current->{'contents'}}) {
+ my $leading_spaces = 0;
+ my $before_item;
+ if ($current->{'contents'}->[0]->{'type'}
+ and $current->{'contents'}->[0]->{'type'} eq 'empty_line_after_command'
+ and $current->{'contents'}->[1]
+ and $current->{'contents'}->[1]->{'type'}
+ and $current->{'contents'}->[1]->{'type'} eq 'before_item') {
+ $leading_spaces = 1;
+ $before_item = $current->{'contents'}->[1];
+ } elsif ($current->{'contents'}->[0]->{'type'}
+ and $current->{'contents'}->[0]->{'type'} eq 'before_item') {
+ $before_item = $current->{'contents'}->[0];
+ }
+ if ($before_item) {
+ if ($current->{'extra'}->{'end_command'}
+ and @{$before_item->{'contents'}}
+ and $before_item->{'contents'}->[-1] eq $current->{'extra'}->{'end_command'}) {
+ my $end = pop @{$before_item->{'contents'}};
+ $end->{'parent'} = $current;
+ push @{$current->{'contents'}}, $end;
+ }
+ # remove empty before_items
+ if (!@{$before_item->{'contents'}}) {
+ if ($leading_spaces) {
+ my $space = shift @{$current->{'contents'}};
+ shift @{$current->{'contents'}};
+ unshift @{$current->{'contents'}}, $space;
+ } else {
+ shift @{$current->{'contents'}};
+ }
+ } else {
+ # warn if not empty before_item, but format is empty
+ my $empty_before_item = 1;
+ foreach my $before_item_content (@{$before_item->{'contents'}}) {
+ if (!$before_item_content->{'cmdname'} or
+ ($before_item_content->{'cmdname'} ne 'c'
+ and $before_item_content->{'cmdname'} ne 'comment')) {
+ $empty_before_item = 0;
+ last;
+ }
+ }
+ if (!$empty_before_item) {
+ my $empty_format = 1;
+ foreach my $format_content (@{$current->{'contents'}}) {
+ next if ($format_content eq $before_item);
+ if (($format_content->{'cmdname'} and
+ ($format_content->{'cmdname'} ne 'c'
+ and $format_content->{'cmdname'} ne 'comment'
+ and $format_content->{'cmdname'} ne 'end'))
+ or ($format_content->{'type'} and
+ ($format_content->{'type'} ne 'empty_line_after_command'))) {
+ $empty_format = 0;
+ last;
+ }
+ }
+ if ($empty_format) {
+ $self->line_warn(sprintf($self->__("\@%s has text but no \@item"),
+ $current->{'cmdname'}), $current->{'line_nr'});
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+# close the current command, with error messages and give the parent.
+# If the last argument is given it is the command being closed if
+# hadn't there be an error, currently only block command, used for a
+# better error message.
+sub _close_current($$$;$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $closed_command = shift;
+ my $interrupting_command = shift;
+
+ if ($current->{'cmdname'}) {
+ print STDERR "CLOSING(_close_current) \@$current->{'cmdname'}\n" if ($self->{'DEBUG'});
+ if (exists($brace_commands{$current->{'cmdname'}})) {
+ pop @{$self->{'context_stack'}}
+ if (exists $context_brace_commands{$current->{'cmdname'}});
+ $current = _close_brace_command($self, $current, $line_nr,
+ $closed_command, $interrupting_command);
+ } elsif (exists($block_commands{$current->{'cmdname'}})) {
+ if (defined($closed_command)) {
+ $self->line_error(sprintf($self->__("`\@end' expected `%s', but saw `%s'"),
+ $current->{'cmdname'}, $closed_command), $line_nr);
+ } elsif ($interrupting_command) {
+ $self->line_error(sprintf($self->__("\@%s seen before \@end %s"),
+ $interrupting_command, $current->{'cmdname'}),
+ $line_nr);
+ } else {
+ $self->line_error(sprintf($self->__("no matching `%cend %s'"),
+ ord('@'), $current->{'cmdname'}), $line_nr);
+ if ($block_commands{$current->{'cmdname'}} eq 'conditional') {
+ # in this case we are within an ignored conditional
+ my $conditional = pop @{$current->{'parent'}->{'contents'}};
+ }
+ }
+ if ($preformatted_commands{$current->{'cmdname'}}
+ or $menu_commands{$current->{'cmdname'}}
+ or $format_raw_commands{$current->{'cmdname'}}) {
+ my $context = pop @{$self->{'context_stack'}};
+ pop @{$self->{'expanded_formats_stack'}}
+ if ($format_raw_commands{$current->{'cmdname'}});
+ }
+ pop @{$self->{'regions_stack'}}
+ if ($region_commands{$current->{'cmdname'}});
+ $current = $current->{'parent'};
+ } else {
+ # There @item and @tab commands are closed, and also line commands
+ # with invalid content
+ $current = $current->{'parent'};
+ }
+ } elsif ($current->{'type'}) {
+ print STDERR "CLOSING type $current->{'type'}\n" if ($self->{'DEBUG'});
+ if ($current->{'type'} eq 'bracketed') {
+ $self->_command_error($current, $line_nr,
+ $self->__("misplaced %c"), ord('{'));
+ } elsif ($current->{'type'} eq 'menu_comment'
+ or $current->{'type'} eq 'menu_entry_description') {
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'preformatted') {
+ $self->_bug_message("context $context instead of preformatted",
+ $line_nr, $current);
+ }
+ # close empty menu_comment
+ if (!@{$current->{'contents'}}) {
+ pop @{$current->{'parent'}->{'contents'}};
+ }
+ } elsif ($current->{'type'} eq 'misc_line_arg'
+ or $current->{'type'} eq 'block_line_arg') {
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'line' and $context ne 'def') {
+ $self->_bug_message("context $context instead of line or def",
+ $line_nr, $current);
+ die;
+ }
+ }
+ $current = $current->{'parent'};
+ } else { # Should never go here.
+ $current = $current->{'parent'} if ($current->{'parent'});
+ $self->_bug_message("No type nor cmdname when closing",
+ $line_nr, $current);
+ }
+ return $current;
+}
+
+# a closed_command arg means closing until that command is found.
+# no command arg means closing until the root or a root_command
+# is found.
+sub _close_commands($$$;$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $closed_command = shift;
+ my $interrupting_command = shift;;
+
+ $current = _end_paragraph($self, $current, $line_nr, $closed_command,
+ $interrupting_command);
+ $current = _end_preformatted($self, $current, $line_nr, $closed_command,
+ $interrupting_command);
+
+ # stop if the command is found
+ while (!($closed_command and $current->{'cmdname'}
+ and $current->{'cmdname'} eq $closed_command)
+ # stop if at the root
+ and $current->{'parent'}
+ # stop if in a root command
+ # or in a context_brace_commands and searching for a specific
+ # end block command (with $closed_command set).
+ # This second condition means that a footnote is not closed when
+ # looking for the end of a block command, but is closed when
+ # completly closing the stack.
+ and !($current->{'cmdname'}
+ and ($root_commands{$current->{'cmdname'}}
+ or ($closed_command and $current->{'parent'}->{'cmdname'}
+ and $context_brace_commands{$current->{'parent'}->{'cmdname'}})))){
+ $self->_close_command_cleanup($current);
+ $current = $self->_close_current($current, $line_nr, $closed_command,
+ $interrupting_command);
+ }
+
+ my $closed_element;
+ if ($closed_command and $current->{'cmdname'}
+ and $current->{'cmdname'} eq $closed_command) {
+ if ($preformatted_commands{$current->{'cmdname'}}) {
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'preformatted') {
+ $self->_bug_message("context $context instead of preformatted for $closed_command",
+ $line_nr, $current);
+ }
+ } elsif ($format_raw_commands{$current->{'cmdname'}}) {
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'rawpreformatted') {
+ $self->_bug_message("context $context instead of rawpreformatted for $closed_command",
+ $line_nr, $current);
+ }
+ pop @{$self->{'expanded_formats_stack'}};
+ } elsif ($menu_commands{$current->{'cmdname'}}) {
+ my $context = pop @{$self->{'context_stack'}};
+ # may be in menu, but context is preformatted if in a preformatted too.
+ if ($context ne 'menu' and $context ne 'preformatted') {
+ $self->_bug_message("context $context instead of preformatted or menu for $closed_command",
+ $line_nr, $current);
+ }
+ }
+ #print STDERR "close context $context for $current->{'cmdname'}\n"
+ # if ($self->{'DEBUG'});
+ pop @{$self->{'regions_stack'}}
+ if ($region_commands{$current->{'cmdname'}});
+ $closed_element = $current;
+ #$self->_close_command_cleanup($current);
+ $current = $current->{'parent'};
+ } elsif ($closed_command) {
+ $self->line_error(sprintf($self->__("unmatched `%c%s'"),
+ ord('@'), "end $closed_command"), $line_nr);
+ }
+ return ($closed_element, $current);
+}
+
+# begin paragraph if needed. If not try to merge with the previous
+# content if it is also some text.
+sub _merge_text($$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $text = shift;
+
+ my $paragraph;
+
+ my $no_merge_with_following_text = 0;
+ if ($text =~ /\S/) {
+ my $leading_spaces;
+ if ($text =~ /^(\s+)/) {
+ $leading_spaces = $1;
+ }
+ if ($current->{'contents'} and @{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and ($current->{'contents'}->[-1]->{'type'} eq 'empty_line_after_command'
+ or $current->{'contents'}->[-1]->{'type'} eq 'empty_spaces_before_argument'
+ or $current->{'contents'}->[-1]->{'type'} eq 'empty_spaces_after_close_brace')) {
+ $no_merge_with_following_text = 1;
+ }
+ if (_abort_empty_line($self, $current, $leading_spaces)) {
+ $text =~ s/^(\s+)//;
+ }
+
+ $paragraph = _begin_paragraph($self, $current);
+ $current = $paragraph if ($paragraph);
+ }
+
+ if (!defined($current->{'contents'})) {
+ $self->_bug_message("No contents in _merge_text",
+ undef, $current);
+ die;
+ }
+
+ if (@{$current->{'contents'}}
+ and exists($current->{'contents'}->[-1]->{'text'})
+ and $current->{'contents'}->[-1]->{'text'} !~ /\n/
+ and !$no_merge_with_following_text) {
+ $current->{'contents'}->[-1]->{'text'} .= $text;
+ print STDERR "MERGED TEXT: $text|||\n" if ($self->{'DEBUG'});
+ } else {
+ push @{$current->{'contents'}}, { 'text' => $text, 'parent' => $current };
+ print STDERR "NEW TEXT: $text|||\n" if ($self->{'DEBUG'});
+ }
+ return $current;
+}
+
+# return the parent if in a item_container command, itemize or enumerate
+sub _item_container_parent($)
+{
+ my $current = shift;
+ if ((($current->{'cmdname'} and $current->{'cmdname'} eq 'item')
+ or ($current->{'type'} and $current->{'type'} eq 'before_item'))
+ and ($current->{'parent'} and $current->{'parent'}->{'cmdname'}
+ and $item_container_commands{$current->{'parent'}->{'cmdname'}})) {
+ return ($current->{'parent'});
+ }
+ return undef;
+}
+
+# return the parent if in a item_line command, @*table
+sub _item_line_parent($)
+{
+ my $current = shift;
+ if ($current->{'cmdname'} and ($current->{'cmdname'} eq 'item'
+ or $current->{'cmdname'} eq 'itemx')) {
+ $current = $current->{'parent'}->{'parent'};
+ } elsif ($current->{'type'} and $current->{'type'} eq 'before_item'
+ and $current->{'parent'}) {
+ $current = $current->{'parent'};
+ }
+ return $current if ($current->{'cmdname'}
+ and $item_line_commands{$current->{'cmdname'}});
+ return undef;
+}
+
+# return the parent if in a multitable
+sub _item_multitable_parent($)
+{
+ my $current = shift;
+ if (($current->{'cmdname'} and ($current->{'cmdname'} eq 'headitem'
+ or $current->{'cmdname'} eq 'item' or $current->{'cmdname'} eq 'tab'))
+ and $current->{'parent'} and $current->{'parent'}->{'parent'}) {
+ $current = $current->{'parent'}->{'parent'};
+ } elsif ($current->{'type'} and $current->{'type'} eq 'before_item'
+ and $current->{'parent'}) {
+ $current = $current->{'parent'};
+ }
+ return $current if ($current->{'cmdname'}
+ and $current->{'cmdname'} eq 'multitable');
+ return undef;
+}
+
+# returns next text fragment, be it pending from a macro expansion or
+# text or file
+sub _next_text($$$)
+{
+ my $self = shift;
+ my $line_nr = shift;
+ my $current = shift;
+
+ while (@{$self->{'input'}}) {
+ my $input = $self->{'input'}->[0];
+ if (@{$input->{'pending'}}) {
+ my $new_text = shift @{$input->{'pending'}};
+ if ($new_text->[1] and $new_text->[1]->{'end_macro'}) {
+ delete $new_text->[1]->{'end_macro'};
+ my $top_macro = shift @{$self->{'macro_stack'}};
+ print STDERR "SHIFT MACRO_STACK(@{$self->{'macro_stack'}}): $top_macro->{'args'}->[0]->{'text'}\n"
+ if ($self->{'DEBUG'});
+ }
+ return ($new_text->[0], $new_text->[1]);
+ } elsif ($input->{'fh'}) {
+ my $fh = $input->{'fh'};
+ my $line = <$fh>;
+ while (defined($line)) {
+ # add an end of line if there is none at the end of file
+ if (eof($fh) and $line !~ /\n/) {
+ $line .= "\n";
+ }
+ $line =~ s/\x{7F}.*\s*//;
+ if ($self->{'CPP_LINE_DIRECTIVES'}
+ # no cpp directives in ignored/macro/verbatim
+ and defined ($current)
+ and not
+ (($current->{'cmdname'}
+ and $block_commands{$current->{'cmdname'}}
+ and ($block_commands{$current->{'cmdname'}} eq 'raw'
+ or $block_commands{$current->{'cmdname'}} eq 'conditional'))
+ or
+ ($current->{'parent'} and $current->{'parent'}->{'cmdname'}
+ and $current->{'parent'}->{'cmdname'} eq 'verb')
+ )
+ and $line =~ /^\s*#\s*(line)? (\d+)(( "([^"]+)")(\s+\d+)*)?\s*$/) {
+ $input->{'line_nr'} = $2;
+ if (defined($5)) {
+ $input->{'name'} = $5;
+ }
+ $line = <$fh>;
+ } else {
+ $input->{'line_nr'}++;
+ return ($line, {'line_nr' => $input->{'line_nr'},
+ 'file_name' => $input->{'name'},
+ 'macro' => ''});
+ }
+ }
+ }
+ my $previous_input = shift(@{$self->{'input'}});
+ # Don't close STDIN
+ if ($previous_input->{'fh'} and $previous_input->{'name'} ne '-') {
+ if (!close($previous_input->{'fh'})) {
+ $self->document_warn(sprintf($self->__("error on closing %s: %s"),
+ $previous_input->{'name'}, $!));
+
+ }
+ }
+ }
+
+ return (undef, $line_nr);
+}
+
+# collect text and line numbers until an end of line is found.
+sub _new_line($$$)
+{
+ my $self = shift;
+ my $line_nr = shift;
+ my $current = shift;
+ my $new_line = '';
+
+ while (1) {
+ my $new_text;
+ ($new_text, $line_nr) = _next_text($self, $line_nr, $current);
+ if (!defined($new_text)) {
+ $new_line = undef if ($new_line eq '');
+ last;
+ }
+
+ $new_line .= $new_text;
+
+ my $chomped_text = $new_text;
+ last if chomp($chomped_text);
+ }
+ return ($new_line, $line_nr);
+}
+
+sub _expand_macro_arguments($$$$)
+{
+ my $self = shift;
+ my $macro = shift;
+ my $line = shift;
+ my $line_nr = shift;
+ my $braces_level = 1;
+ my $arguments = [ '' ];
+ my $arg_nr = 0;
+ my $args_total = scalar(@{$macro->{'args'}}) -1;
+ my $name = $macro->{'args'}->[0]->{'text'};
+
+ my $line_nr_orig = $line_nr;
+
+ while (1) {
+ if ($line =~ s/([^\\{},]*)([\\{},])//) {
+ my $separator = $2;
+ $arguments->[-1] .= $1;
+ if ($separator eq '\\') {
+ if ($line =~ s/^(.)//) {
+ my $protected_char = $1;
+ if ($protected_char !~ /[\\{},]/) {
+ $arguments->[-1] .= '\\';
+ }
+ $arguments->[-1] .= $protected_char;
+
+ print STDERR "MACRO ARG: $separator: $protected_char\n" if ($self->{'DEBUG'});
+ } else {
+ $arguments->[-1] .= '\\';
+ print STDERR "MACRO ARG: $separator\n" if ($self->{'DEBUG'});
+ }
+ } elsif ($separator eq ',') {
+ if ($braces_level == 1) {
+ if (scalar(@$arguments) < $args_total) {
+ push @$arguments, '';
+ $line =~ s/^[^\S\f]*//;
+ print STDERR "MACRO NEW ARG\n" if ($self->{'DEBUG'});
+ } else {
+ # implicit quoting when there is one argument.
+ if ($args_total != 1) {
+ $self->line_error(sprintf($self->__(
+ "macro `%s' called with too many args"),
+ $name), $line_nr);
+ }
+ $arguments->[-1] .= ',';
+ }
+ } else {
+ $arguments->[-1] .= ',';
+ }
+ } elsif ($separator eq '}') {
+ $braces_level--;
+ last if ($braces_level == 0);
+ $arguments->[-1] .= $separator;
+ } elsif ($separator eq '{') {
+ $braces_level++;
+ $arguments->[-1] .= $separator;
+ }
+ } else {
+ print STDERR "MACRO ARG end of line\n" if ($self->{'DEBUG'});
+ $arguments->[-1] .= $line;
+
+ ($line, $line_nr) = _new_line($self, $line_nr, $macro);
+ if (!defined($line)) {
+ $self->line_error(sprintf($self->__("\@%s missing close brace"),
+ $name), $line_nr_orig);
+ return ($arguments, "\n", $line_nr);
+ }
+ }
+ }
+ if ($args_total == 0 and $arguments->[0] =~ /[\S\f]/) {
+ $self->line_error(sprintf($self->__(
+ "macro `%s' declared without argument called with an argument"),
+ $name), $line_nr);
+ }
+ print STDERR "END MACRO ARGS EXPANSION(".scalar(@$arguments)."): ".
+ join("|\n", @$arguments) ."|\n" if ($self->{'DEBUG'});
+ return ($arguments, $line, $line_nr);
+}
+
+sub _expand_macro_body($$$$) {
+ my $self = shift;
+ my $macro = shift;
+ my $args = shift;
+ my $line_nr = shift;
+
+ my $macrobody = $macro->{'extra'}->{'macrobody'};
+ my $args_total = scalar(@{$macro->{'args'}}) -1;
+ my $args_index = $macro->{'extra'}->{'args_index'};
+
+ my $i;
+ for ($i=0; $i<=$args_total; $i++) {
+ $args->[$i] = "" unless (defined($args->[$i]));
+ }
+
+ my $result = '';
+ while ($macrobody ne '') {
+ if ($macrobody =~ s/^([^\\]*)\\//o) {
+ $result .= $1;
+ if ($macrobody =~ s/^\\//) {
+ $result .= '\\';
+ } elsif ($macrobody =~ s/^([^\\]*)\\//) {
+ my $arg = $1;
+ if (defined($args_index->{$arg})) {
+ $result .= $args->[$args_index->{$arg}];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\\ in \@%s expansion followed `%s' instead of parameter name or \\"),
+ $macro->{'args'}->[0]->{'text'}, $arg), $line_nr);
+ $result .= '\\' . $arg;
+ }
+ }
+ next;
+ }
+ $result .= $macrobody;
+ last;
+ }
+ return $result;
+}
+
+# each time a new line appeared, a container is opened to hold the text
+# consisting only of spaces. This container is removed here, typically
+# this is called when non-space happens on a line.
+sub _abort_empty_line($$;$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $additional_text = shift;
+ $additional_text = '' if (!defined($additional_text));
+ if ($current->{'contents'} and @{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and ($current->{'contents'}->[-1]->{'type'} eq 'empty_line'
+ or $current->{'contents'}->[-1]->{'type'} eq 'empty_line_after_command'
+ or $current->{'contents'}->[-1]->{'type'} eq 'empty_spaces_before_argument'
+ or $current->{'contents'}->[-1]->{'type'} eq 'empty_spaces_after_close_brace')) {
+ print STDERR "ABORT EMPTY additional text |$additional_text|, current |$current->{'contents'}->[-1]->{'text'}|\n" if ($self->{'DEBUG'});
+ $current->{'contents'}->[-1]->{'text'} .= $additional_text;
+ # remove empty 'empty*before'.
+ if ($current->{'contents'}->[-1]->{'text'} eq '') {
+ # as we remove 'empty_spaces_before_argument', 'spaces_before_argument'
+ # is removed from 'extra' too.
+ if ($current->{'extra'}
+ and $current->{'extra'}->{'spaces_before_argument'}
+ and $current->{'extra'}->{'spaces_before_argument'}
+ eq $current->{'contents'}->[-1]) {
+ delete ($current->{'extra'}->{'spaces_before_argument'});
+ delete ($current->{'extra'}) if !(keys(%{$current->{'extra'}}));
+ }
+ pop @{$current->{'contents'}}
+ } elsif ($current->{'contents'}->[-1]->{'type'} eq 'empty_line') {
+ # exactly the same condition than to begin a paragraph
+ if ((!$current->{'type'} or $type_with_paragraph{$current->{'type'}})
+ and !$no_paragraph_contexts{$self->{'context_stack'}->[-1]}) {
+ $current->{'contents'}->[-1]->{'type'} = 'empty_spaces_before_paragraph';
+ } else {
+ delete $current->{'contents'}->[-1]->{'type'};
+ }
+ } elsif ($current->{'contents'}->[-1]->{'type'} eq 'empty_line_after_command') {
+ $current->{'contents'}->[-1]->{'type'} = 'empty_spaces_after_command';
+ }
+ return 1;
+ }
+ return 0;
+}
+
+# isolate last space in a command to help expansion disregard unuseful spaces.
+sub _isolate_last_space($$;$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $type = shift;
+ $type = 'spaces_at_end' if (!defined($type));
+ if ($current->{'contents'} and @{$current->{'contents'}}) {
+ my $index = -1;
+ # we ignore space before a misc command that is last on line.
+ # This is primarily to tag spaces before comments, but this will
+ # also tag and, in most converter lead to removal of spaces
+ # before any misc command, which is not really problematic as
+ # in most cases, if it is not a comment, we are in an invalid
+ # nesting of misc command on another @-command line.
+ $index = -2
+ if (scalar(@{$current->{'contents'}}) > 1
+ and $current->{'contents'}->[-1]->{'cmdname'}
+ and $self->{'misc_commands'}->{$current->{'contents'}->[-1]->{'cmdname'}});
+ if (defined($current->{'contents'}->[$index]->{'text'})
+ and !$current->{'contents'}->[$index]->{'type'}
+ and $current->{'contents'}->[$index]->{'text'} =~ /\s+$/) {
+ if ($current->{'contents'}->[$index]->{'text'} !~ /\S/) {
+ $current->{'contents'}->[$index]->{'type'} = $type;
+ } else {
+ $current->{'contents'}->[$index]->{'text'} =~ s/(\s+)$//;
+ my $spaces = $1;
+ my $new_spaces = { 'text' => $spaces, 'parent' => $current,
+ 'type' => $type };
+ if ($index == -1) {
+ push @{$current->{'contents'}}, $new_spaces;
+ } else {
+ splice (@{$current->{'contents'}}, $index+1, 0, $new_spaces);
+ }
+ }
+ }
+ }
+}
+
+# used to put a node name in error messages.
+sub _node_extra_to_texi($)
+{
+ my $node = shift;
+ my $result = '';
+ if ($node->{'manual_content'}) {
+ $result = '('.Texinfo::Convert::Texinfo::convert({'contents'
+ => $node->{'manual_content'}}) .')';
+ }
+ if ($node->{'node_content'}) {
+ $result .= Texinfo::Convert::Texinfo::convert ({'contents'
+ => $node->{'node_content'}});
+ }
+ return $result;
+}
+
+sub _find_end_brace($$)
+{
+ my $text = shift;
+ my $braces_count = shift;
+
+ my $before = '';
+ while ($braces_count > 0 and length($text)) {
+ if ($text =~ s/([^()]*)([()])//) {
+ $before .= $1.$2;
+ my $brace = $2;
+ if ($brace eq '(') {
+ $braces_count++;
+ } else {
+ $braces_count--;
+ if ($braces_count == 0) {
+ return ($before, $text, 0);
+ }
+ }
+ } else {
+ $before .= $text;
+ $text = '';
+ }
+ }
+ return ($before, undef, $braces_count);
+}
+
+# This only counts opening braces, and returns 0 once all the parentheses
+# are closed
+sub _count_opened_tree_braces($$);
+sub _count_opened_tree_braces($$)
+{
+ my $current = shift;
+ my $braces_count = shift;
+ if (defined($current->{'text'})) {
+ my ($before, $after);
+ ($before, $after, $braces_count) = _find_end_brace($current->{'text'},
+ $braces_count);
+ }
+ if ($current->{'args'}) {
+ foreach my $arg (@{$current->{'args'}}) {
+ $braces_count = _count_opened_tree_braces($arg, $braces_count);
+ return $braces_count if ($braces_count == 0);
+ }
+ }
+ if ($current->{'contents'}) {
+ foreach my $content (@{$current->{'contents'}}) {
+ $braces_count = _count_opened_tree_braces($content, $braces_count);
+ return $braces_count if ($braces_count == 0);
+ }
+ }
+ return $braces_count;
+}
+
+# retrieve a leading manual name in parentheses, if there is one.
+sub _parse_node_manual($)
+{
+ my $node = shift;
+ my @contents = @{$node->{'contents'}};
+ _trim_spaces_comment_from_content(\@contents);
+
+ my $manual;
+ my $result;
+#print STDERR "RRR $contents[0] and $contents[0]->{'text'} \n";
+ if ($contents[0] and $contents[0]->{'text'} and $contents[0]->{'text'} =~ /^\(/) {
+ my $braces_count = 1;
+ if ($contents[0]->{'text'} !~ /^\($/) {
+ my $brace = shift @contents;
+ my $brace_text = $brace->{'text'};
+ $brace_text =~ s/^\(//;
+ unshift @contents, { 'text' => $brace_text, 'type' => $brace->{'type'},
+ 'parent' => $brace->{'parent'} } if $brace_text ne '';
+ } else {
+ shift @contents;
+ }
+ while(@contents) {
+ my $content = shift @contents;
+ if (!defined($content->{'text'}) or $content->{'text'} !~ /\)/) {
+ push @$manual, $content;
+ $braces_count = _count_opened_tree_braces($content, $braces_count);
+ # This is an error, braces were closed in a command
+ if ($braces_count == 0) {
+ last;
+ }
+ } else {
+ my ($before, $after);
+ ($before, $after, $braces_count) = _find_end_brace($content->{'text'},
+ $braces_count);
+ if ($braces_count == 0) {
+ $before =~ s/\)$//;
+ push @$manual, { 'text' => $before, 'parent' => $content->{'parent'} }
+ if ($before ne '');
+ $after =~ s/^\s*//;
+ unshift @contents, { 'text' => $after, 'parent' => $content->{'parent'} }
+ if ($after ne '');
+ last;
+ } else {
+ push @$manual, $content;
+ }
+ }
+ }
+ $result->{'manual_content'} = $manual if (defined($manual));
+ }
+ if (@contents) {
+ $result->{'node_content'} = \@contents;
+ $result->{'normalized'} =
+ Texinfo::Convert::NodeNameNormalization::normalize_node({'contents' => \@contents});
+ }
+ return $result;
+}
+
+sub _parse_float_type($)
+{
+ my $current = shift;
+ if (@{$current->{'args'}}) {
+ my @type_contents = @{$current->{'args'}->[0]->{'contents'}};
+ _trim_spaces_comment_from_content(\@type_contents);
+ if (@type_contents) {
+ my $normalized
+ = Texinfo::Convert::NodeNameNormalization::normalize_node(
+ {'contents' => \@type_contents});
+ $current->{'extra'}->{'type'}->{'content'} = \@type_contents;
+ if ($normalized =~ /[^-]/) {
+ $current->{'extra'}->{'type'}->{'normalized'} = $normalized;
+ return 1;
+ }
+ }
+ }
+ $current->{'extra'}->{'type'}->{'normalized'} = '';
+ return 0;
+}
+
+# used for definition line parsing
+sub _next_bracketed_or_word($$)
+{
+ my $self = shift;
+ my $contents = shift;
+ return undef if (!scalar(@{$contents}));
+ my $spaces;
+ $spaces = shift @{$contents} if (defined($contents->[0]->{'text'}) and
+ $contents->[0]->{'text'} !~ /\S/);
+ if (defined($spaces)) {
+ $spaces->{'type'} = 'spaces';
+ chomp $spaces->{'text'};
+ $spaces = undef if ($spaces->{'text'} eq '');
+ }
+ return ($spaces, undef) if (!scalar(@{$contents}));
+
+ #print STDERR "BEFORE PROCESSING ".Texinfo::Convert::Texinfo::convert({'contents' => $contents});
+ if ($contents->[0]->{'type'} and $contents->[0]->{'type'} eq 'bracketed') {
+ #print STDERR "Return bracketed\n";
+ my $bracketed = shift @{$contents};
+ $self->_isolate_last_space($bracketed, 'empty_space_at_end_def_bracketed');
+ my $bracketed_def_content = { 'contents' => $bracketed->{'contents'},
+ 'parent' => $bracketed->{'parent'},
+ 'type' => 'bracketed_def_content', };
+ if ($bracketed->{'extra'} and $bracketed->{'extra'}->{'spaces_before_argument'}) {
+ $bracketed_def_content->{'extra'}->{'spaces_before_argument'}
+ = $bracketed->{'extra'}->{'spaces_before_argument'};
+ }
+ return ($spaces, $bracketed_def_content);
+ } elsif ($contents->[0]->{'cmdname'}) {
+ #print STDERR "Return command $contents->[0]->{'cmdname'}\n";
+ return ($spaces, shift @{$contents});
+ } else {
+ #print STDERR "Process $contents->[0]->{'text'}\n";
+ $contents->[0]->{'text'} =~ s/^(\s*)//;
+ my $space_text = $1;
+ $spaces = {'text' => $space_text, 'type' => 'spaces'} if ($space_text);
+ $contents->[0]->{'text'} =~ s/^(\S+)//;
+ shift @{$contents} if ($contents->[0]->{'text'} eq '');
+ return ($spaces, {'text' => $1});
+ }
+}
+
+# definition line parsing
+sub _parse_def($$$)
+{
+ my $self = shift;
+ my $command = shift;
+ my $contents = shift;
+
+ my @contents = @$contents;
+ shift @contents if ($contents[0] and $contents[0]->{'type'}
+ and $contents[0]->{'type'} eq 'empty_spaces_after_command');
+ if ($def_aliases{$command}) {
+ my $real_command = $def_aliases{$command};
+ my $prepended = $def_map{$command}->{$real_command};
+ my @prepended_content;
+
+ my $text;
+ my $in_bracketed;
+ if ($prepended =~ /^\{/) {
+ $text = $prepended;
+ $text =~ s/\{([^\}]+)\}/$1/;
+ $in_bracketed = 1;
+ } else {
+ $text = $prepended;
+ }
+ my $tree = $self->gdt($text);
+ if ($in_bracketed or @{$tree->{'contents'}} > 1) {
+ my $bracketed = { 'type' => 'bracketed' };
+ $bracketed->{'contents'} = $tree->{'contents'};
+ foreach my $content (@{$tree->{'contents'}}) {
+ $content->{'parent'} = $bracketed;
+ }
+ @prepended_content = ($bracketed);
+ } else {
+ @prepended_content = (@{$tree->{'contents'}});
+ }
+ push @prepended_content, { 'text' => ' ' };
+
+ unshift @contents, @prepended_content;
+
+ $command = $def_aliases{$command};
+ }
+ foreach (my $i = 0; $i < scalar(@contents); $i++) {
+ # copy, to avoid changing the original
+ $contents[$i] = {'text' => $contents[$i]->{'text'}}
+ if (defined($contents[$i]->{'text'}));
+ }
+ my @result;
+ my @args = @{$def_map{$command}};
+ my $arg_type;
+ # Even when $arg_type is not set, that is for def* that is not documented
+ # to take args, everything is as is arg_type was set to arg.
+ $arg_type = pop @args if ($args[-1] eq 'arg' or $args[-1] eq 'argtype');
+ foreach my $arg (@args) {
+ #print STDERR "$command $arg"._print_current($contents[0]);
+ #foreach my $content (@contents) {print STDERR " "._print_current($content)};
+ #print STDERR " contents ->".Texinfo::Convert::Texinfo::convert ({'contents' => \@contents});
+ my ($spaces, $next) = $self->_next_bracketed_or_word(\@contents);
+ last if (!defined($next));
+ #print STDERR "NEXT[$arg] ".Texinfo::Convert::Texinfo::convert($next)."\n";
+ push @result, ['spaces', $spaces] if (defined($spaces));
+ push @result, [$arg, $next];
+ }
+
+ my @args_results;
+ while (@contents) {
+ my ($spaces, $next) = $self->_next_bracketed_or_word(\@contents);
+ push @args_results, ['spaces', $spaces] if (defined($spaces));
+ last if (!defined($next));
+ if (defined($next->{'text'})) {
+ while (1) {
+ if ($next->{'text'} =~ s/^([^\[\](),]+)//) {
+ push @args_results, ['arg', {'text' => $1}];
+ } elsif ($next->{'text'} =~ s/^([\[\](),])//) {
+ push @args_results, ['delimiter',
+ {'text' => $1, 'type' => 'delimiter'}];
+ } else {
+ last;
+ }
+ }
+ } else {
+ push @args_results, [ 'arg', $next ];
+ }
+ }
+ if ($arg_type and $arg_type eq 'argtype') {
+ my $next_is_type = 1;
+ foreach my $arg(@args_results) {
+ if ($arg->[0] eq 'spaces') {
+ } elsif ($arg->[0] eq 'delimiter') {
+ $next_is_type = 1;
+ } elsif ($arg->[1]->{'cmdname'} and $arg->[1]->{'cmdname'} ne 'code') {
+ $next_is_type = 1;
+ } elsif ($next_is_type) {
+ $arg->[0] = 'typearg';
+ $next_is_type = 0;
+ } else {
+ $next_is_type = 1;
+ }
+ }
+ }
+ return [@result, @args_results];
+}
+
+# register a label, that is something that may be the target of a reference
+# and must be unique in the document. Corresponds with @node, @anchor and
+# @float second arg.
+sub _register_label($$$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $label = shift;
+ my $line_nr = shift;
+ my $normalized = $label->{'normalized'};
+ if (_ignore_global_commands($self)) {
+ $current->{'extra'}->{'normalized'} = $normalized;
+ $current->{'extra'}->{'node_content'} = $label->{'node_content'};
+ return 0;
+ } elsif ($self->{'labels'}->{$normalized}) {
+ $self->line_error(sprintf($self->__("\@%s `%s' previously defined"),
+ $current->{'cmdname'},
+ Texinfo::Convert::Texinfo::convert({'contents' =>
+ $label->{'node_content'}})),
+ $line_nr);
+ $self->line_error(sprintf($self->__("here is the previous definition as \@%s"),
+ $self->{'labels'}->{$normalized}->{'cmdname'}),
+ $self->{'labels'}->{$normalized}->{'line_nr'}, 1);
+ return 0;
+ } else {
+ $current->{'extra'}->{'normalized'} = $normalized;
+ $current->{'extra'}->{'node_content'} = $label->{'node_content'};
+ $self->{'labels'}->{$normalized} = $current;
+ return 1;
+ }
+}
+
+sub _non_bracketed_contents($)
+{
+ my $current = shift;
+ if ($current->{'type'} and $current->{'type'} eq 'bracketed') {
+ my $new = {};
+ $new->{'contents'} = $current->{'contents'} if ($current->{'parent'});
+ $new->{'parent'} = $current->{'parent'} if ($current->{'parent'});
+ return $new;
+ } else {
+ return $current;
+ }
+}
+
+# store an index entry.
+# $current is the command element.
+# $content holds the actual content.
+# for index entries and v|ftable items, it is the index entry content,
+# for def, it is the parsed arguments, based on the definition line
+# arguments.
+sub _enter_index_entry($$$$$$$)
+{
+ my $self = shift;
+ my $command_container = shift;
+ my $command = shift;
+ my $current = shift;
+ my $content = shift;
+ my $content_normalized = shift;
+ my $line_nr = shift;
+
+ $content_normalized = $content if (!defined($content_normalized));
+
+ my $prefix = $self->{'command_index_prefix'}->{$command_container};
+ my $index_name = $self->{'prefix_to_index_name'}->{$prefix};
+ my $index = $self->{'index_names'}->{$index_name};
+
+ my $number;
+ unless (_ignore_global_commands($self)) {
+ $number = (defined($index->{'index_entries'})
+ ? (scalar(@{$index->{'index_entries'}}) + 1)
+ : 1);
+ }
+ my $index_entry = { 'index_name' => $index_name,
+ 'index_at_command' => $command,
+ 'index_type_command' => $command_container,
+ 'index_prefix' => $prefix,
+ 'content' => $content,
+ 'content_normalized' => $content_normalized,
+ 'command' => $current,
+ 'number' => $number,
+ };
+ if (@{$self->{'regions_stack'}}) {
+ $index_entry->{'region'} = $self->{'regions_stack'}->[-1];
+ } elsif ($self->{'current_node'}) {
+ $index_entry->{'node'} = $self->{'current_node'};
+ } elsif (!$self->{'current_section'}) {
+ $self->line_warn(sprintf($self->__("entry for index `%s' outside of any node"),
+ $index_name), $line_nr);
+ }
+ #print STDERR "INDEX ENTRY \@$command->{'cmdname'} $index_name($number)\n";
+ unless (_ignore_global_commands($self)) {
+ push @{$index->{'index_entries'}}, $index_entry;
+ }
+ $current->{'extra'}->{'index_entry'} = $index_entry;
+}
+
+# This is always called at command closing.
+sub _remove_empty_content_arguments($)
+{
+ my $current = shift;
+ my $type;
+ if ($current->{'extra'}) {
+ if ($current->{'extra'}->{'block_command_line_contents'}) {
+ $type = 'block_command_line_contents';
+ } elsif ($current->{'extra'}->{'brace_command_contents'}) {
+ $type = 'brace_command_contents';
+ }
+ }
+ if ($type) {
+ while (@{$current->{'extra'}->{$type}}
+ and not defined($current->{'extra'}->{$type}->[-1])) {
+ pop @{$current->{'extra'}->{$type}};
+ }
+ delete $current->{'extra'}->{$type} if (!@{$current->{'extra'}->{$type}});
+ delete $current->{'extra'} if (!keys(%{$current->{'extra'}}));
+ }
+}
+
+sub _strip_macrobody_leading_space($$)
+{
+ my $self = shift;
+ my $text = shift;
+ if ($self->{'MACRO_BODY_IGNORES_LEADING_SPACE'}) {
+ $text =~ s/^\s*//mg;
+ }
+ return $text;
+}
+
+# close constructs and do stuff at end of line (or end of the document)
+sub _end_line($$$);
+sub _end_line($$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+
+ my $current_old = $current;
+
+ my $included_file = 0;
+
+ # a line consisting only of spaces.
+ if ($current->{'contents'} and @{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'empty_line') {
+ print STDERR "END EMPTY LINE\n" if ($self->{'DEBUG'});
+ if ($current->{'type'} and $current->{'type'} eq 'paragraph') {
+ my $empty_line = pop @{$current->{'contents'}};
+ $current = _end_paragraph($self, $current, $line_nr);
+ push @{$current->{'contents'}}, $empty_line;
+ $empty_line->{'parent'} = $current;
+ } elsif ($current->{'type'}
+ and $current->{'type'} eq 'preformatted'
+ and $current->{'parent'}->{'type'}
+ and $current->{'parent'}->{'type'} eq 'menu_entry_description') {
+ my $empty_line = pop @{$current->{'contents'}};
+ if ($current->{'type'} eq 'preformatted') {
+ my $empty_preformatted = (!@{$current->{'contents'}});
+ $current = $current->{'parent'};
+ pop @{$current->{'contents'}} if ($empty_preformatted);
+ }
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'preformatted') {
+ $self->_bug_message("context $context instead of preformatted in empty line after menu_entry_description",
+ $line_nr, $current);
+ }
+
+ # first parent is menu_entry
+ $current = $current->{'parent'}->{'parent'};
+
+ push @{$current->{'contents'}}, { 'type' => 'menu_comment',
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ push @{$current->{'contents'}}, { 'type' => 'preformatted',
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ push @{$current->{'contents'}}, { 'type' => 'after_description_line',
+ 'text' => $empty_line->{'text'},
+ 'parent' => $current };
+ push @{$self->{'context_stack'}}, 'preformatted';
+ print STDERR "MENU: END DESCRIPTION, OPEN COMMENT\n" if ($self->{'DEBUG'});
+ } elsif (!$no_paragraph_contexts{$self->{'context_stack'}->[-1]}) {
+ $current = _end_paragraph($self, $current, $line_nr);
+ }
+
+ # end of a menu line.
+ } elsif ($current->{'type'}
+ and ($current->{'type'} eq 'menu_entry_name'
+ or $current->{'type'} eq 'menu_entry_node')) {
+ my $empty_menu_entry_node = 0;
+ my $end_comment;
+ if ($current->{'type'} eq 'menu_entry_node') {
+ if (@{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'cmdname'}
+ and ($current->{'contents'}->[-1]->{'cmdname'} eq 'c'
+ or $current->{'contents'}->[-1]->{'cmdname'} eq 'comment')) {
+ $end_comment = pop @{$current->{'contents'}};
+ }
+ if (!@{$current->{'contents'}}
+ # empty if only the end of line or spaces
+ or (@{$current->{'contents'}} == 1
+ and defined($current->{'contents'}->[-1]->{'text'})
+ and $current->{'contents'}->[-1]->{'text'} !~ /\S/)) {
+ $empty_menu_entry_node = 1;
+ push @{$current->{'contents'}}, $end_comment if ($end_comment);
+ }
+ }
+ # we abort the menu entry if there is no node name
+ if ($empty_menu_entry_node
+ or $current->{'type'} eq 'menu_entry_name') {
+ my $description_or_menu_comment;
+ print STDERR "FINALLY NOT MENU ENTRY\n" if ($self->{'DEBUG'});
+ my $menu = $current->{'parent'}->{'parent'};
+ my $menu_entry = pop @{$menu->{'contents'}};
+ if (@{$menu->{'contents'}} and $menu->{'contents'}->[-1]->{'type'}
+ and $menu->{'contents'}->[-1]->{'type'} eq 'menu_entry') {
+ my $entry = $menu->{'contents'}->[-1];
+ my $description;
+ foreach my $entry_element (reverse(@{$entry->{'args'}})) {
+ if ($entry_element->{'type'} eq 'menu_entry_description') {
+ $description = $entry_element;
+ last;
+ }
+ }
+ if ($description) {
+ $description_or_menu_comment = $description;
+ } else {
+ # Normally this cannot happen
+ $self->_bug_message("No description in menu_entry",
+ $line_nr, $current);
+ push @{$entry->{'args'}}, {'type' => 'menu_entry_description',
+ 'parent' => $entry,
+ 'contents' => [] };
+ $description_or_menu_comment = $entry->{'args'}->[-1];
+ }
+ } elsif (@{$menu->{'contents'}} and $menu->{'contents'}->[-1]->{'type'}
+ and $menu->{'contents'}->[-1]->{'type'} eq 'menu_comment') {
+ $description_or_menu_comment = $menu->{'contents'}->[-1];
+ }
+ if ($description_or_menu_comment) {
+ $current = $description_or_menu_comment;
+ if ($current->{'contents'}->[-1] and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'preformatted') {
+ $current = $current->{'contents'}->[-1];
+ } else {
+ # this should not happen
+ $self->_bug_message("description or menu comment not in preformatted",
+ $line_nr, $current);
+ push @{$current->{'contents'}}, {'type' => 'preformatted',
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ }
+ push @{$self->{'context_stack'}}, 'preformatted';
+ } else {
+ push @{$menu->{'contents'}}, {'type' => 'menu_comment',
+ 'parent' => $menu,
+ 'contents' => [] };
+ $current = $menu->{'contents'}->[-1];
+ push @{$current->{'contents'}}, {'type' => 'preformatted',
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ push @{$self->{'context_stack'}}, 'preformatted';
+ print STDERR "THEN MENU_COMMENT OPEN\n" if ($self->{'DEBUG'});
+ }
+ while (@{$menu_entry->{'args'}}) {
+ my $arg = shift @{$menu_entry->{'args'}};
+ if (defined($arg->{'text'})) {
+ $current = _merge_text($self, $current, $arg->{'text'});
+ } else {
+ while (@{$arg->{'contents'}}) {
+ my $content = shift @{$arg->{'contents'}};
+ if (defined($content->{'text'})) {
+ $current = _merge_text($self, $current,
+ $content->{'text'});
+ $content = undef;
+ } else {
+ $content->{'parent'} = $current;
+ push @{$current->{'contents'}}, $content;
+ }
+ }
+ }
+ $arg = undef;
+ }
+ # MENU_COMMENT open
+ $menu_entry = undef;
+ } else {
+ print STDERR "MENU ENTRY END LINE\n" if ($self->{'DEBUG'});
+ $current = $current->{'parent'};
+ $current = _enter_menu_entry_node($self, $current, $line_nr);
+ if (defined($end_comment)) {
+ $end_comment->{'parent'} = $current;
+ push @{$current->{'contents'}}, $end_comment;
+ }
+ }
+ # def line
+ } elsif ($current->{'parent'}
+ and $current->{'parent'}->{'type'}
+ and $current->{'parent'}->{'type'} eq 'def_line') {
+ my $def_context = pop @{$self->{'context_stack'}};
+ if ($def_context ne 'def') {
+ $self->_bug_message("context $def_context instead of def",
+ $line_nr, $current);
+ die;
+ }
+ my $def_command = $current->{'parent'}->{'extra'}->{'def_command'};
+ my $arguments = $self->_parse_def($def_command,
+ $current->{'contents'});
+ if (scalar(@$arguments)) {
+ $current->{'parent'}->{'extra'}->{'def_args'} = $arguments;
+ my $def_parsed_hash;
+ foreach my $arg (@$arguments) {
+ die if (!defined($arg->[0]));
+ last if ($arg->[0] eq 'arg' or $arg->[0] eq 'typearg'
+ or $arg->[0] eq 'delimiter');
+ next if ($arg->[0] eq 'spaces');
+ # change of type is done in _parse_def.
+ #if ($arg->[1]->{'type'} and $arg->[1]->{'type'} eq 'bracketed') {
+ # $def_parsed_hash->{$arg->[0]} = { 'contents' => $arg->[1]->{'contents'},
+ # 'type' => 'bracketed_def_content',
+ # 'parent' => $arg->[1]->{'parent'}};
+ #} else {
+ # $def_parsed_hash->{$arg->[0]} = $arg->[1];
+ #}
+ $def_parsed_hash->{$arg->[0]} = $arg->[1];
+ }
+ $current->{'parent'}->{'extra'}->{'def_parsed_hash'} = $def_parsed_hash;
+ # do an standard index entry tree
+ my $index_entry;
+ if (defined($def_parsed_hash->{'name'})) {
+ $index_entry = $def_parsed_hash->{'name'}
+ # empty bracketed
+ unless ($def_parsed_hash->{'name'}->{'type'}
+ and $def_parsed_hash->{'name'}->{'type'} eq 'bracketed_def_content'
+ and (!$def_parsed_hash->{'name'}->{'contents'}
+ or (!scalar(@{$def_parsed_hash->{'name'}->{'contents'}}))
+ or (scalar(@{$def_parsed_hash->{'name'}->{'contents'}}) == 1
+ and defined($def_parsed_hash->{'name'}->{'contents'}->[0]->{'text'})
+ and $def_parsed_hash->{'name'}->{'contents'}->[0]->{'text'} !~ /\S/)));
+ }
+ if (defined($index_entry)) {
+ my $index_contents_normalized;
+ if ($def_parsed_hash->{'class'}) {
+ if ($command_index_prefix{$def_command} eq 'f') {
+ $index_entry = $self->gdt('{name} on {class}',
+ {'name' => $def_parsed_hash->{'name'},
+ 'class' => $def_parsed_hash->{'class'}});
+ $index_contents_normalized
+ = [_non_bracketed_contents($def_parsed_hash->{'name'}),
+ { 'text' => ' on '},
+ _non_bracketed_contents($def_parsed_hash->{'class'})];
+ } elsif ($command_index_prefix{$def_command} eq 'v'
+ and $def_command ne 'defcv') {
+ $index_entry = $self->gdt('{name} of {class}',
+ {'name' => $def_parsed_hash->{'name'},
+ 'class' => $def_parsed_hash->{'class'}});
+ $index_contents_normalized
+ = [_non_bracketed_contents($def_parsed_hash->{'name'}),
+ { 'text' => ' of '},
+ _non_bracketed_contents($def_parsed_hash->{'class'})];
+ }
+ }
+ $index_contents_normalized = [$index_entry]
+ if (!defined($index_contents_normalized));
+ my $index_contents;
+ # 'root_line' is the container returned by gdt.
+ if ($index_entry->{'type'} and $index_entry->{'type'} eq 'root_line') {
+ $index_contents = $index_entry->{'contents'};
+ } else {
+ $index_contents = [$index_entry];
+ }
+ _enter_index_entry($self,
+ $current->{'parent'}->{'extra'}->{'def_command'},
+ $current->{'parent'}->{'extra'}->{'original_def_cmdname'},
+ $current->{'parent'}, $index_contents,
+ $index_contents_normalized, $line_nr);
+ } else {
+ $self->_command_warn($current->{'parent'}, $line_nr,
+ $self->__('missing name for @%s'),
+ $current->{'parent'}->{'extra'}->{'original_def_cmdname'});
+ }
+ } else {
+ $self->_command_warn($current->{'parent'}, $line_nr,
+ $self->__('missing category for @%s'),
+ $current->{'parent'}->{'extra'}->{'original_def_cmdname'});
+ }
+ $current = $current->{'parent'}->{'parent'};
+ $current = $self->_begin_preformatted($current);
+
+ # other block command lines
+ } elsif ($current->{'type'}
+ and $current->{'type'} eq 'block_line_arg') {
+ my $empty_text;
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'line') {
+ $self->_bug_message("context $context instead of line in block_line_arg",
+ $line_nr, $current);
+ }
+ # @multitable args
+ if ($current->{'parent'}->{'cmdname'}
+ and $current->{'parent'}->{'cmdname'} eq 'multitable') {
+ # parse the prototypes and put them in a special arg
+ my @prototype_row;
+ # do the same but keeping spaces information
+ my @prototype_line;
+ foreach my $content (@{$current->{'contents'}}) {
+ if ($content->{'type'} and $content->{'type'} eq 'bracketed') {
+ push @prototype_row, { 'contents' => $content->{'contents'},
+ 'parent' => $content->{'parent'},
+ 'type' => 'bracketed_multitable_prototype'};
+ push @prototype_line, $content;
+ } elsif ($content->{'text'}) {
+ if ($content->{'text'} =~ /\S/) {
+ foreach my $prototype (split /\s+/, $content->{'text'}) {
+ push @prototype_row, { 'text' => $prototype,
+ 'type' => 'row_prototype' } unless ($prototype eq '');
+ }
+ }
+ # The regexp breaks between characters, with a non space followed
+ # by a space or a space followed by non space. It is like \b, but
+ # for \s \S, and not \w \W.
+ foreach my $prototype_or_space (split /(?<=\S)(?=\s)|(?=\S)(?<=\s)/,
+ $content->{'text'}) {
+ if ($prototype_or_space =~ /\S/) {
+ push @prototype_line, {'text' => $prototype_or_space,
+ 'type' => 'row_prototype' };
+ } elsif ($prototype_or_space =~ /\s/) {
+ push @prototype_line, {'text' => $prototype_or_space,
+ 'type' => 'prototype_space' };
+ }
+ }
+ } else {
+ # FIXME could this happen? Should be a debug message?
+ if (!$content->{'cmdname'}) {
+ $self->_command_warn($current, $line_nr,
+ $self->__("unexpected argument on \@%s line: %s"),
+ $current->{'cmdname'},
+ Texinfo::Convert::Texinfo::convert({ $content->{'contents'} }));
+ } elsif ($content->{'cmdname'} eq 'c'
+ or $content->{'cmdname'} eq 'comment') {
+ } else {
+ push @prototype_row, $content;
+ push @prototype_line, $content;
+ }
+ }
+ }
+
+ my $multitable = $current->{'parent'};
+ $multitable->{'extra'}->{'max_columns'} = scalar(@prototype_row);
+ if (!scalar(@prototype_row)) {
+ $self->_command_warn($multitable, $line_nr,
+ $self->__("empty multitable"));
+ }
+ $multitable->{'extra'}->{'prototypes'} = \@prototype_row;
+ $multitable->{'extra'}->{'prototypes_line'} = \@prototype_line;
+
+ } else {
+ $self->_isolate_last_space($current, 'space_at_end_block_command');
+ $self->_register_command_arg($current, 'block_command_line_contents');
+ }
+ # @float args
+ if ($current->{'parent'}->{'cmdname'}
+ and $current->{'parent'}->{'cmdname'} eq 'float') {
+ my $float = $current->{'parent'};
+ $float->{'line_nr'} = $line_nr;
+ my $type = '';
+ if (@{$float->{'args'}}) {
+ if ($float->{'args'}->[1]) {
+ my $float_label = _parse_node_manual($float->{'args'}->[1]);
+ _check_internal_node($self, $float_label, $line_nr);
+ if (defined($float_label) and $float_label->{'node_content'}
+ and $float_label->{'normalized'} =~ /[^-]/) {
+ _register_label($self, $float, $float_label, $line_nr);
+ }
+ }
+ _parse_float_type($float);
+ $type = $float->{'extra'}->{'type'}->{'normalized'};
+ }
+ push @{$self->{'floats'}->{$type}}, $float
+ unless (_ignore_global_commands($self));
+ $float->{'float_section'} = $self->{'current_section'}
+ if (defined($self->{'current_section'}));
+ }
+ $current = $current->{'parent'};
+ delete $current->{'remaining_args'};
+ # don't consider empty argument of block @-commands as argument,
+ # reparent them as contents
+ if ($current->{'args'}->[0]->{'contents'}->[0]
+ and $current->{'args'}->[0]->{'contents'}->[0]->{'type'}
+ and $current->{'args'}->[0]->{'contents'}->[0]->{'type'} eq 'empty_line_after_command')
+ {
+ my $empty_text = $current->{'args'}->[0]->{'contents'}->[0];
+ $empty_text->{'parent'} = $current;
+ unshift @{$current->{'contents'}}, $empty_text;
+ delete $current->{'args'};
+ }
+ # Additionally, remove empty arguments as far as possible
+ _remove_empty_content_arguments($current);
+
+ if ($current->{'cmdname'}
+ and $block_item_commands{$current->{'cmdname'}}) {
+ if ($current->{'cmdname'} eq 'enumerate') {
+ my $spec = 1;
+ if ($current->{'extra'}->{'block_command_line_contents'}
+ and defined($current->{'extra'}->{'block_command_line_contents'}->[0])) {
+ if (scalar(@{$current->{'extra'}->{'block_command_line_contents'}->[0]}) > 1) {
+ $self->_command_error($current, $line_nr,
+ $self->__("superfluous argument to \@%s"),
+ $current->{'cmdname'});
+ }
+ my $arg = $current->{'extra'}->{'block_command_line_contents'}->[0]->[0];
+ if (!defined($arg->{'text'}) or $arg->{'text'} !~ /^(([[:digit:]]+)|([[:alpha:]]+))$/) {
+ $self->_command_error($current, $line_nr,
+ $self->__("bad argument to \@%s"),
+ $current->{'cmdname'});
+ } else {
+ $spec = $arg->{'text'};
+ }
+ }
+ $current->{'extra'}->{'enumerate_specification'} = $spec;
+ } elsif ($item_line_commands{$current->{'cmdname'}}) {
+ if (!$current->{'extra'}
+ or !$current->{'extra'}->{'command_as_argument'}) {
+ $self->_command_error($current, $line_nr,
+ $self->__("%s requires an argument: the formatter for %citem"),
+ $current->{'cmdname'}, ord('@'));
+ } elsif (!$brace_commands{$current->{'extra'}->{'command_as_argument'}->{'cmdname'}}
+ and !$self->{'definfoenclose'}->{$current->{'extra'}->{'command_as_argument'}->{'cmdname'}}) {
+ $self->_command_error($current, $line_nr,
+ $self->__("command \@%s not accepting argument in brace should not be on \@%s line"),
+ $current->{'extra'}->{'command_as_argument'}->{'cmdname'},
+ $current->{'cmdname'});
+ delete $current->{'extra'}->{'command_as_argument'};
+ }
+ }
+ # This code checks that the command_as_argument of the @itemize
+ # is alone on the line, otherwise it is not a command_as_argument.
+ if ($current->{'extra'}
+ and $current->{'extra'}->{'command_as_argument'}
+ and $current->{'cmdname'} eq 'itemize') {
+ my @args = @{$current->{'args'}->[0]->{'contents'}};
+ while (@args) {
+ my $arg = shift @args;
+ last if ($arg eq $current->{'extra'}->{'command_as_argument'});
+ }
+ while (@args) {
+ my $arg = shift @args;
+ if (!(($arg->{'cmdname'}
+ and ($arg->{'cmdname'} eq 'c'
+ or $arg->{'cmdname'} eq 'comment'))
+ or (defined($arg->{'text'}) and $arg->{'text'} !~ /\S/))) {
+ #print STDERR " -> stop at "._print_current($arg)."\n";
+ delete $current->{'extra'}->{'command_as_argument'}->{'type'};
+ delete $current->{'extra'}->{'command_as_argument'};
+ last;
+ }
+ }
+ }
+ if ($current->{'extra'}
+ and $current->{'extra'}->{'command_as_argument'}
+ and $accent_commands{$current->{'extra'}->{'command_as_argument'}->{'cmdname'}}
+ and ($current->{'cmdname'} eq 'itemize'
+ or $item_line_commands{$current->{'cmdname'}})) {
+ $self->_command_warn($current, $line_nr,
+ $self->__("accent command `\@%s' not allowed as \@%s argument"),
+ $current->{'extra'}->{'command_as_argument'}->{'cmdname'},
+ $current->{'cmdname'});
+ delete $current->{'extra'}->{'command_as_argument'};
+ delete $current->{'extra'}->{'block_command_line_contents'};
+ }
+ if (!$current->{'extra'}->{'block_command_line_contents'}
+ and $current->{'cmdname'} eq 'itemize') {
+ $current->{'extra'}->{'block_command_line_contents'} = [
+ [ { 'cmdname' => 'bullet',
+ 'type' => 'command_as_argument',
+ 'parent' => $current }
+ ]
+ ];
+ $current->{'extra'}->{'command_as_argument'} =
+ $current->{'extra'}->{'block_command_line_contents'}->[0]->[0];
+ } elsif ($item_line_commands{$current->{'cmdname'}} and
+ ! $current->{'extra'}->{'command_as_argument'}) {
+ $current->{'extra'}->{'block_command_line_contents'} = [
+ [ { 'cmdname' => 'asis',
+ 'type' => 'command_as_argument',
+ 'parent' => $current }
+ ]
+ ];
+ $current->{'extra'}->{'command_as_argument'} =
+ $current->{'extra'}->{'block_command_line_contents'}->[0]->[0];
+ }
+ push @{$current->{'contents'}}, { 'type' => 'before_item',
+ 'contents' => [], 'parent', $current };
+ $current = $current->{'contents'}->[-1];
+ }
+ if ($current->{'cmdname'} and $menu_commands{$current->{'cmdname'}}) {
+ push @{$current->{'contents'}}, {'type' => 'menu_comment',
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ print STDERR "MENU_COMMENT OPEN\n" if ($self->{'DEBUG'});
+ push @{$self->{'context_stack'}}, 'preformatted';
+ }
+ $current = $self->_begin_preformatted($current);
+
+ # if we are after a @end verbatim, we must restart a preformatted if needed,
+ # since there is no @end command explicitly associated to raw commands
+ # it won't be done elsewhere.
+ } elsif ($current->{'contents'}
+ and $current->{'contents'}->[-1]
+ and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'empty_line_after_command'
+ and $current->{'contents'}->[-1]->{'extra'}
+ and $current->{'contents'}->[-1]->{'extra'}->{'command'}
+ and $current->{'contents'}->[-1]->{'extra'}->{'command'}->{'cmdname'} eq 'verbatim') {
+ $current = $self->_begin_preformatted($current);
+ # misc command line arguments
+ # Never go here if skipline/noarg/...
+ } elsif ($current->{'type'}
+ and $current->{'type'} eq 'misc_line_arg') {
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'line') {
+ $self->_bug_message("context $context instead of line in misc_line_arg",
+ $line_nr, $current);
+ }
+ $self->_isolate_last_space($current);
+
+ # first parent is the @command, second is the parent
+ $current = $current->{'parent'};
+ my $misc_cmd = $current;
+ my $command = $current->{'cmdname'};
+ my $end_command;
+ print STDERR "MISC END \@$command\n" if ($self->{'DEBUG'});
+ if ($self->{'misc_commands'}->{$command} =~ /^\d$/) {
+ my $args = _parse_line_command_args($self, $current, $line_nr);
+ $current->{'extra'}->{'misc_args'} = $args if (defined($args));
+ } elsif ($self->{'misc_commands'}->{$command} eq 'text') {
+ my $text = Texinfo::Convert::Text::convert($current->{'args'}->[0],
+ {'code' => 1,
+ Texinfo::Common::_convert_text_options($self)});
+ if ($text eq '') {
+ $self->_command_warn($current, $line_nr,
+ $self->__("\@%s missing argument"), $command);
+ $current->{'extra'}->{'missing_argument'} = 1;
+ } else {
+ $current->{'extra'}->{'text_arg'} = $text;
+ if ($command eq 'end') {
+ # REMACRO
+ my $line = $text;
+ if ($line =~ s/^([[:alnum:]][[:alnum:]-]+)//) {
+ $end_command = $1;
+
+ if (!exists $block_commands{$end_command}) {
+ $self->_command_warn($current, $line_nr,
+ $self->__("unknown \@end %s"), $end_command);
+ $end_command = undef;
+ } else {
+ print STDERR "END BLOCK $end_command\n" if ($self->{'DEBUG'});
+ if ($block_commands{$end_command} eq 'conditional') {
+ if (@{$self->{'conditionals_stack'}}
+ and $self->{'conditionals_stack'}->[-1] eq $end_command) {
+ pop @{$self->{'conditionals_stack'}};
+ } else {
+ $self->_command_error($current, $line_nr,
+ $self->__("unmatched `%c%s'"), ord('@'), 'end');
+ $end_command = undef;
+ }
+ }
+ $current->{'extra'}->{'command_argument'} = $end_command
+ if (defined($end_command));
+ }
+ if ($line =~ /\S/ and defined($end_command)) {
+ my $texi_line
+ = Texinfo::Convert::Texinfo::convert($current->{'args'}->[0]);
+ $texi_line =~ s/^\s*([[:alnum:]][[:alnum:]-]+)//;
+ $self->_command_error($current, $line_nr,
+ $self->__("superfluous argument to \@%s %s: %s"),
+ $command, $end_command, $texi_line);
+ }
+ } else {
+ $self->_command_error($current, $line_nr,
+ $self->__("bad argument to \@%s: %s"),
+ $command, $line);
+ }
+ } elsif ($command eq 'include') {
+ my $file = Texinfo::Common::locate_include_file($self, $text) ;
+ if (defined($file)) {
+ my $filehandle = do { local *FH };
+ if (open ($filehandle, $file)) {
+ $included_file = 1;
+ binmode($filehandle, ":encoding($self->{'INPUT_PERL_ENCODING'})")
+ if (defined($self->{'INPUT_PERL_ENCODING'}));
+ print STDERR "Included $file($filehandle)\n" if ($self->{'DEBUG'});
+ unshift @{$self->{'input'}}, {
+ 'name' => $file,
+ 'line_nr' => 0,
+ 'pending' => [],
+ 'fh' => $filehandle };
+ } else {
+ $self->_command_error($current, $line_nr,
+ $self->__("\@%s: could not open %s: %s"),
+ $command, $text, $!);
+ }
+ } else {
+ $self->_command_error($current, $line_nr,
+ $self->__("\@%s: could not find %s"),
+ $command, $text);
+ }
+ } elsif ($command eq 'documentencoding') {
+ my ($texinfo_encoding, $perl_encoding, $input_encoding)
+ = Texinfo::Encoding::encoding_alias($text);
+ $self->_command_warn($current, $line_nr,
+ $self->__("encoding `%s' is not a canonical texinfo encoding"),
+ $text)
+ if (!$texinfo_encoding or $texinfo_encoding ne lc($text));
+ if (! _ignore_global_commands($self)) {
+ if ($input_encoding) {
+ $current->{'extra'}->{'input_encoding_name'} = $input_encoding;
+ }
+ if (!$perl_encoding) {
+ $self->_command_warn($current, $line_nr,
+ $self->__("unrecognized encoding name `%s'"), $text);
+ } else {
+ $current->{'extra'}->{'input_perl_encoding'} = $perl_encoding;
+
+ if ($input_encoding) {
+ if (!$self->{'set'}->{'INPUT_ENCODING_NAME'}) {
+ $self->{'INPUT_ENCODING_NAME'} = $input_encoding;
+ $self->{'info'}->{'input_encoding_name'} = $input_encoding;
+ }
+ }
+
+ if (!$self->{'set'}->{'INPUT_PERL_ENCODING'}) {
+ $self->{'INPUT_PERL_ENCODING'} = $perl_encoding;
+ $self->{'info'}->{'input_perl_encoding'} = $perl_encoding;
+ foreach my $input (@{$self->{'input'}}) {
+ binmode($input->{'fh'}, ":encoding($perl_encoding)") if ($input->{'fh'});
+ }
+ }
+ }
+ }
+ } elsif ($command eq 'documentlanguage') {
+ my @messages = Texinfo::Common::warn_unknown_language($text,
+ $self->{'gettext'});
+ foreach my $message(@messages) {
+ $self->_command_warn($current, $line_nr, $message);
+ }
+ if (!$self->{'set'}->{'documentlanguage'}
+ and !_ignore_global_commands($self)) {
+ $self->{'documentlanguage'} = $text;
+ }
+ }
+ }
+ } elsif ($command eq 'node') {
+ foreach my $arg (@{$current->{'args'}}) {
+ my $node = _parse_node_manual($arg);
+ push @{$current->{'extra'}->{'nodes_manuals'}}, $node;
+ }
+ if (_check_node_label($self, $current->{'extra'}->{'nodes_manuals'}->[0],
+ $command, $line_nr)) {
+ if (_register_label($self, $current,
+ $current->{'extra'}->{'nodes_manuals'}->[0], $line_nr)) {
+ $self->{'current_node'} = $current;
+ push @{$self->{'nodes'}}, $current;
+ }
+ }
+ } elsif ($command eq 'listoffloats') {
+ # Empty listoffloats is allowed
+ _parse_float_type($current);
+ #if (!_parse_float_type($current)) {
+ # $self->line_error (sprintf($self->__("\@%s missing argument"),
+ # $command), $line_nr);
+ #}
+ # handle all the other 'line' commands. Here just check that they
+ # have an argument and prepare contents without spaces.
+ } else {
+ my @contents = @{$current->{'args'}->[0]->{'contents'}};
+ _trim_spaces_comment_from_content(\@contents);
+ # empty @top is allowed
+ if (!scalar(@contents) and $command ne 'top') {
+ $self->_command_warn($current, $line_nr,
+ $self->__("\@%s missing argument"), $command);
+ $current->{'extra'}->{'missing_argument'} = 1;
+ } else {
+ $current->{'extra'}->{'misc_content'} = \@contents;
+ if (($command eq 'item' or $command eq 'itemx')
+ and $self->{'command_index_prefix'}->{$current->{'parent'}->{'cmdname'}}) {
+ _enter_index_entry($self, $current->{'parent'}->{'cmdname'},
+ $command, $current,
+ $current->{'extra'}->{'misc_content'},
+ undef, $line_nr);
+ } elsif ($self->{'command_index_prefix'}->{$current->{'cmdname'}}) {
+ _enter_index_entry($self, $current->{'cmdname'},
+ $current->{'cmdname'}, $current,
+ $current->{'extra'}->{'misc_content'},
+ undef, $line_nr);
+ $current->{'type'} = 'index_entry_command';
+ }
+ }
+ if (defined($command_structuring_level{$command})) {
+ $current->{'level'} = $command_structuring_level{$command};
+ }
+ }
+ $current = $current->{'parent'};
+ if ($end_command) {
+ print STDERR "END COMMAND $end_command\n" if ($self->{'DEBUG'});
+ my $end = pop @{$current->{'contents'}};
+ if ($block_commands{$end_command} ne 'conditional') {
+ my $closed_command;
+ ($closed_command, $current)
+ = _close_commands($self, $current, $line_nr, $end_command);
+ my $inline_copying;
+ if ($closed_command) {
+ $misc_cmd->{'extra'}->{'command'} = $closed_command;
+ $closed_command->{'extra'}->{'end_command'} = $misc_cmd;
+ $self->_close_command_cleanup($closed_command);
+ $end->{'parent'} = $closed_command;
+
+ # register @insertcopying as a macro if INLINE_INSERTCOPYING is set.
+ if ($end_command eq 'copying' and $self->{'INLINE_INSERTCOPYING'}) {
+ # remove the end of line following @copying.
+ my @contents = @{$closed_command->{'contents'}};
+ shift @contents if ($contents[0] and $contents[0]->{'type'}
+ and ($contents[0]->{'type'} eq 'empty_line_after_command'
+ or $contents[0]->{'type'} eq 'empty_spaces_after_command'));
+ # the macrobody is the @copying content converted to Texinfo.
+ my $body = Texinfo::Convert::Texinfo::convert(
+ {'contents' => \@contents});
+
+ #chomp ($body);
+ $self->{'macros'}->{'insertcopying'} = {
+ 'args' => [{'text' => 'insertcopying', 'type' => 'macro_name'}],
+ 'cmdname' => 'macro',
+ 'extra' => {'macrobody' =>
+ $self->_strip_macrobody_leading_space($body)}
+ };
+ $inline_copying = 1;
+ print STDERR "INLINE_INSERTCOPYING as macro\n" if ($self->{'DEBUG'});
+ }
+ push @{$closed_command->{'contents'}}, $end;
+
+ # closing a menu command, but still in a menu. Open a menu_comment
+ if ($menu_commands{$closed_command->{'cmdname'}}
+ and $self->{'context_stack'}->[-1] eq 'menu') {
+ print STDERR "CLOSE MENU but still in menu context\n"
+ if ($self->{'DEBUG'});
+ push @{$current->{'contents'}}, {'type' => 'menu_comment',
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ push @{$self->{'context_stack'}}, 'preformatted';
+ }
+ } else {
+ #print STDERR "LLLLLLLLLLLL Cannot be here...\n";
+ }
+ $current = $self->_begin_preformatted($current)
+ if ($close_preformatted_commands{$end_command});
+ }
+ } else {
+ $current = $self->_begin_preformatted($current)
+ if ($close_preformatted_commands{$command});
+ }
+ # if a file was included, remove completly the include file command.
+ # Also ignore @setfilename in included file, as said in the manual.
+ if ($included_file or ($command eq 'setfilename'
+ and scalar(@{$self->{'input'}}) > 1)) {
+ # TODO keep the information with sourcemark
+ pop @{$current->{'contents'}};
+ } elsif ($command eq 'setfilename'
+ and ($self->{'current_node'} or $self->{'current_section'})) {
+ $self->_command_warn($misc_cmd, $line_nr,
+ $self->__("\@%s after the first element"), $command);
+ # columnfractions
+ } elsif ($command eq 'columnfractions') {
+ # in a multitable, we are in a block_line_arg
+ if (!$current->{'parent'} or !$current->{'parent'}->{'cmdname'}
+ or $current->{'parent'}->{'cmdname'} ne 'multitable') {
+ $self->_command_error($current, $line_nr,
+ $self->__("\@%s only meaningful on a \@multitable line"),
+ $command);
+ } else {
+ # This is the multitable block_line_arg line context
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'line') {
+ $self->_bug_message("context $context instead of line for multitable",
+ $line_nr, $current);
+ }
+ $current = $current->{'parent'};
+ $current->{'extra'}->{'max_columns'} = 0;
+ if (defined($misc_cmd->{'extra'}->{'misc_args'})) {
+ $current->{'extra'}->{'max_columns'} =
+ scalar(@{$misc_cmd->{'extra'}->{'misc_args'}});
+ $current->{'extra'}->{'columnfractions'} = $misc_cmd->{'extra'}->{'misc_args'};
+ }
+ push @{$current->{'contents'}}, { 'type' => 'before_item',
+ 'contents' => [], 'parent', $current };
+ $current = $current->{'contents'}->[-1];
+ }
+ } elsif ($root_commands{$command}) {
+ $current = $current->{'contents'}->[-1];
+ delete $current->{'remaining_args'};
+ $current->{'contents'} = [];
+
+ # we never should be in a raw format bock, so we don't check for
+ # _ignore_global_commands($self)
+ # associate the section (not part) with the current node.
+ if ($command ne 'node' and $command ne 'part') {
+ if ($self->{'current_node'}
+ and !$self->{'current_node'}->{'extra'}->{'associated_section'}) {
+ $self->{'current_node'}->{'extra'}->{'associated_section'} = $current;
+ $current->{'extra'}->{'associated_node'} = $self->{'current_node'};
+ }
+ if ($self->{'current_parts'}) {
+ $current->{'extra'}->{'associated_part'} = $self->{'current_parts'}->[-1];
+ foreach my $part (@{$self->{'current_parts'}}) {
+ $part->{'extra'}->{'part_associated_section'} = $current;
+ if ($current->{'cmdname'} eq 'top') {
+ $self->line_warn(sprintf($self->__(
+ "\@%s should not be associated with \@top"),
+ $part->{'cmdname'}), $part->{'line_nr'});
+ }
+ }
+ delete $self->{'current_parts'};
+ }
+ $self->{'current_section'} = $current;
+ } elsif ($command eq 'part') {
+ push @{$self->{'current_parts'}}, $current;
+ if ($self->{'current_node'}
+ and !$self->{'current_node'}->{'extra'}->{'associated_section'}) {
+ $self->line_warn (sprintf($self->__(
+ "\@node precedes \@%s, but parts may not be associated with nodes"),
+ $command), $line_nr);
+ }
+ }
+ }
+ # do that last in order to have the line processed if one of the above
+ # case is also set.
+ } elsif (
+ $current->{'contents'}
+ and (scalar(@{$current->{'contents'}}) == 1
+ and (($current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'empty_line_after_command'))
+ or (scalar(@{$current->{'contents'}}) == 2
+ and $current->{'contents'}->[-1]->{'cmdname'}
+ and ($current->{'contents'}->[-1]->{'cmdname'} eq 'c'
+ or $current->{'contents'}->[-1]->{'cmdname'} eq 'comment')
+ and $current->{'contents'}->[-2]
+ and $current->{'contents'}->[-2]->{'type'}
+ and $current->{'contents'}->[-2]->{'type'} eq 'empty_line_after_command'))) {
+ # empty line after a @menu or before a preformatted. Reparent to the menu
+ # or other format
+ if ($current->{'type'}
+ and $preformatted_contexts{$current->{'type'}}) {
+ my $parent = $current->{'parent'};
+ if ($parent->{'type'} and $parent->{'type'} eq 'menu_comment'
+ and scalar(@{$parent->{'contents'}}) == 1) {
+ $parent = $parent->{'parent'};
+ }
+ my $to_reparent = pop @{$parent->{'contents'}};
+ print STDERR "LINE AFTER COMMAND IN PREFORMATTED ($to_reparent->{'type'})\n" if ($self->{'DEBUG'});
+ while (@{$current->{'contents'}}) {
+ my $content = shift @{$current->{'contents'}};
+ $content->{'parent'} = $parent;
+ push @{$parent->{'contents'}}, $content;
+ }
+ push @{$parent->{'contents'}}, $to_reparent;
+ }
+ }
+
+ # this happens if there is a nesting of line @-commands on a line.
+ # they are reprocessed here.
+ if ($self->{'context_stack'}->[-1] eq 'line'
+ or $self->{'context_stack'}->[-1] eq 'def') {
+ print STDERR "Still opened line command $self->{'context_stack'}->[-1]:"._print_current($current)
+ if ($self->{'DEBUG'});
+ if ($self->{'context_stack'}->[-1] eq 'def') {
+ while ($current->{'parent'} and !($current->{'parent'}->{'type'}
+ and $current->{'parent'}->{'type'} eq 'def_line')) {
+ $current = $self->_close_current($current, $line_nr);
+ }
+ } else {
+ while ($current->{'parent'} and !($current->{'type'}
+ and ($current->{'type'} eq 'block_line_arg'
+ or $current->{'type'} eq 'misc_line_arg'))) {
+ $current = $self->_close_current($current, $line_nr);
+ }
+ }
+
+ # check for infinite loop bugs...
+ if ($current eq $current_old) {
+ my $indent = '- ';
+ my $tree_msg = $indent . _print_current($current);
+ while ($current->{'parent'}) {
+ $indent = '-'.$indent;
+ $current = $current->{'parent'};
+ $tree_msg .= $indent . _print_current($current);
+ }
+ $self->_bug_message("Nothing closed while a line context remains\n$tree_msg",
+ $line_nr);
+ die;
+ }
+
+ $current = $self->_end_line($current, $line_nr);
+ }
+ return $current;
+}
+
+# $command may be undef if we are after a wrong misc command such as
+# a buggy @tab.
+sub _start_empty_line_after_command($$$) {
+ my $line = shift;
+ my $current = shift;
+ my $command = shift;
+ $line =~ s/^([^\S\r\n]*)//;
+ push @{$current->{'contents'}}, { 'type' => 'empty_line_after_command',
+ 'text' => $1,
+ 'parent' => $current,
+ };
+ if (defined($command)) {
+ $current->{'contents'}->[-1]->{'extra'} = {'command' => $command};
+ $command->{'extra'}->{'spaces_after_command'} = $current->{'contents'}->[-1];
+ }
+ return $line;
+}
+
+sub _check_empty_node($$$$)
+{
+ my $self = shift;
+ my $parsed_node = shift;
+ my $command = shift;
+ my $line_nr = shift;
+ if (!defined($parsed_node) or !$parsed_node->{'node_content'}) {
+ $self->line_error (sprintf($self->__("empty argument in \@%s"),
+ $command), $line_nr);
+ return 0;
+ } elsif ($parsed_node->{'normalized'} !~ /[^-]/) {
+ $self->line_error (sprintf($self->__("empty node name after expansion `%s'"),
+ Texinfo::Convert::Texinfo::convert({'contents'
+ => $parsed_node->{'node_content'}})),
+ $line_nr);
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+sub _check_internal_node($$$)
+{
+ my $self = shift;
+ my $parsed_node = shift;
+ my $line_nr = shift;
+ if ($parsed_node and $parsed_node->{'manual_content'}) {
+ $self->line_error (sprintf($self->__("syntax for an external node used for `%s'"),
+ _node_extra_to_texi($parsed_node)), $line_nr)
+ }
+}
+
+sub _check_node_label($$$$)
+{
+ my $self = shift;
+ my $parsed_node = shift;
+ my $command = shift;
+ my $line_nr = shift;
+ _check_internal_node($self, $parsed_node, $line_nr);
+ return _check_empty_node($self, $parsed_node, $command, $line_nr);
+}
+
+sub _register_extra_menu_entry_information($$;$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+
+ foreach my $arg (@{$current->{'args'}}) {
+ if ($arg->{'type'} eq 'menu_entry_name') {
+ $current->{'extra'}->{'menu_entry_name'} = $arg;
+ my $normalized_menu_entry_name =
+ Texinfo::Convert::NodeNameNormalization::normalize_node($arg);
+ if ($normalized_menu_entry_name !~ /[^-]/) {
+ $self->line_warn(sprintf($self->__("empty menu entry name in `%s'"),
+ Texinfo::Convert::Texinfo::convert($current)), $line_nr);
+ }
+ } elsif ($arg->{'type'} eq 'menu_entry_node') {
+ $self->_isolate_last_space($arg, 'space_at_end_menu_node');
+ my $parsed_entry_node = _parse_node_manual($arg);
+ if (! defined($parsed_entry_node)) {
+ if ($self->{'SHOW_MENU'}) {
+ $self->line_error ($self->__("empty node name in menu entry"), $line_nr);
+ }
+ } else {
+ $current->{'extra'}->{'menu_entry_node'} = $parsed_entry_node;
+ }
+ } elsif ($arg->{'type'} eq 'menu_entry_description') {
+ $current->{'extra'}->{'menu_entry_description'} = $arg;
+ }
+ }
+}
+
+sub _enter_menu_entry_node($$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $line_nr = shift;
+ my $description = { 'type' => 'menu_entry_description',
+ 'contents' => [],
+ 'parent' => $current };
+ push @{$current->{'args'}}, $description;
+ _register_extra_menu_entry_information($self, $current, $line_nr);
+ $current->{'line_nr'} = $line_nr;
+ $current = $description;
+ push @{$current->{'contents'}}, {'type' => 'preformatted',
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ push @{$self->{'context_stack'}}, 'preformatted';
+ return $current;
+}
+
+sub _register_command_arg($$$)
+{
+ my $self = shift;
+ my $current = shift;
+ my $type = shift;
+ my @contents = @{$current->{'contents'}};
+ _trim_spaces_comment_from_content(\@contents);
+ if (scalar(@contents)) {
+ push @{$current->{'parent'}->{'extra'}->{$type}}, \@contents;
+ } else {
+ push @{$current->{'parent'}->{'extra'}->{$type}}, undef;
+ }
+}
+
+sub _command_with_command_as_argument($)
+{
+ my $current = shift;
+ return ($current and $current->{'type'}
+ and $current->{'type'} eq 'block_line_arg'
+ and $current->{'parent'}
+ and $current->{'parent'}->{'cmdname'} and
+ ($current->{'parent'}->{'cmdname'} eq 'itemize'
+ or $item_line_commands{$current->{'parent'}->{'cmdname'}})
+ and (scalar(@{$current->{'contents'}}) == 1
+ or (scalar(@{$current->{'contents'}}) == 2
+ and defined($current->{'contents'}->[0]->{'text'})
+ and $current->{'contents'}->[0]->{'text'}
+ =~ /^[^\S\r\n]*/)))
+}
+
+# $marked_as_invalid_command may be undef, if there is no
+# tree element because the @-command construct is incorrect, for example
+# wrong @tab.
+sub _mark_and_warn_invalid($$$$$)
+{
+ my $self = shift;
+ my $command = shift;
+ my $invalid_parent = shift;
+ my $line_nr = shift;
+ my $marked_as_invalid_command = shift;
+
+ if (defined($invalid_parent)) {
+ $self->line_warn(sprintf($self->__("\@%s should not appear in \@%s"),
+ $command, $invalid_parent), $line_nr);
+ $marked_as_invalid_command->{'extra'}->{'invalid_nesting'} = 1
+ if (defined($marked_as_invalid_command));
+ }
+}
+# the different types
+#c 'menu_entry'
+#c 'menu_entry'
+# t 'menu_entry_leading_text'
+#
+#t 'macro_arg_name'
+#t 'macro_arg_args'
+#
+#t 'raw'
+#
+#t 'misc_arg'
+#c 'misc_line_arg'
+#
+#c 'block_line_arg'
+#
+#c 'brace_command_arg'
+#c 'brace_command_context'
+#
+#c 'before_item' what comes after @*table, @itemize, @enumerate before
+# an @item
+#
+#c 'paragraph'
+#
+#a 'def_line'
+#
+#special for @verb, type is the character
+
+# the main subroutine
+sub _parse_texi($;$)
+{
+ my $self = shift;
+ my $root = shift;
+
+ $root = { 'contents' => [], 'type' => 'text_root' } if (!defined($root));
+ my $current = $root;
+
+ my $line_nr;
+
+ NEXT_LINE:
+ while (1) {
+ my $line;
+ ($line, $line_nr) = _next_text($self, $line_nr, $current);
+ last if (!defined($line));
+
+ if ($self->{'DEBUG'}) {
+ $current->{'HERE !!!!'} = 1; # marks where we are in the tree
+ if ($self->{'DEBUG'} >= 100) {
+ local $Data::Dumper::Indent = 1;
+ local $Data::Dumper::Purity = 1;
+ print STDERR "".Data::Dumper->Dump([$root], ['$root']);
+ }
+ my $line_text = '';
+ $line_text = "$line_nr->{'line_nr'}.$line_nr->{'macro'}" if ($line_nr);
+ print STDERR "NEW LINE(".join('|', @{$self->{'context_stack'}}).":@{$self->{'conditionals_stack'}}:$line_text): $line";
+ #print STDERR "CONTEXT_STACK ".join('|',@{$self->{'context_stack'}})."\n";
+ delete $current->{'HERE !!!!'};
+ }
+
+ if (not
+ # raw format or verb
+ (($current->{'cmdname'}
+ and $block_commands{$current->{'cmdname'}}
+ and ($block_commands{$current->{'cmdname'}} eq 'raw'
+ or $block_commands{$current->{'cmdname'}} eq 'conditional'))
+ or
+ ($current->{'parent'} and $current->{'parent'}->{'cmdname'}
+ and $current->{'parent'}->{'cmdname'} eq 'verb')
+ )
+ # not def line
+ and $self->{'context_stack'}->[-1] ne 'def') {
+ print STDERR "BEGIN LINE\n" if ($self->{'DEBUG'});
+ $line =~ s/^([^\S\r\n]*)//;
+ push @{$current->{'contents'}}, { 'type' => 'empty_line',
+ 'text' => $1,
+ 'parent' => $current };
+ }
+
+ while (1) {
+ # in a raw or ignored conditional block command
+ if ($current->{'cmdname'} and
+ $block_commands{$current->{'cmdname'}} and
+ ($block_commands{$current->{'cmdname'}} eq 'raw'
+ or $block_commands{$current->{'cmdname'}} eq 'conditional')) {
+ # r?macro may be nested
+ if (($current->{'cmdname'} eq 'macro'
+ or $current->{'cmdname'} eq 'rmacro')
+ and $line =~ /^\s*\@r?macro\s+/) {
+ $line =~ s/\s*\@(r?macro)//;
+ push @{$current->{'contents'}}, { 'cmdname' => $1,
+ 'parent' => $current,
+ 'contents' => [],
+ 'extra' => {'arg_line' => $line }};
+ $current = $current->{'contents'}->[-1];
+ last;
+ # ifclear/ifset may be nested
+ } elsif (($current->{'cmdname'} eq 'ifclear'
+ or $current->{'cmdname'} eq 'ifset'
+ or $current->{'cmdname'} eq 'ifcommanddefined'
+ or $current->{'cmdname'} eq 'ifcommandnotdefined')
+ and $line =~ /^\s*\@$current->{'cmdname'}/) {
+ $line =~ s/\s*\@($current->{'cmdname'})//;
+ push @{$current->{'contents'}}, { 'cmdname' => $1,
+ 'parent' => $current,
+ 'contents' => [],
+ 'extra' => {'line' => $line }};
+ $current = $current->{'contents'}->[-1];
+ last;
+ } elsif ($line =~ /^(\s*?)\@end\s+([a-zA-Z][\w-]*)/
+ and ($2 eq $current->{'cmdname'})) {
+ my $end_command = $2;
+ my $raw_command = $current;
+ $line =~ s/^(\s*?)(\@end\s+$current->{'cmdname'})//;
+ if ($1 eq '') {
+ # FIXME exclude other formats, like @macro, @ifset, @ignore?
+ if ($current->{'cmdname'} ne 'verbatim'
+ and @{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'raw') {
+ if ($current->{'contents'}->[-1]->{'text'} =~ s/(\n)//) {
+ push @{$current->{'contents'}}, {'type' => 'last_raw_newline',
+ 'text' => $1, 'parent' => $current};
+ }
+ }
+ } else {
+ push @{$current->{'contents'}},
+ { 'text' => $1, 'type' => 'raw', 'parent' => $current };
+ $self->line_warn(sprintf($self->__("\@end %s should only appear at a line beginning"),
+ $end_command), $line_nr);
+ }
+ # if there is a user defined macro that expandes to spaces, there
+ # will be a spurious warning.
+ $self->line_warn(sprintf($self->
+ __("superfluous argument to \@%s %s: %s"), 'end', $end_command,
+ $line), $line_nr)
+ if ($line =~ /\S/ and $line !~ /^\s*\@c(omment)?\b/);
+ # store toplevel macro specification
+ if (($end_command eq 'macro' or $end_command eq 'rmacro')
+ and (! $current->{'parent'}
+ or !$current->{'parent'}->{'cmdname'}
+ or ($current->{'parent'}->{'cmdname'} ne 'macro'
+ and $current->{'parent'}->{'cmdname'} ne 'rmacro'))) {
+ $current->{'extra'}->{'macrobody'} =
+ $self->_strip_macrobody_leading_space(
+ Texinfo::Convert::Texinfo::convert({ 'contents'
+ => $current->{'contents'} }));
+ if ($current->{'args'} and $current->{'args'}->[0]
+ and !_ignore_global_commands($self)) {
+ my $name = $current->{'args'}->[0]->{'text'};
+ if (exists($self->{'macros'}->{$name})) {
+ $self->line_warn(sprintf($self->__("macro `%s' previously defined"),
+ $name), $current->{'line_nr'});
+ $self->line_warn(sprintf($self->__(
+ "here is the previous definition of `%s'"),
+ $name), $self->{'macros'}->{$name}->{'line_nr'});
+ }
+ if ($all_commands{$name}) {
+ $self->line_warn(sprintf($self->__(
+ "redefining Texinfo language command: \@%s"),
+ $name), $current->{'line_nr'});
+ }
+ $self->{'macros'}->{$name} = $current
+ unless ($current->{'extra'}->{'invalid_syntax'});
+ }
+ }
+ $current = $current->{'parent'};
+ if ($block_commands{$end_command} eq 'conditional') {
+ # don't store ignored @if*
+ my $conditional = pop @{$current->{'contents'}};
+ if (!defined($conditional->{'cmdname'}
+ or $conditional->{'cmdname'} ne $end_command)) {
+ $self->_bug_message("Ignored command is not the conditional $end_command",
+ $line_nr, $conditional);
+ die;
+ }
+ # Ignore until end of line
+ if ($line !~ /\n/) {
+ ($line, $line_nr) = _new_line($self, $line_nr, $conditional);
+ print STDERR "IGNORE CLOSE line: $line" if ($self->{'DEBUG'});
+ }
+ print STDERR "CLOSED conditional $end_command\n" if ($self->{'DEBUG'});
+ last;
+ } else {
+ print STDERR "CLOSED raw $end_command\n" if ($self->{'DEBUG'});
+ $line = _start_empty_line_after_command($line, $current, $raw_command);
+ }
+ } else {
+ if (@{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'empty_line_after_command'
+ and $current->{'contents'}->[-1]->{'text'} !~ /\n/
+ and $line !~ /\S/) {
+ $current->{'contents'}->[-1]->{'text'} .= $line;
+ } else {
+ push @{$current->{'contents'}},
+ { 'text' => $line, 'type' => 'raw', 'parent' => $current };
+ }
+ last;
+ }
+ # in @verb. type should be 'brace_command_arg'
+ } elsif ($current->{'parent'} and $current->{'parent'}->{'cmdname'}
+ and $current->{'parent'}->{'cmdname'} eq 'verb') {
+ # collect the first character if not already done
+ if (!defined($current->{'parent'}->{'type'})) {
+ if ($line =~ /^$/) {
+ $current->{'parent'}->{'type'} = '';
+ $self->line_error(sprintf($self->
+ __("\@%s without associated character"), 'verb'), $line_nr);
+ } else {
+ $line =~ s/^(.)//;
+ $current->{'parent'}->{'type'} = $1;
+ }
+ }
+ my $char = quotemeta($current->{'parent'}->{'type'});
+ if ($line =~ s/^(.*?)$char\}/\}/) {
+ push @{$current->{'contents'}},
+ { 'text' => $1, 'type' => 'raw', 'parent' => $current }
+ if ($1 ne '');
+ print STDERR "END VERB\n" if ($self->{'DEBUG'});
+ } else {
+ push @{$current->{'contents'}},
+ { 'text' => $line, 'type' => 'raw', 'parent' => $current };
+ print STDERR "LINE VERB: $line" if ($self->{'DEBUG'});
+ last;
+ }
+ }
+
+ # this mostly happens in the following cases:
+ # after expansion of user defined macro that doesn't end with EOL
+ # after a protection of @\n in @def* line
+ # at the end of an expanded Texinfo fragment
+ while ($line eq '') {
+ print STDERR "EMPTY TEXT\n"
+ if ($self->{'DEBUG'});
+ ($line, $line_nr) = _next_text($self, $line_nr, $current);
+ if (!defined($line)) {
+ # end of the file or of a text fragment.
+ $current = _end_line ($self, $current, $line_nr);
+ # It may happen that there is an @include file on the line, it
+ # will be picked up at NEXT_LINE, beginning a new line
+ next NEXT_LINE;
+ }
+ }
+
+ # handle user defined macros before anything else since
+ # their expansion may lead to changes in the line
+ # REMACRO
+ if ($line =~ /^\@([[:alnum:]][[:alnum:]-]*)/
+ and ($self->{'macros'}->{$1}
+ or (exists $self->{'aliases'}->{$1} and
+ $self->{'macros'}->{$self->{'aliases'}->{$1}}))) {
+ $line =~ s/^\@([[:alnum:]][[:alnum:]-]*)//o;
+ my $command = $1;
+ my $alias_command;
+ if (exists($self->{'aliases'}->{$command})) {
+ $alias_command = $command;
+ $command = $self->{'aliases'}->{$command};
+ }
+
+ my $expanded_macro = $self->{'macros'}->{$command};
+ my $args_number = scalar(@{$expanded_macro->{'args'}}) -1;
+ my $arguments = [];
+ if ($line =~ s/^\s*{[^\S\f]*//) { # macro with args
+ ($arguments, $line, $line_nr) =
+ _expand_macro_arguments($self, $expanded_macro, $line, $line_nr);
+ } elsif (($args_number >= 2) or ($args_number <1)) {
+ # as agreed on the bug-texinfo mailing list, no warn when zero
+ # arg and not called with {}.
+ $self->line_warn(sprintf($self->__(
+ "\@%s defined with zero or more than one argument should be invoked with {}"),
+ $command), $line_nr)
+ if ($args_number >= 2);
+ } else {
+ if ($line !~ /\n/) {
+ ($line, $line_nr) = _new_line($self, $line_nr, $expanded_macro);
+ $line = '' if (!defined($line));
+ }
+ $line =~ s/^[^\S\f]*// if ($line =~ /[\S\f]/);
+ my $has_end_of_line = chomp $line;
+ $arguments = [$line];
+ $line = "\n" if ($has_end_of_line);
+ }
+ my $expanded = _expand_macro_body ($self, $expanded_macro,
+ $arguments, $line_nr);
+ print STDERR "MACROBODY: $expanded".'||||||'."\n"
+ if ($self->{'DEBUG'});
+ # empty result. It is ignored here.
+ if ($expanded eq '') {
+ next;
+ }
+ if ($self->{'MAX_MACRO_CALL_NESTING'}
+ and scalar(@{$self->{'macro_stack'}}) > $self->{'MAX_MACRO_CALL_NESTING'}) {
+ $self->line_warn(sprintf($self->__(
+"macro call nested too deeply (set MAX_NESTED_MACROS to override; current value %d)"),
+ $self->{'MAX_MACRO_CALL_NESTING'}), $line_nr);
+ next;
+ }
+ if ($expanded_macro->{'cmdname'} eq 'macro') {
+ my $found = 0;
+ foreach my $macro (@{$self->{'macro_stack'}}) {
+ if ($macro->{'args'}->[0]->{'text'} eq $command) {
+ $self->line_error(sprintf($self->__(
+ "recursive call of macro %s is not allowed; use \@rmacro if needed"),
+ $command), $line_nr);
+ $found = 1;
+ last;
+ }
+ }
+ next if ($found);
+ }
+
+ unshift @{$self->{'macro_stack'}}, $expanded_macro;
+ print STDERR "UNSHIFT MACRO_STACK: $expanded_macro->{'args'}->[0]->{'text'}\n"
+ if ($self->{'DEBUG'});
+ my $expanded_lines = _text_to_lines($expanded);
+ chomp ($expanded_lines->[-1]);
+ pop @$expanded_lines if ($expanded_lines->[-1] eq '');
+ print STDERR "MACRO EXPANSION LINES: ".join('|', @$expanded_lines)
+ ."|\nEND LINES MACRO EXPANSION\n" if ($self->{'DEBUG'});
+ next if (!@$expanded_lines);
+ my $new_lines = _complete_line_nr($expanded_lines,
+ $line_nr->{'line_nr'}, $line_nr->{'file_name'},
+ $expanded_macro->{'args'}->[0]->{'text'}, 1);
+ $line_nr->{'end_macro'} = 1;
+ unshift @{$self->{'input'}->[0]->{'pending'}}, [$line, $line_nr];
+ my $new_text = shift @$new_lines;
+ ($line, $line_nr) = ($new_text->[0], $new_text->[1]);
+ unshift @{$self->{'input'}->[0]->{'pending'}}, @$new_lines;
+
+ # Now handle all the cases that may lead to command closing
+ # or following character association with an @-command, especially
+ # accent command, that is handle @-command with braces that don't
+ # always need a brace.
+
+ # The condition below is only caught right after command opening,
+ # otherwise we are in the 'args' and not right in the command container.
+ } elsif ($current->{'cmdname'} and
+ (defined($brace_commands{$current->{'cmdname'}}) or
+ $self->{'definfoenclose'}->{$current->{'cmdname'}})
+ and $line !~ /^{/) {
+ # special case for @-command as argument of @itemize or @*table.
+ if (_command_with_command_as_argument($current->{'parent'})) {
+ delete $current->{'contents'};
+ print STDERR "FOR PARENT \@$current->{'parent'}->{'parent'}->{'cmdname'} command_as_argument $current->{'cmdname'}\n" if ($self->{'DEBUG'});
+ $current->{'type'} = 'command_as_argument' if (!$current->{'type'});
+ $current->{'parent'}->{'parent'}->{'extra'}->{'command_as_argument'}
+ = $current;
+ $current = $current->{'parent'};
+ # now accent commands
+ } elsif ($accent_commands{$current->{'cmdname'}}) {
+ if ($line =~ /^[^\S\r\n]/) {
+ if ($current->{'cmdname'} =~ /^[a-zA-Z]/) {
+ $line =~ s/^([^\S\r\n]+)//;
+ $current->{'extra'}->{'spaces'} = ''
+ if (!defined($current->{'extra'}->{'spaces'}));
+ $current->{'extra'}->{'spaces'} .= $1;
+ } else {
+ $self->line_warn(sprintf($self->
+ __("accent command `\@%s' must not be followed by whitespace"),
+ $current->{'cmdname'}), $line_nr);
+ $current = $current->{'parent'};
+ }
+ } elsif ($line =~ /^\@/) {
+ $self->line_error(sprintf($self->
+ __("use braces to give a command as an argument to \@%s"),
+ $current->{'cmdname'}), $line_nr);
+ $current = $current->{'parent'};
+ } elsif ($line =~ s/^(.)//o) {
+ print STDERR "ACCENT \@$current->{'cmdname'}\n"
+ if ($self->{'DEBUG'});
+ my $following_arg = {'type' => 'following_arg',
+ 'parent' => $current};
+ $following_arg->{'contents'} = [{ 'text' => $1,
+ 'parent' => $following_arg } ];
+ $current->{'args'} = [ $following_arg ];
+ if ($current->{'cmdname'} eq 'dotless' and $1 ne 'i' and $1 ne 'j') {
+ $self->line_error(sprintf($self->
+ __("%c%s expects `i' or `j' as argument, not `%s'"),
+ ord('@'), $current->{'cmdname'}, $1), $line_nr);
+ }
+ if ($current->{'cmdname'} =~ /^[a-zA-Z]/) {
+ $current->{'args'}->[-1]->{'type'} = 'space_command_arg';
+ }
+ delete $current->{'contents'};
+ $current = $current->{'parent'};
+ } else { # The accent is at end of line
+ # whitespace for commands with letter.
+ print STDERR "STRANGE ACC \@$current->{'cmdname'}\n" if ($self->{'DEBUG'});
+ $self->line_warn(sprintf($self->
+ __("accent command `\@%s' must not be followed by new line"),
+ $current->{'cmdname'}), $line_nr);
+ $current = $current->{'parent'};
+ }
+ next;
+ } else {
+ # ignore space after a braced @-command like TeX does
+ if ($self->{'IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME'}
+ and $line =~ s/^\s+//) {
+ next;
+ }
+ $self->line_error(sprintf($self->__("\@%s expected braces"),
+ $current->{'cmdname'}), $line_nr);
+ $current = $current->{'parent'};
+ }
+ # maybe a menu entry beginning: a * at the beginning of a menu line
+ } elsif ($line =~ /^\*/ and $current->{'type'}
+ and $current->{'type'} eq 'preformatted'
+ and $current->{'parent'}->{'type'}
+ and ($current->{'parent'}->{'type'} eq 'menu_comment'
+ or $current->{'parent'}->{'type'} eq 'menu_entry_description')
+ and @{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'empty_line'
+ and $current->{'contents'}->[-1]->{'text'} eq '') {
+ print STDERR "MENU STAR\n" if ($self->{'DEBUG'});
+ _abort_empty_line($self, $current);
+ $line =~ s/^\*//;
+ push @{$current->{'contents'}}, { 'type' => 'menu_star',
+ 'text' => '*' };
+ # a space after a * at the beginning of a menu line
+ } elsif ($line =~ /^\s+/ and $current->{'contents'}
+ and @{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'menu_star') {
+ print STDERR "MENU ENTRY (certainly)\n" if ($self->{'DEBUG'});
+ # this is the menu star collected previously
+ pop @{$current->{'contents'}};
+ $line =~ s/^(\s+)//;
+ my $leading_text = '*' . $1;
+ if ($current->{'type'} eq 'preformatted'
+ and $current->{'parent'}->{'type'}
+ and $current->{'parent'}->{'type'} eq 'menu_comment') {
+ my $menu = $current->{'parent'}->{'parent'};
+ if (!@{$current->{'contents'}}) {
+ pop @{$current->{'parent'}->{'contents'}};
+ if (!scalar(@{$current->{'parent'}->{'contents'}})) {
+ pop @{$menu->{'contents'}};
+ }
+ }
+ $current = $menu;
+ #print STDERR "Close MENU_COMMENT because new menu entry\n";
+ } else {
+ # first parent preformatted, third is menu_entry
+ if ($current->{'type'} ne 'preformatted'
+ or $current->{'parent'}->{'type'} ne 'menu_entry_description'
+ or $current->{'parent'}->{'parent'}->{'type'} ne 'menu_entry'
+ or !$menu_commands{$current->{'parent'}->{'parent'}->{'parent'}->{'cmdname'}}) {
+ $self->_bug_message("Not in menu comment nor description",
+ $line_nr, $current);
+ }
+ $current = $current->{'parent'}->{'parent'}->{'parent'};
+ }
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'preformatted') {
+ $self->_bug_message("context $context instead of preformatted after menu leading star",
+ $line_nr, $current);
+ }
+ push @{$current->{'contents'}}, { 'type' => 'menu_entry',
+ 'parent' => $current,
+ };
+ $current = $current->{'contents'}->[-1];
+ $current->{'args'} = [ { 'type' => 'menu_entry_leading_text',
+ 'text' => $leading_text,
+ 'parent' => $current },
+ { 'type' => 'menu_entry_name',
+ 'contents' => [],
+ 'parent' => $current } ];
+ $current = $current->{'args'}->[-1];
+ # * followed by something else than a space.
+ } elsif ($current->{'contents'} and @{$current->{'contents'}}
+ and $current->{'contents'}->[-1]->{'type'}
+ and $current->{'contents'}->[-1]->{'type'} eq 'menu_star') {
+ print STDERR "ABORT MENU STAR ($line)\n" if ($self->{'DEBUG'});
+ delete $current->{'contents'}->[-1]->{'type'};
+ # after a separator in menu
+ } elsif ($current->{'args'} and @{$current->{'args'}}
+ and $current->{'args'}->[-1]->{'type'}
+ and $current->{'args'}->[-1]->{'type'} eq 'menu_entry_separator') {
+ my $separator = $current->{'args'}->[-1]->{'text'};
+ # separator is ::, we concatenate and let the while restart
+ # in order to collect spaces below
+ if ($separator eq ':' and $line =~ s/^(:)//) {
+ $current->{'args'}->[-1]->{'text'} .= $1;
+ # a . not followed by a space. Not a separator.
+ } elsif ($separator eq '.' and $line =~ /^\S/) {
+ pop @{$current->{'args'}};
+ $current = $current->{'args'}->[-1];
+ $current = _merge_text($self, $current, $separator);
+ # here we collect spaces following separators.
+ } elsif ($line =~ s/^([^\S\r\n]+)//) {
+ # FIXME a trailing end of line could be considered to be part
+ # of the separator. Right now it is part of the description,
+ # since it is catched (in the next while) as one of the case below
+ $current->{'args'}->[-1]->{'text'} .= $1;
+ # now handle the menu part that was closed
+ } elsif ($separator =~ /^::/) {
+ print STDERR "MENU NODE no entry $separator\n" if ($self->{'DEBUG'});
+ # it was previously registered as menu_entry_name, it is
+ # changed to node
+ $current->{'args'}->[-2]->{'type'} = 'menu_entry_node';
+ $current = _enter_menu_entry_node($self, $current, $line_nr);
+ # end of the menu entry name
+ } elsif ($separator =~ /^:/) {
+ print STDERR "MENU ENTRY $separator\n" if ($self->{'DEBUG'});
+ push @{$current->{'args'}}, { 'type' => 'menu_entry_node',
+ 'contents' => [],
+ 'parent' => $current };
+ $current = $current->{'args'}->[-1];
+ # anything else is the end of the menu node following a menu_entry_name
+ } else {
+ print STDERR "MENU NODE $separator\n" if ($self->{'DEBUG'});
+ $current = _enter_menu_entry_node($self, $current, $line_nr);
+ }
+ # REMACRO
+ } elsif ($line =~ s/^\@(["'~\@\}\{,\.!\?\s\*\-\^`=:\|\/\\])//o
+ or $line =~ s/^\@([[:alnum:]][[:alnum:]-]*)//o) {
+ my $command = $1;
+ my $alias_command;
+ if (exists($self->{'aliases'}->{$command})) {
+ $alias_command = $command;
+ $command = $self->{'aliases'}->{$command};
+ }
+ print STDERR "COMMAND $command\n" if ($self->{'DEBUG'});
+
+
+ if ($command eq 'value') {
+ $line =~ s/^\s*//
+ if ($self->{'IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME'});
+ # REVALUE
+ if ($line =~ s/^{([\w\-][^\s{\\}~`\^+"<>|@]*)}//) {
+ my $value = $1;
+ if (exists($self->{'values'}->{$value})) {
+ if (!defined($self->{'values'}->{$value})) {
+ print STDERR "BUG? $value exists but not defined\n";
+ } elsif (!ref($self->{'values'}->{$value})) {
+ $line = $self->{'values'}->{$value} . $line;
+ # the push @{$current->{'contents'}}, {}; prevents a trailing
+ # text to be merged, to avoid having the value tree modified.
+ } elsif (ref($self->{'values'}->{$value}) eq 'ARRAY') {
+ # we don't know for sure, but if we don't do it here it
+ # won't be done
+ _abort_empty_line($self, $current);
+ foreach my $content (@{$self->{'values'}->{$value}}) {
+ push @{$current->{'contents'}}, $content;
+ }
+ push @{$current->{'contents'}}, {};
+ } elsif (ref($self->{'values'}->{$value}) eq 'HASH') {
+ # we don't know for sure, but if we don't do it here it
+ # won't be done
+ _abort_empty_line($self, $current);
+ my $content = $self->{'values'}->{$value};
+ push @{$current->{'contents'}}, $content;
+ push @{$current->{'contents'}}, {};
+ }
+ } else {
+ # caller should expand something along
+ # gdt('@{No value for `{value}\'@}', {'value' => $value}, {'keep_texi'=> 1});
+ push @{$current->{'contents'}}, { 'cmdname' => 'value',
+ 'type' => $value };
+ $self->line_warn(
+ sprintf($self->__("undefined flag: %s"), $value), $line_nr);
+ }
+ } else {
+ $self->line_error($self->__("bad syntax for \@value"), $line_nr);
+ }
+ next;
+ }
+
+ if (defined($deprecated_commands{$command})) {
+ if ($deprecated_commands{$command} eq '') {
+ $self->line_warn(sprintf($self->__("%c%s is obsolete."),
+ ord('@'), $command), $line_nr);
+ } else {
+ $self->line_warn(sprintf($self->__("%c%s is obsolete; %s"),
+ ord('@'), $command,
+ $self->__($deprecated_commands{$command})), $line_nr);
+ }
+ }
+
+ if (not _abort_empty_line($self, $current)
+ and $begin_line_commands{$command}) {
+ $self->line_warn(
+ sprintf($self->__("\@%s should only appear at a line beginning"),
+ $command), $line_nr);
+ }
+
+ my $invalid_parent;
+ # error messages for forbidden constructs, like @node in @r,
+ # block command on line command, @xref in @anchor or node...
+ if ($current->{'parent'}) {
+ if ($current->{'parent'}->{'cmdname'}) {
+ if (defined($self->{'valid_nestings'}->{$current->{'parent'}->{'cmdname'}})
+ and !$self->{'valid_nestings'}->{$current->{'parent'}->{'cmdname'}}->{$command}
+ # we make sure that we are on a root @-command line and
+ # not in contents
+ and (!$root_commands{$current->{'parent'}->{'cmdname'}}
+ or ($current->{'type'}
+ and $current->{'type'} eq 'misc_line_arg'))
+ # we make sure that we are on a block @-command line and
+ # not in contents
+ and (!($block_commands{$current->{'parent'}->{'cmdname'}})
+ or ($current->{'type'}
+ and $current->{'type'} eq 'block_line_arg'))
+ # we make sure that we are on an @item/@itemx line and
+ # not in an @enumerate, @multitable or @itemize @item.
+ and (($current->{'parent'}->{'cmdname'} ne 'itemx'
+ and $current->{'parent'}->{'cmdname'} ne 'item')
+ or ($current->{'type'}
+ and $current->{'type'} eq 'misc_line_arg'))) {
+ $invalid_parent = $current->{'parent'}->{'cmdname'};
+ }
+ } elsif ($self->{'context_stack'}->[-1] eq 'def'
+ # FIXME instead of hardcoding in_full_line_commands_no_refs
+ # it would be better to use the parent command valid_nesting.
+ and !$in_full_line_commands_no_refs{$command}) {
+ my $def_block = $current;
+ while ($def_block->{'parent'} and (!$def_block->{'parent'}->{'type'}
+ or $def_block->{'parent'}->{'type'} ne 'def_line')) {
+ $def_block = $def_block->{'parent'};
+ }
+
+ $invalid_parent = $def_block->{'parent'}->{'parent'}->{'cmdname'};
+ }
+ }
+
+ # special case with @ followed by a newline protecting end of lines
+ # in @def*
+ last if ($self->{'context_stack'}->[-1] eq 'def' and $command eq "\n");
+
+ unless ($self->{'no_paragraph_commands'}->{$command}) {
+ my $paragraph = _begin_paragraph($self, $current, $line_nr);
+ $current = $paragraph if ($paragraph);
+ }
+
+ if ($self->{'close_paragraph_commands'}->{$command}) {
+ $current = _end_paragraph($self, $current, $line_nr);
+ }
+ if ($self->{'close_preformatted_commands'}->{$command}) {
+ $current = _end_preformatted($self, $current, $line_nr);
+ }
+
+ # commands without braces and not block commands, ie no @end
+ if (defined($self->{'misc_commands'}->{$command})) {
+ if ($root_commands{$command} or $command eq 'bye') {
+ $current = _close_commands($self, $current, $line_nr, undef,
+ $command);
+ # root_level commands leads to setting a new root
+ # for the whole document and stuffing the preceding text
+ # as the first content, this is done only once.
+ if ($current->{'type'} and $current->{'type'} eq 'text_root') {
+ if ($command ne 'bye') {
+ $root = { 'type' => 'document_root', 'contents' => [$current] };
+ $current->{'parent'} = $root;
+ $current = $root;
+ }
+ } else {
+ die if (!defined($current->{'parent'}));
+ $current = $current->{'parent'};
+ }
+ }
+
+ # noarg skipline skipspace text line lineraw /^\d$/
+ my $arg_spec = $self->{'misc_commands'}->{$command};
+ my $misc;
+
+ if ($arg_spec eq 'noarg') {
+ my $ignored = 0;
+ my $only_in_headings = 0;
+ if ($command eq 'insertcopying') {
+ my $parent = $current;
+ while ($parent) {
+ if ($parent->{'cmdname'} and $parent->{'cmdname'} eq 'copying') {
+ $self->line_error(
+ sprintf($self->__("\@%s not allowed inside `\@%s' block"),
+ $command, $parent->{'cmdname'}), $line_nr);
+ $ignored = 1;
+ last;
+ }
+ $parent = $parent->{'parent'};
+ }
+ } elsif ($in_heading_commands{$command}) {
+ $self->line_error(
+ sprintf($self->__("\@%s should only appear in heading or footing"),
+ $command), $line_nr);
+ $only_in_headings = 1;
+ }
+ if (!$ignored) {
+ $misc = {'cmdname' => $command,
+ 'parent' => $current};
+ push @{$current->{'contents'}}, $misc;
+ # also sets invalid_nesting in that case
+ $misc->{'extra'}->{'invalid_nesting'} = 1 if ($only_in_headings);
+ $self->_register_global_command($command, $misc, $line_nr);
+ }
+ $self->_mark_and_warn_invalid($command, $invalid_parent,
+ $line_nr, $misc);
+ $current = $self->_begin_preformatted($current)
+ if ($close_preformatted_commands{$command});
+
+ # all the cases using the raw line
+ } elsif ($arg_spec eq 'skipline' or $arg_spec eq 'lineraw'
+ or $arg_spec eq 'special') {
+ # complete the line if there was a user macro expansion
+ if ($line !~ /\n/) {
+ my ($new_line, $new_line_nr) = _new_line($self, $line_nr, undef);
+ $line .= $new_line if (defined($new_line));
+ }
+ $misc = {'cmdname' => $command,
+ 'parent' => $current};
+ my $args = [];
+ my $has_comment;
+ if ($arg_spec eq 'lineraw' or $arg_spec eq 'skipline') {
+ $args = [ $line ];
+ } elsif ($arg_spec eq 'special') {
+ ($args, $has_comment)
+ = $self->_parse_special_misc_command($line, $command, $line_nr);
+ $misc->{'extra'}->{'arg_line'} = $line;
+ }
+
+ # if using the @set txi* instead of a proper @-command, replace
+ # by the tree obtained with the @-command. Even though
+ # _end_line is called below, as $current is not misc_line_arg
+ # there should not be anything done in addition than what is
+ # done for @clear or @set.
+ if (($command eq 'set' or $command eq 'clear')
+ and scalar(@$args) >= 1
+ and $set_flag_command_equivalent{$args->[0]}) {
+ my $arg;
+ if ($command eq 'set') {
+ $arg = 'on';
+ } else {
+ $arg = 'off';
+ }
+ $command = $set_flag_command_equivalent{$args->[0]};
+ $misc = {'cmdname' => $command,
+ 'parent' => $current,
+ 'line_nr' => $line_nr,
+ 'extra' => {'misc_args' => [$arg]}};
+ my $misc_line_args = {'type' => 'misc_line_arg',
+ 'parent' => $misc};
+ $misc->{'args'} = [$misc_line_args];
+ my $spaces_after_command
+ = { 'type' => 'empty_spaces_after_command',
+ 'text' => ' ',
+ 'parent' => $misc_line_args,
+ 'extra' => {'command' => $misc} };
+ $misc->{'extra'}->{'spaces_after_command'}
+ = $spaces_after_command;
+ $misc_line_args->{'contents'} = [ $spaces_after_command,
+ { 'text' => $arg,
+ 'parent' => $misc_line_args, },
+ { 'text' => "\n",
+ 'parent' => $misc_line_args,
+ 'type' => 'spaces_at_end', } ];
+ push @{$current->{'contents'}}, $misc;
+ } else {
+ push @{$current->{'contents'}}, $misc;
+ foreach my $arg (@$args) {
+ push @{$misc->{'args'}},
+ { 'type' => 'misc_arg', 'text' => $arg,
+ 'parent' => $current->{'contents'}->[-1] };
+ }
+ $misc->{'extra'}->{'misc_args'} = $args
+ if (scalar(@$args) and $arg_spec ne 'skipline');
+ }
+ if (! _ignore_global_commands($self)) {
+ if ($command eq 'raisesections') {
+ $self->{'sections_level'}++;
+ } elsif ($command eq 'lowersections') {
+ $self->{'sections_level'}--;
+ } elsif ($command eq 'novalidate') {
+ $self->{'novalidate'} = 1;
+ }
+ }
+ $self->_mark_and_warn_invalid($command, $invalid_parent,
+ $line_nr, $misc);
+ $self->_register_global_command($command, $misc, $line_nr);
+ # the end of line is ignored for special commands
+ if ($arg_spec ne 'special' or !$has_comment) {
+ $current = _end_line($self, $current, $line_nr);
+ }
+
+ last NEXT_LINE if ($command eq 'bye');
+ # Even if _end_line is called, it is not done since there is
+ # no misc_line_arg
+ $current = $self->_begin_preformatted($current)
+ if ($close_preformatted_commands{$command});
+ last;
+ } else {
+ # $arg_spec is text, line, skipspace or a number
+ my $line_arg = 0;
+ $line_arg = 1 if ($arg_spec ne 'skipspace');
+ if ($command eq 'item' or $command eq 'itemx'
+ or $command eq 'headitem' or $command eq 'tab') {
+ my $parent;
+ # itemize or enumerate
+ if ($parent = _item_container_parent($current)) {
+ if ($command eq 'item') {
+ print STDERR "ITEM_CONTAINER\n" if ($self->{'DEBUG'});
+ $parent->{'items_count'}++;
+ $misc = { 'cmdname' => $command, 'parent' => $parent,
+ 'contents' => [],
+ 'extra' =>
+ {'item_number' => $parent->{'items_count'}} };
+ push @{$parent->{'contents'}}, $misc;
+ $current = $parent->{'contents'}->[-1];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@%s not meaningful inside `\@%s' block"),
+ $command, $parent->{'cmdname'}), $line_nr);
+ }
+ $current = $self->_begin_preformatted($current);
+ # *table
+ } elsif ($parent = _item_line_parent($current)) {
+ if ($command eq 'item' or $command eq 'itemx') {
+ print STDERR "ITEM_LINE\n" if ($self->{'DEBUG'});
+ $current = $parent;
+ $self->_gather_previous_item($current, $command, $line_nr);
+ $misc = { 'cmdname' => $command, 'parent' => $current };
+ push @{$current->{'contents'}}, $misc;
+ # since in the %misc_commands hash the entry for those
+ # commands is 'skipspace' we set $line_arg here.
+ $line_arg = 1;
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@%s not meaningful inside `\@%s' block"),
+ $command, $parent->{'cmdname'}), $line_nr);
+ $current = $self->_begin_preformatted($current);
+ }
+ # multitable
+ } elsif ($parent = _item_multitable_parent($current)) {
+ if ($command eq 'item' or $command eq 'headitem'
+ or $command eq 'tab') {
+ if (!$parent->{'extra'}->{'max_columns'}) {
+ $self->line_warn(
+ sprintf($self->__("\@%s in empty multitable"),
+ $command), $line_nr);
+ } elsif ($command eq 'tab') {
+ my $row = $parent->{'contents'}->[-1];
+ die if (!$row->{'type'});
+ if ($row->{'type'} eq 'before_item') {
+ $self->line_error($self->__("\@tab before \@item"), $line_nr);
+ } elsif ($row->{'cells_count'} >= $parent->{'extra'}->{'max_columns'}) {
+ $self->line_error(sprintf($self->__(
+ "too many columns in multitable item (max %d)"),
+ $parent->{'extra'}->{'max_columns'}), $line_nr);
+ } else {
+ $row->{'cells_count'}++;
+ $misc = { 'cmdname' => $command,
+ 'parent' => $row,
+ 'contents' => [],
+ 'extra' =>
+ {'cell_number' => $row->{'cells_count'}} };
+ push @{$row->{'contents'}}, $misc;
+ $current = $row->{'contents'}->[-1];
+ #$current = $self->_begin_preformatted($current);
+ print STDERR "TAB\n" if ($self->{'DEBUG'});
+ }
+ } else {
+ print STDERR "ROW\n" if ($self->{'DEBUG'});
+ $parent->{'rows_count'}++;
+ my $row = { 'type' => 'row', 'contents' => [],
+ 'cells_count' => 1,
+ 'extra' => {'row_number' => $parent->{'rows_count'} },
+ 'parent' => $parent };
+ push @{$parent->{'contents'}}, $row;
+ $misc = { 'cmdname' => $command,
+ 'parent' => $row,
+ 'contents' => [],
+ 'extra' => {'cell_number' => 1}};
+ push @{$row->{'contents'}}, $misc;
+ $current = $row->{'contents'}->[-1];
+ }
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@%s not meaningful inside `\@%s' block"),
+ $command, $parent->{'cmdname'}), $line_nr);
+ }
+ $current = $self->_begin_preformatted($current);
+ } elsif ($command eq 'tab') {
+ $self->line_error($self->__(
+ "ignoring \@tab outside of multitable"), $line_nr);
+ $current = $self->_begin_preformatted($current);
+ } else {
+ $self->line_error (sprintf($self->__(
+ "\@%s outside of table or list"), $command), $line_nr);
+ $current = $self->_begin_preformatted($current);
+ }
+ $misc->{'line_nr'} = $line_nr if (defined($misc));
+ } else {
+ $misc = { 'cmdname' => $command, 'parent' => $current,
+ 'line_nr' => $line_nr };
+ push @{$current->{'contents'}}, $misc;
+ if ($self->{'sections_level'} and $root_commands{$command}
+ and $command ne 'node' and $command ne 'part') {
+ $current->{'contents'}->[-1]->{'extra'}->{'sections_level'}
+ = $self->{'sections_level'};
+ }
+ # def*x
+ if ($def_commands{$command}) {
+ my $base_command = $command;
+ $base_command =~ s/x$//;
+ # check that the def*x is first after @def*, no paragraph
+ # in-between.
+ my $after_paragraph = _check_no_text($current);
+ push @{$self->{'context_stack'}}, 'def';
+ $current->{'contents'}->[-1]->{'type'} = 'def_line';
+ $current->{'contents'}->[-1]->{'extra'} =
+ {'def_command' => $base_command,
+ 'original_def_cmdname' => $command};
+ if ($current->{'cmdname'}
+ and $current->{'cmdname'} eq $base_command) {
+ pop @{$current->{'contents'}};
+ _gather_def_item($current, $command);
+ push @{$current->{'contents'}}, $misc;
+ }
+ if (!$current->{'cmdname'}
+ or $current->{'cmdname'} ne $base_command
+ or $after_paragraph) {
+ $self->line_error(sprintf($self->__(
+ "must be after `\@%s' to use `\@%s'"),
+ $base_command, $command), $line_nr);
+ $current->{'contents'}->[-1]->{'extra'}->{'not_after_command'} = 1;
+ }
+ }
+ }
+ # a container for what is on the @-command line, considered to
+ # be the @-command argument
+ if ($line_arg) {
+ $current = $current->{'contents'}->[-1];
+ $current->{'args'} = [{ 'type' => 'misc_line_arg',
+ 'contents' => [],
+ 'parent' => $current }];
+ # @node is the only misc command with args separated with comma
+ # FIXME a 3 lingering here deep into the code may not
+ # be very wise... However having a hash only for one @-command
+ # is not very appealing either...
+ if ($command eq 'node') {
+ $current->{'remaining_args'} = 3;
+ } elsif ($command eq 'author') {
+ my $parent = $current;
+ my $found;
+ while ($parent->{'parent'}) {
+ $parent = $parent->{'parent'};
+ last if ($parent->{'type'}
+ and $parent->{'type'} eq 'brace_command_context');
+ if ($parent->{'cmdname'}) {
+ if ($parent->{'cmdname'} eq 'titlepage') {
+ push @{$self->{'extra'}->{'author'}}, $current;
+ $current->{'extra'}->{'titlepage'} = $parent;
+ $found = 1;
+ } elsif ($parent->{'cmdname'} eq 'quotation' or
+ $parent->{'cmdname'} eq 'smallquotation') {
+ push @{$parent->{'extra'}->{'authors'}}, $current;
+ $current->{'extra'}->{'quotation'} = $parent;
+ $found = 1;
+ }
+ last if ($found);
+ }
+ }
+ if (!$found) {
+ $self->line_warn(sprintf($self->__(
+ "\@%s not meaningful outside `\@titlepage' and `\@quotation' environments"),
+ $command), $current->{'line_nr'});
+ }
+ } elsif ($command eq 'dircategory' and $self->{'current_node'}) {
+ $self->line_warn($self->__("\@dircategory after first node"),
+ $line_nr);
+ }
+
+ $current = $current->{'args'}->[-1];
+ push @{$self->{'context_stack'}}, 'line'
+ unless ($def_commands{$command});
+ }
+ $line = _start_empty_line_after_command($line, $current, $misc);
+ }
+ $self->_mark_and_warn_invalid($command, $invalid_parent,
+ $line_nr, $misc);
+
+ $self->_register_global_command($command, $misc, $line_nr);
+ if ($command eq 'dircategory'
+ and ! _ignore_global_commands($self)) {
+ push @{$self->{'info'}->{'dircategory_direntry'}}, $misc;
+ }
+ # @-command with matching @end opening
+ } elsif (exists($block_commands{$command})) {
+ if ($command eq 'macro' or $command eq 'rmacro') {
+ my $macro = _parse_macro_command_line($self, $command, $line,
+ $current, $line_nr);
+ push @{$current->{'contents'}}, $macro;
+ $self->_mark_and_warn_invalid($command, $invalid_parent,
+ $line_nr, $current->{'contents'}->[-1]);
+ $current = $current->{'contents'}->[-1];
+ last;
+ } elsif ($block_commands{$command} eq 'conditional') {
+ my $ifvalue_true = 0;
+ if ($command eq 'ifclear' or $command eq 'ifset') {
+ # REVALUE
+ if ($line =~ /^\s+([\w\-][^\s{\\}~`\^+"<>|@]*)\s*(\@(c|comment)((\@|\s+).*)?)?$/) {
+ my $name = $1;
+ if ((exists($self->{'values'}->{$name}) and $command eq 'ifset')
+ or (!exists($self->{'values'}->{$name})
+ and $command eq 'ifclear')) {
+ $ifvalue_true = 1;
+ }
+ print STDERR "CONDITIONAL \@$command $name: $ifvalue_true\n" if ($self->{'DEBUG'});
+ } elsif ($line !~ /\S/) {
+ $self->line_error(sprintf($self->
+ __("%c%s requires a name"), ord('@'), $command), $line_nr);
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad name for \@%s"), $command), $line_nr);
+ }
+ } elsif ($command eq 'ifcommanddefined'
+ or $command eq 'ifcommandnotdefined') {
+ # REMACRO
+ if ($line =~ /^\s+([[:alnum:]][[:alnum:]\-]*)\s*(\@(c|comment)((\@|\s+).*)?)?$/) {
+ my $name = $1;
+ my $command_is_defined = (
+ exists($Texinfo::Common::all_commands{$name})
+ or $self->{'macros'}->{$name}
+ or $self->{'definfoenclose'}->{$name}
+ or $self->{'aliases'}->{$name}
+ or $self->{'command_index_prefix'}->{$name}
+ );
+ if (($command_is_defined
+ and $command eq 'ifcommanddefined')
+ or (! $command_is_defined
+ and $command eq 'ifcommandnotdefined')) {
+ $ifvalue_true = 1;
+ }
+ print STDERR "CONDITIONAL \@$command $name: $ifvalue_true\n" if ($self->{'DEBUG'});
+ } elsif ($line !~ /\S/) {
+ $self->line_error(sprintf($self->
+ __("%c%s requires a name"), ord('@'), $command), $line_nr);
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad name for \@%s"), $command), $line_nr);
+ }
+ } elsif ($command =~ /^ifnot(.*)/) {
+ $ifvalue_true = 1 if !($self->{'expanded_formats_hash'}->{$1}
+ # exception as explained in the texinfo manual
+ or ($1 eq 'info'
+ and $self->{'expanded_formats_hash'}->{'plaintext'}));
+ print STDERR "CONDITIONAL \@$command format $1: $ifvalue_true\n" if ($self->{'DEBUG'});
+ } else {
+ die unless ($command =~ /^if(.*)/);
+ $ifvalue_true = 1 if ($self->{'expanded_formats_hash'}->{$1}
+ or ($1 eq 'info'
+ and $self->{'expanded_formats_hash'}->{'plaintext'}));
+ print STDERR "CONDITIONAL \@$command format $1: $ifvalue_true\n" if ($self->{'DEBUG'});
+ }
+ if ($ifvalue_true) {
+ push @{$self->{'conditionals_stack'}}, $command;
+ } else {
+ push @{$current->{'contents'}}, { 'cmdname' => $command,
+ 'parent' => $current,
+ 'contents' => [] };
+ $current = $current->{'contents'}->[-1];
+ }
+ # FIXME(Karl) ignore what is remaining on the line, to eat
+ # the end of line?
+ last;
+ } else {
+ my $block;
+ # a menu command closes a menu_comment, but not the other
+ # block commands. This won't catch menu commands buried in
+ # other formats (that are incorrect anyway).
+ if ($menu_commands{$command} and $current->{'type'}
+ and ($current->{'type'} eq 'menu_comment'
+ or $current->{'type'} eq 'menu_entry_description')) {
+
+ my $menu;
+
+ $menu = $current->{'parent'};
+ pop @{$menu->{'contents'}}
+ if (!@{$current->{'contents'}});
+
+ my $context = pop @{$self->{'context_stack'}};
+ if ($context ne 'preformatted') {
+ $self->_bug_message("context $context instead of preformatted in new menu",
+ $line_nr, $current);
+ }
+
+ if ($menu->{'type'} and $menu->{'type'} eq 'menu_entry') {
+ $menu = $menu->{'parent'};
+ }
+
+ $current = $menu;
+ }
+ # the def command holds a line_def* which corresponds with the
+ # definition line. This allows to have a treatement similar
+ # with def*x.
+ if ($def_commands{$command}) {
+ push @{$self->{'context_stack'}}, 'def';
+ $block = { 'parent' => $current,
+ 'cmdname' => $command,
+ 'contents' => [] };
+ push @{$current->{'contents'}}, $block;
+ $current = $current->{'contents'}->[-1];
+ push @{$current->{'contents'}}, {
+ 'type' => 'def_line',
+ 'parent' => $current,
+ 'line_nr' => $line_nr,
+ 'extra' =>
+ {'def_command' => $command,
+ 'original_def_cmdname' => $command}
+ };
+ } else {
+ $block = { 'cmdname' => $command,
+ 'parent' => $current,
+ 'contents' => [] };
+ push @{$current->{'contents'}}, $block;
+ }
+ $current = $current->{'contents'}->[-1];
+
+ if ($block_arg_commands{$command}) {
+ if ($preformatted_commands{$command}) {
+ push @{$self->{'context_stack'}}, 'preformatted';
+ } elsif ($format_raw_commands{$command}) {
+ push @{$self->{'context_stack'}}, 'rawpreformatted';
+ if ($self->{'expanded_formats_hash'}->{$command}
+ and $self->{'expanded_formats_stack'}->[-1]) {
+ push @{$self->{'expanded_formats_stack'}}, $command;
+ } else {
+ push @{$self->{'expanded_formats_stack'}}, 0;
+ }
+ }
+ if ($region_commands{$command}) {
+ if (@{$self->{'regions_stack'}}) {
+ $self->line_error(
+ sprintf($self->__("region %s inside region %s is not allowed"),
+ $command, $self->{'regions_stack'}->[-1]->{'cmdname'}),
+ $line_nr);
+ }
+ push @{$self->{'regions_stack'}}, $block;
+ }
+ if ($menu_commands{$command}) {
+ if ($self->{'context_stack'}->[-1] eq 'preformatted') {
+ push @{$self->{'context_stack'}}, 'preformatted';
+ } else {
+ push @{$self->{'context_stack'}}, 'menu';
+ }
+ if (! _ignore_global_commands($self)) {
+ push @{$self->{'info'}->{'dircategory_direntry'}}, $block
+ if ($command eq 'direntry');
+ if ($self->{'current_node'}) {
+ if ($command eq 'direntry') {
+ if ($self->{'SHOW_MENU'}) {
+ $self->line_warn($self->__("\@direntry after first node"),
+ $line_nr);
+ }
+ } elsif ($command eq 'menu') {
+ push @{$self->{'current_node'}->{'menus'}}, $current;
+ }
+ } elsif ($command ne 'direntry') {
+ if ($self->{'SHOW_MENU'}) {
+ $self->line_error(sprintf($self->__("\@%s seen before first \@node"),
+ $command), $line_nr);
+ $self->line_error($self->__(
+ "perhaps your \@top node should be wrapped in \@ifnottex rather than \@ifinfo?"),
+ $line_nr, 1);
+ }
+ if ($command eq 'menu') {
+ push @{$self->{'info'}->{'unassociated_menus'}}, $current;
+ }
+ }
+ }
+ }
+ $current->{'args'} = [ {
+ 'type' => 'block_line_arg',
+ 'contents' => [],
+ 'parent' => $current } ];
+
+ $current->{'remaining_args'} = $block_commands{$command} -1
+ if ($block_commands{$command} =~ /^\d+$/
+ and $block_commands{$command} -1 > 0);
+ $current = $current->{'args'}->[-1];
+ push @{$self->{'context_stack'}}, 'line'
+ unless ($def_commands{$command});
+ }
+ $block->{'line_nr'} = $line_nr;
+ $self->_mark_and_warn_invalid($command, $invalid_parent,
+ $line_nr, $block);
+ $self->_register_global_command($command, $block, $line_nr);
+
+ $line = _start_empty_line_after_command($line, $current, $block);
+ }
+ } elsif (defined($brace_commands{$command})
+ or defined($self->{'definfoenclose'}->{$command})) {
+
+ push @{$current->{'contents'}}, { 'cmdname' => $command,
+ 'parent' => $current,
+ 'contents' => [] };
+ $current->{'contents'}->[-1]->{'line_nr'} = $line_nr
+ if ($keep_line_nr_brace_commands{$command});
+ $self->_mark_and_warn_invalid($command, $invalid_parent,
+ $line_nr, $current->{'contents'}->[-1]);
+ $current = $current->{'contents'}->[-1];
+ if ($command eq 'click') {
+ $current->{'extra'}->{'clickstyle'} = $self->{'clickstyle'};
+ } elsif ($command eq 'kbd') {
+ if ($self->{'context_stack'}->[-1] eq 'preformatted'
+ and $self->{'kbdinputstyle'} ne 'distinct') {
+ $current->{'extra'}->{'code'} = 1;
+ } elsif ($self->{'kbdinputstyle'} eq 'code'
+ or ($self->{'kbdinputstyle'} eq 'example'
+ and $self->_in_code($current->{'parent'}))) {
+ $current->{'extra'}->{'code'} = 1;
+ }
+ }
+ if ($self->{'definfoenclose'}->{$command}) {
+ $current->{'type'} = 'definfoenclose_command';
+ $current->{'extra'} = {
+ 'begin' => $self->{'definfoenclose'}->{$command}->[0],
+ 'end' => $self->{'definfoenclose'}->{$command}->[1] };
+ }
+ } elsif (exists ($no_brace_commands{$command})) {
+ push @{$current->{'contents'}},
+ { 'cmdname' => $command, 'parent' => $current };
+ # FIXME generalize?
+ if ($command eq '\\' and $self->{'context_stack'}->[-1] ne 'math') {
+ $self->line_warn(sprintf($self->__("\@%s should only appear in math context"),
+ $command), $line_nr);
+ }
+ if ($command eq "\n") {
+ $current = _end_line($self, $current, $line_nr);
+ last;
+ }
+ } else {
+ $self->line_error(sprintf($self->__("unknown command `%s'"),
+ $command), $line_nr);
+ }
+
+ } elsif ($line =~ s/^([{}@,:\t.\f])//) {
+ my $separator = $1;
+ print STDERR "SEPARATOR: $separator\n" if ($self->{'DEBUG'});
+ if ($separator eq '@') {
+ # this may happen with a @ at the very end of a file, therefore
+ # not followed by anything.
+ $self->line_error($self->__("unexpected \@"), $line_nr);
+ } elsif ($separator eq '{') {
+ _abort_empty_line($self, $current);
+ if ($current->{'cmdname'}
+ and (defined($brace_commands{$current->{'cmdname'}})
+ or $self->{'definfoenclose'}->{$current->{'cmdname'}})) {
+ my $command = $current->{'cmdname'};
+ $current->{'args'} = [ { 'parent' => $current,
+ 'contents' => [] } ];
+ $current->{'remaining_args'} = $brace_commands{$command} -1
+ if ($brace_commands{$command} and $brace_commands{$command} -1);
+ $current->{'line_nr'} = $line_nr if ($brace_commands{$command});
+ if ($self->{'definfoenclose'}->{$command}) {
+ $current->{'remaining_args'} = 0;
+ }
+ $current = $current->{'args'}->[-1];
+ if ($context_brace_commands{$command}) {
+ if ($command eq 'caption' or $command eq 'shortcaption') {
+ my $float;
+ if (!$current->{'parent'}->{'parent'}
+ or !$current->{'parent'}->{'parent'}->{'cmdname'}
+ or $current->{'parent'}->{'parent'}->{'cmdname'} ne 'float') {
+ $float = $current->{'parent'};
+ while ($float->{'parent'} and !($float->{'cmdname'}
+ and $float->{'cmdname'} eq 'float')) {
+ $float = $float->{'parent'};
+ }
+ if (!($float->{'cmdname'} and $float->{'cmdname'} eq 'float')) {
+ $self->line_error(sprintf($self->__(
+ "\@%s is not meaningful outside `\@float' environment"),
+ $command), $line_nr);
+ $float = undef;
+ } else {
+ $self->line_warn(sprintf($self->__(
+ "\@%s should be right below `\@float'"),
+ $command), $line_nr);
+ }
+ } else {
+ $float = $current->{'parent'}->{'parent'};
+ }
+ if ($float) {
+ if ($float->{'extra'}->{$command}) {
+ $self->line_warn(sprintf($self->__("ignoring multiple \@%s"),
+ $command), $line_nr);
+ } else {
+ $current->{'parent'}->{'extra'}->{'float'} = $float;
+ $float->{'extra'}->{$command} = $current->{'parent'};
+ }
+ }
+ }
+ push @{$self->{'context_stack'}}, $command;
+ $line =~ s/([^\S\f\n]*)//;
+ $current->{'type'} = 'brace_command_context';
+ push @{$current->{'contents'}}, { 'type' => 'empty_spaces_before_argument',
+ 'text' => $1,
+ 'parent' => $current };
+ $current->{'parent'}->{'extra'}->{'spaces_before_argument'}
+ = $current->{'contents'}->[-1];
+ } else {
+ $current->{'type'} = 'brace_command_arg';
+ if ($brace_commands{$command}
+ and ($brace_commands{$command} > 1
+ or $simple_text_commands{$command})) {
+ push @{$current->{'contents'}},
+ {'type' => 'empty_spaces_before_argument',
+ 'text' => '' };
+ $current->{'parent'}->{'extra'}->{'spaces_before_argument'}
+ = $current->{'contents'}->[-1];
+ }
+ if ($inline_commands{$command}) {
+ # this is changed when the first argument is known.
+ push @{$self->{'expanded_formats_stack'}}, 0;
+ push @{$self->{'context_stack'}}, $command
+ if ($command eq 'inlineraw');
+ }
+ }
+ print STDERR "OPENED \@$current->{'parent'}->{'cmdname'}, remaining: "
+ .(defined($current->{'parent'}->{'remaining_args'}) ? "remaining: $current->{'parent'}->{'remaining_args'}, " : '')
+ .($current->{'type'} ? "type: $current->{'type'}" : '')."\n"
+ if ($self->{'DEBUG'});
+ } elsif ($current->{'parent'}
+ and (($current->{'parent'}->{'cmdname'}
+ and $current->{'parent'}->{'cmdname'} eq 'multitable')
+ or ($current->{'parent'}->{'type'}
+ and $current->{'parent'}->{'type'} eq 'def_line'))) {
+ push @{$current->{'contents'}},
+ { 'type' => 'bracketed', 'contents' => [],
+ 'parent' => $current };
+ $current = $current->{'contents'}->[-1];
+ # we need the line number here in case @ protects end of line
+ $current->{'line_nr'} = $line_nr
+ if ($current->{'parent'}->{'parent'}->{'type'}
+ and $current->{'parent'}->{'parent'}->{'type'} eq 'def_line');
+ push @{$current->{'contents'}},
+ {'type' => 'empty_spaces_before_argument',
+ 'text' => '' };
+ print STDERR "BRACKETED in def/multitable\n" if ($self->{'DEBUG'});
+ $current->{'extra'}->{'spaces_before_argument'}
+ = $current->{'contents'}->[-1];
+
+ # lone braces accepted right in a rawpreformatted
+ } elsif ($current->{'type'}
+ and $current->{'type'} eq 'rawpreformatted') {
+ push @{$current->{'contents'}}, {'text' => '{' };
+ # matching braces accepted in a rawpreformatted or math or ignored
+ # code
+ } elsif ($self->{'context_stack'}->[-1] eq 'math'
+ or $self->{'context_stack'}->[-1] eq 'rawpreformatted'
+ or $self->{'context_stack'}->[-1] eq 'inlineraw'
+ or $self->_ignore_global_commands()) {
+ push @{$current->{'contents'}},
+ { 'type' => 'bracketed', 'contents' => [],
+ 'parent' => $current, 'line_nr' => $line_nr };
+ $current = $current->{'contents'}->[-1];
+ print STDERR "BRACKETED in math\n" if ($self->{'DEBUG'});
+ } else {
+ $self->line_error(sprintf($self->__("misplaced %c"),
+ ord('{')), $line_nr);
+ }
+
+ } elsif ($separator eq '}') {
+ _abort_empty_line($self, $current);
+ #print STDERR "GGGGG". _print_current ($current);
+ if ($current->{'type'} and ($current->{'type'} eq 'bracketed')) {
+ $current = $current->{'parent'};
+ # the following will not happen for footnote if there is
+ # a paragraph withing the footnote
+ } elsif ($current->{'parent'}
+ and $current->{'parent'}->{'cmdname'}
+ and (exists $brace_commands{$current->{'parent'}->{'cmdname'}}
+ or $self->{'definfoenclose'}->{$current->{'parent'}->{'cmdname'}})) {
+ # for math and footnote out of paragraph
+ if ($context_brace_commands{$current->{'parent'}->{'cmdname'}}) {
+ my $context_command = pop @{$self->{'context_stack'}};
+ if ($context_command ne $current->{'parent'}->{'cmdname'}) {
+ $self->_bug_message("context $context_command instead of brace command $current->{'parent'}->{'cmdname'}",
+ $line_nr, $current);
+ die;
+ }
+ }
+ # first is the arg.
+
+ if ($brace_commands{$current->{'parent'}->{'cmdname'}}
+ and ($brace_commands{$current->{'parent'}->{'cmdname'}} > 1
+ or $simple_text_commands{$current->{'parent'}->{'cmdname'}})
+ and $current->{'parent'}->{'cmdname'} ne 'math') {
+ # @inline* always have end spaces considered as normal text
+ $self->_isolate_last_space($current)
+ unless ($inline_commands{$current->{'parent'}->{'cmdname'}});
+ $self->_register_command_arg($current, 'brace_command_contents');
+ # Remove empty arguments, as far as possible
+ _remove_empty_content_arguments($current);
+ }
+ my $closed_command = $current->{'parent'}->{'cmdname'};
+ print STDERR "CLOSING(brace) \@$current->{'parent'}->{'cmdname'}\n"
+ if ($self->{'DEBUG'});
+ delete $current->{'parent'}->{'remaining_args'};
+ if (defined($brace_commands{$closed_command})
+ and $brace_commands{$closed_command} == 0
+ and @{$current->{'contents'}}) {
+ $self->line_warn(sprintf($self->__(
+ "command \@%s does not accept arguments"),
+ $closed_command), $line_nr);
+ }
+ if ($current->{'parent'}->{'cmdname'} eq 'anchor') {
+ $current->{'parent'}->{'line_nr'} = $line_nr;
+ my $parsed_anchor = _parse_node_manual($current);
+ if (_check_node_label($self, $parsed_anchor,
+ $current->{'parent'}->{'cmdname'}, $line_nr)) {
+ _register_label($self, $current->{'parent'},
+ $parsed_anchor, $line_nr);
+ if (@{$self->{'regions_stack'}}) {
+ $current->{'extra'}->{'region'} = $self->{'regions_stack'}->[-1];
+ }
+ }
+ } elsif ($ref_commands{$current->{'parent'}->{'cmdname'}}) {
+ my $ref = $current->{'parent'};
+ if (@{$ref->{'args'}}) {
+ my @args = @{$ref->{'extra'}->{'brace_command_contents'}};
+ if (($closed_command eq 'inforef'
+ and !defined($args[0]) and !defined($args[2]))
+ or ($closed_command ne 'inforef'
+ and !defined($args[0]) and !defined($args[3])
+ and !defined($args[4]))) {
+ $self->line_warn(sprintf($self->__(
+ "command \@%s missing a node or external manual argument"),
+ $closed_command), $line_nr);
+ } else {
+ my $parsed_ref_node = _parse_node_manual($ref->{'args'}->[0]);
+ $ref->{'extra'}->{'node_argument'} = $parsed_ref_node
+ if (defined($parsed_ref_node));
+ if ($closed_command ne 'inforef'
+ and !defined($args[3]) and !defined($args[4])
+ and !$parsed_ref_node->{'manual_content'}
+ and ! _ignore_global_commands($self)) {
+ push @{$self->{'internal_references'}}, $ref;
+ }
+ }
+ if (defined($args[1])) {
+ my $normalized_cross_ref_name =
+ Texinfo::Convert::NodeNameNormalization::normalize_node(
+ {'contents' => $args[1]});
+ if ($normalized_cross_ref_name !~ /[^-]/) {
+ $self->line_warn(sprintf($self->__(
+ "in \@%s empty cross reference name after expansion `%s'"),
+ $closed_command,
+ Texinfo::Convert::Texinfo::convert({'contents' => $args[1]})),
+ $line_nr);
+ }
+ }
+ if ($closed_command ne 'inforef' and defined($args[2])) {
+ my $normalized_cross_ref_title =
+ Texinfo::Convert::NodeNameNormalization::normalize_node({'contents' => $args[2]});
+ if ($normalized_cross_ref_title !~ /[^-]/) {
+ $self->line_warn(sprintf($self->__(
+ "in \@%s empty cross reference title after expansion `%s'"),
+ $closed_command,
+ Texinfo::Convert::Texinfo::convert({'contents' => $args[2]})),
+ $line_nr);
+ }
+ }
+ }
+ } elsif ($current->{'parent'}->{'cmdname'} eq 'image') {
+ my $image = $current->{'parent'};
+ if (!@{$image->{'args'}}
+ or !@{$image->{'extra'}->{'brace_command_contents'}}
+ or !defined($image->{'extra'}->{'brace_command_contents'}->[0])) {
+ $self->line_error(
+ $self->__("\@image missing filename argument"), $line_nr);
+ }
+ } elsif($current->{'parent'}->{'cmdname'} eq 'dotless') {
+ my $dotless = $current->{'parent'};
+ if (@{$current->{'contents'}}) {
+ my $text = $current->{'contents'}->[0]->{'text'};
+ if (!defined ($text)
+ or ($text ne 'i' and $text ne 'j')) {
+ $self->line_error(sprintf($self->
+ __("%c%s expects `i' or `j' as argument, not `%s'"),
+ ord('@'), $dotless->{'cmdname'},
+ Texinfo::Convert::Texinfo::convert($current)), $line_nr);
+ }
+ }
+ } elsif ($explained_commands{$current->{'parent'}->{'cmdname'}}
+ or $inline_commands{$current->{'parent'}->{'cmdname'}}) {
+ my $current_command = $current->{'parent'};
+ if ($inline_commands{$current_command->{'cmdname'}}) {
+ if ($current_command->{'cmdname'} eq 'inlineraw') {
+ my $context_command = pop @{$self->{'context_stack'}};
+ if ($context_command ne $current_command->{'cmdname'}) {
+ $self->_bug_message("context $context_command instead of inlineraw $current_command->{'cmdname'}",
+ $line_nr, $current);
+ die;
+ }
+ }
+ pop @{$self->{'expanded_formats_stack'}};
+ }
+ if (!@{$current_command->{'args'}}
+ or !@{$current_command->{'extra'}->{'brace_command_contents'}}
+ or !defined($current_command->{'extra'}->{'brace_command_contents'}->[0])) {
+ $self->line_warn(
+ sprintf($self->__("\@%s missing first argument"),
+ $current_command->{'cmdname'}), $line_nr);
+ } else {
+ if ($explained_commands{$current_command->{'cmdname'}}) {
+ my $normalized_type
+ = Texinfo::Convert::NodeNameNormalization::normalize_node(
+ {'contents' =>
+ $current_command->{'extra'}->{'brace_command_contents'}->[0]});
+ $current_command->{'extra'}->{'normalized'} = $normalized_type;
+ if (!$current_command->{'extra'}->{'brace_command_contents'}->[1]) {
+ if ($self->{'explained_commands'}->{$current_command->{'cmdname'}}->{$normalized_type}) {
+ $current_command->{'extra'}->{'explanation_contents'}
+ = $self->{'explained_commands'}->{$current_command->{'cmdname'}}->{$normalized_type};
+ }
+ } elsif (! _ignore_global_commands($self)) {
+ $self->{'explained_commands'}->{$current_command->{'cmdname'}}->{$normalized_type}
+ = $current_command->{'extra'}->{'brace_command_contents'}->[1];
+ }
+ }# else {
+ # my $argument
+ # = Texinfo::Convert::Text::convert({'contents' =>
+ # $current_command->{'extra'}->{'brace_command_contents'}->[0]},
+ # {Texinfo::Common::_convert_text_options($self)});
+ # $current_command->{'extra'}->{'format'} = $argument;
+ #}
+ }
+ } elsif ($current->{'parent'}->{'cmdname'} eq 'errormsg') {
+ if (! _ignore_global_commands($self)) {
+ my $error_message_text
+ = Texinfo::Convert::Text::convert($current,
+ {Texinfo::Common::_convert_text_options($self)});
+ $self->line_error($error_message_text, $line_nr);
+ }
+ } elsif (_command_with_command_as_argument($current->{'parent'}->{'parent'})
+ and scalar(@{$current->{'contents'}}) == 0) {
+ print STDERR "FOR PARENT \@$current->{'parent'}->{'parent'}->{'parent'}->{'cmdname'} command_as_argument braces $current->{'cmdname'}\n" if ($self->{'DEBUG'});
+ $current->{'parent'}->{'type'} = 'command_as_argument'
+ if (!$current->{'parent'}->{'type'});
+ $current->{'parent'}->{'parent'}->{'parent'}->{'extra'}->{'command_as_argument'}
+ = $current->{'parent'};
+ }
+ $self->_register_global_command($current->{'parent'}->{'cmdname'},
+ $current->{'parent'}, $line_nr);
+ if ($command_ignore_space_after{$current->{'parent'}->{'cmdname'}}) {
+ push @{$current->{'parent'}->{'parent'}->{'contents'}},
+ {'type' => 'empty_spaces_after_close_brace',
+ 'text' => '' };
+ }
+ $current = $current->{'parent'}->{'parent'};
+ $current = $self->_begin_preformatted ($current)
+ if ($close_preformatted_commands{$closed_command});
+ # lone braces accepted right in a rawpreformatted
+ } elsif ($current->{'type'}
+ and $current->{'type'} eq 'rawpreformatted') {
+ push @{$current->{'contents'}}, {'text' => '}' };
+ # footnote caption closing, when there is a paragraph inside.
+ } elsif ($context_brace_commands{$self->{'context_stack'}->[-1]}) {
+ # closing the context under broader situations
+ $current = _end_paragraph($self, $current, $line_nr);
+ if ($current->{'parent'}
+ and $current->{'parent'}->{'cmdname'}
+ and $context_brace_commands{$current->{'parent'}->{'cmdname'}}
+ and $current->{'parent'}->{'cmdname'} eq $self->{'context_stack'}->[-1]) {
+ my $context_command = pop @{$self->{'context_stack'}};
+ if ($context_command ne $current->{'parent'}->{'cmdname'}) {
+ $self->_bug_message("context $context_command instead of brace isolated $current->{'parent'}->{'cmdname'}",
+ $line_nr, $current);
+ die;
+ }
+ print STDERR "CLOSING(context command) \@$current->{'parent'}->{'cmdname'}\n" if ($self->{'DEBUG'});
+ my $closed_command = $current->{'parent'}->{'cmdname'};
+ $self->_register_global_command($current->{'parent'}->{'cmdname'},
+ $current->{'parent'}, $line_nr);
+ $current = $current->{'parent'}->{'parent'};
+ $current = $self->_begin_preformatted ($current)
+ if ($close_preformatted_commands{$closed_command});
+ }
+ } else {
+ $self->line_error(sprintf($self->__("misplaced %c"),
+ ord('}')), $line_nr);
+ }
+ } elsif ($separator eq ','
+ and $current->{'parent'}->{'remaining_args'}) {
+ _abort_empty_line ($self, $current);
+ if ($brace_commands{$current->{'parent'}->{'cmdname'}}
+ and ($brace_commands{$current->{'parent'}->{'cmdname'}} > 1
+ or $simple_text_commands{$current->{'parent'}->{'cmdname'}})) {
+ $self->_isolate_last_space($current);
+ $self->_register_command_arg($current, 'brace_command_contents');
+ } else {
+ $self->_isolate_last_space($current);
+ if (exists $block_commands{$current->{'parent'}->{'cmdname'}}) {
+ $self->_register_command_arg($current, 'block_command_line_contents');
+ }
+ }
+ my $type = $current->{'type'};
+ $current = $current->{'parent'};
+ if ($inline_commands{$current->{'cmdname'}}) {
+ if (! $current->{'extra'}->{'format'}) {
+ # change the top of the raw_formats_stack now that we know the
+ # first arg of the inlineraw
+ my $inline_type
+ = Texinfo::Convert::Text::convert({'contents' =>
+ $current->{'extra'}->{'brace_command_contents'}->[0]},
+ {Texinfo::Common::_convert_text_options($self)});
+ if ($self->{'expanded_formats_stack'}->[-2]) {
+ if ($inline_format_commands{$current->{'cmdname'}}) {
+ if ($self->{'expanded_formats_hash'}->{$inline_type}) {
+ $self->{'expanded_formats_stack'}->[-1] = $inline_type;
+ $current->{'extra'}->{'expand_index'} = 1;
+ } else {
+ $self->{'expanded_formats_stack'}->[-1] = 0;
+ }
+ } elsif (($current->{'cmdname'} eq 'inlineifset'
+ and exists($self->{'values'}->{$inline_type}))
+ or ($current->{'cmdname'} eq 'inlineifclear'
+ and ! exists($self->{'values'}->{$inline_type}))) {
+ $self->{'expanded_formats_stack'}->[-1]
+ = "$current->{'cmdname'} $inline_type";
+ $current->{'extra'}->{'expand_index'} = 1;
+ } else {
+ $self->{'expanded_formats_stack'}->[-1] = 0;
+ }
+ } else {
+ $self->{'expanded_formats_stack'}->[-1] = 0;
+ }
+ $current->{'extra'}->{'format'} = $inline_type;
+ } else {
+ # happens for the second arg of inlinefmtifelse
+ my $inline_type = $current->{'extra'}->{'format'};
+ if ($self->{'expanded_formats_stack'}->[-2]
+ and ! ($self->{'expanded_formats_hash'}->{$inline_type})) {
+ $self->{'expanded_formats_stack'}->[-1] = $inline_type;
+ $current->{'extra'}->{'expand_index'} = 2;
+ } else {
+ $self->{'expanded_formats_stack'}->[-1] = 0;
+ }
+ }
+ }
+ $current->{'remaining_args'}--;
+ push @{$current->{'args'}},
+ { 'type' => $type, 'parent' => $current, 'contents' => [] };
+ #if ($inline_commands{$current->{'cmdname'}}
+ # and ! $self->{'expanded_formats_stack'}->[-1]) {
+ # $current->{'args'}->[-1]->{'extra'}->{'ignore'} = 1;
+ #}
+ $current = $current->{'args'}->[-1];
+ push @{$current->{'contents'}},
+ {'type' => 'empty_spaces_before_argument',
+ 'text' => '' };
+ } elsif ($separator eq ',' and $current->{'type'}
+ and $current->{'type'} eq 'misc_line_arg'
+ and $current->{'parent'}->{'cmdname'}
+ and $current->{'parent'}->{'cmdname'} eq 'node') {
+ $self->line_warn($self->__("superfluous arguments for node"), $line_nr);
+ # end of menu node (. must be followed by a space to stop the node).
+ } elsif (($separator =~ /[,\t.]/ and $current->{'type'}
+ and $current->{'type'} eq 'menu_entry_node')
+ or ($separator eq ':' and $current->{'type'}
+ and $current->{'type'} eq 'menu_entry_name')) {
+ $current = $current->{'parent'};
+ push @{$current->{'args'}}, { 'type' => 'menu_entry_separator',
+ 'text' => $separator,
+ 'parent' => $current };
+ } elsif ($separator eq "\f" and $current->{'type'}
+ and $current->{'type'} eq 'paragraph') {
+ # form feed stops and restart a paragraph.
+ $current = $self->_end_paragraph($current);
+ push @{$current->{'contents'}}, {'text' => $separator,
+ 'type' => 'empty_line',
+ 'parent' => $current };
+ push @{$current->{'contents'}}, { 'type' => 'empty_line',
+ 'text' => '',
+ 'parent' => $current };
+
+ } else {
+ $current = _merge_text($self, $current, $separator);
+ }
+ # Misc text except end of line
+ } elsif ($line =~ s/^([^{}@,:\t.\n\f]+)//) {
+ my $new_text = $1;
+ $current = _merge_text($self, $current, $new_text);
+ # end of line
+ } else {
+ if ($self->{'DEBUG'}) {
+ print STDERR "END LINE: ". _print_current($current)."\n";
+ }
+ if ($line =~ s/^(\n)//) {
+ $current = _merge_text($self, $current, $1);
+ } else {
+ if (scalar(@{$self->{'input'}})) {
+ $self->_bug_message("Text remaining without normal text but `$line'",
+ $line_nr, $current);
+ die;
+ }
+ }
+ #print STDERR "END LINE AFTER MERGE END OF LINE: ". _print_current($current)."\n";
+ $current = _end_line($self, $current, $line_nr);
+ last;
+ }
+ }
+ }
+ while (@{$self->{'conditionals_stack'}}) {
+ my $end_conditional = pop @{$self->{'conditionals_stack'}};
+ $self->line_error(sprintf($self->__("expected \@end %s"), $end_conditional),
+ $line_nr);
+ }
+ $current = _close_commands($self, $current, $line_nr);
+
+ if (@{$self->{'context_stack'}} != 1) {
+ # This happens in 2 cases in the tests:
+ # @verb not closed on misc commands line
+ # def line escaped with @ ending the file
+ if ($self->{'DEBUG'}) {
+ print STDERR "CONTEXT_STACK no empty end _parse_texi: ".join('|', @{$self->{'context_stack'}})."\n";
+ }
+ @{$self->{'context_stack'}} = ($self->{'context'});
+ }
+ if (@{$self->{'expanded_formats_stack'}} != 1) {
+ if ($self->{'DEBUG'}) {
+ print STDERR "EXPANDED_FORMATS_STACK no empty end _parse_texi: ".join('|', @{$self->{'expanded_formats_stack'}})."\n";
+ }
+ @{$self->{'expanded_formats_stack'}} = ($self->{'expanded_formats_stack'}->[0]);
+ }
+ return $root;
+}
+
+
+# parse special line @-commands, unmacro, set, clear, clickstyle.
+# Also remove spaces or ignore text, as specified in the misc_commands hash.
+sub _parse_special_misc_command($$$$)
+{
+ my $self = shift;
+ my $line = shift;
+ my $command = shift;
+ my $line_nr = shift;
+ my $args = [];
+
+ my $has_comment = 0;
+ my $remaining;
+ if ($command eq 'set') {
+ # REVALUE
+ #if ($line =~ s/^\s+([\w\-]+)(\s+(.*?))\s*$//) {
+ if ($line =~ /^\s+([\w\-][^\s{\\}~`\^+"<>|@]*)(\@(c|comment)((\@|\s+).*)?|[^\S\f]+(.*?))?[^\S\f]*$/) {
+ if ($line =~ s/\@(c|comment)((\@|\s+).*)?$//) {
+ $has_comment = 1;
+ }
+ $line =~ /^\s+([\w\-][^\s{\\}~`\^+"<>|@]*)([^\S\f]+(.*?))?[^\S\f]*$/;
+ my $name = $1;
+ my $arg = $3;
+ $arg = '' if (!defined($arg));
+ $args = [$name, $arg];
+ $self->{'values'}->{$name} = $arg
+ unless(_ignore_global_commands($self));
+ } elsif ($line !~ /\S/) {
+ $self->line_error(sprintf($self->
+ __("%c%s requires a name"), ord('@'), $command), $line_nr);
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad name for \@%s"), $command), $line_nr);
+ }
+ } elsif ($command eq 'clear') {
+ # REVALUE
+ if ($line =~ /^\s+([\w\-][^\s{\\}~`\^+"<>|@]*)\s*(\@(c|comment)((\@|\s+).*)?)?$/) {
+ $args = [$1];
+ delete $self->{'values'}->{$1}
+ unless(_ignore_global_commands($self));
+ $has_comment = 1 if (defined($3));
+ #$remaining = $line;
+ #$remaining =~ s/^\s+([\w\-]+)\s*(\@(c|comment)((\@|\s+).*)?)?//;
+ } elsif ($line !~ /\S/) {
+ $self->line_error(sprintf($self->
+ __("%c%s requires a name"), ord('@'), $command), $line_nr);
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad name for \@%s"), $command), $line_nr);
+ }
+ } elsif ($command eq 'unmacro') {
+ # REMACRO
+ if ($line =~ /^\s+([[:alnum:]][[:alnum:]\-]*)\s*(\@(c|comment)((\@|\s+).*)?)?$/) {
+ $args = [$1];
+ delete $self->{'macros'}->{$1}
+ unless(_ignore_global_commands($self));
+ $has_comment = 1 if (defined($3));
+ print STDERR "UNMACRO $1\n" if ($self->{'DEBUG'});
+ } elsif ($line !~ /\S/) {
+ $self->line_error(sprintf($self->
+ __("%c%s requires a name"), ord('@'), $command), $line_nr);
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad name for \@%s"), $command), $line_nr);
+ }
+ } elsif ($command eq 'clickstyle') {
+ # REMACRO
+ if ($line =~ /^\s+@([[:alnum:]][[:alnum:]\-]*)({})?\s*/) {
+ $args = ['@'.$1];
+ $self->{'clickstyle'} = $1
+ unless(_ignore_global_commands($self));
+ $remaining = $line;
+ $remaining =~ s/^\s+@([[:alnum:]][[:alnum:]\-]*)({})?\s*(\@(c|comment)((\@|\s+).*)?)?//;
+ $has_comment = 1 if (defined($4));
+ } else {
+ $self->line_error (sprintf($self->__(
+ "\@%s should only accept a \@-command as argument, not `%s'"),
+ $command, $line), $line_nr);
+ }
+ } else {
+ die $self->_bug_message("Unknown special command $command", $line_nr);
+ }
+ if (defined($remaining)) {
+ chomp($remaining);
+ if ($remaining ne '') {
+ $self->line_warn(sprintf($self->__(
+ "remaining argument on \@%s line: %s"),
+ $command, $remaining), $line_nr);
+ }
+ }
+ return ($args, $has_comment);
+}
+
+sub _trim_spaces_comment_from_content($)
+{
+ Texinfo::Common::trim_spaces_comment_from_content($_[0]);
+}
+
+# at the end of a @-command line with arguments, parse the resulting
+# text, to collect aliases, definfoenclose and collect errors on
+# wrong arguments.
+sub _parse_line_command_args($$$)
+{
+ my $self = shift;
+ my $line_command = shift;
+ my $line_nr = shift;
+
+ my $args;
+
+ my $command = $line_command->{'cmdname'};
+ my $arg = $line_command->{'args'}->[0];
+
+ if ($self->{'DEBUG'}) {
+ print STDERR "MISC ARGS \@$command\n";
+ if (@{$arg->{'contents'}}) {
+ my $idx = 0;
+ foreach my $content (@{$arg->{'contents'}}) {
+ my $name = '';
+ $name = '@' . $content->{'cmdname'} if ($content->{'cmdname'});
+ my $type = ', t: ';
+ $type .= $content->{'type'} if ($content->{'type'});
+ my $text = ', ';
+ $type .= $content->{'text'} if ($content->{'text'});
+ print STDERR " -> $idx $name $type $text\n";
+ $idx++;
+ }
+ }
+ }
+
+ my @contents = @{$arg->{'contents'}};
+
+ _trim_spaces_comment_from_content(\@contents);
+
+ if (! @contents) {
+ $self->_command_error($line_command, $line_nr,
+ $self->__("\@%s missing argument"), $command);
+ $line_command->{'extra'}->{'missing_argument'} = 1;
+ return undef;
+ }
+
+ if (@contents > 1
+ or (!defined($contents[0]->{'text'}))) {
+ $self->line_error (sprintf($self->__("superfluous argument to \@%s"),
+ $command), $line_nr);
+ }
+ return undef if (!defined($contents[0]->{'text'}));
+
+ my $line = $contents[0]->{'text'};
+
+ if ($command eq 'alias') {
+ # REMACRO
+ if ($line =~ s/^([[:alnum:]][[:alnum:]-]*)(\s*=\s*)([[:alnum:]][[:alnum:]-]*)$//) {
+ my $new_command = $1;
+ my $existing_command = $3;
+ $args = [$1, $3];
+ $self->{'aliases'}->{$new_command} = $existing_command
+ unless (_ignore_global_commands($self));
+ if (exists($block_commands{$existing_command})) {
+ $self->line_warn(sprintf($self->
+ __("environment command %s as argument to \@%s"),
+ $existing_command, $command), $line_nr);
+ }
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad argument to \@%s"), $command), $line_nr);
+ }
+
+ } elsif ($command eq 'definfoenclose') {
+ # REMACRO
+ if ($line =~ s/^([[:alnum:]][[:alnum:]\-]*)\s*,\s*([^\s,]*)\s*,\s*([^\s,]*)$//) {
+ $args = [$1, $2, $3 ];
+ $self->{'definfoenclose'}->{$1} = [ $2, $3 ]
+ unless (_ignore_global_commands($self));
+ print STDERR "DEFINFOENCLOSE \@$1: $2, $3\n" if ($self->{'DEBUG'});
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad argument to \@%s"), $command), $line_nr);
+ }
+ } elsif ($command eq 'columnfractions') {
+ my @possible_fractions = split (/\s+/, $line);
+ if (!@possible_fractions) {
+ $self->line_error (sprintf($self->__("empty \@%s"), $command),
+ $line_nr);
+ } else {
+ foreach my $fraction (@possible_fractions) {
+ if ($fraction =~ /^(\d*\.\d+)|(\d+)\.?$/) {
+ push @$args, $fraction;
+ } else {
+ $self->line_error (sprintf($self->
+ __("column fraction not a number: %s"),
+ $fraction), $line_nr);
+ }
+ }
+ }
+ } elsif ($command eq 'sp') {
+ if ($line =~ /^([0-9]+)$/) {
+ $args = [$1];
+ } else {
+ $self->line_error(sprintf($self->__("\@sp arg must be numeric, not `%s'"),
+ $line), $line_nr);
+ }
+ } elsif ($command eq 'defindex' || $command eq 'defcodeindex') {
+ # REMACRO
+ if ($line =~ /^([[:alnum:]][[:alnum:]\-]*)$/) {
+ my $name = $1;
+ if ($forbidden_index_name{$name}) {
+ $self->line_error(sprintf($self->
+ __("reserved index name %s"),$name), $line_nr);
+ } else {
+ my $in_code = 0;
+ $in_code = 1 if ($command eq 'defcodeindex');
+ $args = [$name];
+ if (! _ignore_global_commands($self)) {
+ $self->{'index_names'}->{$name} = {'in_code' => $in_code};
+ $self->_register_index_commands($name);
+ }
+ }
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad argument to \@%s: %s"), $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'synindex' || $command eq 'syncodeindex') {
+ # REMACRO
+ if ($line =~ /^([[:alnum:]][[:alnum:]\-]*)\s+([[:alnum:]][[:alnum:]\-]*)$/) {
+ my $index_from = $1;
+ my $index_to = $2;
+ $self->line_error(sprintf($self->__("unknown source index in \@%s: %s"),
+ $command, $index_from), $line_nr)
+ unless $self->{'index_names'}->{$index_from};
+ $self->line_error(sprintf($self->__("unknown destination index in \@%s: %s"),
+ $command, $index_to), $line_nr)
+ unless $self->{'index_names'}->{$index_to};
+ if ($self->{'index_names'}->{$index_from}
+ and $self->{'index_names'}->{$index_to}) {
+ my $current_to = $index_to;
+ # find the merged indices recursively avoiding loops
+ while ($current_to ne $index_from
+ and $self->{'merged_indices'}->{$current_to}) {
+ $current_to = $self->{'merged_indices'}->{$current_to};
+ }
+ if ($current_to ne $index_from) {
+ my $index_from_info = $self->{'index_names'}->{$index_from};
+ my $index_to_info = $self->{'index_names'}->{$current_to};
+
+ my $in_code = 0;
+ $in_code = 1 if ($command eq 'syncodeindex');
+ if (! _ignore_global_commands($self)) {
+ $self->{'merged_indices'}->{$index_from} = $current_to;
+ $index_from_info->{'in_code'} = $in_code;
+ foreach my $contained_index (keys %{$index_from_info->{'contained_indices'}}) {
+ $index_to_info->{'contained_indices'}->{$contained_index} = 1;
+ $self->{'index_names'}->{$contained_index}->{'merged_in'} = $current_to;
+ }
+ $index_from_info->{'merged_in'} = $current_to;
+ $index_to_info->{'contained_indices'}->{$index_from} = 1;
+ }
+ $args = [$index_from, $index_to];
+ } else {
+ $self->line_warn(sprintf($self->__(
+ "\@%s leads to a merging of %s in itself, ignoring"),
+ $command, $index_from), $line_nr);
+ }
+ }
+ } else {
+ $self->line_error(sprintf($self->__("bad argument to \@%s: %s"),
+ $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'printindex') {
+ # REMACRO
+ if ($line =~ /^([[:alnum:]][[:alnum:]\-]*)$/) {
+ my $name = $1;
+ if (!exists($self->{'index_names'}->{$name})) {
+ $self->line_error(sprintf($self->__("unknown index `%s' in \@printindex"),
+ $name), $line_nr);
+
+ } else {
+ if ($self->{'merged_indices'}->{$name}) {
+ $self->line_warn(sprintf($self->__(
+ "printing an index `%s' merged in another one `%s'"),
+ $name, $self->{'merged_indices'}->{$name}),
+ $line_nr);
+ }
+ if (!defined($self->{'current_node'})
+ and !defined($self->{'current_section'})
+ and !scalar(@{$self->{'regions_stack'}})) {
+ $self->line_warn(sprintf($self->__(
+ "printindex before document beginning: \@printindex %s"),
+ $name), $line_nr);
+ }
+ $args = [$name];
+ }
+ } else {
+ $self->line_error(sprintf($self->
+ __("bad argument to \@%s: %s"), $command, $line), $line_nr);
+ }
+ } elsif (grep {$_ eq $command} ('everyheadingmarks', 'everyfootingmarks',
+ 'evenheadingmarks', 'oddheadingmarks',
+ 'evenfootingmarks', 'oddfootingmarks')) {
+ if ($line eq 'top' or $line eq 'bottom') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@%s arg must be `top' or `bottom', not `%s'"),
+ $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'fonttextsize') {
+ if ($line eq '10' or $line eq '11') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "Only \@%s 10 or 11 is supported, not `%s'"),
+ $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'footnotestyle') {
+ if ($line eq 'separate' or $line eq 'end') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@%s arg must be `separate' or `end', not `%s'"),
+ $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'setchapternewpage') {
+ if ($line eq 'on' or $line eq 'off' or $line eq 'odd') {
+ $args = [$1];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@%s arg must be `on', `off' or `odd', not `%s'"),
+ $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'need') { # only a warning
+ if (($line =~ /^([0-9]+(\.[0-9]*)?)$/) or
+ ($line =~ /^(\.[0-9]+)$/)) {
+ $args = [$1];
+ } else {
+ $self->line_error(sprintf($self->__("bad argument to \@%s: %s"),
+ $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'paragraphindent') {
+ if ($line =~ /^([\w\-]+)$/) {
+ my $value = $1;
+ if ($value =~ /^([0-9]+)$/ or $value eq 'none' or $value eq 'asis') {
+ $args = [$1];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@paragraphindent arg must be numeric/`none'/`asis', not `%s'"),
+ $value), $line_nr);
+ }
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@paragraphindent arg must be numeric/`none'/`asis', not `%s'"),
+ $line), $line_nr);
+ }
+ } elsif ($command eq 'firstparagraphindent') {
+ if ($line eq 'none' or $line eq 'insert') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@firstparagraphindent arg must be `none' or `insert', not `%s'"),
+ $line), $line_nr);
+ }
+ } elsif ($command eq 'exampleindent') {
+ if ($line =~ /^([0-9]+)/) {
+ $args = [$1];
+ } elsif ($line =~ /^(asis)$/) {
+ $args = [$1];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@exampleindent arg must be numeric/`asis', not `%s'"),
+ $line), $line_nr);
+ }
+ } elsif ($command eq 'frenchspacing'
+ or $command eq 'xrefautomaticsectiontitle'
+ or $command eq 'codequoteundirected'
+ or $command eq 'codequotebacktick'
+ or $command eq 'deftypefnnewline') {
+ if ($line eq 'on' or $line eq 'off') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__("expected \@%s on or off, not `%s'"),
+ $command, $line), $line_nr);
+ }
+ } elsif ($command eq 'kbdinputstyle') {
+ if ($line eq 'code' or $line eq 'example' or $line eq 'distinct') {
+ $self->{'kbdinputstyle'} = $line
+ unless (_ignore_global_commands($self));
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@kbdinputstyle arg must be `code'/`example'/`distinct', not `%s'"),
+ $line), $line_nr);
+ }
+ } elsif ($command eq 'allowcodebreaks') {
+ if ($line eq 'true' or $line eq 'false') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@allowcodebreaks arg must be `true' or `false', not `%s'"),
+ $line), $line_nr);
+ }
+ } elsif ($command eq 'urefbreakstyle') {
+ if ($line eq 'after' or $line eq 'before' or $line eq 'none') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__(
+ "\@urefbreakstyle arg must be `after'/`before'/`none', not `%s'"),
+ $line), $line_nr);
+ }
+ } elsif ($command eq 'headings') {
+ if ($line eq 'off' or $line eq 'on' or $line eq 'single'
+ or $line eq 'double' or $line eq 'singleafter' or $line eq 'doubleafter') {
+ $args = [$line];
+ } else {
+ $self->line_error(sprintf($self->__("bad argument to \@%s: %s"),
+ $command, $line), $line_nr);
+ }
+ }
+ return $args;
+}
+
+1;
+__END__
+=head1 NAME
+
+Texinfo::Parser - Parse Texinfo code in a Perl tree
+
+=head1 SYNOPSIS
+
+ use Texinfo::Parser;
+ my $parser = Texinfo::Parser::parser();
+ my $tree = $parser->parse_texi_file("somefile.texi");
+ my ($errors, $errors_count) = $parser->errors();
+ foreach my $error_message (@$errors) {
+ warn $error_message->{'error_line'};
+ }
+
+ my ($index_names, $merged_indices_hash)
+ = $parser->indices_information();
+ my $float_types_arrays = $parser->floats_information();
+ my $internal_references_array
+ = $parser->internal_references_information();
+ # An hash reference on normalized node/float/anchor names
+ my $labels_information = $parser->labels_information();
+ # A hash reference, keys are @-command names, value is an
+ # array reference holding all the corresponding @-commands.
+ my $global_commands_information = $parser->global_commands_information();
+ # a hash reference on some document informations (encodings,
+ # input file name, dircategory and direntry list, for exampel).
+ my $global_informations = $parser->global_informations();
+
+=head1 DESCRIPTION
+
+Texinfo::Parser will parse Texinfo text into a perl tree. In one pass
+it expands user defined @-commands, conditionals (@ifset, @ifinfo...)
+and @value and constructs the tree. Some extra information is gathered
+while doing the tree, for example the block command associated with @end,
+the number of row in a multitable, the node associated with a section.
+
+
+
+=head1 METHODS
+
+No method is exported in the default case. The module allows both
+an object oriented syntax, or traditional function, with the parser
+as an opaque data structure given in argument to every function.
+
+=head2 Initialization
+
+The following method is used to construct a new C<Texinfo::Parser> object:
+
+=over
+
+=item $parser = Texinfo::Parser::parser($options);
+
+This method creates a new parser. The options may be provided as a hash
+reference. There are two types of options. The first type of options
+change the way the parser behave, they are described right here. The
+other type of options allow to give to the parser some information as if
+it came from texinfo code, for example allow to set aliases (as with
+C<@alias>), values (as with C<@set>), merged indices (as with
+C<@synindex>). These options are described below in L</Texinfo Parser options>.
+
+=over
+
+=item expanded_formats
+
+An array reference of the output formats for which C<@ifI<FORMAT>>
+conditional blocks should be expanded. Default is empty.
+
+The raw block formats (within C<@html> blocks, for example) are
+always kept.
+
+=item gettext
+
+If set, the function reference is used to translate error and warning
+messages. It takes a string as argument and returns a string. The default
+function returns the error message as is.
+
+=item GLOBAL_COMMANDS
+
+The associated value is a reference on an array. All the commands in the
+array are collected during parsing. They are afterwards available
+through L<global_informations|/$info = global_informations($parser)>.
+
+=item include_directories
+
+An array reference of directories in which C<@include> files should be
+searched for. Default contains the working directory, F<.>.
+
+=item INLINE_INSERTCOPYING
+
+If set, C<@insertcopying> is replaced by the C<@copying> content as if
+C<@insertcopying> was a user-defined macro. In the default case, it is
+considered to be a simple @-command and kept as is in the tree.
+
+=item IGNORE_BEFORE_SETFILENAME
+
+If set, and C<@setfilename> exists, everything before C<@setfilename>
+is put in a special container type, @C<preamble_before_setfilename>.
+This option is set in the default case.
+
+=item IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME
+
+If set, spaces after an @-commande name that take braces are ignored.
+Default on.
+
+=item MACRO_BODY_IGNORES_LEADING_SPACE
+
+If set, leading spaces are stripped from user defined macro bodies.
+
+=item MAX_MACRO_CALL_NESTING
+
+Maximal number of nested user-defined macro calls. Default is 100000.
+
+=item SHOW_MENU
+
+If false, no menu related error are reported. Default is true.
+
+=begin :comment
+
+Used by Sectioning only
+=item TOP_NODE_UP
+
+Text for the up node of the Top node. The default is C<(dir)>. The
+string may contain @-commands.
+
+=end :comment
+
+=back
+
+=back
+
+=head2 Parsing Texinfo text
+
+There are three methods that may be called to parse some Texinfo code,
+C<parse_texi_line> for a line, C<parse_texi_text> for a text fragment,
+and C<parse_texi_file> for a file.
+
+For all those functions, if the I<$parser> argument is undef, a new
+parser object is generated to parse the line, otherwise the parser given
+in argument is used to parse into a tree.
+
+When C<parse_texi_text> is used, the resulting tree is rooted at
+a C<root_line> type container. Otherwise, the resulting tree should be
+rooted at a C<text_root> type container if it do not contain nodes or
+sections, at a C<document_root> type container otherwise.
+
+=over
+
+=item $tree = parse_texi_line($parser, $text, $first_line_number, $file_name, $macro_name, $fixed_line_number)
+
+This function is used to parse a short fragment of Texinfo code.
+
+I<$text> may be either an array reference of lines, or a text.
+
+The other arguments are optional and allow to specify the position
+information of the Texinfo code. I<$first_line_number> is the line number
+of the first text line. I<$file_name> is the name of the file the
+text comes from. I<$macro> is for the user-defined macro name the text
+is expanded from. If I<$fixed_line_number> is set, the line number is
+not increased for the different lines, as if the text was the expansion
+of a macro.
+
+=item $tree = parse_texi_text ($parser, $text, $line_numbers_specification, $file_name, $macro_name, $fixed_line_number)
+
+This function is used to parse some Texinfo text.
+
+I<$text> may be either an array reference of lines, or a text.
+
+The other arguments are optional and allow to specify the position
+information of the Texinfo code. There are two distinct cases for
+I<$line_numbers_specification>.
+
+=over
+
+=item 1.
+
+If it is an array reference, it is considered to hold objects describing
+the position information of each text lines.
+
+=item 2.
+
+If I<$line_numbers_specification> is a scalar, it is the line number of
+the first text line. In that case (like for C<parse_texi_text>),
+I<$file_name> is the name of the file the text comes from.
+I<$macro> is for the user-defined macro name the text
+is expanded from. If I<$fixed_line_number> is set, the line number is
+not increased for the different lines, as if the text was the expansion
+of a macro.
+
+=back
+
+=item $tree = parse_texi_file($parser, $file_name)
+
+The file with name I<$file_name> is considered to be a Texinfo file and
+is parsed into a tree.
+
+undef is returned if the file couldn't be read.
+
+=back
+
+The errors collected during the tree parsing are available through the
+C<errors> method. This method comes from C<Texinfo::Report>, and is
+described in L<errors|Texinfo::Report/($error_warnings_list, $error_count) = errors ($converter)>.
+
+=head2 Getting informations on the document
+
+After parsing some informations about the Texinfo code that was processed
+are available from the parser.
+
+Some global informations is available through C<global_informations>
+
+=over
+
+=item $info = global_informations($parser)
+
+The I<$info> returned is a hash reference. The possible keys are
+
+=over
+
+=item input_file_name
+
+The name of the main Texinfo input file.
+
+=item input_encoding_name
+
+=item input_perl_encoding
+
+C<input_encoding_name> string is the encoding name used for the
+Texinfo code.
+C<input_perl_encoding> string is a corresponding perl encoding name.
+
+=item dircategory_direntry
+
+An array of successive C<@dircategory> and C<@direntry> as they appear
+in the document.
+
+=item unassociated_menus
+
+An array of menus that are not associated with a node.
+
+=back
+
+=back
+
+Some command lists are available, such that it is possible to go through
+the corresponding tree elements without walking the tree. They are
+available through C<global_commands_information>
+
+=over
+
+=item $commands = global_commands_information($parser)
+
+I<$commands> is an hash reference. The keys are @-command names. The
+associated values are array references containing all the corresponding
+tree elements.
+
+=back
+
+All the @-commands that have an associated label, that can be the
+target of cross references, C<@node>, C<@anchor> and C<@float> with
+label have a normalized name associated, constructed as described in the
+B<HTML Xref> node in the Texinfo manual. Those normalized labels and
+the association with @-commands is available through C<labels_information>:
+
+=over
+
+=item $labels_information = labels_information($parser)
+
+I<$labels_information> is a hash reference whose keys are normalized
+labels, and the associated value is the corresponding @-command.
+
+=back
+
+Information on C<@float> is also available, grouped by type of
+floats, each type correponding to potential C<@listoffloats>.
+This information is available through the method C<floats_information>.
+
+=over
+
+=item $float_types = floats_information($parser)
+
+I<$float_types> is a hash reference whose keys are normalized float
+types (the first float argument, or the C<@listoffloats> argument).
+The normalization is the same than for node names. The value is the list
+of float tree elements appearing in the texinfo document.
+
+=back
+
+Internal references, that is, @-commands that refers to node, anchors
+or floats within the document are also available:
+
+=over
+
+=item $internal_references_array = internal_references_information($parser);
+
+The function returns a list of cross reference commands referring to
+the same document.
+
+=back
+
+Information about defined indices, merged indices and index entries is
+also available through the C<indices_information> method.
+
+=over
+
+=item indices_information
+
+ ($index_names, $merged_indices_hash)
+ = indices_information($parser);
+
+The index names is a hash reference. The keys are
+
+=over
+
+=item in_code
+
+1 if the index entries should be formatted as code, 0 in the opposite case.
+
+=item name
+
+The index name.
+
+=item prefix
+
+An array reference of prefix associated to the index.
+
+=item merged_in
+
+In case the index is merged to another index, this key holds the name of
+the index the index is merged into. It takes into account indirectly
+merged indices.
+
+=item contained_indices
+
+An hash reference holding names of indices that are merged to the index,
+including itself. It also contains indirectly merged indices. This key
+is present even if the index is itself later merged to another index.
+
+=item index_entries
+
+An array reference containing index entry structures for index entries
+associated with the index. The index entry could be associated to
+@-commands like C<@cindex>, or C<@item> in C<@vtable>, or definition
+commands entries like C<@deffn>.
+
+The keys of the index entry structures are
+
+=over
+
+=item index_name
+
+The index name.
+
+=item index_prefix
+
+The associated index prefix.
+
+=item index_at_command
+
+The name of the @-command associated with the index entry.
+
+=item index_type_command
+
+The @-command associated with the index entry allowing to
+find the index type.
+
+=item content
+
+An array reference corresponding to the index entry content.
+
+=item content_normalized
+
+An array reference corresponding to the index entry content, independent
+of the current language.
+
+=item command
+
+The element in the parsed tree associated with the @-command holding the
+index entry.
+
+=item node
+
+The node in the parsed tree containing the index entry.
+
+=item number
+
+The number of the index entry.
+
+=item region
+
+The region command (C<@copying>, C<@titlepage>) containing the index entry,
+if it is in such an environement.
+
+=back
+
+=back
+
+The following shows the references corresponding with the default indexes
+I<cp> and I<fn>, the I<fn> index having its entries formatted as code and
+the indices corresponding to the following texinfo
+
+ @defindex some
+ @defcodeindex code
+
+ $index_names = {'cp' => {'name' => 'cp', 'in_code' => 0,
+ 'prefix' => ['c', 'cp']},
+ 'fn' => {'name' => 'fn', 'in_code' => 1,
+ 'prefix' => ['f', 'fn']},
+ 'some' => {'in_code' => 0},
+ 'code' => {'in_code' => 1}};
+
+If C<name> is not set, it is set to the index name. If C<prefix> is
+not set, it is set to an array containing the index name.
+
+I<$merged_indices_hash> is a hash reference, the key is an index
+name merged in the value.
+
+=back
+
+=head2 Texinfo Parser options
+
+Setting those options is the same as seeing some Texinfo constructs in the
+document.
+
+=over
+
+=item aliases
+
+A hash reference. The key is a command name, the value is the alias, as
+could be set by C<@alias>.
+
+=item clickstyle
+
+A string, the command name associated with C<@clickstyle>.
+
+=item documentlanguage
+
+A string corresponding to a document language set by C<@documentlanguage>.
+
+=item explained_commands
+
+A hash reference of explained commands (currently abbr or acronym).
+The value is also a hash reference. The key of this hash is a normalized
+first argument of these commands, the value is a content array
+corresponding to the explanation associated with this first argument.
+
+For example giving as an option:
+
+ 'explained_commands'
+ => {'acronym' => {'EU' => [{'text' => 'European Union'}]}
+
+is the same as having the following texinfo code in the document:
+
+ @acronym{EU, European Union}
+
+=item INPUT_ENCODING_NAME
+
+=item INPUT_PERL_ENCODING
+
+C<INPUT_ENCODING_NAME> string is the encoding name as set
+by C<@documentencoding>.
+C<INPUT_PERL_ENCODING> string is a corresponding perl encoding name.
+In general those two strings should be set simultaneously.
+
+=item indices
+
+If it is a hash reference, the keys are index names, the values are
+index prefix hash references. The index prefix hash reference values are
+prefix, the value is set if the corresponding index entries should be
+formatted as if in C<@code>. An example is as L</indices_information>.
+
+If it is an array reference, it is a list of index names, as if they were
+entered as
+
+ @defindex name
+
+=item kbdinputstyle
+
+A string, the C<@kbdinputstyle> style.
+
+=item labels
+
+A hash reference. Keys are normalized node names as described in the
+B<HTML Xref> node in the Texinfo manual. Instead of a node, it may also
+be a float label or an anchor name. The value is the corresponding
+@-command element in the tree.
+
+=item macros
+
+The associated hash reference has as key user-defined macro names. The
+value is the reference on a macro definition element as obtained by
+the Parser when parsing a C<@macro>. For example
+
+ @macro mymacro{arg}
+ coucou \arg\ after arg
+ @end macro
+
+Is associated to a macro definition element
+
+ {'cmdname' => 'macro',
+ 'args' => [{'text' => 'mymacro', 'type' => 'macro_name'},
+ {'text' => 'arg', 'type' => 'macro_arg}],
+ 'contents' => [{'text' => "coucou \arg\ after arg\n", 'type' => 'raw'}],
+ 'extra' => {'arg_line' => " mymacro{arg}\n",
+ 'macrobody' => "coucou \arg\ after arg\n"}}
+
+= item merged_indices
+
+The associated hash reference holds merged indices information, each key
+is merged in the value. Same as setting C<@synindex> or C<syncodeindex>.
+
+=item novalidate
+
+If set, it is as if C<@novalidate> was set in the document.
+
+=item sections_level
+
+Modifier of the sections level. Same as calling C<@lowersections> or
+C<@raisesections>.
+
+=item values
+
+A hash reference. Keys are names, values are the corresponding values.
+Same as values set by C<@set>.
+
+=back
+
+=head1 TEXINFO TREE
+
+A Texinfo tree element (called element because node is overloaded in
+the Texinfo world) is an hash reference. There are three main category
+of tree element. Tree elements associated with an @-command have a
+C<cmdname> key holding the @-command name. Tree element corresponding
+to text fragments have a C<text> key holding the corresponding text.
+The last category corresponds to other containers (hereafter called
+containers). In most case these containers have a C<type> key holding
+their name. Text fragments and @-command elements may also have an
+associated type when such information is needed.
+
+The children of @-command or container elements are in the array
+correponding with the C<args> key or with the C<contents> key. The
+C<args> key is for arguments of @-commands, in braces or on the @-command
+line. C<args> is also used for the elements of a menu entry, as a menu
+entry is well structured with a limited number of arguments.
+The C<contents> key array holds the contents of the texinfo
+code appearing within a block @-command, within a container,
+within a C<@node> or sectioning @-command.
+
+Another important key for the elements is the C<extra> key which is
+associated to a hash reference and holds all kinds of informations gathered
+during the parsing and that may help with the conversion.
+
+=head2 Element keys
+
+=over
+
+=item cmdname
+
+The command name of @-command elements.
+
+=item text
+
+The text fragment of text elements.
+
+=item type
+
+The type of the element. For C<@verb> it is the delimiter. But otherwise
+it is the type of element considered as a container. Frequent types
+encountered are I<paragraph> for a paragraph container,
+I<brace_command_arg> for the container holding the brace @-commands
+contents, I<misc_line_arg> and I<block_line_arg> contain the arguments
+appearing on the line of @-commands. Text fragments may have a type to
+give an information of the kind of text fragment, for example
+C<empty_spaces_before_argument> is associated to spaces after a brace
+opening and before the argument. Many @-commands elements don't have
+a type associated.
+
+=item args
+
+Arguments in braces or on @-command line, and the elements of a menu entry.
+
+=item contents
+
+The Texinfo appearing in the element. For block commands, other
+containers, C<@node> and sectioning commands.
+
+=item parent
+
+The parent element.
+
+=item line_nr
+
+An hash reference corresponding to information on the location of the
+element in the Texinfo input manual. It should only be available for
+@-command elements, and only for @-commands that are considered to be
+complex enough that the location in the document is needed, for example
+to prepare an error message.
+
+The keys of the line number hash references are
+
+=over
+
+=item line_nr
+
+The line number of the @-command.
+
+=item file_name
+
+The file name where @-command appeared.
+
+=item macro
+
+The user macro name the @-command is expanded from.
+
+=back
+
+=item extra
+
+A hash reference holding any additional information.
+See L</Information available in the extra key>.
+
+=back
+
+=head2 The containers and types
+
+Some types are associated with @-commands. As said above, for C<@verb>
+the type is the delimiter. For a C<@value> command that is not
+expanded because there is no corresponding value set, the type is the
+value argument string.
+
+The following types also happen for @-commands:
+
+=over
+
+=item def_line
+
+This type may be associated with a definition command with a x form,
+like C<@defunx>, C<@defvrx>. For the form without x, the associated
+I<def_line> is the first C<contents> element. It is described in more
+details below.
+
+=item command_as_argument
+
+This is the type of a command given in argument of C<@itemize>,
+C<@table>, C<@vtable> or C<@ftable>. For example in
+
+ @itemize @bullet
+ @item item
+ @end itemize
+
+the element corresponding with bullet has the following keys:
+
+ 'cmdname' => 'bullet'
+ 'type' => 'command_as_argument'
+
+The parent @-command has an entry in extra for the I<command_as_argument>
+element:
+
+ 'cmdname' => 'itemize'
+ 'extra => {'command_as_argument' => $command_element_as_argument}
+
+=item index_entry_command
+
+This is the type of index entry command like C<@cindex>, and, more
+importantly user defined index entry commands. So for example if there
+is
+
+ @defindex foo
+ ...
+
+ @fooindex index entry
+
+the C<@fooindex> @-command element will have the I<index_entry_command>
+type.
+
+=item following_arg
+
+This type is set for non alphabetic accent @-commands that don't use brace
+but instead have their argument right after them, as
+
+ @~n
+
+=item space_command_arg
+
+This type is set for accent @-commands that don't use brace but instead
+have their argument after some space, as
+
+ @ringaccent A
+
+=item definfoenclose_command
+
+This type is set for an @-command that is redefined by C<@definfoenclose>.
+The beginning is in C<< {'extra'}->{'begin'} >> and the end in
+C<< {'extra'}->{'end'} >>.
+
+=back
+
+The text elements may be associated to the following types:
+
+=over
+
+=item empty_line
+
+An empty line.
+
+=item raw
+
+Text in an environment where it should be kept as is (in C<@verbatim>,
+C<@verb>, C<@html>, C<@macro> body).
+
+=item last_raw_newline
+
+The last end of line in a raw block (except for C<@verbatim>).
+
+=item empty_line_after_command
+
+=item empty_spaces_after_command
+
+The text is spaces for I<empty_spaces_after_command>
+or spaces followed by a newline for
+I<empty_line_after_command>, appearing after a @-command that
+takes an argument on the line or a block
+@-commands.
+
+=item spaces_at_end
+
+Space at the end of a @-command line, at the end of some @-commands
+with braces or at the end of a bracketed content on a
+C<@multitable> line.
+
+=item empty_space_at_end_def_bracketed
+
+Space at the end of a bracketed content on definition line.
+
+=item space_at_end_block_command
+
+Space at the end of a block @-command line.
+
+=item empty_spaces_before_argument
+
+The text is spaces appearing after an opening brace of after a
+comma separated @-command arguments.
+
+=item empty_spaces_after_close_brace
+
+Spaces appearing after a closing brace, for some rare commands for which
+this space should be ignorable (like C<@caption>).
+
+=item empty_spaces_before_paragraph
+
+Space appearing before a paragraph beginning.
+
+=item preamble_text
+
+Text appearing before real content, including the C<\input texinfo.tex>.
+
+=item space_at_end_menu_node
+
+=item after_description_line
+
+Space after a node in the menu entry, when there is no description,
+and space appearing after the description line.
+
+=back
+
+Other special types are described in the following.
+
+=over
+
+=item text_root
+
+=item document_root
+
+=item root_line
+
+These types correspond to document roots. C<text_root> is the document
+root when there is no C<@node> or sectioning command. When
+such a command appears, a new root container is used, C<document_root>,
+and C<text_root> becomes the first content of C<document_root>.
+C<root_line> is the type of the root tree when parsing Texinfo line
+fragments using C<parse_texi_line>.
+
+=item preamble
+
+This container holds the text appearing before the first content, including
+the C<\input texinfo.tex> line and following blank lines.
+
+=item preamble_before_setfilename
+
+This container holds everything that appears before C<@setfilename>
+if I<IGNORE_BEFORE_SETFILENAME> parser option is set.
+
+=item paragraph
+
+A paragraph.
+
+=item preformatted
+
+Texinfo code within a format that is not filled. Happens within some
+block commands as C<@example>, but also in menu (in menu descriptions,
+menu comments...).
+
+=item brace_command_arg
+
+=item brace_command_context
+
+=item block_line_arg
+
+=item misc_line_arg
+
+Those containers are within C<args> of @-commands with braces for
+I<brace_command_arg>, @-commands with braces that start a new context
+(C<@footnote>, C<@caption>, C<@math>) for I<brace_command_context>,
+block command argument on their line for I<block_line_arg> and
+other commands that take texinfo code as argument on their line
+(C<@settitle>, C<@node>, C<@section> and similar) for I<misc_line_arg>.
+They hold the content of the command argument.
+
+For example
+
+ @code{in code}
+
+leads to
+
+ {'cmdname' => 'code',
+ 'args' => [{'type' => 'brace_command_arg',
+ 'contents' => [{'text' => 'in code'}]}]}
+
+=item misc_arg
+
+Argument of @-command taking specific textual arguments on the line.
+For example C<@set>, C<@clickstyle>, C<@unmacro>, C<@comment>.
+The argument is associated to the I<text> key.
+
+=item menu_entry
+
+=item menu_entry_leading_text
+
+=item menu_entry_name
+
+=item menu_entry_separator
+
+=item menu_entry_node
+
+=item menu_entry_description
+
+A I<menu_entry> holds a full menu entry, like
+
+ * node:: description.
+
+The different elements of the menu entry are directly in the
+I<menu_entry> C<args> array reference.
+
+I<menu_entry_leading_text> holds the star and following spaces.
+I<menu_entry_name> is the menu entry name (if present), I<menu_entry_node>
+corresponds to the node in the menu entry, I<menu_entry_separator> holds
+the text after the node and before the description, in most case
+C<:: >. Last I<menu_entry_description> is for the description.
+
+=item menu_comment
+
+The I<menu_comment> container holds what is between menu entries
+in menus. For example in
+
+ @menu
+ Menu title
+
+ * entry::
+
+ Between entries
+ * other::
+ @end menu
+
+Both
+
+ Menu title
+
+and
+
+ Between entries
+
+will be in I<menu_comment>.
+
+=item macro_name
+
+=item macro_arg
+
+Taken from C<@macro> definition and put in the C<args> key array of
+the macro, I<macro_name> is the type of the text fragment corresponding
+to the macro name, I<macro_arg> is the type of the text fragments
+correponding to macro formal arguments.
+
+=item before_item
+
+A container for content before the first C<@item> of block @-commands
+with items (C<@table>, C<@multitable>, C<@enumerate>...).
+
+=item table_entry
+
+=item table_term
+
+=item table_item
+
+=item inter_item
+
+Those containers appear in C<@table>, C<@ftable> and C<@vtable>.
+A I<table_entry> container contains C<@item> and C<@itemx> and
+the text following the C<@item> and C<@itemx> entries. A I<table_term>
+container holds all the C<@item> and C<@itemx> of the I<table_entry>.
+The I<table_item> container holds the content following the I<table_term>.
+If there is some content before an C<@itemx> (normally only comments,
+empty lines or maybe index entriees are allowed), it will be in
+a container with type I<inter_item>.
+
+=item def_line
+
+=item def_item
+
+=item inter_def_item
+
+The I<def_line> type is either associated with a container within a
+definition command, or is the type of a definition command with a x
+form, like C<@deffnx>. It holds the definition line arguments.
+The container with type I<def_item> holds the definition text content.
+Content appearing before a definition command with a x form is in
+an I<inter_def_item> container.
+
+=item multitable_head
+
+=item multitable_body
+
+=item row
+
+In C<@multitable>, a I<multitable_head> container contains all the row
+with C<@headitem>, while I<multitable_body> contains the rows associated
+with C<@item>. A I<row> container contains the C<@item> and @<tab>
+forming a row.
+
+=item bracketed
+
+This a special type containing content in brackets in the context
+where they are valid, in C<@math>.
+
+=item bracketed_def_content
+
+Content in brackets on definition command lines.
+
+=item bracketed_multitable_prototype
+
+=item row_prototype
+
+On C<@multitable> line, content in brackets is in
+I<bracketed_multitable_prototype>, text not in brackets
+is in I<row_prototype>.
+
+=back
+
+=head2 Information available in the extra key
+
+Some extra keys are available for more than one @-command:
+
+=over
+
+=item block_command_line_contents
+
+=item brace_command_contents
+
+An array associated with block @-commands or @-commands with braces
+taking more than one argument or with a simple text content
+(C<@anchor>, C<@titlefont>, C<@dmn>). Each of the element of the
+array is either undef, if there is no argument at that place,
+or an array reference holding the argument contents.
+
+=item misc_content
+
+The contents of an @-command taking regular Texinfo code as
+argument, like C<@sttitle> or C<@exdent>.
+
+=item end_command
+
+The C<@end> associated to the block @-command.
+
+=item missing_argument
+
+Set for some @-commands with line arguments and a missing argument.
+
+=item invalid_nesting
+
+Set if the @-command appears in a context it shouldn't appear in,
+like a block @-command on sectioning @-command line.
+
+=item arg_line
+
+The string correspond to the line after the @-command
+for @-commands that have special arguments on their line,
+and for C<@macro> line.
+
+=item text_arg
+
+The string correspond to the line after the @-command for @-commands
+that have an argument interpreted as simple text, like C<@setfilename>,
+C<@end> or C<@documentencoding>.
+
+=item index_entry
+
+The index entry information (described in L</index_entries>
+in details) is associated to @-commands that have an associated
+index entry.
+
+=item misc_args
+
+An array holding strings, the arguments of @-commands taking simple
+textual arguments as arguments, like C<@everyheadingmarks>,
+C<@frenchspacing>, C<@alias>, C<@synindex>, C<@columnfractions>.
+Also filled for C<@set>, C<@clickstyle>, C<@unmacro> or C<@comment>
+arguments.
+
+=item spaces_after_command
+
+For @-commands followed by spaces, a reference to the corresponding
+text element.
+
+=item spaces_before_argument
+
+For @-commands with opening brace followed by spaces held in a
+C<empty_spaces_before_argument> element, a reference to that element.
+
+=item spaces
+
+For accent commands consisting in letter only, like C<@ringaccent>
+appearing like
+
+ @ringaccent A
+
+there is a I<spaces> key which holds the spaces appearing between
+the command and the argument.
+
+=back
+
+Then there are extra keys specific of @-commands or containers.
+
+=over
+
+=item C<@macro>
+
+I<invalid_syntax> is set if there was an error on the C<@macro>
+line. The key I<args_index> associates format arguments with
+their index on the @macro line formal arguments definition.
+The I<macrobody> holds the @macro body. I<arg_line> holds the
+line after C<@macro>.
+
+=item C<@node>
+
+The arguments are in the I<nodes_manuals> array. Each
+of the entry has a I<node_content> key for
+an array holding the corresponding content, a I<manual_content>
+if there is an associated external manual name, and I<normalized>
+key for the normalized label, built as specified in the Texinfo manual
+in the B<HTML Xref> node.
+
+An I<associated_section> key holds the tree element of the
+sectioning command that follows the node.
+
+=item C<@part>
+
+The next sectioning command is in I<part_associated_section>.
+
+=item sectioning command
+
+The node preceding the command is in I<associated_node>.
+The part preceding the command is in I<associated_part>.
+If the level of the document was modified by C<@raisections>
+or C<@lowersections>, the differential level is in I<sections_level>.
+
+=item C<@float>
+
+=item C<@listoffloats>
+
+If float has a second argument, and for C<@listoffloats>
+argument there is a I<type> key which is also a hash reference,
+with two keys. I<content> is an array holding the associated
+contents, I<normalized> holds the normalized float type.
+
+I<caption> and I<shortcaption> holds the corresponding
+tree elements for float. The C<@caption> or C<@shortcaption>
+have the float tree element stored in I<float>.
+
+=item C<@float>
+
+=item C<@anchor>
+
+@-Commands that are targets for cross references have a I<normalized>
+key for the normalized label, built as specified in the Texinfo manual
+in the B<HTML Xref> node. There is also a I<node_content> key for
+an array holding the corresponding content.
+
+C<@anchor> also has I<region> set to the special region name if
+in a special region (C<@copying>, C<@titlepage>).
+
+=item C<@ref>
+
+=item C<@xref>
+
+=item C<@pxref>
+
+=item C<@inforef>
+
+The I<node_argument> entry holds a parsed node entry, like
+the one appearing in the I<nodes_manuals> array for C<@node>.
+
+=item C<@abbr>
+
+=item C<@acronym>
+
+The first argument normalized is in I<normalized>. If there is no
+second argument, but a second argument appeared previously for the
+same first argument, the second argument content of the previous
+command is stored in I<explanation_contents>.
+
+=item definition command
+
+I<def_command> holds the command name, without x if it is
+an x form of a definition command.
+I<original_def_cmdname> is the original def command.
+
+If it is an x form, it has I<not_after_command> set if not
+appearing after the definition command without x.
+
+=item def_line
+
+The I<def_arg> extra key holds an array reference corresponding to
+the parsed definition line argument. Each of the the element of the
+array is a two element array reference. The first element is the type
+which could be I<spaces> for a space, types specific of the
+definition, like I<category>, I<name>, I<class>, I<type>, and, at the
+end, a mix of I<arg>, I<typearg>, I<delimiter> depending on the definition.
+The second element is a hash reference holding the content of the
+type.
+
+The I<def_parsed_hash> hash reference has as key the type specific
+of the definition, and as value the corresponding content tree.
+
+=item C<@multitable>
+
+The key I<max_columns> holds the maximal number of columns. If there
+are prototypes on the line they are in the array associated with
+I<prototypes>. In that case, I<prototypes_line> also holds this
+information, and, in addition, keeps spaces with type C<prototype_space>.
+If there is a C<@columnfractions> as argument, then the
+I<columnfractions> key is associated with the array of columnfractions
+arguments, holding all the column fractions.
+
+=item C<@enumerate>
+
+The extra key I<enumerate_specification> contains the enumerate
+argument.
+
+=item C<@itemize>
+
+=item C<@table>
+
+=item C<@vtable>
+
+=item C<@ftable>
+
+The I<command_as_argument> extra key points on the @-command on
+as argument on the @-command line.
+
+=item paragraph
+
+The I<indent> or I<noindent> key value is set if the corresponding
+@-commands are associated with that paragraph.
+
+=item C<@item> in C<@enumerate> or C<@itemize>
+
+The I<item_number> extra key holds the number of this item.
+
+=item C<@item> and C<@tab> in C<@multitable>
+
+The I<cell_count> index key holds the index of the column of
+the cell.
+
+=item row
+
+The I<row_number> index key holds the index of the row in
+the C<@multitable>.
+
+=item C<@author>
+
+If in a C<@titlepage>, the titlepage is in I<titlepage>, if in
+C<@quotation> or C<@smallquotation>, the corresponding tree element
+is in I<quotation>.
+
+The author tree element is in the I<author> array of the C<@titlepage>
+or the C<@quotation> or C<@smallquotation> it is associated with.
+
+=item C<@ifclear>
+
+=item C<@ifset>
+
+The original line is in I<line>.
+
+=item C<@end>
+
+The textual argument is in I<command_argument>.
+The corresponding @-command is in I<command>.
+
+=item C<@documentencoding>
+
+The argument, normalized is in I<input_encoding_name> if it is recognized.
+The corresponding perl encoding name is in I<input_perl_encoding>.
+
+=item C<@click>
+
+In I<clickstyle> there is the current clickstyle command.
+
+=item C<@kbd>
+
+I<code> is set depending on the context and C<@kbdinputstyle>.
+
+=item definfoenclose defined commands
+
+I<begin> holds the string beginning the definfoenclose,
+I<end> holds the string ending the definfoenclose.
+
+=item menu_entry
+
+The I<menu_entry_description> and I<menu_entry_name> keys
+are associated with the corresponding tree elements. The
+I<menu_entry_node> holds the parsed node entry, like
+the one appearing in the I<nodes_manuals> array for C<@node>.
+
+=item empty_line_after_command
+
+The corresponding command is in I<command>.
+
+=item C<@inlinefmt>
+
+=item C<@inlineraw>
+
+=item C<@inlinefmtifelse>
+
+=item C<@inlineifclear>
+
+=item C<@inlineifset>
+
+The first argument is in I<format>. If an argument has been determined
+as being expanded by the Parser, the index of this argument is in
+I<expand_index>. Index numbering begins at 0, but the first argument is
+always the format or flag name, so, if set, it should be 1 or 2 for
+C<@inlinefmtifelse>, and 1 for other commands.
+
+=back
+
+=head1 SEE ALSO
+
+L<Texinfo manual|http://www.gnu.org/s/texinfo/manual/texinfo/>
+
+=begin :comment
+
+Mention other useful documentation such as the documentation of
+related modules or operating system documentation (such as man pages
+in UNIX), or any relevant external documentation such as RFCs or
+standards.
+
+If you have a mailing list set up for your module, mention it here.
+
+If you have a web site set up for your module, mention it here.
+
+=end :comment
+
+=head1 AUTHOR
+
+Patrice Dumas, E<lt>pertusus@free.frE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2010, 2011, 2012 Free Software Foundation, Inc.
+
+This library 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.
+
+=cut
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.c
new file mode 100644
index 0000000000..e538e7871a
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.c
@@ -0,0 +1,664 @@
+/*
+ * This file was generated automatically by ExtUtils::ParseXS version 3.39 from the
+ * contents of Parsetexi.xs. Do not edit this file, edit Parsetexi.xs instead.
+ *
+ * ANY CHANGES MADE HERE WILL BE LOST!
+ *
+ */
+
+#line 1 "parsetexi/Parsetexi.xs"
+/* Avoid namespace conflicts. */
+#define context perl_context
+
+#define PERL_NO_GET_CONTEXT
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+#undef context
+
+#include "ppport.h"
+
+#include "parser.h"
+#include "api.h"
+#include "indices.h"
+#include "input.h"
+
+#line 28 "parsetexi/Parsetexi.c"
+#ifndef PERL_UNUSED_VAR
+# define PERL_UNUSED_VAR(var) if (0) var = var
+#endif
+
+#ifndef dVAR
+# define dVAR dNOOP
+#endif
+
+
+/* This stuff is not part of the API! You have been warned. */
+#ifndef PERL_VERSION_DECIMAL
+# define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
+#endif
+#ifndef PERL_DECIMAL_VERSION
+# define PERL_DECIMAL_VERSION \
+ PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
+#endif
+#ifndef PERL_VERSION_GE
+# define PERL_VERSION_GE(r,v,s) \
+ (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
+#endif
+#ifndef PERL_VERSION_LE
+# define PERL_VERSION_LE(r,v,s) \
+ (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
+#endif
+
+/* XS_INTERNAL is the explicit static-linkage variant of the default
+ * XS macro.
+ *
+ * XS_EXTERNAL is the same as XS_INTERNAL except it does not include
+ * "STATIC", ie. it exports XSUB symbols. You probably don't want that
+ * for anything but the BOOT XSUB.
+ *
+ * See XSUB.h in core!
+ */
+
+
+/* TODO: This might be compatible further back than 5.10.0. */
+#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
+# undef XS_EXTERNAL
+# undef XS_INTERNAL
+# if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
+# define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
+# define XS_INTERNAL(name) STATIC XSPROTO(name)
+# endif
+# if defined(__SYMBIAN32__)
+# define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
+# define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
+# endif
+# ifndef XS_EXTERNAL
+# if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
+# define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
+# define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
+# else
+# ifdef __cplusplus
+# define XS_EXTERNAL(name) extern "C" XSPROTO(name)
+# define XS_INTERNAL(name) static XSPROTO(name)
+# else
+# define XS_EXTERNAL(name) XSPROTO(name)
+# define XS_INTERNAL(name) STATIC XSPROTO(name)
+# endif
+# endif
+# endif
+#endif
+
+/* perl >= 5.10.0 && perl <= 5.15.1 */
+
+
+/* The XS_EXTERNAL macro is used for functions that must not be static
+ * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
+ * macro defined, the best we can do is assume XS is the same.
+ * Dito for XS_INTERNAL.
+ */
+#ifndef XS_EXTERNAL
+# define XS_EXTERNAL(name) XS(name)
+#endif
+#ifndef XS_INTERNAL
+# define XS_INTERNAL(name) XS(name)
+#endif
+
+/* Now, finally, after all this mess, we want an ExtUtils::ParseXS
+ * internal macro that we're free to redefine for varying linkage due
+ * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
+ * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
+ */
+
+#undef XS_EUPXS
+#if defined(PERL_EUPXS_ALWAYS_EXPORT)
+# define XS_EUPXS(name) XS_EXTERNAL(name)
+#else
+ /* default to internal */
+# define XS_EUPXS(name) XS_INTERNAL(name)
+#endif
+
+#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
+#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
+
+/* prototype to pass -Wmissing-prototypes */
+STATIC void
+S_croak_xs_usage(const CV *const cv, const char *const params);
+
+STATIC void
+S_croak_xs_usage(const CV *const cv, const char *const params)
+{
+ const GV *const gv = CvGV(cv);
+
+ PERL_ARGS_ASSERT_CROAK_XS_USAGE;
+
+ if (gv) {
+ const char *const gvname = GvNAME(gv);
+ const HV *const stash = GvSTASH(gv);
+ const char *const hvname = stash ? HvNAME(stash) : NULL;
+
+ if (hvname)
+ Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params);
+ else
+ Perl_croak_nocontext("Usage: %s(%s)", gvname, params);
+ } else {
+ /* Pants. I don't think that it should be possible to get here. */
+ Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params);
+ }
+}
+#undef PERL_ARGS_ASSERT_CROAK_XS_USAGE
+
+#define croak_xs_usage S_croak_xs_usage
+
+#endif
+
+/* NOTE: the prototype of newXSproto() is different in versions of perls,
+ * so we define a portable version of newXSproto()
+ */
+#ifdef newXS_flags
+#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
+#else
+#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
+#endif /* !defined(newXS_flags) */
+
+#if PERL_VERSION_LE(5, 21, 5)
+# define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file)
+#else
+# define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b)
+#endif
+
+#line 172 "parsetexi/Parsetexi.c"
+
+XS_EUPXS(XS_Parsetexi_dump_errors); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_dump_errors)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ char * RETVAL;
+ dXSTARG;
+
+ RETVAL = dump_errors();
+ sv_setpv(TARG, RETVAL); XSprePUSH; PUSHTARG;
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_wipe_errors); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_wipe_errors)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+
+ wipe_errors();
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_parse_file); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_parse_file)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "filename");
+ {
+ char * filename = (char *)SvPV_nolen(ST(0))
+;
+ int RETVAL;
+ dXSTARG;
+
+ RETVAL = parse_file(filename);
+ XSprePUSH; PUSHi((IV)RETVAL);
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_parse_string); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_parse_string)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "string");
+ {
+ char * string = (char *)SvPV_nolen(ST(0))
+;
+
+ parse_string(string);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_parse_text); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_parse_text)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "string");
+ {
+ char * string = (char *)SvPV_nolen(ST(0))
+;
+
+ parse_text(string);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_store_value); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_store_value)
+{
+ dVAR; dXSARGS;
+ if (items != 2)
+ croak_xs_usage(cv, "name, value");
+ {
+ char * name = (char *)SvPV_nolen(ST(0))
+;
+ char * value = (char *)SvPV_nolen(ST(1))
+;
+
+ store_value(name, value);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_wipe_values); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_wipe_values)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+
+ wipe_values();
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_reset_context_stack); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_reset_context_stack)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+
+ reset_context_stack();
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_init_index_commands); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_init_index_commands)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+
+ init_index_commands();
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_add_include_directory); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_add_include_directory)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "filename");
+ {
+ char * filename = (char *)SvPV_nolen(ST(0))
+;
+
+ add_include_directory(filename);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_build_texinfo_tree); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_build_texinfo_tree)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ HV * RETVAL;
+
+ RETVAL = build_texinfo_tree();
+ {
+ SV * RETVALSV;
+ RETVALSV = newRV((SV*)RETVAL);
+ RETVALSV = sv_2mortal(RETVALSV);
+ ST(0) = RETVALSV;
+ }
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_build_label_list); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_build_label_list)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ AV * RETVAL;
+
+ RETVAL = build_label_list();
+ {
+ SV * RETVALSV;
+ RETVALSV = newRV((SV*)RETVAL);
+ RETVALSV = sv_2mortal(RETVALSV);
+ ST(0) = RETVALSV;
+ }
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_build_internal_xref_list); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_build_internal_xref_list)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ AV * RETVAL;
+
+ RETVAL = build_internal_xref_list();
+ {
+ SV * RETVALSV;
+ RETVALSV = newRV((SV*)RETVAL);
+ RETVALSV = sv_2mortal(RETVALSV);
+ ST(0) = RETVALSV;
+ }
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_build_float_list); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_build_float_list)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ HV * RETVAL;
+
+ RETVAL = build_float_list();
+ {
+ SV * RETVALSV;
+ RETVALSV = newRV((SV*)RETVAL);
+ RETVALSV = sv_2mortal(RETVALSV);
+ ST(0) = RETVALSV;
+ }
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_build_index_data); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_build_index_data)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ HV * RETVAL;
+
+ RETVAL = build_index_data();
+ {
+ SV * RETVALSV;
+ RETVALSV = newRV((SV*)RETVAL);
+ RETVALSV = sv_2mortal(RETVALSV);
+ ST(0) = RETVALSV;
+ }
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_build_global_info); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_build_global_info)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ HV * RETVAL;
+
+ RETVAL = build_global_info();
+ {
+ SV * RETVALSV;
+ RETVALSV = newRV((SV*)RETVAL);
+ RETVALSV = sv_2mortal(RETVALSV);
+ ST(0) = RETVALSV;
+ }
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_build_global_info2); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_build_global_info2)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+ HV * RETVAL;
+
+ RETVAL = build_global_info2();
+ {
+ SV * RETVALSV;
+ RETVALSV = newRV((SV*)RETVAL);
+ RETVALSV = sv_2mortal(RETVALSV);
+ ST(0) = RETVALSV;
+ }
+ }
+ XSRETURN(1);
+}
+
+
+XS_EUPXS(XS_Parsetexi_reset_parser); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_reset_parser)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+
+ reset_parser();
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_clear_expanded_formats); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_clear_expanded_formats)
+{
+ dVAR; dXSARGS;
+ if (items != 0)
+ croak_xs_usage(cv, "");
+ {
+
+ clear_expanded_formats();
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_add_expanded_format); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_add_expanded_format)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "format");
+ {
+ char * format = (char *)SvPV_nolen(ST(0))
+;
+
+ add_expanded_format(format);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_conf_set_show_menu); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_conf_set_show_menu)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "i");
+ {
+ int i = (int)SvIV(ST(0))
+;
+
+ conf_set_show_menu(i);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_conf_set_CPP_LINE_DIRECTIVES); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_conf_set_CPP_LINE_DIRECTIVES)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "i");
+ {
+ int i = (int)SvIV(ST(0))
+;
+
+ conf_set_CPP_LINE_DIRECTIVES(i);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "i");
+ {
+ int i = (int)SvIV(ST(0))
+;
+
+ conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME(i);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_set_documentlanguage); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_set_documentlanguage)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "value");
+ {
+ char * value = (char *)SvPV_nolen(ST(0))
+;
+
+ set_documentlanguage(value);
+ }
+ XSRETURN_EMPTY;
+}
+
+
+XS_EUPXS(XS_Parsetexi_set_debug); /* prototype to pass -Wmissing-prototypes */
+XS_EUPXS(XS_Parsetexi_set_debug)
+{
+ dVAR; dXSARGS;
+ if (items != 1)
+ croak_xs_usage(cv, "i");
+ {
+ int i = (int)SvIV(ST(0))
+;
+
+ set_debug(i);
+ }
+ XSRETURN_EMPTY;
+}
+
+#ifdef __cplusplus
+extern "C"
+#endif
+XS_EXTERNAL(boot_Parsetexi); /* prototype to pass -Wmissing-prototypes */
+XS_EXTERNAL(boot_Parsetexi)
+{
+#if PERL_VERSION_LE(5, 21, 5)
+ dVAR; dXSARGS;
+#else
+ dVAR; dXSBOOTARGSXSAPIVERCHK;
+#endif
+#if (PERL_REVISION == 5 && PERL_VERSION < 9)
+ char* file = __FILE__;
+#else
+ const char* file = __FILE__;
+#endif
+
+ PERL_UNUSED_VAR(file);
+
+ PERL_UNUSED_VAR(cv); /* -W */
+ PERL_UNUSED_VAR(items); /* -W */
+#if PERL_VERSION_LE(5, 21, 5)
+ XS_VERSION_BOOTCHECK;
+# ifdef XS_APIVERSION_BOOTCHECK
+ XS_APIVERSION_BOOTCHECK;
+# endif
+#endif
+
+ (void)newXSproto_portable("Parsetexi::dump_errors", XS_Parsetexi_dump_errors, file, "");
+ (void)newXSproto_portable("Parsetexi::wipe_errors", XS_Parsetexi_wipe_errors, file, "");
+ (void)newXSproto_portable("Parsetexi::parse_file", XS_Parsetexi_parse_file, file, "$");
+ (void)newXSproto_portable("Parsetexi::parse_string", XS_Parsetexi_parse_string, file, "$");
+ (void)newXSproto_portable("Parsetexi::parse_text", XS_Parsetexi_parse_text, file, "$");
+ (void)newXSproto_portable("Parsetexi::store_value", XS_Parsetexi_store_value, file, "$$");
+ (void)newXSproto_portable("Parsetexi::wipe_values", XS_Parsetexi_wipe_values, file, "");
+ (void)newXSproto_portable("Parsetexi::reset_context_stack", XS_Parsetexi_reset_context_stack, file, "");
+ (void)newXSproto_portable("Parsetexi::init_index_commands", XS_Parsetexi_init_index_commands, file, "");
+ (void)newXSproto_portable("Parsetexi::add_include_directory", XS_Parsetexi_add_include_directory, file, "$");
+ (void)newXSproto_portable("Parsetexi::build_texinfo_tree", XS_Parsetexi_build_texinfo_tree, file, "");
+ (void)newXSproto_portable("Parsetexi::build_label_list", XS_Parsetexi_build_label_list, file, "");
+ (void)newXSproto_portable("Parsetexi::build_internal_xref_list", XS_Parsetexi_build_internal_xref_list, file, "");
+ (void)newXSproto_portable("Parsetexi::build_float_list", XS_Parsetexi_build_float_list, file, "");
+ (void)newXSproto_portable("Parsetexi::build_index_data", XS_Parsetexi_build_index_data, file, "");
+ (void)newXSproto_portable("Parsetexi::build_global_info", XS_Parsetexi_build_global_info, file, "");
+ (void)newXSproto_portable("Parsetexi::build_global_info2", XS_Parsetexi_build_global_info2, file, "");
+ (void)newXSproto_portable("Parsetexi::reset_parser", XS_Parsetexi_reset_parser, file, "");
+ (void)newXSproto_portable("Parsetexi::clear_expanded_formats", XS_Parsetexi_clear_expanded_formats, file, "");
+ (void)newXSproto_portable("Parsetexi::add_expanded_format", XS_Parsetexi_add_expanded_format, file, "$");
+ (void)newXSproto_portable("Parsetexi::conf_set_show_menu", XS_Parsetexi_conf_set_show_menu, file, "$");
+ (void)newXSproto_portable("Parsetexi::conf_set_CPP_LINE_DIRECTIVES", XS_Parsetexi_conf_set_CPP_LINE_DIRECTIVES, file, "$");
+ (void)newXSproto_portable("Parsetexi::conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME", XS_Parsetexi_conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME, file, "$");
+ (void)newXSproto_portable("Parsetexi::set_documentlanguage", XS_Parsetexi_set_documentlanguage, file, "$");
+ (void)newXSproto_portable("Parsetexi::set_debug", XS_Parsetexi_set_debug, file, "$");
+#if PERL_VERSION_LE(5, 21, 5)
+# if PERL_VERSION_GE(5, 9, 0)
+ if (PL_unitcheckav)
+ call_list(PL_scopestack_ix, PL_unitcheckav);
+# endif
+ XSRETURN_YES;
+#else
+ Perl_xs_boot_epilog(aTHX_ ax);
+#endif
+}
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.pm b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.pm
new file mode 100644
index 0000000000..87c9a0a0d6
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.pm
@@ -0,0 +1,445 @@
+# Copyright 2014-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 <http://www.gnu.org/licenses/>.
+
+package Parsetexi;
+
+use Texinfo::XSLoader;
+
+# same as texi2any.pl, although I don't know what the real requirement
+# is for this module.
+use 5.00405;
+use strict;
+use warnings;
+
+require Exporter;
+
+use Texinfo::Common;
+use Texinfo::Encoding;
+use Texinfo::Convert::NodeNameNormalization;
+
+our @ISA = qw(Exporter DynaLoader Texinfo::Report);
+our %EXPORT_TAGS = ( 'all' => [ qw(
+ parser
+ parse_texi_text
+ parse_texi_line
+ parse_texi_file
+) ] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+our @EXPORT = qw(
+
+);
+
+sub get_conf($$)
+{
+ my $self = shift;
+ my $var = shift;
+ return $self->{$var};
+}
+
+my %parser_default_configuration =
+ (%Texinfo::Common::default_parser_state_configuration,
+ %Texinfo::Common::default_customization_values);
+
+my %tree_informations;
+foreach my $tree_information ('values', 'macros', 'explained_commands', 'labels') {
+ $tree_informations{$tree_information} = 1;
+}
+
+use Storable qw(dclone); # standard in 5.007003
+
+sub duplicate_parser {
+ my $old_parser = shift;
+
+ my $parser = dclone(\%parser_default_configuration);
+
+ foreach my $key (keys(%parser_default_configuration)) {
+ if ($tree_informations{$key}) {
+ if (defined($old_parser->{$key})) {
+ foreach my $info_key (keys(%{$old_parser->{$key}})) {
+ $parser->{$key}->{$info_key}
+ = $old_parser->{$key}->{$info_key};
+ }
+ }
+ } elsif(ref($old_parser->{$key})) {
+ $parser->{$key} = dclone($old_parser->{$key});
+ } else {
+ $parser->{$key} = $old_parser->{$key};
+ }
+ }
+ bless $parser, ref($old_parser);
+
+ $parser->Texinfo::Report::new;
+ return $parser;
+}
+
+sub simple_parser {
+ goto &parser;
+}
+
+# Stub for Texinfo::Parser::parser (line 574)
+sub parser (;$$)
+{
+ my $conf = shift;
+
+ my $parser = dclone(\%parser_default_configuration);
+
+ reset_parser ();
+ # fixme: these are overwritten immediately after
+ if (defined($conf)) {
+ foreach my $key (keys (%$conf)) {
+ if ($key ne 'values' and ref($conf->{$key})) {
+ $parser->{$key} = dclone($conf->{$key});
+ } else {
+ $parser->{$key} = $conf->{$key};
+ }
+
+ if ($key eq 'include_directories') {
+ foreach my $d (@{$conf->{'include_directories'}}) {
+ add_include_directory ($d);
+ }
+ } elsif ($key eq 'values') {
+ # This is used by Texinfo::Report::gdt for substituted values
+ for my $v (keys %{$conf->{'values'}}) {
+ if (!ref($conf->{'values'}->{$v})) {
+ store_value ($v, $conf->{'values'}->{$v});
+ } elsif (ref($conf->{'values'}->{$v}) eq 'HASH') {
+ store_value ($v, "<<HASH VALUE>>");
+ } elsif (ref($conf->{'values'}->{$v}) eq 'ARRAY') {
+ store_value ($v, "<<ARRAY VALUE>>");
+ } else {
+ store_value ($v, "<<UNKNOWN VALUE>>");
+ }
+ }
+ } elsif ($key eq 'expanded_formats') {
+ clear_expanded_formats ();
+
+ for my $f (@{$parser->{$key}}) {
+ add_expanded_format ($f);
+ }
+ } elsif ($key eq 'documentlanguage') {
+ if (defined ($conf->{$key})) {
+ set_documentlanguage ($conf->{$key});
+ }
+ } elsif ($key eq 'SHOW_MENU') {
+ conf_set_show_menu ($conf->{$key});
+ } elsif ($key eq 'IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME') {
+ conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME ($conf->{$key});
+ } elsif ($key eq 'CPP_LINE_DIRECTIVES') {
+ conf_set_CPP_LINE_DIRECTIVES($conf->{$key});
+ } else {
+ #warn "ignoring parser configuration value \"$key\"\n";
+ }
+ }
+ }
+
+ bless $parser;
+
+ $parser->Texinfo::Report::new;
+
+ return $parser;
+}
+
+# Record any @menu elements under $root in the 'menus' array of $node.
+sub _find_menus_of_node {
+ my $node = shift;
+ my $root = shift;
+
+ if ($root->{'contents'}) {
+ my $contents = $root->{'contents'};
+ foreach my $child (@{$contents}) {
+ if ($child->{'cmdname'} and $child->{'cmdname'} eq 'menu') {
+ push @{$node->{'menus'}}, $child;
+ }
+ }
+ }
+}
+
+# Set 'menus' array for each node. This accounts for malformed input where
+# the number of sectioning commands between @node and @menu is not exactly 1.
+sub _complete_node_menus {
+ my $self = shift;
+ my $root = shift;
+
+ if (!defined $self->{'nodes'}) {
+ $self->{'nodes'} = [];
+ }
+ my $node;
+ foreach my $child (@{$root->{'contents'}}) {
+ if ($child->{'cmdname'} and $child->{'cmdname'} eq 'node') {
+ $node = $child;
+ }
+ _find_menus_of_node ($node, $child) unless !defined $node;
+ }
+}
+
+sub get_parser_info {
+ my $self = shift;
+
+ my ($TARGETS, $INTL_XREFS, $FLOATS,
+ $INDEX_NAMES, $ERRORS, $GLOBAL_INFO, $GLOBAL_INFO2);
+
+ $TARGETS = build_label_list ();
+ $INTL_XREFS = build_internal_xref_list ();
+ $FLOATS = build_float_list ();
+
+ $INDEX_NAMES = build_index_data ();
+ $self->{'index_names'} = $INDEX_NAMES;
+
+ $GLOBAL_INFO = build_global_info ();
+ $GLOBAL_INFO2 = build_global_info2 ();
+
+ $self->{'targets'} = $TARGETS;
+ $self->{'labels'} = {};
+ $self->{'internal_references'} = $INTL_XREFS;
+ $self->{'floats'} = $FLOATS;
+ $self->{'info'} = $GLOBAL_INFO;
+ $self->{'extra'} = $GLOBAL_INFO2;
+
+ _get_errors ($self);
+ Texinfo::Common::complete_indices ($self);
+}
+
+use File::Basename; # for fileparse
+
+# Handle 'IGNORE_BEFORE_SETFILENAME' conf value.
+# Put everything before @setfilename in a special type. This allows
+# ignoring everything before @setfilename.
+sub _maybe_ignore_before_setfilename {
+ my ($self, $text_root) = @_;
+
+ if ($self->{'IGNORE_BEFORE_SETFILENAME'} and $text_root
+ and $self->{'extra'}->{'setfilename'}
+ and $self->{'extra'}->{'setfilename'}->{'parent'} eq $text_root) {
+ my $before_setfilename = {'type' => 'preamble_before_setfilename',
+ 'parent' => $text_root,
+ 'contents' => []};
+ while (@{$text_root->{'contents'}}
+ and (!$text_root->{'contents'}->[0]->{'cmdname'}
+ or $text_root->{'contents'}->[0]->{'cmdname'} ne 'setfilename')) {
+ my $content = shift @{$text_root->{'contents'}};
+ $content->{'parent'} = $before_setfilename;
+ push @{$before_setfilename->{'contents'}}, $content;
+ }
+ if (!@{$text_root->{'contents'}}) {
+ # not found
+ #splice @{$text_root->{'contents'}}, 0, 0, @$before_setfilename;
+ $text_root->{'contents'} = $before_setfilename->{'contents'};
+ } else {
+ unshift (@{$text_root->{'contents'}}, $before_setfilename)
+ if (@{$before_setfilename->{'contents'}});
+ }
+ }
+}
+
+# Replacement for Texinfo::Parser::parse_texi_file (line 835)
+sub parse_texi_file ($$)
+{
+ my $self = shift;
+ my $file_name = shift;
+ my $tree_stream;
+
+ my $status = parse_file ($file_name);
+ if ($status) {
+ # TODO: internationalise this message?
+ $self->document_error(sprintf("could not open %s: %s", $file_name, $!));
+ return undef;
+ }
+
+ my $TREE = build_texinfo_tree ();
+ get_parser_info ($self);
+ _complete_node_menus ($self, $TREE);
+
+ # line 899
+ my $text_root;
+ if ($TREE->{'type'} eq 'text_root') {
+ $text_root = $TREE;
+ } elsif ($TREE->{'contents'} and $TREE->{'contents'}->[0]
+ and $TREE->{'contents'}->[0]->{'type'} eq 'text_root') {
+ $text_root = $TREE->{'contents'}->[0];
+ }
+
+ _maybe_ignore_before_setfilename($self, $text_root);
+
+ ############################################################
+
+ if (defined($self->{'info'}->{'input_encoding_name'})) {
+ my ($texinfo_encoding, $perl_encoding, $input_encoding)
+ = Texinfo::Encoding::encoding_alias(
+ $self->{'info'}->{'input_encoding_name'});
+ $self->{'info'}->{'input_encoding_name'} = $input_encoding;
+ }
+
+ my ($basename, $directories, $suffix) = fileparse($file_name);
+ $self->{'info'}->{'input_file_name'} = $basename;
+ $self->{'info'}->{'input_directory'} = $directories;
+
+ return $TREE;
+}
+
+# Copy the errors into the error list in Texinfo::Report.
+# TODO: Could we just access the error list directly instead of going
+# through Texinfo::Report line_error?
+sub _get_errors($)
+{
+ my $self = shift;
+ my $ERRORS;
+ my $tree_stream = dump_errors();
+ eval $tree_stream;
+ for my $error (@{$ERRORS}) {
+ if ($error->{'type'} eq 'error') {
+ $self->line_error ($error->{'message'}, $error->{'line_nr'});
+ } else {
+ $self->line_warn ($error->{'message'}, $error->{'line_nr'});
+ }
+ }
+}
+
+# Replacement for Texinfo::Parser::parse_texi_text (line 757)
+#
+# Used in tests under tp/t.
+sub parse_texi_text($$;$$$$)
+{
+ my $self = shift;
+ my $text = shift;
+ my $lines_nr = shift;
+ my $file = shift;
+ my $macro = shift;
+ my $fixed_line_number = shift;
+
+ return undef if (!defined($text));
+
+ $self = parser() if (!defined($self));
+
+ # make sure that internal byte buffer is in UTF-8 before we pass
+ # it in to the XS code.
+ utf8::upgrade($text);
+
+ parse_text($text);
+ my $tree = build_texinfo_tree ();
+ my $INDEX_NAMES = build_index_data ();
+ $self->{'index_names'} = $INDEX_NAMES;
+
+ for my $index (keys %$INDEX_NAMES) {
+ if ($INDEX_NAMES->{$index}->{'merged_in'}) {
+ $self->{'merged_indices'}-> {$index}
+ = $INDEX_NAMES->{$index}->{'merged_in'};
+ }
+ }
+
+ get_parser_info($self);
+ _complete_node_menus ($self, $tree);
+ return $tree;
+}
+
+# Replacement for Texinfo::Parser::parse_texi_line (line 918)
+sub parse_texi_line($$;$$$$)
+{
+ my $self = shift;
+ my $text = shift;
+ my $lines_nr = shift;
+ my $file = shift;
+ my $macro = shift;
+ my $fixed_line_number = shift;
+
+ return undef if (!defined($text));
+
+ $self = parser() if (!defined($self));
+ utf8::upgrade($text);
+ parse_string($text);
+ my $tree = build_texinfo_tree ();
+ return $tree;
+}
+
+# Public interfaces of Texinfo::Parser (starting line 942)
+sub indices_information($)
+{
+ my $self = shift;
+
+ my $INDEX_NAMES;
+ if (!$self->{'index_names'}) {
+ $INDEX_NAMES = build_index_data ();
+ $self->{'index_names'} = $INDEX_NAMES;
+ }
+ return $self->{'index_names'};
+}
+
+sub floats_information($)
+{
+ my $self = shift;
+ return $self->{'floats'};
+}
+
+sub internal_references_information($)
+{
+ my $self = shift;
+ return $self->{'internal_references'};
+}
+
+sub global_commands_information($)
+{
+ my $self = shift;
+ return $self->{'extra'};
+}
+
+sub global_informations($)
+{
+ my $self = shift;
+ return $self->{'info'};
+}
+
+# Setup labels and nodes info and return labels
+sub labels_information($)
+{
+ goto &Texinfo::Common::labels_information;
+}
+
+BEGIN {
+ Texinfo::XSLoader::init (
+ "Texinfo::Parser",
+ "Parsetexi",
+ "Texinfo::ParserNonXS",
+ "Parsetexi",
+ 0);
+} # end BEGIN
+
+END {
+ #reset_parser (); # for debugging memory leaks
+}
+
+# This "use" statement is all the way down here because Texinfo::Report
+# and Texinfo::Parser both "use" each other. If we "use Texinfo::Report"
+# at the top of the file, this has the problem that the symbol table
+# for the Texinfo::Parser namespace, %Texinfo::Parser::, is a different
+# hash (i.e. it has a different address) from what it ends up with by the
+# end of this module. Once the Perl interpreter has read in a module and
+# interpreted variables in terms of a given symbol table, it does not
+# do so them again. Hence, use of subroutines in the Texinfo::Parser namespace
+# fails. This remains the case even if we arrange for the symbol table hash
+# to remain at the same address, because is empty while the file is being read.
+use Texinfo::Report;
+
+# NB Don't add more functions down here, because this can cause an error
+# with some versions of Perl, connected with the typeglob assignment just
+# above. ("Can't call mro_method_changed_in() on anonymous symbol table").
+#
+# See http://perl5.git.perl.org/perl.git/commitdiff/03d9f026ae253e9e69212a3cf6f1944437e9f070?hp=ac73ea1ec401df889d312b067f78b618f7ffecc3
+#
+# (change to Perl interpreter on 22 Oct 2011)
+
+
+1;
+__END__
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.xs b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.xs
new file mode 100644
index 0000000000..6b96bc7edb
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/Parsetexi.xs
@@ -0,0 +1,103 @@
+/* Avoid namespace conflicts. */
+#define context perl_context
+
+#define PERL_NO_GET_CONTEXT
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+#undef context
+
+#include "ppport.h"
+
+#include "parser.h"
+#include "api.h"
+#include "indices.h"
+#include "input.h"
+
+MODULE = Parsetexi PACKAGE = Parsetexi
+
+PROTOTYPES: ENABLE
+
+char *
+dump_errors ()
+
+void
+wipe_errors ()
+
+int
+parse_file(filename)
+ char * filename
+
+void
+parse_string(string)
+ char * string
+
+void
+parse_text(string)
+ char * string
+
+void
+store_value (name, value)
+ char *name
+ char *value
+
+void
+wipe_values ()
+
+void
+reset_context_stack ()
+
+void
+init_index_commands ()
+
+void
+add_include_directory (filename)
+ char *filename
+
+HV *
+build_texinfo_tree ()
+
+AV *
+build_label_list ()
+
+AV *
+build_internal_xref_list ()
+
+HV *
+build_float_list ()
+
+HV *
+build_index_data ()
+
+HV *
+build_global_info ()
+
+HV *
+build_global_info2 ()
+
+void
+reset_parser ()
+
+void
+clear_expanded_formats ()
+
+void
+add_expanded_format (format)
+ char *format
+
+void
+conf_set_show_menu (int i)
+
+void
+conf_set_CPP_LINE_DIRECTIVES (int i)
+
+void
+conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME (int i)
+
+void
+set_documentlanguage (value)
+ char *value
+
+void
+set_debug (int i)
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.c
new file mode 100644
index 0000000000..81f612014b
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.c
@@ -0,0 +1,940 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+/* Avoid namespace conflicts. */
+#define context perl_context
+
+#define PERL_NO_GET_CONTEXT
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+#undef context
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "parser.h"
+#include "input.h"
+#include "labels.h"
+#include "indices.h"
+#include "api.h"
+
+ELEMENT *Root;
+
+static void
+reset_floats ()
+{
+ floats_number = 0;
+}
+
+void
+reset_parser_except_conf (void)
+{
+ /* do before destroying tree because index entries usually refer to in-tree
+ elements. */
+ wipe_indices ();
+ if (Root)
+ {
+ destroy_element_and_children (Root);
+ Root = 0;
+ }
+ wipe_user_commands ();
+ wipe_macros ();
+ init_index_commands ();
+ wipe_errors ();
+ reset_context_stack ();
+ reset_region_stack ();
+ reset_floats ();
+ wipe_global_info ();
+ reset_internal_xrefs ();
+ reset_labels ();
+ input_reset_input_stack ();
+ free_small_strings ();
+
+ current_node = current_section = current_part = 0;
+}
+
+void
+reset_parser (void)
+{
+ debug ("!!!!!!!!!!!!!!!! RESETTING THE PARSER !!!!!!!!!!!!!!!!!!!!!");
+
+ reset_parser_except_conf ();
+ wipe_values ();
+ clear_expanded_formats ();
+ reset_conf ();
+}
+
+/* Set ROOT to root of tree obtained by parsing FILENAME. */
+int
+parse_file (char *filename)
+{
+ debug_output = 0;
+ Root = parse_texi_file (filename);
+ if (Root)
+ return 0;
+ return 1;
+}
+
+/* Set ROOT to root of tree obtained by parsing the Texinfo code in STRING.
+ STRING should be a UTF-8 buffer. Used for parse_texi_line. */
+void
+parse_string (char *string)
+{
+ reset_parser_except_conf ();
+ input_push_text (strdup (string), 0);
+ Root = parse_texi (new_element (ET_root_line));
+}
+
+/* Used for parse_texi_text. STRING should be a UTF-8 buffer. */
+void
+parse_text (char *string)
+{
+ reset_parser_except_conf ();
+ input_push_text_with_line_nos (strdup (string), 1);
+ Root = parse_texi (new_element (ET_text_root));
+}
+
+
+static void element_to_perl_hash (ELEMENT *e);
+
+/* Return reference to Perl array built from e. If any of
+ the elements in E don't have 'hv' set, set it to an empty
+ hash table, or create it if there is no parent element, indicating the
+ element is not in the tree. */
+static SV *
+build_perl_array (ELEMENT_LIST *e)
+{
+ SV *sv;
+ AV *av;
+ int i;
+
+ dTHX;
+
+ av = newAV ();
+ sv = newRV_inc ((SV *) av);
+ for (i = 0; i < e->number; i++)
+ {
+ if (!e->list[i]) /* For arrays only, allow elements to be undef. */
+ av_push (av, newSV (0));
+ if (!e->list[i]->hv)
+ {
+ if (e->list[i]->parent)
+ e->list[i]->hv = newHV ();
+ else
+ {
+ /* Out-of-tree element */
+ /* WARNING: This is possibly recursive. */
+ element_to_perl_hash (e->list[i]);
+ }
+ }
+ av_push (av, newRV_inc ((SV *) e->list[i]->hv));
+ }
+ return sv;
+}
+
+/* Return reference to hash corresponding to VALUE. */
+static SV *
+build_node_spec (NODE_SPEC_EXTRA *value)
+{
+ HV *hv;
+
+ dTHX;
+
+ if (!value->manual_content && !value->node_content)
+ return newSV(0); /* Perl 'undef' */
+
+ hv = newHV ();
+
+ if (value->manual_content)
+ {
+ hv_store (hv, "manual_content", strlen ("manual_content"),
+ build_perl_array (&value->manual_content->contents), 0);
+ }
+
+ if (value->node_content)
+ {
+ hv_store (hv, "node_content", strlen ("node_content"),
+ build_perl_array (&value->node_content->contents), 0);
+ }
+
+ return newRV_inc ((SV *)hv);
+}
+
+/* Set E->hv and 'hv' on E's descendants. e->parent->hv is assumed
+ to already exist. */
+static void
+element_to_perl_hash (ELEMENT *e)
+{
+ SV *sv;
+
+ dTHX;
+
+ /* e->hv may already exist if there was an extra value elsewhere
+ referring to e. */
+ if (!e->hv)
+ {
+ e->hv = newHV ();
+ }
+
+ if (e->parent)
+ {
+ if (!e->parent->hv)
+ e->parent->hv = newHV ();
+ sv = newRV_inc ((SV *) e->parent->hv);
+ hv_store (e->hv, "parent", strlen ("parent"), sv, 0);
+ }
+
+ if (e->type)
+ {
+ sv = newSVpv (element_type_names[e->type], 0);
+ hv_store (e->hv, "type", strlen ("type"), sv, 0);
+ }
+
+ if (e->cmd)
+ {
+ sv = newSVpv (command_name(e->cmd), 0);
+ hv_store (e->hv, "cmdname", strlen ("cmdname"), sv, 0);
+
+ /* TODO: Same optimizations as for 'type'. */
+ }
+
+ /* TODO sort out all these special cases.
+ Makes no sense to have 'contents' created for glyph commands like
+ @arrow{} or for accent commands. */
+ if (e->contents.number > 0
+ || e->type == ET_text_root
+ || e->type == ET_root_line
+ || e->type == ET_bracketed
+ || e->type == ET_bracketed_def_content
+ || e->type == ET_line_arg
+ || e->cmd == CM_image // why image?
+ || e->cmd == CM_item && e->parent && e->parent->type == ET_row
+ || e->cmd == CM_tab && e->parent && e->parent->type == ET_row
+ || e->cmd == CM_anchor
+ || e->cmd == CM_macro
+ || e->cmd == CM_multitable
+ || e->type == ET_menu_entry_name
+ || e->type == ET_brace_command_arg
+ || e->type == ET_brace_command_context
+ || e->type == ET_block_line_arg
+ || e->type == ET_before_item
+ || e->type == ET_inter_item
+ || e->cmd == CM_TeX
+ || e->type == ET_elided
+ || e->type == ET_elided_block
+ || e->type == ET_preformatted
+ || (command_flags(e) & CF_root)
+ || (command_data(e->cmd).flags & CF_brace
+ && (command_data(e->cmd).data >= 0
+ || command_data(e->cmd).data == BRACE_style
+ || command_data(e->cmd).data == BRACE_context
+ || command_data(e->cmd).data == BRACE_other
+ || command_data(e->cmd).data == BRACE_accent
+ ))
+ || e->cmd == CM_node)
+ {
+ AV *av;
+ int i;
+
+ av = newAV ();
+ sv = newRV_inc ((SV *) av);
+ hv_store (e->hv, "contents", strlen ("contents"), sv, 0);
+ for (i = 0; i < e->contents.number; i++)
+ {
+ element_to_perl_hash (e->contents.list[i]);
+ sv = newRV_inc ((SV *) e->contents.list[i]->hv);
+ av_push (av, sv);
+ }
+ }
+
+ if (e->args.number > 0)
+ {
+ AV *av;
+ int i;
+
+ av = newAV ();
+ sv = newRV_inc ((SV *) av);
+ hv_store (e->hv, "args", strlen ("args"), sv, 0);
+ for (i = 0; i < e->args.number; i++)
+ {
+ element_to_perl_hash (e->args.list[i]);
+ sv = newRV_inc ((SV *) e->args.list[i]->hv);
+ av_push (av, sv);
+ }
+ }
+
+ if (e->text.space > 0)
+ {
+ sv = newSVpv (e->text.text, e->text.end);
+ if (e->cmd != CM_value)
+ hv_store (e->hv, "text", strlen ("text"), sv, 0);
+ else
+ hv_store (e->hv, "type", strlen ("type"), sv, 0);
+
+ SvUTF8_on (sv);
+ /* The strings here have to be in UTF-8 to start with.
+ This leads to an unnecessary round trip with "@documentencoding
+ ISO-8859-1" for Info and plain text output, when we first convert the
+ characters in the input file to UTF-8, and convert them back again for
+ the output.
+
+ The alternative is to leave the UTF-8 flag off, and hope that Perl
+ interprets 8-bit encodings like ISO-8859-1 correctly. See
+ "How does Perl store UTF-8 strings?" in "man perlguts". */
+ }
+
+ if (e->extra_number > 0)
+ {
+ HV *extra;
+ int i;
+ int all_deleted = 1;
+ extra = newHV ();
+
+ for (i = 0; i < e->extra_number; i++)
+ {
+#define STORE(sv) hv_store (extra, key, strlen (key), sv, 0)
+ char *key = e->extra[i].key;
+ ELEMENT *f = e->extra[i].value;
+
+ if (e->extra[i].type == extra_deleted)
+ continue;
+ all_deleted = 0;
+
+ switch (e->extra[i].type)
+ {
+ case extra_element:
+ /* For references to other parts of the tree, create the hash so
+ we can point to it. */
+ if (!f->hv)
+ f->hv = newHV ();
+ STORE(newRV_inc ((SV *)f->hv));
+ break;
+ case extra_element_oot:
+ if (!f->hv)
+ element_to_perl_hash (f);
+ STORE(newRV_inc ((SV *)f->hv));
+ break;
+ case extra_contents:
+ case extra_contents_oot:
+ {
+ if (f)
+ STORE(build_perl_array (&f->contents));
+ break;
+ }
+ case extra_contents_array:
+ {
+ /* Like extra_contents, but this time output an array
+ of arrays (instead of an array). */
+ int j;
+ AV *av;
+ av = newAV ();
+ STORE(newRV_inc ((SV *)av));
+ for (j = 0; j < f->contents.number; j++)
+ {
+ SV *array;
+ ELEMENT *g;
+
+ g = f->contents.list[j];
+ if (g)
+ array = build_perl_array (&g->contents);
+ else
+ array = newSV (0); /* undef */
+ av_push (av, array);
+ }
+ break;
+ }
+ case extra_string:
+ { /* A simple string. */
+ char *value = (char *) f;
+ STORE(newSVpv (value, 0));
+ break;
+ }
+ case extra_integer:
+ { /* A simple integer. */
+ int value = (int) f;
+ STORE(newSViv (value));
+ break;
+ }
+ case extra_misc_args:
+ {
+ int j;
+ AV *av;
+ av = newAV ();
+ STORE(newRV_inc ((SV *)av));
+ /* An array of strings. */
+ for (j = 0; j < f->contents.number; j++)
+ {
+ if (f->contents.list[j]->text.end > 0)
+ {
+ av_push (av,
+ newSVpv (f->contents.list[j]->text.text,
+ f->contents.list[j]->text.end));
+ }
+ else
+ {
+ /* Empty strings permitted. */
+ av_push (av,
+ newSVpv ("", 0));
+ }
+ }
+ break;
+ }
+ case extra_node_spec:
+ /* A complex structure - see "parse_node_manual" function
+ in end_line.c */
+ if (f)
+ STORE(build_node_spec ((NODE_SPEC_EXTRA *) f));
+ break;
+ case extra_node_spec_array:
+ {
+ AV *av;
+ NODE_SPEC_EXTRA **array;
+ av = newAV ();
+ STORE(newRV_inc ((SV *)av));
+ array = (NODE_SPEC_EXTRA **) f;
+ while (*array)
+ {
+ av_push (av, build_node_spec (*array));
+ array++;
+ }
+ break;
+ }
+ case extra_index_entry:
+ /* A "index_entry" extra key on a command defining an index
+ entry. Unlike the other keys, the value is not in the
+ main parse tree, but in the indices_information. It would
+ be much nicer if we could get rid of the need for this key.
+ We set this afterwards in build_index_data. */
+ break;
+ case extra_def_info:
+ {
+ DEF_INFO *d = (DEF_INFO *) f;
+ HV *def_parsed_hash;
+
+ /* Create a "def_parsed_hash" extra value. */
+ def_parsed_hash = newHV ();
+ STORE(newRV_inc ((SV *)def_parsed_hash));
+
+#define SAVE_DEF(X) { if (!d->X->hv) \
+ element_to_perl_hash (d->X); \
+ hv_store (def_parsed_hash, #X, strlen (#X), \
+ newRV_inc ((SV *)d->X->hv), 0) ; }
+
+ if (d->category)
+ SAVE_DEF(category)
+ if (d->class)
+ SAVE_DEF(class)
+ if (d->type)
+ SAVE_DEF(type)
+ if (d->name)
+ SAVE_DEF(name)
+ break;
+ }
+ case extra_float_type:
+ {
+ EXTRA_FLOAT_TYPE *eft = (EXTRA_FLOAT_TYPE *) f;
+ HV *type = newHV ();
+ if (eft->content)
+ hv_store (type, "content", strlen ("content"),
+ build_perl_array (&eft->content->contents), 0);
+ if (eft->normalized)
+ hv_store (type, "normalized", strlen ("normalized"),
+ newSVpv (eft->normalized, 0), 0);
+ STORE(newRV_inc ((SV *)type));
+ break;
+ }
+ default:
+ abort ();
+ break;
+ }
+ }
+#undef STORE
+
+ if (!all_deleted)
+ hv_store (e->hv, "extra", strlen ("extra"),
+ newRV_inc((SV *)extra), 0);
+ }
+
+ if (e->line_nr.line_nr
+ && !(command_flags(e) & CF_INFOENCLOSE))
+ {
+#define STORE(key, sv) hv_store (hv, key, strlen (key), sv, 0)
+ LINE_NR *line_nr = &e->line_nr;
+ HV *hv = newHV ();
+ hv_store (e->hv, "line_nr", strlen ("line_nr"),
+ newRV_inc((SV *)hv), 0);
+
+ if (line_nr->file_name)
+ {
+ STORE("file_name", newSVpv (line_nr->file_name, 0));
+ }
+ else
+ STORE("file_name", newSVpv ("", 0));
+
+ if (line_nr->line_nr)
+ {
+ STORE("line_nr", newSViv (line_nr->line_nr));
+ }
+
+ if (line_nr->macro)
+ {
+ STORE("macro", newSVpv (line_nr->macro, 0));
+ }
+ else
+ STORE("macro", newSVpv ("", 0));
+#undef STORE
+ }
+}
+
+HV *
+build_texinfo_tree (void)
+{
+ element_to_perl_hash (Root);
+ return Root->hv;
+}
+
+/* Return array of target elements. build_texinfo_tree must
+ be called first. */
+AV *
+build_label_list (void)
+{
+ AV *target_array;
+ SV *sv;
+ int i;
+
+ dTHX;
+
+ target_array = newAV ();
+
+ for (i = 0; i < labels_number; i++)
+ {
+ sv = newRV_inc (labels_list[i].target->hv);
+ av_push (target_array, sv);
+ }
+
+ return target_array;
+}
+
+AV *
+build_internal_xref_list (void)
+{
+ AV *list_av;
+ SV *sv;
+ int i;
+
+ dTHX;
+
+ list_av = newAV ();
+
+ for (i = 0; i < internal_xref_number; i++)
+ {
+ sv = newRV_inc (internal_xref_list[i]->hv);
+ av_push (list_av, sv);
+ }
+
+ return list_av;
+}
+
+/* Return hash for list of @float's that appeared in the file. */
+HV *
+build_float_list (void)
+{
+ HV *float_hash;
+ SV **type_array;
+ SV *sv;
+ AV *av;
+ int i;
+
+ dTHX;
+
+ float_hash = newHV ();
+
+ for (i = 0; i < floats_number; i++)
+ {
+ type_array = hv_fetch (float_hash,
+ floats_list[i].type,
+ strlen (floats_list[i].type),
+ 0);
+ if (!type_array)
+ {
+ av = newAV ();
+ hv_store (float_hash,
+ floats_list[i].type,
+ strlen (floats_list[i].type),
+ newRV_inc ((SV *)av),
+ 0);
+ }
+ else
+ {
+ av = (AV *)SvRV (*type_array);
+ }
+ sv = newRV_inc ((SV *)floats_list[i].element->hv);
+ av_push (av, sv);
+ }
+
+ return float_hash;
+}
+
+/* Ensure that I->hv is a hash value for a single entry in
+ $self->{'index_names'}, containing information about a single index. */
+static void
+build_single_index_data (INDEX *i)
+{
+#define STORE(key, value) hv_store (hv, key, strlen (key), value, 0)
+
+ HV *hv;
+ AV *entries;
+ int j;
+
+ dTHX;
+
+ if (!i->hv)
+ {
+ hv = newHV ();
+ i->hv = (void *) hv;
+ }
+ else
+ {
+ hv = (HV *) i->hv;
+ }
+
+ STORE("name", newSVpv (i->name, 0));
+ STORE("in_code", i->in_code ? newSViv(1) : newSViv(0));
+
+ if (i->merged_in)
+ {
+ /* This index is merged in another one. */
+ INDEX *ultimate = ultimate_index (i);
+
+ if (!ultimate->hv)
+ {
+ ultimate->hv = (void *) newHV ();
+ ultimate->contained_hv = (void *) newHV ();
+ hv_store (ultimate->hv,
+ "contained_indices",
+ strlen ("contained_indices"),
+ newRV_inc ((SV *)(HV *) ultimate->contained_hv),
+ 0);
+ }
+
+ hv_store (ultimate->contained_hv, i->name, strlen (i->name),
+ newSViv (1), 0);
+
+ STORE("merged_in", newSVpv (ultimate->name, 0));
+
+ if (i->contained_hv)
+ {
+ /* This is unlikely to happen, as if this index is merged into
+ another one, any indices merged into this index would have been
+ recorded under that one, and not this one. */
+ hv_delete (i->hv,
+ "contained_indices", strlen ("contained_indices"),
+ G_DISCARD);
+ i->contained_hv = 0;
+ }
+ }
+ else
+ {
+ if (!i->contained_hv)
+ {
+ i->contained_hv = newHV ();
+ STORE("contained_indices", newRV_inc ((SV *)(HV *) i->contained_hv));
+ }
+ /* Record that this index "contains itself". */
+ hv_store (i->contained_hv, i->name, strlen (i->name), newSViv(1), 0);
+ }
+
+ if (i->index_number > 0)
+ {
+ entries = newAV ();
+ STORE("index_entries", newRV_inc ((SV *) entries));
+ }
+#undef STORE
+
+ if (i->index_number > 0)
+ for (j = 0; j < i->index_number; j++)
+ {
+#define STORE2(key, value) hv_store (entry, key, strlen (key), value, 0)
+ HV *entry;
+ INDEX_ENTRY *e;
+
+ e = &i->index_entries[j];
+ entry = newHV ();
+ av_push (entries, newRV_inc ((SV *)entry));
+
+ STORE2("index_name", newSVpv (i->name, 0));
+ STORE2("index_at_command",
+ newSVpv (command_name(e->index_at_command), 0));
+ STORE2("index_type_command",
+ newSVpv (command_name(e->index_type_command), 0));
+ STORE2("command",
+ newRV_inc ((SV *)e->command->hv));
+ STORE2("number", newSViv (j + 1));
+ if (e->region)
+ {
+ STORE2("region", newRV_inc ((SV *)e->region->hv));
+ }
+ if (e->content)
+ {
+ SV **contents_array;
+ if (!e->content->hv)
+ {
+ if (e->content->parent)
+ abort (); /* element should not be in-tree */
+ element_to_perl_hash (e->content);
+ }
+ contents_array = hv_fetch (e->content->hv,
+ "contents", strlen ("contents"), 0);
+
+ if (!contents_array)
+ {
+ element_to_perl_hash (e->content);
+ contents_array = hv_fetch (e->content->hv,
+ "contents", strlen ("contents"), 0);
+ }
+
+ if (contents_array)
+ {
+ /* Copy the reference to the array. */
+ STORE2("content", newRV_inc ((SV *)(AV *)SvRV(*contents_array)));
+
+ STORE2("content_normalized",
+ newRV_inc ((SV *)(AV *)SvRV(*contents_array)));
+ }
+ else
+ {
+ STORE2("content", newRV_inc ((SV *)newAV ()));
+ STORE2("content_normalized", newRV_inc ((SV *)newAV ()));
+ }
+ }
+ else
+ ; /* will be set in Texinfo::Common::complete_indices */
+
+ if (e->node)
+ STORE2("node", newRV_inc ((SV *)e->node->hv));
+ if (e->sortas)
+ STORE2("sortas", newSVpv (e->sortas, 0));
+
+ /* We set this now because the index data structures don't
+ exist at the time that the main tree is built. */
+ {
+ SV **extra_hash;
+ extra_hash = hv_fetch (e->command->hv, "extra", strlen ("extra"), 0);
+ if (!extra_hash)
+ {
+ /* There's no guarantee that the "extra" value was set on
+ the element. */
+ extra_hash = hv_store (e->command->hv, "extra", strlen ("extra"),
+ newRV_inc ((SV *)newHV ()), 0);
+ }
+
+ hv_store ((HV *)SvRV(*extra_hash), "index_entry", strlen ("index_entry"),
+ newRV_inc ((SV *)entry), 0);
+ }
+#undef STORE2
+ }
+}
+
+/* Return object to be used as $self->{'index_names'} in the perl code.
+ build_texinfo_tree must be called before this so all the 'hv' fields
+ are set on the elements in the tree. */
+HV *
+build_index_data (void)
+{
+ HV *hv;
+ INDEX **i, *idx;
+
+ dTHX;
+
+ hv = newHV ();
+
+ for (i = index_names; (idx = *i); i++)
+ {
+ HV *hv2;
+ build_single_index_data (idx);
+ hv2 = idx->hv;
+ hv_store (hv, idx->name, strlen (idx->name), newRV_inc ((SV *)hv2), 0);
+ }
+
+ return hv;
+}
+
+
+/* Return object to be used as $self->{'info'} in the Perl code, retrievable
+ with the 'global_informations' function. */
+HV *
+build_global_info (void)
+{
+ HV *hv;
+ int i;
+ ELEMENT *e;
+
+ dTHX;
+
+ hv = newHV ();
+ if (global_info.input_encoding_name)
+ hv_store (hv, "input_encoding_name", strlen ("input_encoding_name"),
+ newSVpv (global_info.input_encoding_name, 0), 0);
+ if (global_info.input_perl_encoding)
+ hv_store (hv, "input_perl_encoding", strlen ("input_perl_encoding"),
+ newSVpv (global_info.input_perl_encoding, 0), 0);
+
+ if (global_info.dircategory_direntry.contents.number > 0)
+ {
+ AV *av = newAV ();
+ hv_store (hv, "dircategory_direntry", strlen ("dircategory_direntry"),
+ newRV_inc ((SV *) av), 0);
+ for (i = 0; i < global_info.dircategory_direntry.contents.number; i++)
+ {
+ e = contents_child_by_index (&global_info.dircategory_direntry, i);
+ if (e->hv)
+ av_push (av, newRV_inc ((SV *) e->hv));
+ }
+ }
+
+ if (global_info.novalidate)
+ {
+ hv_store (hv, "novalidate", strlen ("novalidate"),
+ newSVpv ("1", 0), 0);
+ }
+ return hv;
+}
+
+/* Return object to be used as $self->{'extra'} in the Perl code, which
+ are mostly references to tree elements. */
+HV *
+build_global_info2 (void)
+{
+ HV *hv;
+ AV *av;
+ int i;
+ ELEMENT *e;
+
+ dTHX;
+
+ hv = newHV ();
+
+ /* These should be unique elements. */
+
+#define BUILD_GLOBAL_UNIQ(cmd) \
+ if (global_info.cmd && global_info.cmd->hv) \
+ { \
+ hv_store (hv, #cmd, strlen (#cmd), \
+ newRV_inc ((SV *) global_info.cmd->hv), 0); \
+ }
+
+ BUILD_GLOBAL_UNIQ(setfilename);
+ BUILD_GLOBAL_UNIQ(settitle);
+ BUILD_GLOBAL_UNIQ(copying);
+ BUILD_GLOBAL_UNIQ(titlepage);
+ BUILD_GLOBAL_UNIQ(top);
+ BUILD_GLOBAL_UNIQ(documentdescription);
+ BUILD_GLOBAL_UNIQ(setcontentsaftertitlepage);
+ BUILD_GLOBAL_UNIQ(setshortcontentsaftertitlepage);
+ BUILD_GLOBAL_UNIQ(novalidate);
+ BUILD_GLOBAL_UNIQ(validatemenus);
+ BUILD_GLOBAL_UNIQ(pagesizes);
+ BUILD_GLOBAL_UNIQ(fonttextsize);
+ BUILD_GLOBAL_UNIQ(footnotestyle);
+ BUILD_GLOBAL_UNIQ(setchapternewpage);
+ BUILD_GLOBAL_UNIQ(everyheading);
+ BUILD_GLOBAL_UNIQ(everyfooting);
+ BUILD_GLOBAL_UNIQ(evenheading);
+ BUILD_GLOBAL_UNIQ(evenfooting);
+ BUILD_GLOBAL_UNIQ(oddheading);
+ BUILD_GLOBAL_UNIQ(oddfooting);
+ BUILD_GLOBAL_UNIQ(everyheadingmarks);
+ BUILD_GLOBAL_UNIQ(everyfootingmarks);
+ BUILD_GLOBAL_UNIQ(evenheadingmarks);
+ BUILD_GLOBAL_UNIQ(oddheadingmarks);
+ BUILD_GLOBAL_UNIQ(evenfootingmarks);
+ BUILD_GLOBAL_UNIQ(oddfootingmarks);
+ BUILD_GLOBAL_UNIQ(shorttitlepage);
+ BUILD_GLOBAL_UNIQ(title);
+#undef BUILD_GLOBAL_UNIQ
+
+ /* NOTE: Same list in handle_commands.c:register_global_command. */
+
+ /* The following are arrays of elements. */
+
+
+ if (global_info.footnotes.contents.number > 0)
+ {
+ av = newAV ();
+ hv_store (hv, "footnote", strlen ("footnote"),
+ newRV_inc ((SV *) av), 0);
+ for (i = 0; i < global_info.footnotes.contents.number; i++)
+ {
+ e = contents_child_by_index (&global_info.footnotes, i);
+ if (e->hv)
+ av_push (av, newRV_inc ((SV *) e->hv));
+ }
+ }
+
+#define BUILD_GLOBAL_ARRAY(cmd) \
+ if (global_info.cmd.contents.number > 0) \
+ { \
+ av = newAV (); \
+ hv_store (hv, #cmd, strlen (#cmd), \
+ newRV_inc ((SV *) av), 0); \
+ for (i = 0; i < global_info.cmd.contents.number; i++) \
+ { \
+ e = contents_child_by_index (&global_info.cmd, i); \
+ if (e->hv) \
+ av_push (av, newRV_inc ((SV *) e->hv)); \
+ } \
+ }
+
+ BUILD_GLOBAL_ARRAY(hyphenation);
+ BUILD_GLOBAL_ARRAY(insertcopying);
+ BUILD_GLOBAL_ARRAY(printindex);
+ BUILD_GLOBAL_ARRAY(subtitle);
+ BUILD_GLOBAL_ARRAY(titlefont);
+ BUILD_GLOBAL_ARRAY(listoffloats);
+ BUILD_GLOBAL_ARRAY(detailmenu);
+ BUILD_GLOBAL_ARRAY(part);
+
+ /* from Common.pm %document_settable_at_commands */
+ BUILD_GLOBAL_ARRAY(allowcodebreaks);
+ BUILD_GLOBAL_ARRAY(clickstyle);
+ BUILD_GLOBAL_ARRAY(codequotebacktick);
+ BUILD_GLOBAL_ARRAY(codequoteundirected);
+ BUILD_GLOBAL_ARRAY(contents);
+ BUILD_GLOBAL_ARRAY(deftypefnnewline);
+ BUILD_GLOBAL_ARRAY(documentencoding);
+ BUILD_GLOBAL_ARRAY(documentlanguage);
+ BUILD_GLOBAL_ARRAY(exampleindent);
+ BUILD_GLOBAL_ARRAY(firstparagraphindent);
+ BUILD_GLOBAL_ARRAY(frenchspacing);
+ BUILD_GLOBAL_ARRAY(headings);
+ BUILD_GLOBAL_ARRAY(kbdinputstyle);
+ BUILD_GLOBAL_ARRAY(paragraphindent);
+ BUILD_GLOBAL_ARRAY(shortcontents);
+ BUILD_GLOBAL_ARRAY(urefbreakstyle);
+ BUILD_GLOBAL_ARRAY(xrefautomaticsectiontitle);
+ return hv;
+}
+
+/* for debugging */
+void
+set_debug (int value)
+{
+ debug_output = value;
+}
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.h
new file mode 100644
index 0000000000..12b52b5dda
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/api.h
@@ -0,0 +1,21 @@
+/* api.h - declarations for api.c */
+extern ELEMENT *Root;
+
+void parse_text (char *);
+void parse_string(char *);
+int parse_file (char *filename);
+void reset_parser (void);
+void reset_parser_except_conf (void);
+void set_debug (int);
+void wipe_values (void);
+void reset_context_stack (void);
+
+HV *build_texinfo_tree (void);
+AV *build_label_list (void);
+AV *build_internal_xref_list (void);
+HV *build_float_list (void);
+HV *build_index_data (void);
+HV *build_global_info (void);
+HV *build_global_info2 (void);
+
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/close.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/close.c
new file mode 100644
index 0000000000..3ddb41910b
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/close.c
@@ -0,0 +1,419 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "parser.h"
+
+/* Possibly print an error message, and return CURRENT->parent. */
+static ELEMENT *
+close_brace_command (ELEMENT *current,
+ enum command_id closed_command,
+ enum command_id interrupting_command)
+{
+
+ KEY_PAIR *k;
+
+ if (current->cmd != CM_verb)
+ goto yes;
+ k = lookup_extra (current, "delimiter");
+ if (!k || !*(char *)k->value)
+ goto yes;
+ if (0)
+ {
+yes:
+ if (closed_command)
+ command_error (current,
+ "@end %s seen before @%s closing brace",
+ command_name(closed_command),
+ command_name(current->cmd));
+ else if (interrupting_command)
+ command_error (current,
+ "@%s seen before @%s closing brace",
+ command_name(interrupting_command),
+ command_name(current->cmd));
+ else
+ command_error (current,
+ "@%s missing closing brace",
+ command_name(current->cmd));
+ }
+ else
+ {
+ command_error (current,
+ "@%s missing closing delimiter sequence: %s}",
+ command_name(current->cmd),
+ (char *)k->value);
+ }
+ current = current->parent;
+ return current;
+}
+
+/* Close out any brace commands that mark text, not allowing multiple
+ paragraphs. */
+ELEMENT *
+close_all_style_commands (ELEMENT *current,
+ enum command_id closed_command,
+ enum command_id interrupting_command)
+{
+ while (current->parent
+ && (command_flags(current->parent) & CF_brace)
+ && !(command_data(current->parent->cmd).data == BRACE_context))
+ current = close_brace_command (current->parent,
+ closed_command, interrupting_command);
+
+ return current;
+}
+
+void
+close_command_cleanup (ELEMENT *current)
+{
+ if (!current->cmd)
+ return;
+
+ if (current->cmd == CM_multitable)
+ {
+ int in_head_or_rows = -1, i;
+ ELEMENT_LIST old_contents = current->contents;
+
+ /* Clear current contents. */
+ memset (&current->contents, 0, sizeof (ELEMENT_LIST));
+
+ /* Rearrange the contents of the multitable to collect rows into
+ ET_multitable_head and ET_multitable_body elements. */
+ for (i = 0; i < old_contents.number; i++)
+ {
+ ELEMENT *row = old_contents.list[i];
+
+ if (counter_value (&count_cells, row) != -1)
+ counter_pop (&count_cells);
+
+ if (row->type == ET_row)
+ {
+ /* Check if we need to open a new container. */
+ if (contents_child_by_index (row, 0)->cmd == CM_headitem)
+ {
+ if (in_head_or_rows <= 0)
+ {
+ add_to_element_contents (current,
+ new_element (ET_multitable_head));
+ in_head_or_rows = 1;
+ }
+ }
+ else if (contents_child_by_index (row, 0)->cmd == CM_item)
+ {
+ if (in_head_or_rows == 1 || in_head_or_rows == -1)
+ {
+ add_to_element_contents (current,
+ new_element (ET_multitable_body));
+ in_head_or_rows = 0;
+ }
+ }
+
+ add_to_element_contents (last_contents_child(current), row);
+ }
+ else
+ {
+ add_to_element_contents (current, row);
+ in_head_or_rows = -1;
+ }
+ }
+ free (old_contents.list);
+
+ }
+ else if (current->cmd == CM_itemize || current->cmd == CM_enumerate)
+ {
+ counter_pop (&count_items);
+ }
+
+ /* Put everything after the last @def*x command in a def_item type
+ container. */
+ if (command_data(current->cmd).flags & CF_def)
+ {
+ gather_def_item (current, 0);
+ }
+
+ if (current->cmd == CM_table
+ || current->cmd == CM_ftable
+ || current->cmd == CM_vtable)
+ {
+ if (current->contents.number > 0)
+ gather_previous_item (current, 0);
+ }
+
+ /* Block commands that contain @item's - e.g. @multitable, @table,
+ @itemize. */
+ if (command_data(current->cmd).flags & CF_blockitem
+ && current->contents.number > 0)
+ {
+ int have_leading_spaces = 0;
+ ELEMENT *before_item = 0;
+ if (current->contents.number >= 2
+ && current->contents.list[0]->type == ET_empty_line_after_command
+ && current->contents.list[1]->type == ET_before_item)
+ {
+ have_leading_spaces = 1;
+ before_item = current->contents.list[1];
+ }
+ else if (current->contents.number >= 1
+ && current->contents.list[0]->type == ET_before_item)
+ {
+ before_item = current->contents.list[0];
+ }
+
+ if (before_item)
+ {
+ /* Reparent @end from a ET_before_item to the block command */
+ KEY_PAIR *k = lookup_extra (current, "end_command");
+ ELEMENT *e = k ? k->value : 0;
+ if (k && last_contents_child (before_item)
+ && last_contents_child (before_item) == e)
+ {
+ add_to_element_contents (current,
+ pop_element_from_contents (before_item));
+ }
+
+ /* Now if the ET_before_item is empty, remove it. */
+ if (before_item->contents.number == 0)
+ {
+ destroy_element (remove_from_contents (current,
+ have_leading_spaces ? 1 : 0));
+ }
+ else /* Non-empty ET_before_item */
+ {
+ int empty_before_item = 1, i;
+ /* Check if contents consist soley of @comment's. */
+ for (i = 0; i < before_item->contents.number; i++)
+ {
+ enum command_id c = before_item->contents.list[i]->cmd;
+ if (c != CM_c && c != CM_comment)
+ {
+ empty_before_item = 0;
+ }
+ }
+
+ if (!empty_before_item)
+ {
+ int empty_format = 1;
+ /* Check for an element that could represent an @item in the
+ block. The type of this element will depend on the block
+ command we are in. */
+ for (i = 0; i < current->contents.number; i++)
+ {
+ ELEMENT *e = current->contents.list[i];
+ if (e == before_item)
+ continue;
+ if (e->cmd != CM_NONE
+ && (e->cmd != CM_c && e->cmd != CM_comment
+ && e->cmd != CM_end)
+ || e->type != ET_NONE
+ && e->type != ET_empty_line_after_command)
+ {
+ empty_format = 0;
+ break;
+ }
+ }
+
+ if (empty_format)
+ command_warn (current, "@%s has text but no @item",
+ command_name(current->cmd));
+ }
+ }
+ }
+ }
+}
+
+ELEMENT *
+close_current (ELEMENT *current,
+ enum command_id closed_command,
+ enum command_id interrupting_command)
+{
+ /* Element is a command */
+ if (current->cmd)
+ {
+ debug ("CLOSING (close_current) %s", command_name(current->cmd));
+ if (command_flags(current) & CF_brace)
+ {
+ if (command_data(current->cmd).data == BRACE_context)
+ pop_context ();
+ current = close_brace_command (current,
+ closed_command, interrupting_command);
+ }
+ else if (command_flags(current) & CF_block)
+ {
+ enum command_id cmd = current->cmd;
+ ELEMENT *parent = 0;
+ if (closed_command)
+ {
+ line_error ("`@end' expected `%s', but saw `%s'",
+ command_name(current->cmd),
+ command_name(closed_command));
+ }
+ else if (interrupting_command)
+ {
+ line_error ("@%s seen before @end %s",
+ command_name(interrupting_command),
+ command_name(current->cmd));
+ }
+ else
+ {
+ line_error ("no matching `@end %s'",
+ command_name(current->cmd));
+
+ /* Ignored conditional. */
+ if (command_data(current->cmd).data == BLOCK_conditional)
+ {
+ parent = current->parent;
+ destroy_element_and_children (pop_element_from_contents
+ (parent));
+ }
+ }
+ if (command_data(cmd).flags
+ & (CF_preformatted | CF_menu | CF_format_raw))
+ {
+ pop_context ();
+ }
+ if (command_data(cmd).data == BLOCK_region)
+ {
+ pop_region ();
+ }
+ if (!parent)
+ parent = current->parent;
+ current = parent;
+ }
+ else
+ {
+ /* @item and @tab commands are closed here, as well as line commands
+ with invalid content. */
+ current = current->parent;
+ }
+ }
+ else if (current->type != ET_NONE)
+ {
+ enum context c;
+ debug ("CLOSING type %s", element_type_names[current->type]);
+ switch (current->type)
+ {
+ case ET_bracketed:
+ command_error (current, "misplaced {");
+ if (current->contents.number > 0
+ && current->contents.list[0]->type
+ == ET_empty_spaces_before_argument)
+ {
+ /* remove spaces element from tree and update extra values */
+ abort_empty_line (&current, 0);
+ }
+ current = current->parent;
+
+ break;
+ case ET_menu_comment:
+ case ET_menu_entry_description:
+ c = pop_context ();
+ if (c != ct_preformatted)
+ abort ();
+
+ /* Remove empty menu_comment */
+ if (current->contents.number == 0)
+ {
+ current = current->parent;
+ destroy_element (pop_element_from_contents (current));
+ }
+ else
+ current = current->parent;
+
+ break;
+ case ET_line_arg:
+ case ET_block_line_arg:
+ c = pop_context ();
+ if (c != ct_line && c != ct_def)
+ {
+ /* error */
+ abort ();
+ }
+ current = current->parent;
+ break;
+ default:
+ current = current->parent;
+ break;
+ }
+ }
+ else
+ {
+ /* should never get here */
+ if (current->parent)
+ current = current->parent;
+ }
+
+ return current;
+}
+
+/* Return lowest level ancestor of CURRENT containing a CLOSED_COMMAND
+ element. Set CLOSED_ELEMENT to the element itself. INTERRUPTING is used in
+ close_brace_command to display an error message. Remove a context from
+ context stack if it was added by this command. */
+ELEMENT *
+close_commands (ELEMENT *current, enum command_id closed_command,
+ ELEMENT **closed_element, enum command_id interrupting)
+{
+ *closed_element = 0;
+ current = end_paragraph (current, closed_command, interrupting);
+ current = end_preformatted (current, closed_command, interrupting);
+
+ while (current->parent
+ && (!closed_command || current->cmd != closed_command)
+ /* Stop if in a root command. */
+ && !(current->cmd && command_flags(current) & CF_root))
+ {
+ close_command_cleanup (current);
+ current = close_current (current, closed_command, interrupting);
+ }
+
+ if (closed_command && current->cmd == closed_command)
+ {
+ /* 1758 - various error messages */
+ if (command_data(current->cmd).flags & CF_preformatted)
+ {
+ if (pop_context () != ct_preformatted)
+ abort ();
+ }
+ else if (command_data(current->cmd).flags & CF_format_raw)
+ {
+ if (pop_context () != ct_rawpreformatted)
+ abort ();
+ // pop expanded formats stack
+ }
+ else if (command_data(current->cmd).flags & CF_menu)
+ {
+ enum context c;
+ c = pop_context ();
+ if (c != ct_menu && c != ct_preformatted)
+ abort ();
+ }
+
+ if (command_data(current->cmd).data == BLOCK_region)
+ pop_region ();
+
+ *closed_element = current;
+ current = current->parent;
+ }
+ else if (closed_command)
+ {
+ line_error ("unmatched `@end %s'", command_name(closed_command));
+ }
+
+ return current;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.awk b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.awk
new file mode 100644
index 0000000000..5fea87bf9c
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.awk
@@ -0,0 +1,159 @@
+# 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 <http://www.gnu.org/licenses/>.
+#
+
+#######################################################
+# From gawk manual
+# ord.awk --- do ord and chr
+
+# Global identifiers:
+# _ord_: numerical values indexed by characters
+# _ord_init: function to initialize _ord_
+
+BEGIN { _ord_init() }
+
+function _ord_init( low, high, i, t)
+{
+ low = sprintf("%c", 7) # BEL is ascii 7
+ if (low == "\a") { # regular ascii
+ low = 0
+ high = 127
+ } else if (sprintf("%c", 128 + 7) == "\a") {
+ # ascii, mark parity
+ low = 128
+ high = 255
+ } else { # ebcdic(!)
+ low = 0
+ high = 255
+ }
+
+ for (i = low; i <= high; i++) {
+ t = sprintf("%c", i)
+ _ord_[t] = i
+ }
+}
+
+function ord(str, c)
+{
+ # only first character is of interest
+ c = substr(str, 1, 1)
+ return _ord_[c]
+}
+
+#######################################################
+
+
+BEGIN {
+ bs_escapes["\\n"] = "\n"
+ bs_escapes["\\f"] = "\f"
+ bs_escapes["\\t"] = "\t"
+ bs_escapes["\\\\"] = "\\"
+ bs_escapes["\\\""] = "\""
+ bs_escapes["\\x20"] = " "
+
+ for (v in bs_escapes) {
+ inv_bs_escapes[bs_escapes[v]] = v
+ }
+
+ if (srcdir == "") {
+ srcdir = "."
+ }
+ CD = srcdir "/command_data.c"
+ CI = srcdir "/command_ids.h"
+
+ print "/* This file automatically generated by command_data.awk */" > CI
+ print > CI
+ print "/* Useful aliases */" > CI
+ print "#define CM_hex_09 CM_TAB" > CI
+ print "#define CM_hex_0a CM_NEWLINE" > CI
+ print "#define CM_hex_20 CM_SPACE" > CI
+ print "#define CM_hex_21 CM_EXCLAMATION_MARK" > CI
+ print "#define CM_hex_22 CM_POUND_SIGN" > CI
+ print "#define CM_hex_27 CM_APOSTROPHE" > CI
+ print "#define CM_hex_2a CM_ASTERISK" > CI
+ print "#define CM_hex_2c CM_COMMA" > CI
+ print "#define CM_hex_2d CM_HYPHEN" > CI
+ print "#define CM_hex_2e CM_FULL_STOP" > CI
+ print "#define CM_hex_2f CM_SLASH" > CI
+ print "#define CM_hex_3a CM_COLON" > CI
+ print "#define CM_hex_3d CM_EQUALS" > CI
+ print "#define CM_hex_3f CM_QUESTION_MARK" > CI
+ print "#define CM_hex_40 CM_AT_SIGN" > CI
+ print "#define CM_hex_5c CM_BACKSLASH" > CI
+ print "#define CM_hex_5e CM_CIRCUMFLEX" > CI
+ print "#define CM_hex_60 CM_BACKQUOTE" > CI
+ print "#define CM_hex_7b CM_OPEN_BRACE" > CI
+ print "#define CM_hex_7c CM_VERTICAL_BAR" > CI
+ print "#define CM_hex_7d CM_CLOSE_BRACE" > CI
+ print "#define CM_hex_7e CM_TILDE" > CI
+ print > CI
+ print "/* Defined on MS-Windows */" > CI
+ print "#undef CM_NONE" > CI
+ print > CI
+ print "enum command_id {" > CI
+ print "CM_NONE," > CI
+ print > CI
+
+}
+
+!/^$/ && !/^#/ {
+ if ($1 in bs_escapes) {
+ c = bs_escapes[$1]
+ } else {
+ c = $1
+ }
+ commands[c] = $2
+ data[c] = $3
+}
+
+END {
+ print "COMMAND builtin_command_data[] = {" > CD
+
+ print "0, 0, 0," > CD
+
+ # We want the output sorted so we can use bsearch
+ PROCINFO["sorted_in"]="@ind_str_asc"
+ for (c in commands) {
+ # Single character commands with unusual names
+ if (c ~ /^[^[:alpha:]]$/) {
+ if (c in inv_bs_escapes) {
+ c2 = inv_bs_escapes[c]
+ } else
+ c2 = c
+ printf "CM_hex_%02x,\n", ord(c) > CI
+ } else {
+ c2 = c
+ print "CM_" c "," > CI
+ }
+
+ if (commands[c] != "") {
+ flags = "CF_" commands[c]
+ gsub (/,/, " | CF_", flags)
+ } else {
+ flags = "0"
+ }
+
+ if (data[c] != "") {
+ command_data = data[c]
+ } else {
+ command_data = "0"
+ }
+ print "\"" c2 "\", " flags ", " command_data "," > CD
+ }
+ print "};" > CD
+ print "};" > CI
+}
+
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.c
new file mode 100644
index 0000000000..a8f840d3fe
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.c
@@ -0,0 +1,360 @@
+COMMAND builtin_command_data[] = {
+0, 0, 0,
+"\t", CF_nobrace, 0,
+"\n", CF_nobrace, 0,
+"\x20", CF_nobrace, 0,
+"!", CF_nobrace, 0,
+"\"", CF_brace | CF_accent, BRACE_accent,
+"&", CF_nobrace, 0,
+"'", CF_brace | CF_accent, BRACE_accent,
+"*", CF_nobrace, 0,
+",", CF_brace | CF_accent, BRACE_accent,
+"-", CF_nobrace, 0,
+".", CF_nobrace, 0,
+"/", CF_nobrace, 0,
+":", CF_nobrace, 0,
+"=", CF_brace | CF_accent, BRACE_accent,
+"?", CF_nobrace, 0,
+"@", CF_nobrace, 0,
+"AA", CF_brace, 0,
+"AE", CF_brace, 0,
+"DH", CF_brace, 0,
+"H", CF_brace | CF_accent, BRACE_accent,
+"L", CF_brace, 0,
+"LaTeX", CF_brace, 0,
+"O", CF_brace, 0,
+"OE", CF_brace, 0,
+"TH", CF_brace, 0,
+"TeX", CF_brace, 0,
+"U", CF_brace, 1,
+"\\", CF_nobrace, 0,
+"^", CF_brace | CF_accent, BRACE_accent,
+"`", CF_brace | CF_accent, BRACE_accent,
+"aa", CF_brace, 0,
+"abbr", CF_brace, 2,
+"acronym", CF_brace, 2,
+"ae", CF_brace, 0,
+"afivepaper", CF_line, LINE_skipline,
+"afourlatex", CF_line, LINE_skipline,
+"afourpaper", CF_line, LINE_skipline,
+"afourwide", CF_line, LINE_skipline,
+"alias", CF_line, 2,
+"allowcodebreaks", CF_line | CF_global, 1,
+"ampchar", CF_brace, 0,
+"anchor", CF_brace, 1,
+"appendix", CF_root | CF_line | CF_sectioning, LINE_line,
+"appendixsec", CF_root | CF_line | CF_sectioning, LINE_line,
+"appendixsection", CF_root | CF_line | CF_sectioning, LINE_line,
+"appendixsubsec", CF_root | CF_line | CF_sectioning, LINE_line,
+"appendixsubsubsec", CF_root | CF_line | CF_sectioning, LINE_line,
+"arrow", CF_brace, 0,
+"asis", CF_brace, BRACE_style,
+"atchar", CF_brace, 0,
+"author", CF_line | CF_global, LINE_line,
+"b", CF_brace, BRACE_style,
+"backslashchar", CF_brace, 0,
+"bullet", CF_brace, 0,
+"bye", CF_line, LINE_skipline,
+"c", CF_line, LINE_lineraw,
+"caption", CF_brace, BRACE_context,
+"cartouche", CF_block, 0,
+"center", CF_line, LINE_line,
+"centerchap", CF_root | CF_line | CF_sectioning, LINE_line,
+"chapheading", CF_line | CF_sectioning, LINE_line,
+"chapter", CF_root | CF_line | CF_sectioning, LINE_line,
+"cite", CF_brace, BRACE_style,
+"clear", CF_line, LINE_special,
+"click", CF_brace, 0,
+"clicksequence", CF_brace, BRACE_style,
+"clickstyle", CF_line | CF_global, LINE_special,
+"code", CF_brace | CF_code_style, BRACE_style,
+"codequotebacktick", CF_line | CF_global, 1,
+"codequoteundirected", CF_line | CF_global, 1,
+"columnfractions", CF_line, 1,
+"comma", CF_brace, 0,
+"command", CF_brace | CF_code_style, BRACE_style,
+"comment", CF_line, LINE_lineraw,
+"contents", CF_line | CF_global, LINE_skipline,
+"copying", CF_block | CF_global_unique, BLOCK_region,
+"copyright", CF_brace, 0,
+"defcodeindex", CF_line, 1,
+"defcv", CF_block | CF_def, 0,
+"defcvx", CF_line | CF_def, LINE_line,
+"deffn", CF_block | CF_def, 0,
+"deffnx", CF_line | CF_def, LINE_line,
+"defindex", CF_line, 1,
+"definfoenclose", CF_line, 3,
+"defivar", CF_block | CF_def | CF_def_alias, 0,
+"defivarx", CF_line | CF_def | CF_def_alias, LINE_line,
+"defmac", CF_block | CF_def | CF_def_alias, 0,
+"defmacx", CF_line | CF_def | CF_def_alias, LINE_line,
+"defmethod", CF_block | CF_def | CF_def_alias, 0,
+"defmethodx", CF_line | CF_def | CF_def_alias, LINE_line,
+"defop", CF_block | CF_def, 0,
+"defopt", CF_block | CF_def | CF_def_alias, 0,
+"defoptx", CF_line | CF_def | CF_def_alias, LINE_line,
+"defopx", CF_line | CF_def, LINE_line,
+"defspec", CF_block | CF_def | CF_def_alias, 0,
+"defspecx", CF_line | CF_def | CF_def_alias, LINE_line,
+"deftp", CF_block | CF_def, 0,
+"deftpx", CF_line | CF_def, LINE_line,
+"deftypecv", CF_block | CF_def, 0,
+"deftypecvx", CF_line | CF_def, LINE_line,
+"deftypefn", CF_block | CF_def, 0,
+"deftypefnnewline", CF_line | CF_global, 1,
+"deftypefnx", CF_line | CF_def, LINE_line,
+"deftypefun", CF_block | CF_def | CF_def_alias, 0,
+"deftypefunx", CF_line | CF_def | CF_def_alias, LINE_line,
+"deftypeivar", CF_block | CF_def | CF_def_alias, 0,
+"deftypeivarx", CF_line | CF_def | CF_def_alias, LINE_line,
+"deftypemethod", CF_block | CF_def | CF_def_alias, 0,
+"deftypemethodx", CF_line | CF_def | CF_def_alias, LINE_line,
+"deftypeop", CF_block | CF_def, 0,
+"deftypeopx", CF_line | CF_def, LINE_line,
+"deftypevar", CF_block | CF_def | CF_def_alias, 0,
+"deftypevarx", CF_line | CF_def | CF_def_alias, LINE_line,
+"deftypevr", CF_block | CF_def, 0,
+"deftypevrx", CF_line | CF_def, LINE_line,
+"defun", CF_block | CF_def | CF_def_alias, 0,
+"defunx", CF_line | CF_def | CF_def_alias, LINE_line,
+"defvar", CF_block | CF_def | CF_def_alias, 0,
+"defvarx", CF_line | CF_def | CF_def_alias, LINE_line,
+"defvr", CF_block | CF_def, 0,
+"defvrx", CF_line | CF_def, LINE_line,
+"detailmenu", CF_block | CF_menu | CF_global, 0,
+"dfn", CF_brace, BRACE_style,
+"dh", CF_brace, 0,
+"dircategory", CF_line, LINE_line,
+"direntry", CF_block | CF_menu, 0,
+"display", CF_block | CF_preformatted, 0,
+"dmn", CF_brace, 1,
+"docbook", CF_block | CF_format_raw, 0,
+"documentdescription", CF_block | CF_global_unique, BLOCK_region,
+"documentencoding", CF_line | CF_global, LINE_text,
+"documentlanguage", CF_line | CF_global, LINE_text,
+"dotaccent", CF_brace | CF_accent, BRACE_accent,
+"dotless", CF_brace | CF_accent, BRACE_accent,
+"dots", CF_brace, 0,
+"email", CF_brace, 2,
+"emph", CF_brace, BRACE_style,
+"end", CF_line, LINE_text,
+"enddots", CF_brace, 0,
+"enumerate", CF_block | CF_blockitem, 1,
+"env", CF_brace | CF_code_style, BRACE_style,
+"equiv", CF_brace, 0,
+"error", CF_brace, 0,
+"errormsg", CF_brace, 1,
+"euro", CF_brace, 0,
+"evenfooting", CF_line | CF_global_unique, LINE_lineraw,
+"evenfootingmarks", CF_line | CF_global_unique, 1,
+"evenheading", CF_line | CF_global_unique, LINE_lineraw,
+"evenheadingmarks", CF_line | CF_global_unique, 1,
+"everyfooting", CF_line | CF_global_unique, LINE_lineraw,
+"everyfootingmarks", CF_line | CF_global_unique, 1,
+"everyheading", CF_line | CF_global_unique, LINE_lineraw,
+"everyheadingmarks", CF_line | CF_global_unique, 1,
+"example", CF_block | CF_preformatted | CF_preformatted_code, 0,
+"exampleindent", CF_line | CF_global, 1,
+"exclamdown", CF_brace, 0,
+"exdent", CF_line, LINE_line,
+"expansion", CF_brace, 0,
+"file", CF_brace | CF_code_style, BRACE_style,
+"finalout", CF_line, LINE_skipline,
+"firstparagraphindent", CF_line | CF_global, 1,
+"float", CF_block, 2,
+"flushleft", CF_block, 0,
+"flushright", CF_block, 0,
+"fonttextsize", CF_line | CF_global_unique, 1,
+"footnote", CF_brace | CF_global, BRACE_context,
+"footnotestyle", CF_line | CF_global_unique, 1,
+"format", CF_block | CF_preformatted, 0,
+"frenchspacing", CF_line | CF_global, 1,
+"ftable", CF_block | CF_blockitem, 1,
+"geq", CF_brace, 0,
+"group", CF_block, 0,
+"guillemetleft", CF_brace, 0,
+"guillemetright", CF_brace, 0,
+"guillemotleft", CF_brace, 0,
+"guillemotright", CF_brace, 0,
+"guilsinglleft", CF_brace, 0,
+"guilsinglright", CF_brace, 0,
+"hashchar", CF_brace, 0,
+"heading", CF_line | CF_sectioning, LINE_line,
+"headings", CF_line | CF_global, 1,
+"headitem", CF_other, OTHER_skipspace,
+"headitemfont", CF_brace, BRACE_style,
+"html", CF_block | CF_format_raw, 0,
+"hyphenation", CF_brace | CF_global, BRACE_other,
+"i", CF_brace, BRACE_style,
+"ifclear", CF_block, BLOCK_conditional,
+"ifcommanddefined", CF_block, BLOCK_conditional,
+"ifcommandnotdefined", CF_block, BLOCK_conditional,
+"ifdocbook", CF_block, BLOCK_conditional,
+"ifhtml", CF_block, BLOCK_conditional,
+"ifinfo", CF_block, BLOCK_conditional,
+"ifnotdocbook", CF_block, BLOCK_conditional,
+"ifnothtml", CF_block, BLOCK_conditional,
+"ifnotinfo", CF_block, BLOCK_conditional,
+"ifnotplaintext", CF_block, BLOCK_conditional,
+"ifnottex", CF_block, BLOCK_conditional,
+"ifnotxml", CF_block, BLOCK_conditional,
+"ifplaintext", CF_block, BLOCK_conditional,
+"ifset", CF_block, BLOCK_conditional,
+"iftex", CF_block, BLOCK_conditional,
+"ifxml", CF_block, BLOCK_conditional,
+"ignore", CF_block, BLOCK_raw,
+"image", CF_brace, 5,
+"include", CF_line, LINE_text,
+"indent", CF_other, OTHER_skipspace,
+"indentedblock", CF_block, 0,
+"indicateurl", CF_brace | CF_code_style, 1,
+"inforef", CF_brace | CF_ref, 3,
+"inlinefmt", CF_brace | CF_inline, 2,
+"inlinefmtifelse", CF_brace | CF_inline, 3,
+"inlineifclear", CF_brace | CF_inline, 2,
+"inlineifset", CF_brace | CF_inline, 2,
+"inlineraw", CF_brace | CF_inline, 2,
+"insertcopying", CF_line | CF_global, LINE_skipline,
+"item", CF_other, OTHER_skipspace,
+"item_LINE", CF_line, LINE_line,
+"itemize", CF_block | CF_blockitem, 1,
+"itemx", CF_line, LINE_line,
+"kbd", CF_brace | CF_code_style, BRACE_style,
+"kbdinputstyle", CF_line | CF_global, 1,
+"key", CF_brace, BRACE_other,
+"l", CF_brace, 0,
+"lbracechar", CF_brace, 0,
+"leq", CF_brace, 0,
+"lisp", CF_block | CF_preformatted | CF_preformatted_code, 0,
+"listoffloats", CF_line | CF_global, LINE_line,
+"lowersections", CF_line, LINE_skipline,
+"macro", CF_block, BLOCK_raw,
+"majorheading", CF_line | CF_sectioning, LINE_line,
+"math", CF_brace, BRACE_context,
+"menu", CF_block | CF_menu, 0,
+"minus", CF_brace, 0,
+"multitable", CF_block | CF_blockitem, BLOCK_multitable,
+"need", CF_line, 1,
+"node", CF_root | CF_line, LINE_line,
+"noindent", CF_other, OTHER_skipspace,
+"novalidate", CF_line | CF_global_unique, LINE_skipline,
+"o", CF_brace, 0,
+"oddfooting", CF_line | CF_global_unique, LINE_lineraw,
+"oddfootingmarks", CF_line | CF_global_unique, 1,
+"oddheading", CF_line | CF_global_unique, LINE_lineraw,
+"oddheadingmarks", CF_line | CF_global_unique, 1,
+"oe", CF_brace, 0,
+"ogonek", CF_brace | CF_accent, BRACE_accent,
+"option", CF_brace | CF_code_style, BRACE_style,
+"ordf", CF_brace, 0,
+"ordm", CF_brace, 0,
+"page", CF_line, LINE_skipline,
+"pagesizes", CF_line | CF_global_unique, LINE_line,
+"paragraphindent", CF_line | CF_global, 1,
+"part", CF_root | CF_line | CF_sectioning | CF_global, LINE_line,
+"point", CF_brace, 0,
+"pounds", CF_brace, 0,
+"print", CF_brace, 0,
+"printindex", CF_line | CF_global, 1,
+"pxref", CF_brace | CF_ref, 5,
+"questiondown", CF_brace, 0,
+"quotation", CF_block, 1,
+"quotedblbase", CF_brace, 0,
+"quotedblleft", CF_brace, 0,
+"quotedblright", CF_brace, 0,
+"quoteleft", CF_brace, 0,
+"quoteright", CF_brace, 0,
+"quotesinglbase", CF_brace, 0,
+"r", CF_brace, BRACE_style,
+"raggedright", CF_block, 0,
+"raisesections", CF_line, LINE_skipline,
+"rbracechar", CF_brace, 0,
+"ref", CF_brace | CF_ref, 5,
+"refill", CF_other, OTHER_noarg,
+"registeredsymbol", CF_brace, 0,
+"result", CF_brace, 0,
+"ringaccent", CF_brace | CF_accent, BRACE_accent,
+"rmacro", CF_block, BLOCK_raw,
+"samp", CF_brace | CF_code_style, BRACE_style,
+"sansserif", CF_brace, BRACE_style,
+"sc", CF_brace, BRACE_style,
+"section", CF_root | CF_line | CF_sectioning, LINE_line,
+"set", CF_line, LINE_special,
+"setchapternewpage", CF_line | CF_global_unique, 1,
+"setcontentsaftertitlepage", CF_line | CF_deprecated, LINE_skipline,
+"setfilename", CF_line | CF_global_unique, LINE_text,
+"setshortcontentsaftertitlepage", CF_line | CF_deprecated, LINE_skipline,
+"settitle", CF_line | CF_global_unique, LINE_line,
+"shortcaption", CF_brace, BRACE_context,
+"shortcontents", CF_line | CF_global, LINE_skipline,
+"shorttitlepage", CF_line | CF_global_unique, LINE_line,
+"slanted", CF_brace, BRACE_style,
+"smallbook", CF_line, LINE_skipline,
+"smalldisplay", CF_block | CF_preformatted, 0,
+"smallexample", CF_block | CF_preformatted | CF_preformatted_code, 0,
+"smallformat", CF_block | CF_preformatted, 0,
+"smallindentedblock", CF_block, 0,
+"smalllisp", CF_block | CF_preformatted | CF_preformatted_code, 0,
+"smallquotation", CF_block, 1,
+"sortas", CF_brace, 1,
+"sp", CF_line, 1,
+"ss", CF_brace, 0,
+"strong", CF_brace, BRACE_style,
+"sub", CF_brace, BRACE_style,
+"subheading", CF_line | CF_sectioning, LINE_line,
+"subsection", CF_root | CF_line | CF_sectioning, LINE_line,
+"subsubheading", CF_line | CF_sectioning, LINE_line,
+"subsubsection", CF_root | CF_line | CF_sectioning, LINE_line,
+"subtitle", CF_line | CF_global, LINE_line,
+"summarycontents", CF_line | CF_global, LINE_skipline,
+"sup", CF_brace, BRACE_style,
+"syncodeindex", CF_line, 2,
+"synindex", CF_line, 2,
+"t", CF_brace | CF_code_style, BRACE_other,
+"tab", CF_other, OTHER_skipspace,
+"table", CF_block | CF_blockitem, 1,
+"tex", CF_block | CF_format_raw, 0,
+"textdegree", CF_brace, 0,
+"th", CF_brace, 0,
+"thischapter", CF_other | CF_in_heading, OTHER_noarg,
+"thischaptername", CF_other | CF_in_heading, OTHER_noarg,
+"thischapternum", CF_other | CF_in_heading, OTHER_noarg,
+"thisfile", CF_other | CF_in_heading, OTHER_noarg,
+"thispage", CF_other | CF_in_heading, OTHER_noarg,
+"thistitle", CF_other | CF_in_heading, OTHER_noarg,
+"tie", CF_brace, 0,
+"tieaccent", CF_brace | CF_accent, BRACE_accent,
+"title", CF_line | CF_global_unique, LINE_line,
+"titlefont", CF_brace | CF_global, 1,
+"titlepage", CF_block | CF_global_unique, BLOCK_region,
+"today", CF_brace, 0,
+"top", CF_root | CF_line | CF_sectioning | CF_global_unique, LINE_line,
+"u", CF_brace | CF_accent, BRACE_accent,
+"ubaraccent", CF_brace | CF_accent, BRACE_accent,
+"udotaccent", CF_brace | CF_accent, BRACE_accent,
+"unmacro", CF_line, LINE_special,
+"unnumbered", CF_root | CF_line | CF_sectioning, LINE_line,
+"unnumberedsec", CF_root | CF_line | CF_sectioning, LINE_line,
+"unnumberedsubsec", CF_root | CF_line | CF_sectioning, LINE_line,
+"unnumberedsubsubsec", CF_root | CF_line | CF_sectioning, LINE_line,
+"uref", CF_brace, 3,
+"urefbreakstyle", CF_line | CF_global, 1,
+"url", CF_brace, 3,
+"v", CF_brace | CF_accent, BRACE_accent,
+"validatemenus", CF_line | CF_global_unique, 1,
+"value", CF_brace, 0,
+"var", CF_brace, BRACE_style,
+"verb", CF_brace | CF_code_style, BRACE_other,
+"verbatim", CF_block, BLOCK_raw,
+"verbatiminclude", CF_line, LINE_text,
+"vskip", CF_line, LINE_lineraw,
+"vtable", CF_block | CF_blockitem, 1,
+"w", CF_brace, BRACE_other,
+"xml", CF_block | CF_format_raw, 0,
+"xref", CF_brace | CF_ref, 5,
+"xrefautomaticsectiontitle", CF_line | CF_global, 1,
+"{", CF_nobrace, 0,
+"|", CF_nobrace, 0,
+"}", CF_nobrace, 0,
+"~", CF_brace | CF_accent, BRACE_accent,
+};
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.txt b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.txt
new file mode 100644
index 0000000000..c3167e10bb
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_data.txt
@@ -0,0 +1,497 @@
+# Copyright 2010-2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Texinfo.
+#
+# GNU Texinfo is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License,
+# or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Information from tp/Texinfo/Common.pm
+
+# Not in Common.pm
+value brace
+
+##############################################################
+# line 353 - no brace commands - single letter commands
+
+* nobrace
+# space
+\x20 nobrace
+\t nobrace
+\n nobrace
+- nobrace
+| nobrace
+/ nobrace
+: nobrace
+! nobrace
+? nobrace
+. nobrace
+@ nobrace
+& nobrace
+} nobrace
+{ nobrace
+\\ nobrace
+
+##########################################################
+#
+# line commands
+#
+##########################################################
+node line LINE_line
+bye line LINE_skipline
+end line LINE_text
+# set, clear
+set line LINE_special
+clear line LINE_special
+unmacro line LINE_special
+# comments
+c line LINE_lineraw
+comment line LINE_lineraw
+# special
+definfoenclose line 3
+alias line 2
+# number of arguments is not known in advance.
+columnfractions line 1
+# file names
+setfilename line,global_unique LINE_text
+verbatiminclude line LINE_text
+include line LINE_text
+
+raisesections line LINE_skipline
+lowersections line LINE_skipline
+contents line,global LINE_skipline
+shortcontents line,global LINE_skipline
+summarycontents line,global LINE_skipline
+insertcopying line,global LINE_skipline
+clickstyle line,global LINE_special
+# more relevant in preamble
+setcontentsaftertitlepage line,deprecated LINE_skipline
+setshortcontentsaftertitlepage line,deprecated LINE_skipline
+documentencoding line,global LINE_text
+novalidate line,global_unique LINE_skipline
+validatemenus line,global_unique 1
+dircategory line LINE_line
+# with direntry is significant
+pagesizes line,global_unique LINE_line
+# or one? 200mm,150mm 11.5in
+finalout line LINE_skipline
+paragraphindent line,global 1
+# or a number and forbids anything else on the line
+firstparagraphindent line,global 1
+frenchspacing line,global 1
+codequoteundirected line,global 1
+codequotebacktick line,global 1
+xrefautomaticsectiontitle line,global 1
+deftypefnnewline line,global 1
+fonttextsize line,global_unique 1
+allowcodebreaks line,global 1
+exampleindent line,global 1
+footnotestyle line,global_unique 1
+urefbreakstyle line,global 1
+afourpaper line LINE_skipline
+afivepaper line LINE_skipline
+afourlatex line LINE_skipline
+afourwide line LINE_skipline
+headings line,global 1
+setchapternewpage line,global_unique 1
+
+# only relevant in TeX, and special
+everyheading line,global_unique LINE_lineraw
+everyfooting line,global_unique LINE_lineraw
+evenheading line,global_unique LINE_lineraw
+evenfooting line,global_unique LINE_lineraw
+oddheading line,global_unique LINE_lineraw
+oddfooting line,global_unique LINE_lineraw
+
+smallbook line LINE_skipline
+syncodeindex line 2
+synindex line 2
+defindex line 1
+defcodeindex line 1
+documentlanguage line,global LINE_text
+kbdinputstyle line,global 1
+everyheadingmarks line,global_unique 1
+everyfootingmarks line,global_unique 1
+evenheadingmarks line,global_unique 1
+oddheadingmarks line,global_unique 1
+evenfootingmarks line,global_unique 1
+oddfootingmarks line,global_unique 1
+
+# formatting
+center line LINE_line
+printindex line,global 1
+listoffloats line,global LINE_line
+# especially in titlepage
+# shorttitle
+shorttitlepage line,global_unique LINE_line
+settitle line,global_unique LINE_line
+author line,global LINE_line
+subtitle line,global LINE_line
+title line,global_unique LINE_line
+sp line 1
+page line LINE_skipline
+need line 1
+
+# not valid for info (should be in @iftex)
+vskip line LINE_lineraw
+
+exdent line LINE_line
+
+item_LINE line LINE_line
+itemx line LINE_line
+
+##########################################################
+#
+# miscellaneous commands
+#
+##########################################################
+
+# formatting
+noindent other OTHER_skipspace
+indent other OTHER_skipspace
+headitem other OTHER_skipspace
+item other OTHER_skipspace
+tab other OTHER_skipspace
+# only valid in heading or footing
+thischapter other,in_heading OTHER_noarg
+thischaptername other,in_heading OTHER_noarg
+thischapternum other,in_heading OTHER_noarg
+thisfile other,in_heading OTHER_noarg
+thispage other,in_heading OTHER_noarg
+thistitle other,in_heading OTHER_noarg
+# obsolete @-commands.
+refill other OTHER_noarg
+
+
+
+##########################################################
+#
+# brace commands
+#
+##########################################################
+
+# letter_no_arg_commands
+aa brace 0
+AA brace 0
+ae brace 0
+oe brace 0
+AE brace 0
+OE brace 0
+o brace 0
+O brace 0
+ss brace 0
+l brace 0
+L brace 0
+DH brace 0
+dh brace 0
+TH brace 0
+th brace 0
+
+# no arg commands
+TeX brace 0
+LaTeX brace 0
+bullet brace 0
+copyright brace 0
+registeredsymbol brace 0
+dots brace 0
+enddots brace 0
+equiv brace 0
+error brace 0
+expansion brace 0
+arrow brace 0
+minus brace 0
+point brace 0
+print brace 0
+result brace 0
+today brace 0
+exclamdown brace 0
+questiondown brace 0
+pounds brace 0
+ordf brace 0
+ordm brace 0
+atchar brace 0
+ampchar brace 0
+lbracechar brace 0
+rbracechar brace 0
+backslashchar brace 0
+hashchar brace 0
+comma brace 0
+euro brace 0
+geq brace 0
+leq brace 0
+tie brace 0
+textdegree brace 0
+click brace 0
+quotedblleft brace 0
+quotedblright brace 0
+quoteleft brace 0
+quoteright brace 0
+quotedblbase brace 0
+quotesinglbase brace 0
+guillemetleft brace 0
+guillemetright brace 0
+guillemotleft brace 0
+guillemotright brace 0
+guilsinglleft brace 0
+guilsinglright brace 0
+
+# accent commands - can be called with or without braces
+# TODO do we need "brace" here as well?
+\" brace,accent BRACE_accent
+~ brace,accent BRACE_accent
+^ brace,accent BRACE_accent
+` brace,accent BRACE_accent
+' brace,accent BRACE_accent
+, brace,accent BRACE_accent
+= brace,accent BRACE_accent
+ringaccent brace,accent BRACE_accent
+H brace,accent BRACE_accent
+dotaccent brace,accent BRACE_accent
+u brace,accent BRACE_accent
+ubaraccent brace,accent BRACE_accent
+udotaccent brace,accent BRACE_accent
+v brace,accent BRACE_accent
+ogonek brace,accent BRACE_accent
+tieaccent brace,accent BRACE_accent
+dotless brace,accent BRACE_accent
+
+# Style commands
+asis brace BRACE_style
+cite brace BRACE_style
+clicksequence brace BRACE_style
+dfn brace BRACE_style
+emph brace BRACE_style
+sc brace BRACE_style
+t brace BRACE_style
+var brace BRACE_style
+headitemfont brace BRACE_style
+code brace,code_style BRACE_style
+command brace,code_style BRACE_style
+env brace,code_style BRACE_style
+file brace,code_style BRACE_style
+kbd brace,code_style BRACE_style
+option brace,code_style BRACE_style
+samp brace,code_style BRACE_style
+strong brace BRACE_style
+sub brace BRACE_style
+sup brace BRACE_style
+
+# Regular font style commands 595
+r brace BRACE_style
+i brace BRACE_style
+b brace BRACE_style
+sansserif brace BRACE_style
+slanted brace BRACE_style
+
+U brace 1
+dmn brace 1
+titlefont brace,global 1
+
+w brace BRACE_other
+key brace BRACE_other
+
+hyphenation brace,global BRACE_other
+anchor brace 1
+errormsg brace 1
+sortas brace 1
+
+# more code style commands
+indicateurl brace,code_style 1
+verb brace,code_style BRACE_other
+t brace,code_style BRACE_other
+
+# context brace commands - commands that enclose full texts
+footnote brace,global BRACE_context
+caption brace BRACE_context
+shortcaption brace BRACE_context
+math brace BRACE_context
+
+# explained commands 624
+abbr brace 2
+acronym brace 2
+
+# inline format command
+inlineraw brace,inline 2
+inlinefmt brace,inline 2
+inlinefmtifelse brace,inline 3
+
+# inline conditional commands
+inlineifclear brace,inline 2
+inlineifset brace,inline 2
+
+email brace 2
+
+uref brace 3
+url brace 3
+inforef brace,ref 3
+
+xref brace,ref 5
+ref brace,ref 5
+pxref brace,ref 5
+image brace 5
+
+# line 660
+
+
+##########################################################
+# line 687 - "block commands"
+
+# def* commands
+deffn block,def
+defvr block,def
+deftypefn block,def
+deftypeop block,def
+deftypevr block,def
+defcv block,def
+deftypecv block,def
+defop block,def
+deftp block,def
+# shortcuts
+defun block,def,def_alias
+defmac block,def,def_alias
+defspec block,def,def_alias
+defvar block,def,def_alias
+defopt block,def,def_alias
+deftypefun block,def,def_alias
+deftypevar block,def,def_alias
+defivar block,def,def_alias
+deftypeivar block,def,def_alias
+defmethod block,def,def_alias
+deftypemethod block,def,def_alias
+
+# def*x
+deffnx line,def LINE_line
+defvrx line,def LINE_line
+deftypefnx line,def LINE_line
+deftypeopx line,def LINE_line
+deftypevrx line,def LINE_line
+defcvx line,def LINE_line
+deftypecvx line,def LINE_line
+defopx line,def LINE_line
+deftpx line,def LINE_line
+# shortcuts
+defunx line,def,def_alias LINE_line
+defmacx line,def,def_alias LINE_line
+defspecx line,def,def_alias LINE_line
+defvarx line,def,def_alias LINE_line
+defoptx line,def,def_alias LINE_line
+deftypefunx line,def,def_alias LINE_line
+deftypevarx line,def,def_alias LINE_line
+defivarx line,def,def_alias LINE_line
+deftypeivarx line,def,def_alias LINE_line
+defmethodx line,def,def_alias LINE_line
+deftypemethodx line,def,def_alias LINE_line
+
+# 760
+multitable block,blockitem BLOCK_multitable
+
+# menu commands
+menu block,menu
+detailmenu block,menu,global
+direntry block,menu
+
+# align commands
+raggedright block
+flushleft block
+flushright block
+
+# 777
+cartouche block
+group block
+indentedblock block
+smallindentedblock block
+
+# region commands
+titlepage block,global_unique BLOCK_region
+copying block,global_unique BLOCK_region
+documentdescription block,global_unique BLOCK_region
+
+# preformatted commands
+example block,preformatted,preformatted_code
+smallexample block,preformatted,preformatted_code
+lisp block,preformatted,preformatted_code
+smalllisp block,preformatted,preformatted_code
+display block,preformatted
+smalldisplay block,preformatted
+format block,preformatted
+smallformat block,preformatted
+
+# format raw commands
+html block,format_raw
+tex block,format_raw
+xml block,format_raw
+docbook block,format_raw
+
+# raw commands 811
+verbatim block BLOCK_raw
+ignore block BLOCK_raw
+macro block BLOCK_raw
+rmacro block BLOCK_raw
+
+# line 815
+
+# line 831
+table block,blockitem 1
+ftable block,blockitem 1
+vtable block,blockitem 1
+itemize block,blockitem 1
+enumerate block,blockitem 1
+quotation block 1
+smallquotation block 1
+
+float block 2
+
+##################################################################
+# Conditional commands
+ifhtml block BLOCK_conditional
+ifnothtml block BLOCK_conditional
+iftex block BLOCK_conditional
+ifnottex block BLOCK_conditional
+ifxml block BLOCK_conditional
+ifnotxml block BLOCK_conditional
+ifdocbook block BLOCK_conditional
+ifnotdocbook block BLOCK_conditional
+ifinfo block BLOCK_conditional
+ifnotinfo block BLOCK_conditional
+ifplaintext block BLOCK_conditional
+ifnotplaintext block BLOCK_conditional
+ifset block BLOCK_conditional
+ifclear block BLOCK_conditional
+ifcommanddefined block BLOCK_conditional
+ifcommandnotdefined block BLOCK_conditional
+
+##########################################################
+# Root commands - line 887
+node root,line LINE_line
+top root,line,sectioning,global_unique LINE_line
+chapter root,line,sectioning LINE_line
+unnumbered root,line,sectioning LINE_line
+chapheading line,sectioning LINE_line
+appendix root,line,sectioning LINE_line
+section root,line,sectioning LINE_line
+unnumberedsec root,line,sectioning LINE_line
+heading line,sectioning LINE_line
+appendixsec root,line,sectioning LINE_line
+subsection root,line,sectioning LINE_line
+unnumberedsubsec root,line,sectioning LINE_line
+subheading line,sectioning LINE_line
+appendixsubsec root,line,sectioning LINE_line
+subsubsection root,line,sectioning LINE_line
+unnumberedsubsubsec root,line,sectioning LINE_line
+subsubheading line,sectioning LINE_line
+appendixsubsubsec root,line,sectioning LINE_line
+
+part root,line,sectioning,global LINE_line
+appendixsection root,line,sectioning LINE_line
+majorheading line,sectioning LINE_line
+centerchap root,line,sectioning LINE_line
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_ids.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_ids.h
new file mode 100644
index 0000000000..61202e10ad
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/command_ids.h
@@ -0,0 +1,390 @@
+/* This file automatically generated by command_data.awk */
+
+/* Useful aliases */
+#define CM_hex_09 CM_TAB
+#define CM_hex_0a CM_NEWLINE
+#define CM_hex_20 CM_SPACE
+#define CM_hex_21 CM_EXCLAMATION_MARK
+#define CM_hex_22 CM_POUND_SIGN
+#define CM_hex_27 CM_APOSTROPHE
+#define CM_hex_2a CM_ASTERISK
+#define CM_hex_2c CM_COMMA
+#define CM_hex_2d CM_HYPHEN
+#define CM_hex_2e CM_FULL_STOP
+#define CM_hex_2f CM_SLASH
+#define CM_hex_3a CM_COLON
+#define CM_hex_3d CM_EQUALS
+#define CM_hex_3f CM_QUESTION_MARK
+#define CM_hex_40 CM_AT_SIGN
+#define CM_hex_5c CM_BACKSLASH
+#define CM_hex_5e CM_CIRCUMFLEX
+#define CM_hex_60 CM_BACKQUOTE
+#define CM_hex_7b CM_OPEN_BRACE
+#define CM_hex_7c CM_VERTICAL_BAR
+#define CM_hex_7d CM_CLOSE_BRACE
+#define CM_hex_7e CM_TILDE
+
+/* Defined on MS-Windows */
+#undef CM_NONE
+
+enum command_id {
+CM_NONE,
+
+CM_hex_09,
+CM_hex_0a,
+CM_hex_20,
+CM_hex_21,
+CM_hex_22,
+CM_hex_26,
+CM_hex_27,
+CM_hex_2a,
+CM_hex_2c,
+CM_hex_2d,
+CM_hex_2e,
+CM_hex_2f,
+CM_hex_3a,
+CM_hex_3d,
+CM_hex_3f,
+CM_hex_40,
+CM_AA,
+CM_AE,
+CM_DH,
+CM_H,
+CM_L,
+CM_LaTeX,
+CM_O,
+CM_OE,
+CM_TH,
+CM_TeX,
+CM_U,
+CM_hex_5c,
+CM_hex_5e,
+CM_hex_60,
+CM_aa,
+CM_abbr,
+CM_acronym,
+CM_ae,
+CM_afivepaper,
+CM_afourlatex,
+CM_afourpaper,
+CM_afourwide,
+CM_alias,
+CM_allowcodebreaks,
+CM_ampchar,
+CM_anchor,
+CM_appendix,
+CM_appendixsec,
+CM_appendixsection,
+CM_appendixsubsec,
+CM_appendixsubsubsec,
+CM_arrow,
+CM_asis,
+CM_atchar,
+CM_author,
+CM_b,
+CM_backslashchar,
+CM_bullet,
+CM_bye,
+CM_c,
+CM_caption,
+CM_cartouche,
+CM_center,
+CM_centerchap,
+CM_chapheading,
+CM_chapter,
+CM_cite,
+CM_clear,
+CM_click,
+CM_clicksequence,
+CM_clickstyle,
+CM_code,
+CM_codequotebacktick,
+CM_codequoteundirected,
+CM_columnfractions,
+CM_comma,
+CM_command,
+CM_comment,
+CM_contents,
+CM_copying,
+CM_copyright,
+CM_defcodeindex,
+CM_defcv,
+CM_defcvx,
+CM_deffn,
+CM_deffnx,
+CM_defindex,
+CM_definfoenclose,
+CM_defivar,
+CM_defivarx,
+CM_defmac,
+CM_defmacx,
+CM_defmethod,
+CM_defmethodx,
+CM_defop,
+CM_defopt,
+CM_defoptx,
+CM_defopx,
+CM_defspec,
+CM_defspecx,
+CM_deftp,
+CM_deftpx,
+CM_deftypecv,
+CM_deftypecvx,
+CM_deftypefn,
+CM_deftypefnnewline,
+CM_deftypefnx,
+CM_deftypefun,
+CM_deftypefunx,
+CM_deftypeivar,
+CM_deftypeivarx,
+CM_deftypemethod,
+CM_deftypemethodx,
+CM_deftypeop,
+CM_deftypeopx,
+CM_deftypevar,
+CM_deftypevarx,
+CM_deftypevr,
+CM_deftypevrx,
+CM_defun,
+CM_defunx,
+CM_defvar,
+CM_defvarx,
+CM_defvr,
+CM_defvrx,
+CM_detailmenu,
+CM_dfn,
+CM_dh,
+CM_dircategory,
+CM_direntry,
+CM_display,
+CM_dmn,
+CM_docbook,
+CM_documentdescription,
+CM_documentencoding,
+CM_documentlanguage,
+CM_dotaccent,
+CM_dotless,
+CM_dots,
+CM_email,
+CM_emph,
+CM_end,
+CM_enddots,
+CM_enumerate,
+CM_env,
+CM_equiv,
+CM_error,
+CM_errormsg,
+CM_euro,
+CM_evenfooting,
+CM_evenfootingmarks,
+CM_evenheading,
+CM_evenheadingmarks,
+CM_everyfooting,
+CM_everyfootingmarks,
+CM_everyheading,
+CM_everyheadingmarks,
+CM_example,
+CM_exampleindent,
+CM_exclamdown,
+CM_exdent,
+CM_expansion,
+CM_file,
+CM_finalout,
+CM_firstparagraphindent,
+CM_float,
+CM_flushleft,
+CM_flushright,
+CM_fonttextsize,
+CM_footnote,
+CM_footnotestyle,
+CM_format,
+CM_frenchspacing,
+CM_ftable,
+CM_geq,
+CM_group,
+CM_guillemetleft,
+CM_guillemetright,
+CM_guillemotleft,
+CM_guillemotright,
+CM_guilsinglleft,
+CM_guilsinglright,
+CM_hashchar,
+CM_heading,
+CM_headings,
+CM_headitem,
+CM_headitemfont,
+CM_html,
+CM_hyphenation,
+CM_i,
+CM_ifclear,
+CM_ifcommanddefined,
+CM_ifcommandnotdefined,
+CM_ifdocbook,
+CM_ifhtml,
+CM_ifinfo,
+CM_ifnotdocbook,
+CM_ifnothtml,
+CM_ifnotinfo,
+CM_ifnotplaintext,
+CM_ifnottex,
+CM_ifnotxml,
+CM_ifplaintext,
+CM_ifset,
+CM_iftex,
+CM_ifxml,
+CM_ignore,
+CM_image,
+CM_include,
+CM_indent,
+CM_indentedblock,
+CM_indicateurl,
+CM_inforef,
+CM_inlinefmt,
+CM_inlinefmtifelse,
+CM_inlineifclear,
+CM_inlineifset,
+CM_inlineraw,
+CM_insertcopying,
+CM_item,
+CM_item_LINE,
+CM_itemize,
+CM_itemx,
+CM_kbd,
+CM_kbdinputstyle,
+CM_key,
+CM_l,
+CM_lbracechar,
+CM_leq,
+CM_lisp,
+CM_listoffloats,
+CM_lowersections,
+CM_macro,
+CM_majorheading,
+CM_math,
+CM_menu,
+CM_minus,
+CM_multitable,
+CM_need,
+CM_node,
+CM_noindent,
+CM_novalidate,
+CM_o,
+CM_oddfooting,
+CM_oddfootingmarks,
+CM_oddheading,
+CM_oddheadingmarks,
+CM_oe,
+CM_ogonek,
+CM_option,
+CM_ordf,
+CM_ordm,
+CM_page,
+CM_pagesizes,
+CM_paragraphindent,
+CM_part,
+CM_point,
+CM_pounds,
+CM_print,
+CM_printindex,
+CM_pxref,
+CM_questiondown,
+CM_quotation,
+CM_quotedblbase,
+CM_quotedblleft,
+CM_quotedblright,
+CM_quoteleft,
+CM_quoteright,
+CM_quotesinglbase,
+CM_r,
+CM_raggedright,
+CM_raisesections,
+CM_rbracechar,
+CM_ref,
+CM_refill,
+CM_registeredsymbol,
+CM_result,
+CM_ringaccent,
+CM_rmacro,
+CM_samp,
+CM_sansserif,
+CM_sc,
+CM_section,
+CM_set,
+CM_setchapternewpage,
+CM_setcontentsaftertitlepage,
+CM_setfilename,
+CM_setshortcontentsaftertitlepage,
+CM_settitle,
+CM_shortcaption,
+CM_shortcontents,
+CM_shorttitlepage,
+CM_slanted,
+CM_smallbook,
+CM_smalldisplay,
+CM_smallexample,
+CM_smallformat,
+CM_smallindentedblock,
+CM_smalllisp,
+CM_smallquotation,
+CM_sortas,
+CM_sp,
+CM_ss,
+CM_strong,
+CM_sub,
+CM_subheading,
+CM_subsection,
+CM_subsubheading,
+CM_subsubsection,
+CM_subtitle,
+CM_summarycontents,
+CM_sup,
+CM_syncodeindex,
+CM_synindex,
+CM_t,
+CM_tab,
+CM_table,
+CM_tex,
+CM_textdegree,
+CM_th,
+CM_thischapter,
+CM_thischaptername,
+CM_thischapternum,
+CM_thisfile,
+CM_thispage,
+CM_thistitle,
+CM_tie,
+CM_tieaccent,
+CM_title,
+CM_titlefont,
+CM_titlepage,
+CM_today,
+CM_top,
+CM_u,
+CM_ubaraccent,
+CM_udotaccent,
+CM_unmacro,
+CM_unnumbered,
+CM_unnumberedsec,
+CM_unnumberedsubsec,
+CM_unnumberedsubsubsec,
+CM_uref,
+CM_urefbreakstyle,
+CM_url,
+CM_v,
+CM_validatemenus,
+CM_value,
+CM_var,
+CM_verb,
+CM_verbatim,
+CM_verbatiminclude,
+CM_vskip,
+CM_vtable,
+CM_w,
+CM_xml,
+CM_xref,
+CM_xrefautomaticsectiontitle,
+CM_hex_7b,
+CM_hex_7c,
+CM_hex_7d,
+CM_hex_7e,
+};
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.c
new file mode 100644
index 0000000000..7b2d65a3c9
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.c
@@ -0,0 +1,177 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "command_ids.h"
+#include "commands.h"
+#include "command_data.c"
+
+COMMAND *user_defined_command_data = 0;
+static size_t user_defined_number = 0;
+static size_t user_defined_space = 0;
+
+static int
+compare_command_fn (const void *a, const void *b)
+{
+ const COMMAND *ca = (COMMAND *) a;
+ const COMMAND *cb = (COMMAND *) b;
+
+ return strcmp (ca->cmdname, cb->cmdname);
+}
+
+/* Return element number in command_data array. Return 0 if not found. */
+enum command_id
+lookup_command (char *cmdname)
+{
+ COMMAND *c;
+ COMMAND target;
+ int i;
+
+ target.cmdname = cmdname;
+
+ /* Check for user-defined commands: macros, indexes, etc. */
+ /* Do this before looking in the built-in commands, in case the user uses
+ @definfoenclose or similar to override a command.
+ If speed is a problem, then we could set a bit in the flags on the
+ builtin command (maybe reusing CF_INFOENCLOSE) to say to look in the
+ user commands instead. */
+
+ for (i = 0; i < user_defined_number; i++)
+ {
+ if (!strcmp (user_defined_command_data[i].cmdname, cmdname))
+ return ((enum command_id) i) | USER_COMMAND_BIT;
+ }
+
+ c = (COMMAND *) bsearch (&target, builtin_command_data + 1,
+ /* number of elements */
+ sizeof (builtin_command_data) / sizeof (builtin_command_data[0]) - 1,
+ sizeof (builtin_command_data[0]),
+ compare_command_fn);
+
+ if (c)
+ return c - &builtin_command_data[0];
+
+
+ return 0;
+}
+
+/* Add a new user-defined Texinfo command, like an index or macro command. No
+ reference to NAME is retained. */
+enum command_id
+add_texinfo_command (char *name)
+{
+ if (user_defined_number == user_defined_space)
+ {
+ user_defined_command_data
+ = realloc (user_defined_command_data,
+ (user_defined_space += 10) * sizeof (COMMAND));
+ if (!user_defined_command_data)
+ abort ();
+ }
+
+ user_defined_command_data[user_defined_number].cmdname = strdup (name);
+ user_defined_command_data[user_defined_number].flags = 0;
+ user_defined_command_data[user_defined_number].data = 0;
+
+ return ((enum command_id) user_defined_number++) | USER_COMMAND_BIT;
+}
+
+/* Remove CMD, for @unmacro. */
+void
+remove_texinfo_command (enum command_id cmd)
+{
+ cmd &= ~USER_COMMAND_BIT;
+ free (user_defined_command_data[cmd].cmdname);
+ user_defined_command_data[cmd].cmdname = strdup ("");
+}
+
+void
+wipe_user_commands (void)
+{
+ int i;
+ for (i = 0; i < user_defined_number; i++)
+ free (user_defined_command_data[i].cmdname);
+ user_defined_number = 0;
+}
+
+/* Commands that terminate a paragraph. */
+/* We may replace this function with a macro, or represent this infomation in
+ command_data. */
+int
+close_paragraph_command (enum command_id cmd)
+{
+ if (cmd == CM_verbatim)
+ return 1;
+
+ /* Block commands except 'raw' and 'conditional'. */
+
+ if (command_data(cmd).flags & CF_block)
+ {
+ if (command_data(cmd).data == BLOCK_conditional
+ || command_data(cmd).data == BLOCK_raw)
+ return 0;
+ if (command_data(cmd).flags & CF_format_raw)
+ return 0;
+
+ return 1;
+ }
+
+ if (cmd == CM_titlefont
+ || cmd == CM_insertcopying
+ || cmd == CM_sp
+ || cmd == CM_verbatiminclude
+ || cmd == CM_page
+ || cmd == CM_item
+ || cmd == CM_itemx
+ || cmd == CM_tab
+ || cmd == CM_headitem
+ || cmd == CM_printindex
+ || cmd == CM_listoffloats
+ || cmd == CM_center
+ || cmd == CM_dircategory
+ || cmd == CM_contents
+ || cmd == CM_shortcontents
+ || cmd == CM_summarycontents
+ || cmd == CM_caption
+ || cmd == CM_shortcaption
+ || cmd == CM_setfilename
+ || cmd == CM_exdent)
+ return 1;
+
+ if ((command_data(cmd).flags & CF_sectioning)
+ && !(command_data(cmd).flags & CF_root))
+ return 1;
+
+ if ((command_data(cmd).flags & CF_def))
+ return 1;
+
+ return 0;
+}
+
+int
+close_preformatted_command (enum command_id cmd_id)
+{
+ return cmd_id != CM_sp && close_paragraph_command (cmd_id);
+}
+
+int
+item_line_command (enum command_id cmd_id)
+{
+ return cmd_id == CM_table || cmd_id == CM_ftable || cmd_id == CM_vtable;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.h
new file mode 100644
index 0000000000..df29dff316
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/commands.h
@@ -0,0 +1,113 @@
+/* commands.h - declarations for commands.c */
+/* 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 <http://www.gnu.org/licenses/>. */
+
+typedef struct command_struct {
+ char *cmdname;
+ unsigned long flags; /* Up to 32 flags */
+ int data; /* Number of arguments for brace or line commands. */
+} COMMAND;
+
+extern COMMAND builtin_command_data[];
+extern COMMAND *user_defined_command_data;
+
+/* Command ID's with this bit set represent a user-defined command. */
+#define USER_COMMAND_BIT 0x8000
+
+enum command_id lookup_command (char *cmdname);
+
+#define command_data(id) \
+ (!((id) & USER_COMMAND_BIT) \
+ ? builtin_command_data[(id)] \
+ : user_defined_command_data[(id) & ~USER_COMMAND_BIT])
+
+#define command_flags(e) (!(e) ? 0 : (command_data((e)->cmd).flags))
+#define command_name(cmd) (command_data(cmd).cmdname)
+
+int close_paragraph_command (enum command_id cmd_id);
+int close_preformatted_command (enum command_id cmd_id);
+int item_line_command (enum command_id cmd_id);
+enum command_id add_texinfo_command (char *name);
+void remove_texinfo_command (enum command_id cmd);
+void wipe_user_commands (void);
+
+/* Available command flags. */
+
+#define CF_line 0x0001
+#define CF_deprecated 0x0002
+#define CF_root 0x0004
+#define CF_sectioning 0x0008
+#define CF_brace 0x0010
+#define CF_letter_no_arg 0x0020
+#define CF_accent 0x0040
+#define CF_style 0x0080
+/* CF_code_style is set for brace commands only. */
+#define CF_code_style 0x0100
+#define CF_INFOENCLOSE 0x0200
+#define CF_in_heading 0x0400
+#define CF_ref 0x0800
+#define CF_ALIAS 0x1000
+#define CF_block 0x2000
+#define CF_raw 0x4000
+#define CF_format_raw 0x8000
+#define CF_global 0x00010000
+#define CF_def 0x00020000
+#define CF_def_alias 0x00040000
+#define CF_menu 0x00080000
+#define CF_align 0x00100000
+#define CF_other 0x00200000
+#define CF_preformatted 0x00400000
+#define CF_preformatted_code 0x00800000
+#define CF_item_container 0x01000000
+#define CF_item_line 0x02000000
+#define CF_nobrace 0x04000000
+#define CF_blockitem 0x08000000
+#define CF_inline 0x10000000
+#define CF_MACRO 0x20000000
+#define CF_index_entry_command 0x40000000
+#define CF_global_unique 0x80000000
+
+/* NOTE: We've run out of spaces for flags, but some of these may not
+ be used, or may not be necessary.
+ CF_code_style, CF_deprecated are hardly used.
+
+ Candidates for flags:
+ CF_close_paragraph
+
+ Could combine CF_MACRO, CF_ALIAS, and CF_INFOENCLOSE into 2 bits.
+ */
+
+/* Types of line command (has CF_line flag). Values for COMMAND.data. */
+#define LINE_special -1
+#define LINE_lineraw -2
+#define LINE_skipline -3
+#define LINE_text -6
+#define LINE_line -7
+
+/* Types of other command (has CF_other flag). */
+#define OTHER_skipspace -1
+#define OTHER_noarg -2
+
+/* Types of block command (CF_block). */
+#define BLOCK_conditional -1
+#define BLOCK_raw -2
+#define BLOCK_multitable -3
+#define BLOCK_region -4
+
+/* Types of brace command (CF_brace). */
+#define BRACE_context -1 /* Can enclose paragraph breaks. */
+#define BRACE_accent -2
+#define BRACE_style -3
+#define BRACE_other -4
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.c
new file mode 100644
index 0000000000..4d734942bb
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.c
@@ -0,0 +1,50 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <string.h>
+
+#include "tree_types.h"
+#include "conf.h"
+
+/* Configuration values. */
+CONF conf;
+
+void
+conf_set_show_menu (int i)
+{
+ conf.show_menu = i;
+}
+
+void
+conf_set_CPP_LINE_DIRECTIVES (int i)
+{
+ conf.cpp_line_directives = i;
+}
+
+void
+conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME (int i)
+{
+ conf.ignore_space_after_braced_command_name = i;
+}
+
+void
+reset_conf (void)
+{
+ memset (&conf, 0, sizeof (conf));
+ conf.show_menu = 1;
+ conf.cpp_line_directives = 1;
+ conf.ignore_space_after_braced_command_name = 1;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.h
new file mode 100644
index 0000000000..d55a46614c
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/conf.h
@@ -0,0 +1,29 @@
+/* conf.h - declarations for conf.c */
+/* Copyright 2010-2018 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+typedef struct CONF {
+ int show_menu;
+ int cpp_line_directives;
+ int ignore_space_after_braced_command_name;
+} CONF;
+
+extern CONF conf;
+
+void conf_set_show_menu (int i);
+void conf_set_CPP_LINE_DIRECTIVES (int i);
+void conf_set_IGNORE_SPACE_AFTER_BRACED_COMMAND_NAME (int i);
+void reset_conf (void);
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.c
new file mode 100644
index 0000000000..b2b85cf582
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.c
@@ -0,0 +1,121 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+
+#include "parser.h"
+
+static enum context *stack;
+static size_t top; /* One above last pushed context. */
+static size_t space;
+
+void
+reset_context_stack (void)
+{
+ top = 0;
+}
+
+void
+push_context (enum context c)
+{
+ if (top >= space)
+ {
+ stack = realloc (stack, (space += 5) * sizeof (enum context));
+ }
+
+ debug (">>>>>>>>>>>>>>>>>PUSHING STACK AT %d -- %s", top,
+ c == ct_preformatted ? "preformatted"
+ : c == ct_line ? "line"
+ : c == ct_def ? "def"
+ : c == ct_menu ? "menu"
+ : "");
+ stack[top++] = c;
+}
+
+enum context
+pop_context ()
+{
+ if (top == 0)
+ abort ();
+
+ debug (">>>>>>>>>>>>>POPPING STACK AT %d", top - 1);
+ return stack[--top];
+}
+
+enum context
+current_context (void)
+{
+ if (top == 0)
+ return ct_NONE;
+
+ return stack[top - 1];
+}
+
+
+/* The valid regions are 'titlepage', 'copying', and 'documentdescription'.
+ This stack isn't used that much. */
+
+static ELEMENT **region_stack;
+static size_t region_top; /* One above last pushed region. */
+static size_t region_space;
+
+void
+reset_region_stack (void)
+{
+ region_top = 0;
+}
+
+void
+push_region (ELEMENT *e)
+{
+ if (region_top >= region_space)
+ {
+ region_stack = realloc (region_stack,
+ (region_space += 5) * sizeof (*region_stack));
+ }
+
+ debug (">>>>>>>>>>>>>>>>>PUSHING REGION STACK AT %d", region_top);
+
+ region_stack[region_top++] = e;
+}
+
+ELEMENT *
+pop_region ()
+{
+ if (region_top == 0)
+ abort ();
+
+ debug (">>>>>>>>>>>>>POPPING REGION STACK AT %d", region_top - 1);
+ return region_stack[--region_top];
+}
+
+enum command_id
+current_region_cmd (void)
+{
+ if (region_top == 0)
+ return CM_NONE;
+
+ return region_stack[region_top - 1]->cmd;
+}
+
+ELEMENT *
+current_region (void)
+{
+ if (region_top == 0)
+ return 0;
+
+ return region_stack[region_top - 1];
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.h
new file mode 100644
index 0000000000..4fea34d1f3
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/context_stack.h
@@ -0,0 +1,51 @@
+/* context_stack.h - declarations for context_stack.c */
+/* 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 <http://www.gnu.org/licenses/>. */
+
+enum context {
+ ct_NONE,
+ ct_line,
+ ct_def,
+ ct_preformatted,
+ ct_rawpreformatted,
+ ct_menu,
+ ct_math,
+ ct_footnote,
+ ct_caption,
+ ct_shortcaption,
+ ct_inlineraw
+};
+
+/* Contexts where an empty line doesn't start a new paragraph. */
+#define in_paragraph_context(c) \
+ !((c) == ct_math \
+ || (c) == ct_menu \
+ || (c) == ct_def \
+ || (c) == ct_preformatted \
+ || (c) == ct_rawpreformatted \
+ || (c) == ct_inlineraw)
+
+void push_context (enum context c);
+enum context pop_context ();
+enum context current_context (void);
+void reset_context_stack (void);
+
+
+void push_region (ELEMENT *r);
+ELEMENT *pop_region (void);
+ELEMENT *current_region (void);
+enum command_id current_region_cmd (void);
+
+void reset_region_stack (void);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.c
new file mode 100644
index 0000000000..7138df7ab3
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.c
@@ -0,0 +1,226 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "parser.h"
+#include "text.h"
+#include "convert.h"
+
+
+static void expand_cmd_args_to_texi (ELEMENT *e, TEXT *result);
+static void convert_to_texinfo_internal (ELEMENT *e, TEXT *result);
+
+
+#define ADD(x) text_append (result, x)
+
+/* Return value to be freed by caller. */
+char *
+node_extra_to_texi (NODE_SPEC_EXTRA *nse)
+{
+ TEXT result;
+
+ if (!nse)
+ return "";
+ text_init (&result);
+
+ if (nse->manual_content
+ && nse->manual_content->contents.number > 0)
+ {
+ text_append_n (&result, "(", 1);
+ convert_to_texinfo_internal (nse->manual_content, &result);
+ text_append_n (&result, ")", 1);
+ }
+ if (nse->node_content
+ && nse->node_content->contents.number > 0)
+ {
+ convert_to_texinfo_internal (nse->node_content, &result);
+ }
+ return result.text;
+}
+
+static void
+expand_cmd_args_to_texi (ELEMENT *e, TEXT *result)
+{
+ enum command_id cmd = e->cmd;
+ KEY_PAIR *k;
+
+ if (cmd)
+ {
+ ADD("@"); ADD(command_name(cmd));
+ }
+
+ // TODO extra spaces
+ k = lookup_extra (e, "spaces_before_argument");
+ if (k)
+ ADD((char *)k->value);
+
+ // TODO multitable or block command
+
+ if (cmd == CM_macro || cmd == CM_rmacro)
+ {
+ KEY_PAIR *k;
+ char *s = 0;
+ k = lookup_extra (e, "arg_line");
+ if (k)
+ s = (char *)k->value;
+ if (s)
+ {
+ ADD(s);
+ return;
+ }
+ }
+
+ // TODO node
+
+ if (e->args.number > 0)
+ {
+ int braces, arg_nr, i;
+ braces = (e->args.list[0]->type == ET_brace_command_arg
+ || e->args.list[0]->type == ET_brace_command_context);
+ if (braces)
+ ADD("{");
+
+ if (e->cmd == CM_verb)
+ {
+ k = lookup_extra (e, "delimiter");
+ ADD((char *)k->value);
+ }
+
+ arg_nr = 0;
+ for (i = 0; i < e->args.number; i++)
+ {
+ if (command_data(cmd).flags & CF_brace)
+ {
+ if (arg_nr)
+ ADD(",");
+ arg_nr++;
+ }
+ k = lookup_extra (e->args.list[i], "spaces_before_argument");
+ if (k)
+ ADD((char *)k->value);
+ convert_to_texinfo_internal (e->args.list[i], result);
+ k = lookup_extra (e->args.list[i], "spaces_after_argument");
+ if (k)
+ ADD((char *)k->value);
+ }
+
+ if (e->cmd == CM_verb)
+ {
+ k = lookup_extra (e, "delimiter");
+ ADD((char *)k->value);
+ }
+
+ if (braces)
+ ADD("}");
+ }
+}
+
+static void
+convert_to_texinfo_internal (ELEMENT *e, TEXT *result)
+{
+ if (e->text.end > 0)
+ ADD(e->text.text);
+ else
+ {
+ if (e->cmd
+ || e->type == ET_def_line
+ || e->type == ET_menu_entry
+ || e->type == ET_menu_comment)
+ {
+ expand_cmd_args_to_texi (e, result);
+ }
+
+ if (e->type == ET_bracketed
+ || e->type == ET_bracketed_def_content)
+ {
+ KEY_PAIR *k;
+ ADD("{");
+ k = lookup_extra (e, "spaces_before_argument");
+ if (k)
+ ADD((char *)k->value);
+ }
+ if (e->contents.number > 0)
+ {
+ int i;
+ for (i = 0; i < e->contents.number; i++)
+ convert_to_texinfo_internal (e->contents.list[i], result);
+ }
+ if (e->type == ET_bracketed)
+ ADD("}");
+
+ // TODO: raw block command
+ if (command_flags (e) & CF_block)
+ {
+ ADD("@end ");
+ ADD(command_name(e->cmd));
+ if (command_data(e->cmd).data != BLOCK_raw)
+ ADD("\n");
+ }
+ }
+
+ return;
+}
+#undef ADD
+
+/* Return value to be freed by caller. */
+char *
+convert_to_texinfo (ELEMENT *e)
+{
+ TEXT result;
+
+ if (!e)
+ return strdup ("");
+ text_init (&result);
+ convert_to_texinfo_internal (e, &result);
+ return result.text;
+}
+
+/* Very stripped-down version of Texinfo::Convert::Text.
+ Convert the contents of E to plain text. Suitable for specifying a file
+ name containing an at sign or braces. Set *SUPERFLUOUS_ARG if the contents
+ of E are too complicated to convert properly. */
+char *
+convert_to_text (ELEMENT *e, int *superfluous_arg)
+{
+#define ADD(x) text_append (&result, x)
+
+ TEXT result; int i;
+
+ if (!e)
+ return "";
+ text_init (&result);
+ for (i = 0; i < e->contents.number; i++)
+ {
+ ELEMENT *e1 = contents_child_by_index (e, i);
+ if (e1->text.end > 0)
+ ADD(e1->text.text);
+ else if (e1->cmd == CM_AT_SIGN)
+ ADD("@");
+ else if (e1->cmd == CM_OPEN_BRACE)
+ ADD("{");
+ else if (e1->cmd == CM_CLOSE_BRACE)
+ ADD("}");
+ else
+ *superfluous_arg = 1;
+ }
+ return result.text;
+}
+#undef ADD
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.h
new file mode 100644
index 0000000000..7535bc95e0
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/convert.h
@@ -0,0 +1,4 @@
+/* convert.h - definitions for convert.c */
+char *convert_to_texinfo (ELEMENT *e);
+char *convert_to_text (ELEMENT *e, int *superfluous_arg);
+char *node_extra_to_texi (NODE_SPEC_EXTRA *nse);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.c
new file mode 100644
index 0000000000..1c028e9c5d
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.c
@@ -0,0 +1,72 @@
+/* Copyright 2015-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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "tree_types.h"
+#include "counter.h"
+
+void
+counter_push (COUNTER *c, ELEMENT *elt, int num)
+{
+ if (c->nvalues >= c->space - 1)
+ {
+ c->space += 5;
+ c->values = realloc (c->values, c->space * sizeof (int));
+ c->elts = realloc (c->elts, c->space * sizeof (ELEMENT *));
+ if (!c->values)
+ abort ();
+ }
+ c->values[c->nvalues] = num;
+ c->elts[c->nvalues] = elt;
+
+ c->nvalues++;
+ c->values[c->nvalues] = 0;
+ c->elts[c->nvalues] = 0;
+}
+
+void
+counter_pop (COUNTER *c)
+{
+ if (!c->nvalues)
+ abort ();
+
+ c->nvalues--;
+ c->values[c->nvalues] = 0;
+ c->elts[c->nvalues] = 0;
+}
+
+void
+counter_inc (COUNTER *c)
+{
+ c->values[c->nvalues - 1]++;
+}
+
+void
+counter_dec (COUNTER *c)
+{
+ c->values[c->nvalues - 1]--;
+}
+
+/* Return value of counter if the top counter is for element ELT, otherwise
+ return -1. */
+int
+counter_value (COUNTER *c, ELEMENT *elt)
+{
+ if (c->nvalues > 0 && c->elts[c->nvalues - 1] == elt)
+ return c->values[c->nvalues - 1];
+ else
+ return -1;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.h
new file mode 100644
index 0000000000..48c9bb21ec
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/counter.h
@@ -0,0 +1,28 @@
+/* counter.h - definitions for counter.c */
+/* Copyright 2015-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 <http://www.gnu.org/licenses/>. */
+
+typedef struct {
+ int *values; /* Array of values. */
+ ELEMENT **elts; /* Elements corresponding to each value. */
+ int nvalues;
+ int space;
+} COUNTER;
+
+void counter_push (COUNTER *c, ELEMENT *e, int n);
+void counter_pop (COUNTER *c);
+void counter_inc (COUNTER *c);
+void counter_dec (COUNTER *c);
+int counter_value (COUNTER *c, ELEMENT *e);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/debug.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/debug.c
new file mode 100644
index 0000000000..9f3577d520
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/debug.c
@@ -0,0 +1,46 @@
+/* Copyright 2014-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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "parser.h"
+
+/* Whether to dump debugging output on stderr. */
+int debug_output = 0;
+
+void
+debug (char *s, ...)
+{
+ va_list v;
+
+ if (!debug_output)
+ return;
+ va_start (v, s);
+ vfprintf (stderr, s, v);
+ fputc ('\n', stderr);
+}
+
+void
+debug_nonl (char *s, ...)
+{
+ va_list v;
+
+ if (!debug_output)
+ return;
+ va_start (v, s);
+ vfprintf (stderr, s, v);
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.c
new file mode 100644
index 0000000000..b285096b14
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.c
@@ -0,0 +1,409 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <string.h>
+
+#include "parser.h"
+#include "text.h"
+
+void
+gather_def_item (ELEMENT *current, enum command_id next_command)
+{
+ enum element_type type;
+ ELEMENT *def_item;
+ int contents_count, i;
+
+ if (next_command)
+ type = ET_inter_def_item; /* Between @def*x and @def*. */
+ else
+ type = ET_def_item;
+
+ if (!current->cmd)
+ return;
+
+ /* Check this isn't an "x" type command.
+ "This may happen for a construct like:
+ @deffnx a b @section
+ but otherwise the end of line will lead to the command closing." */
+ if (command_data(current->cmd).flags & CF_line)
+ return;
+
+ def_item = new_element (type);
+
+ /* Starting from the end, collect everything that is not a ET_def_line and
+ put it into the ET_def_item. */
+ contents_count = current->contents.number;
+ for (i = 0; i < contents_count; i++)
+ {
+ ELEMENT *last_child, *item_content;
+ last_child = last_contents_child (current);
+ if (last_child->type == ET_def_line)
+ break;
+ item_content = pop_element_from_contents (current);
+ insert_into_contents (def_item, item_content, 0);
+ }
+
+ if (def_item->contents.number > 0)
+ add_to_element_contents (current, def_item);
+ else
+ destroy_element (def_item);
+}
+
+
+/* Starting at I in the contents, return the next non-whitespace element,
+ incrementing I. Return null if no more elements. */
+ELEMENT *
+next_bracketed_or_word (ELEMENT *current, int *i)
+{
+ while (1)
+ {
+ if (*i == current->contents.number)
+ return 0;
+ if (current->contents.list[*i]->type != ET_spaces
+ && current->contents.list[*i]->type != ET_spaces_inserted
+ && current->contents.list[*i]->type != ET_spaces_at_end
+ && current->contents.list[*i]->type != ET_empty_spaces_after_command
+ && current->contents.list[*i]->type != ET_delimiter)
+ break;
+ (*i)++;
+ }
+ return current->contents.list[(*i)++];
+}
+
+ELEMENT *
+next_bracketed_or_word_agg (ELEMENT *current, int *i)
+{
+ int num = 0;
+ ELEMENT *new;
+ ELEMENT *e;
+ int j;
+ while (1)
+ {
+ if (*i == current->contents.number)
+ break;
+ e = current->contents.list[*i];
+ if (e->type == ET_spaces
+ || e->type == ET_spaces_inserted
+ || e->type == ET_spaces_at_end
+ || e->type == ET_empty_spaces_after_command
+ || e->type == ET_delimiter)
+ {
+ if (num > 0)
+ break;
+ else
+ {
+ (*i)++;
+ continue;
+ }
+ }
+ if (e->type == ET_bracketed_def_content
+ || e->type == ET_bracketed_inserted)
+ {
+ if (num > 0)
+ break;
+ else
+ {
+ (*i)++;
+ return e;
+ }
+ }
+ /* e is a text or command element */
+ (*i)++;
+ num++;
+ }
+ if (num == 0)
+ return 0;
+ if (num == 1)
+ return current->contents.list[*i - 1];
+
+ new = new_element (ET_def_aggregate);
+ for (j = 0; j < num; j++)
+ {
+ add_to_element_contents (new,
+ remove_from_contents (current, *i - num));
+ /* Note: if we did a lot of this could write a special
+ "splicing" function. */
+ }
+ insert_into_contents (current, new, *i - num);
+ *i = *i - num + 1;
+ return new;
+}
+
+typedef struct {
+ enum command_id alias;
+ enum command_id command;
+ char *category;
+} DEF_ALIAS;
+
+DEF_ALIAS def_aliases[] = {
+ CM_defun, CM_deffn, "Function",
+ CM_defmac, CM_deffn, "Macro",
+ CM_defspec, CM_deffn, "Special Form",
+ CM_defvar, CM_defvr, "Variable",
+ CM_defopt, CM_defvr, "User Option",
+ CM_deftypefun, CM_deftypefn, "Function",
+ CM_deftypevar, CM_deftypevr, "Variable",
+ CM_defivar, CM_defcv, "Instance Variable",
+ CM_deftypeivar, CM_deftypecv, "Instance Variable",
+ CM_defmethod, CM_defop, "Method",
+ CM_deftypemethod, CM_deftypeop, "Method",
+ 0, 0, 0
+};
+
+/* Split non-space text elements into strings without [ ] ( ) , and single
+ character strings with one of them. */
+static void
+split_delimiters (ELEMENT *current, int starting_idx)
+{
+ int i;
+ static char *chars = "[](),";
+ for (i = starting_idx; i < current->contents.number; i++)
+ {
+ ELEMENT *e = current->contents.list[i];
+ char *p;
+ ELEMENT *new;
+ int len;
+
+ if (e->type != ET_NONE
+ || e->text.end == 0)
+ continue;
+ if (e->type == ET_empty_spaces_after_command)
+ continue;
+ p = e->text.text;
+
+ while (1)
+ {
+ if (strchr (chars, *p))
+ {
+ new = new_element (ET_delimiter);
+ text_append_n (&new->text, p, 1);
+ insert_into_contents (current, new, i++);
+ add_extra_string_dup (new, "def_role", "delimiter");
+ if (!*++p)
+ break;
+ continue;
+ }
+
+ len = strcspn (p, chars);
+ new = new_element (ET_NONE);
+ text_append_n (&new->text, p, len);
+ insert_into_contents (current, new, i++);
+ if (!*(p += len))
+ break;
+ }
+ destroy_element (remove_from_contents (current, i--));
+ }
+}
+
+
+/* Divide any text elements into separate elements, separating whitespace
+ and non-whitespace. Change ET_bracketed elements to
+ ET_bracketed_def_content. */
+static void
+split_def_args (ELEMENT *current, int starting_idx)
+{
+ int i;
+ for (i = starting_idx; i < current->contents.number; i++)
+ {
+ ELEMENT *e = current->contents.list[i];
+ char *p;
+ ELEMENT *new;
+ int len;
+ if (e->type == ET_bracketed)
+ {
+ isolate_last_space (e);
+ e->type = ET_bracketed_def_content;
+ continue;
+ }
+ if (e->text.end == 0)
+ continue;
+ p = e->text.text;
+
+ while (1)
+ {
+ len = strspn (p, whitespace_chars);
+ if (len)
+ {
+ new = new_element (ET_spaces);
+ text_append_n (&new->text, p, len);
+ insert_into_contents (current, new, i++);
+ add_extra_string_dup (new, "def_role", "spaces");
+ if (!*(p += len))
+ {
+ if (new->text.end > 0
+ && new->text.text[new->text.end - 1] == '\n')
+ new->type = ET_spaces_at_end;
+ break;
+ }
+ }
+
+ len = strcspn (p, whitespace_chars);
+ new = new_element (ET_NONE);
+ text_append_n (&new->text, p, len);
+ insert_into_contents (current, new, i++);
+ if (!*(p += len))
+ break;
+
+ }
+ destroy_element (remove_from_contents (current, i--));
+ }
+}
+
+DEF_INFO *
+parse_def (enum command_id command, ELEMENT *current)
+{
+ DEF_INFO *ret;
+ int contents_idx = 0;
+ int type, next_type;
+ int i;
+ ELEMENT *e, *e1;
+
+ ret = malloc (sizeof (DEF_INFO));
+ memset (ret, 0, sizeof (DEF_INFO));
+
+ if (current->contents.number > 0
+ && (current->contents.list[0]->type == ET_empty_spaces_after_command
+ || current->contents.list[0]->type == ET_empty_line_after_command))
+ contents_idx++;
+
+ split_def_args (current, contents_idx);
+
+ /* Check for "def alias" - for example @defun for @deffn. */
+ if (command_data(command).flags & CF_def_alias)
+ {
+ char *category;
+ int i;
+ for (i = 0; i < sizeof (def_aliases) / sizeof (*def_aliases); i++)
+ {
+ if (def_aliases[i].alias == command)
+ goto found;
+ }
+ abort (); /* Has CF_def_alias but no alias defined. */
+found:
+ /* Prepended content is stuck into contents, so
+ @defun is converted into
+ @deffn Function */
+
+ category = def_aliases[i].category;
+ command = def_aliases[i].command;
+
+ /* Used when category text has a space in it. */
+ e = new_element (ET_bracketed_inserted);
+ insert_into_contents (current, e, contents_idx);
+ e1 = new_element (ET_NONE);
+ text_append_n (&e1->text, category, strlen (category));
+ add_to_element_contents (e, e1);
+ if (global_documentlanguage && *global_documentlanguage)
+ {
+ e1->type = ET_untranslated;
+ add_extra_string_dup (e1, "documentlanguage",
+ global_documentlanguage);
+ }
+
+ e = new_element (ET_spaces_inserted);
+ text_append_n (&e->text, " ", 1);
+ add_extra_string_dup (e, "def_role", "spaces");
+ insert_into_contents (current, e, contents_idx + 1);
+ }
+
+ /* Read arguments as CATEGORY [CLASS] [TYPE] NAME [ARGUMENTS].
+
+ Meaning of these:
+ CATEGORY - type of entity, e.g. "Function"
+ CLASS - class for object-oriented programming
+ TYPE - data type of a variable or function return value
+ NAME - name of entity being documented
+ ARGUMENTS - arguments to a function or macro */
+
+ /* CATEGORY */
+ ret->category = next_bracketed_or_word_agg (current, &contents_idx);
+
+ /* CLASS */
+ if (command == CM_deftypeop
+ || command == CM_defcv
+ || command == CM_deftypecv
+ || command == CM_defop)
+ {
+ ret->class = next_bracketed_or_word_agg (current, &contents_idx);
+ }
+
+ /* TYPE */
+ if (command == CM_deftypefn
+ || command == CM_deftypeop
+ || command == CM_deftypevr
+ || command == CM_deftypecv)
+ {
+ ret->type = next_bracketed_or_word_agg (current, &contents_idx);
+ }
+
+ /* NAME */
+ ret->name = next_bracketed_or_word_agg (current, &contents_idx);
+
+ if (ret->category)
+ {
+ add_extra_string_dup (ret->category, "def_role", "category");
+ }
+ if (ret->class)
+ {
+ add_extra_string_dup (ret->class, "def_role", "class");
+ }
+ if (ret->type)
+ {
+ add_extra_string_dup (ret->type, "def_role", "type");
+ }
+ if (ret->name)
+ {
+ add_extra_string_dup (ret->name, "def_role", "name");
+ }
+
+ /* Process args */
+ split_delimiters (current, contents_idx);
+
+ /* For some commands, alternate between "arg" and "typearg". This matters for
+ the DocBook output. */
+ if (command == CM_deftypefn || command == CM_deftypeop
+ || command == CM_deftp)
+ next_type = -1;
+ else
+ next_type = 1;
+
+ type = next_type;
+ for (i = contents_idx; i < current->contents.number; i++)
+ {
+ e = contents_child_by_index (current, i);
+ if (e->type == ET_spaces
+ || e->type == ET_spaces_inserted
+ || e->type == ET_spaces_at_end)
+ {
+ continue;
+ }
+ if (e->type == ET_delimiter)
+ {
+ type = next_type;
+ continue;
+ }
+ if (e->cmd && e->cmd != CM_code)
+ {
+ add_extra_string_dup (e, "def_role", "arg");
+ type = next_type;
+ continue;
+ }
+ add_extra_string_dup (e, "def_role",
+ (type == 1 ? "arg" : "typearg"));
+ type *= next_type;
+ }
+ return ret;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.h
new file mode 100644
index 0000000000..13e93ed51e
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/def.h
@@ -0,0 +1,3 @@
+/* def.h - definitions for def.c */
+void gather_def_item (ELEMENT *current, enum command_id next_command);
+DEF_INFO *parse_def (enum command_id command, ELEMENT *current);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.awk b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.awk
new file mode 100644
index 0000000000..5c2c062c50
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.awk
@@ -0,0 +1,48 @@
+# Copyright 2014-2018 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License,
+# or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+BEGIN {
+ if (srcdir == "") {
+ srcdir = "."
+ }
+ ETH = srcdir "/element_types.h"
+ ETC = srcdir "/element_types.c"
+
+ print "/* This file automatically generated by element_types.awk */" > ETH
+ print "enum element_type {" > ETH
+ print "ET_NONE," > ETH
+}
+
+!/^$/ && !/^#/ {
+ print "ET_" $1 "," > ETH
+ array = array "\"" $1 "\",\n"
+}
+
+END {
+ print "};" > ETH
+ print > ETH
+ print "extern char *element_type_names[];" > ETH
+
+ print "char *element_type_names[] = {" > ETC
+ print "0," > ETC
+ print array > ETC
+ print "};" > ETC
+}
+
+function output_array (string)
+{
+ print string > ETC
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.c
new file mode 100644
index 0000000000..d76fcb481a
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.c
@@ -0,0 +1,70 @@
+char *element_type_names[] = {
+0,
+"command_as_argument",
+"command_as_argument_inserted",
+"index_entry_command",
+"following_arg",
+"space_command_arg",
+"definfoenclose_command",
+"empty_line",
+"raw",
+"last_raw_newline",
+"empty_line_after_command",
+"empty_spaces_after_command",
+"spaces_at_end",
+"empty_space_at_end_def_bracketed",
+"space_at_end_block_command",
+"empty_spaces_before_argument",
+"empty_spaces_after_close_brace",
+"empty_spaces_before_paragraph",
+"preamble_text",
+"space_at_end_menu_node",
+"after_description_line",
+"spaces",
+"spaces_inserted",
+"text_root",
+"document_root",
+"root_line",
+"preamble",
+"preamble_before_setfilename",
+"paragraph",
+"preformatted",
+"rawpreformatted",
+"brace_command_arg",
+"brace_command_context",
+"block_line_arg",
+"line_arg",
+"misc_arg",
+"menu_entry",
+"menu_entry_leading_text",
+"menu_entry_name",
+"menu_entry_separator",
+"menu_entry_node",
+"menu_entry_description",
+"menu_comment",
+"menu_star",
+"macro_name",
+"macro_arg",
+"before_item",
+"table_entry",
+"table_term",
+"table_item",
+"inter_item",
+"def_line",
+"def_item",
+"inter_def_item",
+"multitable_head",
+"multitable_body",
+"row",
+"bracketed",
+"bracketed_def_content",
+"def_aggregate",
+"bracketed_inserted",
+"bracketed_multitable_prototype",
+"row_prototype",
+"elided",
+"elided_block",
+"delimiter",
+"untranslated",
+
+};
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.h
new file mode 100644
index 0000000000..32ab38121f
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.h
@@ -0,0 +1,72 @@
+/* This file automatically generated by element_types.awk */
+enum element_type {
+ET_NONE,
+ET_command_as_argument,
+ET_command_as_argument_inserted,
+ET_index_entry_command,
+ET_following_arg,
+ET_space_command_arg,
+ET_definfoenclose_command,
+ET_empty_line,
+ET_raw,
+ET_last_raw_newline,
+ET_empty_line_after_command,
+ET_empty_spaces_after_command,
+ET_spaces_at_end,
+ET_empty_space_at_end_def_bracketed,
+ET_space_at_end_block_command,
+ET_empty_spaces_before_argument,
+ET_empty_spaces_after_close_brace,
+ET_empty_spaces_before_paragraph,
+ET_preamble_text,
+ET_space_at_end_menu_node,
+ET_after_description_line,
+ET_spaces,
+ET_spaces_inserted,
+ET_text_root,
+ET_document_root,
+ET_root_line,
+ET_preamble,
+ET_preamble_before_setfilename,
+ET_paragraph,
+ET_preformatted,
+ET_rawpreformatted,
+ET_brace_command_arg,
+ET_brace_command_context,
+ET_block_line_arg,
+ET_line_arg,
+ET_misc_arg,
+ET_menu_entry,
+ET_menu_entry_leading_text,
+ET_menu_entry_name,
+ET_menu_entry_separator,
+ET_menu_entry_node,
+ET_menu_entry_description,
+ET_menu_comment,
+ET_menu_star,
+ET_macro_name,
+ET_macro_arg,
+ET_before_item,
+ET_table_entry,
+ET_table_term,
+ET_table_item,
+ET_inter_item,
+ET_def_line,
+ET_def_item,
+ET_inter_def_item,
+ET_multitable_head,
+ET_multitable_body,
+ET_row,
+ET_bracketed,
+ET_bracketed_def_content,
+ET_def_aggregate,
+ET_bracketed_inserted,
+ET_bracketed_multitable_prototype,
+ET_row_prototype,
+ET_elided,
+ET_elided_block,
+ET_delimiter,
+ET_untranslated,
+};
+
+extern char *element_type_names[];
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.txt b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.txt
new file mode 100644
index 0000000000..68764f6af1
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/element_types.txt
@@ -0,0 +1,98 @@
+# Copyright 2010-2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Texinfo.
+#
+# GNU Texinfo is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License,
+# or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Types for @-commands
+command_as_argument
+command_as_argument_inserted
+index_entry_command
+following_arg
+space_command_arg
+definfoenclose_command
+
+# For text elements
+empty_line
+raw
+last_raw_newline
+empty_line_after_command
+empty_spaces_after_command
+spaces_at_end
+empty_space_at_end_def_bracketed
+space_at_end_block_command
+empty_spaces_before_argument
+empty_spaces_after_close_brace
+empty_spaces_before_paragraph
+preamble_text
+space_at_end_menu_node
+after_description_line
+spaces
+spaces_inserted
+
+# Other special types
+text_root
+document_root
+root_line
+preamble
+preamble_before_setfilename
+paragraph
+preformatted
+rawpreformatted
+brace_command_arg
+brace_command_context
+block_line_arg
+line_arg
+misc_arg
+
+menu_entry
+menu_entry_leading_text
+menu_entry_name
+menu_entry_separator
+menu_entry_node
+menu_entry_description
+menu_comment
+# Used internally
+menu_star
+
+macro_name
+macro_arg
+before_item
+table_entry
+table_term
+table_item
+inter_item
+def_line
+def_item
+inter_def_item
+multitable_head
+multitable_body
+row
+bracketed
+bracketed_def_content
+def_aggregate
+bracketed_inserted
+bracketed_multitable_prototype
+row_prototype
+
+# for unexpanded parts of conditionals
+elided
+elided_block
+
+# for def commands
+delimiter
+
+# for inserted text in def commands
+untranslated
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/end_line.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/end_line.c
new file mode 100644
index 0000000000..d16616e7af
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/end_line.c
@@ -0,0 +1,2277 @@
+/* end_line.c -- what to do at the end of a whole line of input */
+/* Copyright 2010-2019 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "parser.h"
+#include "text.h"
+#include "input.h"
+#include "convert.h"
+#include "labels.h"
+#include "indices.h"
+
+void
+check_internal_node (NODE_SPEC_EXTRA *nse)
+{
+ if (nse && nse->manual_content
+ && nse->manual_content->contents.number > 0)
+ {
+ char *texi = node_extra_to_texi (nse);
+ line_error ("syntax for an external node used for `%s'", texi);
+ free (texi);
+ }
+}
+
+int
+check_empty_node (NODE_SPEC_EXTRA *nse,
+ enum command_id cmd)
+{
+ if (!nse || !nse->node_content || nse->node_content->contents.number == 0)
+ {
+ line_error ("empty argument in @%s", command_name(cmd));
+ return 0;
+ }
+ else
+ return 1;
+}
+
+int
+check_node_label (NODE_SPEC_EXTRA *nse,
+ enum command_id cmd)
+{
+ check_internal_node (nse);
+ return check_empty_node (nse, cmd);
+}
+
+static int
+is_decimal_number (char *string)
+{
+ char *p = string;
+ char *first_digits = 0;
+ char *second_digits = 0;
+
+ if (string[0] == '\0')
+ return 0;
+
+ if (strchr (digit_chars, *p))
+ p = first_digits = string + strspn (string, digit_chars);
+
+ if (*p == '.')
+ {
+ p++;
+ if (strchr (digit_chars, *p))
+ p = second_digits = p + strspn (p, digit_chars);
+ }
+
+ if (*p /* Bytes remaining at end of argument. */
+ || (!first_digits && !second_digits)) /* Need digits either
+ before or after the
+ decimal point. */
+ {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+is_whole_number (char *string)
+{
+ if (string[strspn (string, digit_chars)] == '\0')
+ return 1;
+ return 0;
+}
+
+/* Return end of argument before comment and whitespace. */
+char *
+skip_comment (char *q, int *has_comment)
+{
+ char *q1;
+ while (1)
+ {
+ q1 = strstr (q, "@c");
+ if (!q1)
+ {
+ q = q + strlen (q);
+ break;
+ }
+ q = q1 + 2;
+ if (!memcmp (q, "omment", 6))
+ q += 6;
+
+ /* TeX control sequence name ends at an escape character or
+ whitespace. */
+ if (*q == '@' || strchr (whitespace_chars, *q))
+ {
+ q = q1;
+ *has_comment = 1;
+ break;
+ }
+ }
+
+ /* q is now either at the end of the string, or at the start of a comment.
+ Find the start of any trailing whitespace. */
+ while (strchr (whitespace_chars, q[-1]))
+ q--;
+
+ return q;
+}
+
+/* Process argument to special line command. */
+ELEMENT *
+parse_special_misc_command (char *line, enum command_id cmd, int *has_comment)
+{
+#define ADD_ARG(string, len) do { \
+ ELEMENT *E = new_element (ET_NONE); \
+ text_append_n (&E->text, string, len); \
+ add_to_element_contents (args, E); \
+} while (0)
+
+ ELEMENT *args = new_element (ET_NONE);
+ char *p = 0, *q = 0, *r = 0;
+ char *value = 0, *remaining = 0;;
+
+ switch (cmd)
+ {
+ case CM_set:
+ {
+ p = line;
+ p += strspn (p, whitespace_chars);
+ if (!*p)
+ goto set_no_name;
+ if (!isalnum (*p) && *p != '-' && *p != '_')
+ goto set_invalid;
+ q = strpbrk (p,
+ " \t\f\r\n" /* whitespace */
+ "{\\}~^+\"<>|@"); /* other bytes that aren't allowed */
+
+ r = skip_comment (p, has_comment);
+
+ if (!strchr (whitespace_chars, *q) && *q != '@')
+ goto set_invalid;
+
+ if (*q == '@')
+ {
+ /* Check for a comment, e.g. "@set flag@c comment" */
+ if (q != r)
+ goto set_invalid;
+ }
+
+ ADD_ARG(p, q - p); /* name */
+
+ p = q + strspn (q, whitespace_chars);
+ /* Actually, whitespace characters except form feed. */
+
+ if (r >= p)
+ ADD_ARG(p, r - p); /* value */
+ else
+ ADD_ARG("", 0);
+
+ store_value (args->contents.list[0]->text.text,
+ args->contents.list[1]->text.text);
+
+ break;
+set_no_name:
+ line_error ("@set requires a name");
+ break;
+set_invalid:
+ line_error ("bad name for @set");
+ break;
+ }
+ case CM_clear:
+ {
+ char *flag;
+ p = line;
+ p += strspn (p, whitespace_chars);
+ if (!*p)
+ goto clear_no_name;
+ q = p;
+ flag = read_command_name (&q);
+ if (!flag)
+ goto clear_invalid;
+ free (flag);
+ r = q + strspn (q, whitespace_chars);
+ if (*r)
+ goto clear_invalid; /* Trailing argument. */
+
+ ADD_ARG (p, q - p);
+ clear_value (p, q - p);
+
+ break;
+clear_no_name:
+ line_error ("@clear requires a name");
+ break;
+clear_invalid:
+ line_error ("bad name for @clear");
+ break;
+ }
+ case CM_unmacro:
+ p = line;
+ p += strspn (p, whitespace_chars);
+ if (!*p)
+ goto unmacro_noname;
+ q = p;
+ value = read_command_name (&q);
+ if (!value)
+ goto unmacro_badname;
+ /* FIXME: Check comment syntax is right */
+ delete_macro (value);
+ ADD_ARG(value, q - p);
+ debug ("UNMACRO %s", value);
+ free (value);
+ break;
+unmacro_noname:
+ line_error ("@unmacro requires a name");
+ break;
+unmacro_badname:
+ line_error ("bad name for @unmacro");
+ break;
+ case CM_clickstyle:
+ p = line;
+ p += strspn (p, whitespace_chars);
+ if (*p++ != '@')
+ goto clickstyle_invalid;
+ q = p;
+ value = read_command_name (&q);
+ if (!value)
+ goto clickstyle_invalid;
+ ADD_ARG (p - 1, q - p + 1);
+ free (global_clickstyle); global_clickstyle = value;
+ if (!memcmp (q, "{}", 2))
+ q += 2;
+ remaining = q;
+ /* FIXME: check comment */
+ break;
+clickstyle_invalid:
+ line_error ("@clickstyle should only accept an @-command as argument, "
+ "not `%s'", line);
+ free (value);
+ break;
+ default:
+ abort ();
+ }
+
+ if (remaining)
+ {
+ remaining += strspn (remaining, whitespace_chars);
+ if (*remaining)
+ {
+ line_warn ("remaining argument on @%s line: %s",
+ command_name(cmd), remaining);
+ }
+ }
+ return args;
+#undef ADD_ARG
+}
+
+/* Parse the arguments to a line command. Return an element whose contents
+ is an array of the arguments. For some commands, there is further
+ processing of the arguments (for example, for an @alias, remember the
+ alias.) */
+ELEMENT *
+parse_line_command_args (ELEMENT *line_command)
+{
+#define ADD_ARG(string) do { \
+ ELEMENT *E = new_element (ET_NONE); \
+ text_append (&E->text, string); \
+ add_to_element_contents (line_args, E); \
+} while (0)
+
+ ELEMENT *arg = line_command->args.list[0];
+ ELEMENT *line_args;
+ enum command_id cmd;
+ char *line;
+
+ cmd = line_command->cmd;
+ if (arg->contents.number == 0)
+ {
+ command_error (line_command, "@%s missing argument", command_name(cmd));
+ add_extra_integer (line_command, "missing_argument", 1);
+ return 0;
+ }
+
+ if (arg->contents.number > 1 || arg->contents.list[0]->text.end == 0)
+ {
+ line_error ("superfluous argument to @%s", command_name (cmd));
+ }
+ if (arg->contents.list[0]->text.end == 0)
+ return 0;
+
+ line_args = new_element (ET_NONE);
+ line = arg->contents.list[0]->text.text;
+
+ switch (cmd)
+ {
+ case CM_alias:
+ {
+ /* @alias NEW = EXISTING */
+ char *new = 0, *existing = 0;
+ enum command_id new_cmd, existing_cmd;
+
+ new = read_command_name (&line);
+ if (!new)
+ goto alias_invalid;
+
+ line += strspn (line, whitespace_chars);
+ if (*line != '=')
+ goto alias_invalid;
+ line++;
+ line += strspn (line, whitespace_chars);
+
+ if (!isalnum (*line))
+ goto alias_invalid;
+ existing = read_command_name (&line);
+ if (!existing)
+ goto alias_invalid;
+
+ if (*line)
+ goto alias_invalid; /* Trailing argument. */
+
+ ADD_ARG(new);
+ ADD_ARG(existing);
+
+ existing_cmd = lookup_command (existing);
+ if (!existing_cmd)
+ break; /* TODO: Error message */
+ else
+ {
+ if (command_data(existing_cmd).flags & CF_block)
+ line_warn ("environment command %s as argument to @alias",
+ command_name(existing_cmd));
+ }
+
+ /* Remember the alias. */
+ new_cmd = add_texinfo_command (new);
+ new_cmd &= ~USER_COMMAND_BIT;
+ user_defined_command_data[new_cmd].flags |= CF_ALIAS;
+
+ user_defined_command_data[new_cmd].data = existing_cmd;
+ /* Note the data field is an int, existing_cmd is
+ enum command_id, so would have problems if enum command_id
+ were wider than an int. */
+
+ free (new); free (existing);
+
+ break;
+ alias_invalid:
+ line_error ("bad argument to @alias");
+ free (new); free (existing);
+ break;
+ }
+ case CM_definfoenclose:
+ {
+ /* @definfoenclose phoo,//,\\ */
+ char *new_command = 0, *start = 0, *end = 0;
+ enum command_id new_cmd;
+ int len;
+
+ new_command = read_command_name (&line);
+ if (!new_command)
+ goto definfoenclose_invalid;
+
+ line += strspn (line, whitespace_chars);
+ if (*line != ',')
+ goto definfoenclose_invalid;
+ line++;
+ line += strspn (line, whitespace_chars);
+
+ /* TODO: Can we have spaces in the delimiters? */
+ len = strcspn (line, ",");
+ start = strndup (line, len);
+ line += len;
+
+ if (!*line)
+ goto definfoenclose_invalid; /* Not enough args. */
+ line++; /* Past ','. */
+ line += strspn (line, whitespace_chars);
+ len = strcspn (line, ",");
+ end = strndup (line, len);
+
+ if (*line == ',')
+ goto definfoenclose_invalid; /* Too many args. */
+
+ /* Remember it. */
+ new_cmd = add_texinfo_command (new_command);
+ add_infoenclose (new_cmd, start, end);
+ new_cmd &= ~USER_COMMAND_BIT;
+
+ user_defined_command_data[new_cmd].flags
+ |= (CF_INFOENCLOSE | CF_brace);
+ user_defined_command_data[new_cmd].data = BRACE_style;
+
+ ADD_ARG(new_command); free (new_command);
+ ADD_ARG(start); free (start);
+ ADD_ARG(end); free (end);
+
+ break;
+ definfoenclose_invalid:
+ line_error ("bad argument to @definfoenclose");
+ free (new_command); free (start); free (end);
+ break;
+ }
+ case CM_columnfractions:
+ {
+ /* @multitable @columnfractions .33 .33 .33 */
+ ELEMENT *new;
+ char *p, *q;
+
+ if (!*line)
+ {
+ line_error ("empty @columnfractions");
+ break;
+ }
+ p = line;
+ while (1)
+ {
+ char *arg;
+
+ p += strspn (p, whitespace_chars);
+ if (!*p)
+ break;
+ q = strpbrk (p, whitespace_chars);
+ if (!q)
+ q = p + strlen (p);
+
+ arg = strndup (p, q - p);
+
+ /* Check argument is valid. */
+ if (!is_decimal_number (arg))
+ {
+ line_error ("column fraction not a number: %s", arg);
+ }
+ else
+ {
+ new = new_element (ET_NONE);
+ text_append_n (&new->text, p, q - p);
+ add_to_element_contents (line_args, new);
+ }
+ free (arg);
+ p = q;
+ }
+ break;
+ }
+ case CM_sp:
+ {
+ /* Argument is at least one digit. */
+ if (strchr (digit_chars, *line)
+ && !*(line + 1 + strspn (line + 1, digit_chars)))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("@sp arg must be numeric, not `%s'", line);
+ break;
+ }
+ case CM_defindex:
+ case CM_defcodeindex:
+ {
+ char *name = 0;
+ char *p = line;
+
+ name = read_command_name (&p);
+ if (*p)
+ goto defindex_invalid; /* Trailing characters. */
+
+ /* Disallow index names NAME where it is likely that for
+ a source file BASE.texi, there will be other files called
+ BASE.NAME in the same directory. This is to prevent such
+ files being overwritten by the files read by texindex. */
+ {
+ static char *forbidden_index_names[] = {
+ "info", "ps", "pdf", "htm", "html",
+ "log", "aux", "dvi", "texi", "txi",
+ "texinfo", "tex", "bib", 0
+ };
+ char **ptr;
+ for (ptr = forbidden_index_names; *ptr; ptr++)
+ if (!strcmp (name, *ptr))
+ goto defindex_reserved;
+
+ if (index_by_name (name))
+ goto defindex_reserved;
+ }
+
+ add_index (name, cmd == CM_defcodeindex ? 1 : 0);
+ ADD_ARG(name);
+
+ break;
+ defindex_invalid:
+ line_error ("bad argument to @%s: %s",
+ command_name(cmd), line);
+ free (name);
+ break;
+ defindex_reserved:
+ line_error ("reserved index name %s", name);
+ free (name);
+ break;
+ }
+ case CM_synindex:
+ case CM_syncodeindex:
+ {
+ /* synindex FROM TO */
+ char *from = 0, *to = 0;
+ INDEX *from_index, *to_index;
+ char *p = line;
+
+ if (!isalnum (*p))
+ goto synindex_invalid;
+ from = read_command_name (&p);
+ if (!from)
+ goto synindex_invalid;
+
+ p += strspn (p, whitespace_chars);
+
+ if (!isalnum (*p))
+ goto synindex_invalid;
+ to = read_command_name (&p);
+ if (!to)
+ goto synindex_invalid;
+ if (*p)
+ goto synindex_invalid; /* More at end of line. */
+
+ from_index = index_by_name (from);
+ to_index = index_by_name (to);
+ if (!from_index)
+ line_error ("unknown source index in @%s: %s",
+ command_name(cmd), from);
+ if (!to_index)
+ line_error ("unknown destination index in @%s: %s",
+ command_name(cmd), to);
+
+ if (from_index && to_index)
+ {
+ INDEX *current_to = to_index;
+ /* Find ultimate index this merges to. */
+ current_to = ultimate_index (current_to);
+
+ if (current_to != from_index)
+ {
+ from_index->merged_in = current_to;
+ from_index->in_code = (cmd == CM_syncodeindex);
+ ADD_ARG(from);
+ ADD_ARG(to);
+ /* Note that 'current_to' may not end up as the index
+ 'from_index' merges into if there are further @synindex
+ commands. */
+ }
+ else
+ line_warn ("@%s leads to a merging of %s in itself, ignoring",
+ command_name(cmd), from);
+ }
+
+ free (from);
+ free (to);
+
+ break;
+ synindex_invalid:
+ line_error ("bad argument to @%s: %s",
+ command_name(cmd), line);
+ free (from); free (to);
+ break;
+ }
+ case CM_printindex:
+ {
+ char *arg;
+ char *p = line;
+ arg = read_command_name (&p);
+ if (!arg || *p)
+ line_error ("bad argument to @printindex: %s", line);
+ else
+ {
+ INDEX *idx = index_by_name (arg);
+ if (!idx)
+ line_error ("unknown index `%s' in @printindex", arg);
+ else
+ {
+ if (idx->merged_in)
+ {
+ INDEX *i2;
+ for (i2 = idx; (i2->merged_in); i2 = i2->merged_in)
+ ;
+ line_warn
+ ("printing an index `%s' merged in another one, `%s'",
+ arg, i2->name);
+ }
+ if (!current_node && !current_section && !current_region ())
+ {
+ line_warn ("printindex before document beginning: "
+ "@printindex %s", arg);
+ }
+ ADD_ARG (arg);
+ }
+ }
+ free (arg);
+ break;
+ }
+ case CM_everyheadingmarks:
+ case CM_everyfootingmarks:
+ case CM_evenheadingmarks:
+ case CM_oddheadingmarks:
+ case CM_evenfootingmarks:
+ case CM_oddfootingmarks:
+ {
+ if (!strcmp (line, "top") || !strcmp (line, "bottom"))
+ {
+ ADD_ARG (line);
+ }
+ else
+ line_error ("@%s arg must be `top' or `bottom', not `%s'",
+ command_name(cmd), line);
+
+ break;
+ }
+ case CM_fonttextsize:
+ {
+ if (!strcmp (line, "10") || !strcmp (line, "11"))
+ {
+ ADD_ARG (line);
+ }
+ else
+ line_error ("Only @fonttextsize 10 or 11 is supported, not "
+ "`%s'", line);
+ break;
+ }
+ case CM_footnotestyle:
+ {
+ if (!strcmp (line, "separate") || !strcmp (line, "end"))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("@footnotestyle arg must be "
+ "`separate' or `end', not `%s'", line);
+ break;
+ }
+ case CM_setchapternewpage:
+ {
+ if (!strcmp (line, "on") || !strcmp (line, "off")
+ || !strcmp (line, "odd"))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("@setchapternewpage arg must be "
+ "`on', `off' or `odd', not `%s'", line);
+ break;
+ }
+ case CM_need:
+ {
+ /* valid: 2, 2., .2, 2.2 */
+
+ if (is_decimal_number (line))
+ ADD_ARG(line);
+ else
+ line_error ("bad argument to @need: %s", line);
+
+ break;
+ }
+ case CM_paragraphindent:
+ {
+ if (!strcmp (line, "none") || !strcmp (line, "asis")
+ || is_whole_number (line))
+ ADD_ARG(line);
+ else
+ line_error ("@paragraphindent arg must be "
+ "numeric/`none'/`asis', not `%s'", line);
+ break;
+ }
+ case CM_firstparagraphindent:
+ {
+ if (!strcmp (line, "none") || !strcmp (line, "insert"))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("@firstparagraph arg must be "
+ "`none' or `insert', not `%s'", line);
+
+ break;
+ }
+ case CM_exampleindent:
+ {
+ if (!strcmp (line, "asis") || is_whole_number (line))
+ ADD_ARG(line);
+ else
+ line_error ("@exampleindent arg must be "
+ "numeric/`asis', not `%s'", line);
+ break;
+ }
+ case CM_frenchspacing:
+ case CM_xrefautomaticsectiontitle:
+ case CM_codequoteundirected:
+ case CM_codequotebacktick:
+ case CM_deftypefnnewline:
+ {
+ if (!strcmp (line, "on") || !strcmp (line, "off"))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("expected @%s on or off, not `%s'",
+ command_name(cmd), line);
+
+ break;
+ }
+ case CM_kbdinputstyle:
+ {
+ if (!strcmp (line, "code"))
+ global_kbdinputstyle = kbd_code;
+ else if (!strcmp (line, "example"))
+ global_kbdinputstyle = kbd_example;
+ else if (!strcmp (line, "distinct"))
+ global_kbdinputstyle = kbd_distinct;
+ else goto kdbinputstyle_invalid;
+
+ ADD_ARG(line);
+
+ if (0)
+ {
+kdbinputstyle_invalid:
+ line_error ("@kbdinputstyle arg must be "
+ "`code'/`example'/`distinct', not `%s'", line);
+ }
+ break;
+ }
+ case CM_allowcodebreaks:
+ {
+ if (!strcmp (line, "true") || !strcmp (line, "false"))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("@allowcodebreaks arg must be "
+ "`true' or `false', not `%s'", line);
+ break;
+ }
+ case CM_urefbreakstyle:
+ {
+ if (!strcmp (line, "after") || !strcmp (line, "before")
+ || !strcmp (line, "none"))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("@urefbreakstyle arg must be "
+ "`after'/`before'/`none', not `%s'", line);
+ break;
+ }
+ case CM_headings:
+ {
+ if (!strcmp (line, "off") || !strcmp (line, "on")
+ || !strcmp (line, "double") || !strcmp (line, "singleafter")
+ || !strcmp (line, "doubleafter"))
+ {
+ ADD_ARG(line);
+ }
+ else
+ line_error ("bad argument to @headings: %s", line);
+ break;
+ }
+ default:
+ ;
+ }
+ if (line_args->contents.number == 0)
+ {
+ destroy_element (line_args);
+ return 0;
+ }
+ else
+ return line_args;
+
+#undef ADD_ARG
+}
+
+/* NODE->contents is the Texinfo for the specification of a node. This
+ function sets three fields on the returned object:
+
+ manual_content - Texinfo tree for a manual name extracted from the
+ node specification.
+ node_content - Texinfo tree for the node name on its own
+ normalized - a string with the node name after HTML node name
+ normalization is applied
+
+ Objects returned from this function are used as an 'extra' key in a
+ few places: the elements of a 'nodes_manuals' array (itself an extra key),
+ the 'menu_entry_node' key on a 'menu_entry' element (not to be confused
+ with an ET_menu_entry_node element, which occurs in the args of a
+ 'menu_entry' element), and in the 'node_argument' key of a cross-reference
+ command (like @xref). */
+NODE_SPEC_EXTRA *
+parse_node_manual (ELEMENT *node)
+{
+ NODE_SPEC_EXTRA *result;
+ ELEMENT *new;
+ int idx = 0; /* index into node->contents */
+
+ result = malloc (sizeof (NODE_SPEC_EXTRA));
+ result->manual_content = result->node_content = 0;
+
+ /* If the content starts with a '(', try to get a manual name. */
+ if (node->contents.number > 0 && node->contents.list[0]->text.end > 0
+ && node->contents.list[0]->text.text[0] == '(')
+ {
+ ELEMENT *manual, *first;
+ char *opening_bracket, *closing_bracket;
+
+ /* Handle nested parentheses in the manual name, for whatever reason. */
+ int bracket_count = 1; /* Number of ( seen minus number of ) seen. */
+
+ manual = new_element (ET_NONE);
+
+ /* If the first contents element is "(" followed by more text, split
+ the leading "(" into its own element. */
+ first = node->contents.list[0];
+ if (first->text.end > 1)
+ {
+ memmove (first->text.text, first->text.text + 1, first->text.end);
+ first->text.end--;
+ new = new_element (0);
+ text_append_n (&new->text, "(", 1);
+ insert_into_contents (node, new, 0);
+ }
+ idx++;
+
+ for (; idx < node->contents.number; idx++)
+ {
+ ELEMENT *e = node->contents.list[idx];
+ char *p, *q;
+
+ if (e->text.end == 0)
+ {
+ /* Put this element in the manual contents. */
+ add_to_contents_as_array (manual, e);
+ continue;
+ }
+ p = e->text.text;
+ while (p < e->text.text + e->text.end
+ && bracket_count > 0)
+ {
+ opening_bracket = strchr (p, '(');
+ closing_bracket = strchr (p, ')');
+ if (!opening_bracket && !closing_bracket)
+ {
+ break;
+ }
+ else if (opening_bracket && !closing_bracket)
+ {
+ bracket_count++;
+ p = opening_bracket + 1;
+ }
+ else if (!opening_bracket && closing_bracket)
+ {
+ bracket_count--;
+ p = closing_bracket + 1;
+ }
+ else if (opening_bracket < closing_bracket)
+ {
+ bracket_count++;
+ p = opening_bracket + 1;
+ }
+ else if (opening_bracket > closing_bracket)
+ {
+ bracket_count--;
+ p = closing_bracket + 1;
+ }
+ }
+
+ if (bracket_count > 0)
+ add_to_contents_as_array (manual, e);
+ else /* end of filename component */
+ {
+ /* Split the element in two, putting the part before the ")"
+ in the manual name, leaving the part afterwards for the
+ node name. */
+ remove_from_contents (node, idx); /* Remove 'e'. */
+
+ p--; /* point at ) */
+ if (p > e->text.text)
+ {
+ /* text before ), part of the manual name */
+ new = new_element (ET_NONE);
+ text_append_n (&new->text, e->text.text,
+ p - e->text.text);
+ insert_into_contents (node, new, idx++);
+
+ add_to_contents_as_array (manual, new);
+ }
+
+ new = new_element (0);
+ text_append_n (&new->text, ")", 1);
+ insert_into_contents (node, new, idx++);
+
+ /* Skip ')' and any following whitespace.
+ Note that we don't manage to skip any multibyte
+ UTF-8 space characters here. */
+ p++;
+ q = p + strspn (p, whitespace_chars);
+ if (q > p)
+ {
+ new = new_element (0);
+ text_append_n (&new->text, p, q - p);
+ insert_into_contents (node, new, idx++);
+ }
+
+ p = q;
+ if (*p)
+ {
+ /* text after ), part of the node name. */
+ new = new_element (ET_NONE);
+ text_append_n (&new->text, p,
+ e->text.text + e->text.end - p);
+ insert_into_contents (node, new, idx);
+ }
+ destroy_element (e);
+ break;
+ }
+ } /* for */
+
+ if (bracket_count == 0)
+ result->manual_content = manual;
+ else /* unbalanced */
+ {
+ destroy_element (manual);
+ idx = 0; /* Back to the start, and consider the whole thing
+ as a node name. */
+ }
+ }
+
+ /* If anything left, it is the node name. */
+ if (idx < node->contents.number)
+ {
+ new = new_element (0);
+ insert_slice_into_contents (new, 0, node, idx, node->contents.number);
+ result->node_content = new;
+ }
+
+ return result;
+}
+
+/* Array of recorded @float's. */
+FLOAT_RECORD *floats_list = 0;
+size_t floats_number = 0;
+size_t floats_space = 0;
+
+int
+parse_float_type (ELEMENT *current)
+{
+ EXTRA_FLOAT_TYPE *eft;
+ eft = malloc (sizeof (EXTRA_FLOAT_TYPE));
+ eft->content = 0;
+ eft->normalized = 0;
+
+ if (current->args.number > 0)
+ {
+ if (current->args.list[0]->contents.number > 0)
+ {
+ char *normalized;
+ normalized = convert_to_texinfo (current->args.list[0]);
+ eft->content = current->args.list[0];
+ eft->normalized = normalized;
+
+ add_extra_float_type (current, "type", eft);
+ return 1;
+ }
+ }
+ eft->normalized = strdup ("");
+ add_extra_float_type (current, "type", eft);
+ return 0;
+}
+
+/* Actions to be taken at the end of a line that started a block that
+ has to be ended with "@end". */
+ELEMENT *
+end_line_starting_block (ELEMENT *current)
+{
+ enum context c;
+ c = pop_context ();
+ if (c != ct_line)
+ abort ();
+
+ if (current->parent->cmd == CM_multitable)
+ {
+ /* Parse prototype row for a @multitable. Handling
+ of @columnfractions is done elsewhere. */
+
+ int i;
+ ELEMENT *prototypes = new_element (ET_NONE);
+
+ for (i = 0; i < current->contents.number; i++)
+ {
+ ELEMENT *e = contents_child_by_index(current, i);
+
+ if (e->type == ET_bracketed)
+ {
+ /* Copy and change the type of the element. */
+
+ ELEMENT *new;
+ new = malloc (sizeof (ELEMENT));
+ memcpy (new, e, sizeof (ELEMENT));
+ new->type = ET_bracketed_multitable_prototype;
+ new->parent = 0;
+ new->extra_number = 0;
+ add_to_contents_as_array (prototypes, new);
+ }
+ else if (e->text.end > 0)
+ {
+ /* Split the text up by whitespace. */
+ char *p, *p2;
+ p = e->text.text;
+ while (1)
+ {
+ ELEMENT *new;
+ p2 = p + strspn (p, whitespace_chars);
+ if (!*p2)
+ break;
+ p = p2 + strcspn (p2, whitespace_chars);
+ new = new_element (ET_row_prototype);
+ text_append_n (&new->text, p2, p - p2);
+ add_to_contents_as_array (prototypes, new);
+ }
+ }
+ else
+ {
+ if (e->cmd != CM_c && e->cmd != CM_comment)
+ {
+ char *texi;
+ texi = convert_to_texinfo (e);
+ command_warn (current,
+ "unexpected argument on @%s line: %s",
+ command_name(current->parent->cmd),
+ texi);
+ free (texi);
+ }
+ }
+ }
+
+ {
+ int max_columns = prototypes->contents.number;
+ add_extra_integer (current->parent, "max_columns", max_columns);
+ if (max_columns == 0)
+ command_warn (current->parent, "empty multitable");
+ }
+ add_extra_contents_oot (current->parent, "prototypes", prototypes);
+ /* See code in destroy_element for how prototypes is deallocated. */
+ }
+ isolate_last_space (current);
+
+ current = current->parent;
+ if (counter_value (&count_remaining_args, current) != -1)
+ counter_pop (&count_remaining_args);
+
+ /* Don't consider empty argument of block @-command as argument,
+ reparent them as contents. */
+ if (current->args.list[0]->contents.number > 0
+ && current->args.list[0]->contents.list[0]->type
+ == ET_empty_line_after_command)
+ {
+ ELEMENT *e = remove_from_contents (current->args.list[0], 0);
+ insert_into_contents (current, e, 0);
+ destroy_element (pop_element_from_args (current));
+ }
+
+ if (current->cmd == CM_float)
+ {
+ char *type = "";
+ KEY_PAIR *k;
+ EXTRA_FLOAT_TYPE *eft;
+ current->line_nr = line_nr;
+ if (current->args.number >= 2)
+ {
+ NODE_SPEC_EXTRA *float_label;
+ float_label = parse_node_manual (args_child_by_index (current, 1));
+ check_internal_node (float_label);
+
+ register_label (current, float_label->node_content);
+ if (float_label->manual_content)
+ destroy_element (float_label->manual_content);
+ free (float_label);
+ }
+ parse_float_type (current);
+ k = lookup_extra (current, "type");
+ if (k)
+ {
+ eft = (EXTRA_FLOAT_TYPE *) k->value;
+ type = eft->normalized;
+ }
+ /* add to global 'floats' array */
+ if (floats_number == floats_space)
+ {
+ floats_list = realloc (floats_list,
+ (floats_space += 5) * sizeof (FLOAT_RECORD));
+ }
+ floats_list[floats_number].type = type;
+ floats_list[floats_number++].element = current;
+ if (current_section)
+ add_extra_element (current, "float_section", current_section);
+ }
+
+ if (command_flags(current) & CF_blockitem)
+ {
+ if (current->cmd == CM_enumerate)
+ {
+ char *spec = "1";
+
+ if (current->args.number > 0
+ && current->args.list[0]->contents.number > 0)
+ {
+ if (current->args.list[0]->contents.number > 1)
+ command_error (current, "superfluous argument to @%s",
+ command_name(current->cmd));
+ ELEMENT *g = current->args.list[0]->contents.list[0];
+ /* Check if @enumerate specification is either a single
+ letter or a string of digits. */
+ if (g->text.end == 1 && isalpha (g->text.text[0])
+ || (g->text.end > 0
+ && !*(g->text.text
+ + strspn (g->text.text, "0123456789"))))
+ {
+ spec = g->text.text;
+ }
+ else
+ command_error (current, "bad argument to @%s",
+ command_name(current->cmd));
+ }
+ add_extra_string_dup (current, "enumerate_specification", spec);
+ }
+ else if (item_line_command (current->cmd))
+ {
+ KEY_PAIR *k;
+ k = lookup_extra (current, "command_as_argument");
+ if (!k)
+ command_error (current,
+ "%s requires an argument: the formatter for @item",
+ command_name(current->cmd));
+ else
+ {
+ ELEMENT *e = k->value;
+ if (!(command_flags(e) & CF_brace)
+ || (command_data(e->cmd).data == 0))
+ {
+ command_error (current,
+ "command @%s not accepting argument in brace "
+ "should not be on @%s line",
+ command_name(e->cmd),
+ command_name(current->cmd));
+ k->key = "";
+ k->type = extra_deleted;
+ /* FIXME: Error message for accent commands is done
+ elsewhere (3040). */
+ }
+ }
+ }
+
+ /* check that command_as_argument of the @itemize is alone on the line,
+ otherwise it is not a command_as_argument */
+ if (current->cmd == CM_itemize)
+ {
+ KEY_PAIR *k;
+ k = lookup_extra (current, "command_as_argument");
+ if (k)
+ {
+ int i;
+ ELEMENT *e = args_child_by_index (current, 0);
+
+ for (i = 0; i < e->contents.number; i++)
+ {
+ if (contents_child_by_index (e, i) == k->value)
+ {
+ i++;
+ break;
+ }
+ }
+ for (; i < e->contents.number; i++)
+ {
+ ELEMENT *f = contents_child_by_index (e, i);
+ if (f->cmd != CM_c
+ && f->cmd != CM_comment
+ && !(f->text.end > 0
+ && !*(f->text.text
+ + strspn (f->text.text, whitespace_chars))))
+ {
+ k->value->type = ET_NONE;
+ k->key = "";
+ k->type = extra_deleted;
+ break;
+ }
+ }
+ }
+ }
+
+ // Check if command_as_argument isn't an accent command
+ if (current->cmd == CM_itemize || item_line_command(current->cmd))
+ {
+ KEY_PAIR *k = lookup_extra (current, "command_as_argument");
+ if (k && k->value)
+ {
+ enum command_id cmd = k->value->cmd;
+ if (cmd && (command_data(cmd).flags & CF_accent))
+ {
+ command_warn (current, "accent command `@%s' "
+ "not allowed as @%s argument",
+ command_name(cmd),
+ command_name(current->cmd));
+ k->key = "";
+ k->value = 0;
+ k->type = extra_deleted;
+ }
+ }
+ }
+
+ /* if no command_as_argument given, default to @bullet for
+ @itemize, and @asis for @table. */
+ if (current->cmd == CM_itemize
+ && (current->args.number == 0
+ || current->args.list[0]->contents.number == 0))
+ {
+ ELEMENT *e;
+
+ e = new_element (ET_command_as_argument_inserted);
+ e->cmd = CM_bullet;
+ insert_into_args (current, e, 0);
+ add_extra_element (current, "command_as_argument", e);
+ }
+ else if (item_line_command (current->cmd)
+ && !lookup_extra (current, "command_as_argument"))
+ {
+ ELEMENT *e;
+
+ e = new_element (ET_command_as_argument_inserted);
+ e->cmd = CM_asis;
+ insert_into_args (current, e, 0);
+ add_extra_element (current, "command_as_argument", e);
+ }
+
+ {
+ ELEMENT *bi = new_element (ET_before_item);
+ add_to_element_contents (current, bi);
+ current = bi;
+ }
+ } /* CF_blockitem */
+
+ if (command_flags(current) & CF_menu)
+ {
+ /* Start reading a menu. Processing will continue in
+ handle_menu in menus.c. */
+
+ ELEMENT *menu_comment = new_element (ET_menu_comment);
+ add_to_element_contents (current, menu_comment);
+ current = menu_comment;
+ debug ("MENU_COMMENT OPEN");
+ push_context (ct_preformatted);
+ }
+ current = begin_preformatted (current);
+
+ return current;
+}
+
+/* Actions to be taken at the end of an argument to a line command
+ not starting a block. @end is processed in here. */
+static ELEMENT *
+end_line_misc_line (ELEMENT *current)
+{
+ enum command_id cmd;
+ int arg_type;
+ enum context c;
+ ELEMENT *misc_cmd;
+ char *end_command = 0;
+ enum command_id end_id;
+ int included_file = 0;
+
+ isolate_last_space (current);
+
+ current = current->parent;
+ misc_cmd = current;
+ cmd = current->cmd;
+ if (!cmd)
+ abort ();
+
+ arg_type = command_data(cmd).data;
+
+ /* Check 'line' is top of the context stack */
+ c = pop_context ();
+ if (c != ct_line)
+ {
+ /* error */
+ abort ();
+ }
+
+ debug ("MISC END %s", command_name(cmd));
+
+ if (arg_type > 0)
+ {
+ ELEMENT *args = parse_line_command_args (current);
+ if (args)
+ add_extra_misc_args (current, "misc_args", args);
+ }
+ else if (arg_type == LINE_text)
+ {
+ char *text = 0;
+ int superfluous_arg = 0;
+
+ if (current->args.number > 0)
+ text = convert_to_text (current->args.list[0], &superfluous_arg);
+
+ if (!text || !strcmp (text, ""))
+ {
+ if (!superfluous_arg)
+ line_warn ("@%s missing argument", command_name(cmd));
+ add_extra_integer (current, "missing_argument", 1);
+ free (text);
+ }
+ else
+ {
+ add_extra_string (current, "text_arg", text);
+ if (current->cmd == CM_end)
+ {
+ char *line = text;
+
+ /* Set end_command - used below. */
+ end_command = read_command_name (&line);
+ if (end_command)
+ {
+ /* Check if argument is a block Texinfo command. */
+ end_id = lookup_command (end_command);
+ if (end_id == 0 || !(command_data(end_id).flags & CF_block))
+ {
+ command_warn (current, "unknown @end %s", end_command);
+ free (end_command); end_command = 0;
+ }
+ else
+ {
+ debug ("END BLOCK %s", end_command);
+ /* Handle conditional block commands (e.g. @ifinfo) */
+
+ /* If we are in a non-ignored conditional, there is not
+ an element for the block in the tree; it is recorded
+ in the conditional stack. Pop it and check it is the
+ same as the one given in the @end line. */
+
+ if (command_data(end_id).data == BLOCK_conditional)
+ {
+ enum command_id popped;
+ if (conditional_number == 0)
+ goto conditional_stack_fail;
+ popped = pop_conditional_stack ();
+ if (popped != end_id)
+ {
+ push_conditional_stack (popped);
+ goto conditional_stack_fail;
+ }
+ if (0)
+ {
+ conditional_stack_fail:
+ command_error (current, "unmatched `@end'");
+ free (end_command); end_command = 0;
+ }
+ }
+ if (end_command)
+ {
+ add_extra_string (current, "command_argument",
+ end_command);
+ }
+ if (end_command
+ && (superfluous_arg
+ || line[strspn (line, whitespace_chars)] != '\0'))
+ {
+ char *line, *line2;
+ line = convert_to_texinfo (current->args.list[0]);
+
+ line2 = line;
+ line2 += strspn (line2, whitespace_chars);
+ free (read_command_name (&line2));
+ command_error (current,
+ "superfluous argument to @end %s: "
+ "%s", end_command, line2);
+ superfluous_arg = 0; /* Don't issue another error
+ message below. */
+ free (line);
+ }
+ }
+ }
+ else
+ {
+ command_error (current, "bad argument to @end: %s", line);
+ }
+ }
+ else if (superfluous_arg)
+ {
+ /* An error message is issued below. */
+ }
+ else if (current->cmd == CM_include)
+ {
+ int status;
+ char *fullpath;
+ debug ("Include %s", text);
+
+ fullpath = locate_include_file (text);
+ if (!fullpath)
+ {
+ command_error (current,
+ "@include: could not find %s", text);
+ }
+ else
+ {
+ status = input_push_file (fullpath);
+ if (status)
+ {
+ command_error (current,
+ "@include: could not open %s:",
+ text,
+ strerror (status));
+ }
+ else
+ included_file = 1;
+ free (fullpath);
+ }
+ }
+ else if (current->cmd == CM_verbatiminclude)
+ {
+ if (global_info.input_perl_encoding)
+ add_extra_string_dup (current, "input_perl_encoding",
+ global_info.input_perl_encoding);
+ }
+ else if (current->cmd == CM_documentencoding)
+ {
+ int i; char *p, *text2;
+ char *texinfo_encoding, *perl_encoding, *input_encoding;
+ /* See tp/Texinfo/Encoding.pm (whole file) */
+
+ /* Three concepts of encoding:
+ texinfo_encoding -- one of the encodings supported as an
+ argument to @documentencoding, documented
+ in Texinfo manual
+ perl_encoding -- used for charset conversion within Perl
+ input_encoding -- for output within an HTML file */
+
+ text2 = strdup (text);
+ for (p = text2; *p; p++)
+ *p = tolower (*p);
+
+ /* Get texinfo_encoding from what was in the document */
+ {
+ static char *canonical_encodings[] = {
+ "us-ascii", "utf-8", "iso-8859-1",
+ "iso-8859-15","iso-8859-2","koi8-r", "koi8-u",
+ 0
+ };
+
+ texinfo_encoding = 0;
+ for (i = 0; (canonical_encodings[i]); i++)
+ {
+ if (!strcmp (text2, canonical_encodings[i]))
+ {
+ texinfo_encoding = canonical_encodings[i];
+ break;
+ }
+ }
+ if (!texinfo_encoding)
+ {
+ command_warn (current, "encoding `%s' is not a "
+ "canonical texinfo encoding", text);
+ }
+ }
+
+ /* Get perl_encoding. */
+ perl_encoding = 0;
+ if (texinfo_encoding)
+ perl_encoding = texinfo_encoding;
+ else
+ {
+ int i;
+ static char *known_encodings[] = {
+ "shift_jis",
+ "latin1",
+ 0
+ };
+ for (i = 0; (known_encodings[i]); i++)
+ {
+ if (!strcmp (text2, known_encodings[i]))
+ {
+ perl_encoding = known_encodings[i];
+ break;
+ }
+ }
+ }
+ free (text2);
+
+ if (perl_encoding)
+ {
+ struct encoding_map {
+ char *from; char *to;
+ };
+ static struct encoding_map map[] = {
+ "utf-8", "utf-8-strict",
+ "us-ascii", "ascii",
+ "shift_jis", "shiftjis",
+ "latin1", "iso-8859-1"
+ };
+ for (i = 0; i < sizeof map / sizeof *map; i++)
+ {
+ if (!strcmp (perl_encoding, map[i].from))
+ {
+ perl_encoding = map[i].to;
+ break;
+ }
+ }
+ add_extra_string_dup (current, "input_perl_encoding",
+ perl_encoding);
+ free (global_info.input_perl_encoding);
+ global_info.input_perl_encoding = strdup (perl_encoding);
+ }
+ else
+ {
+ command_warn (current, "unrecognized encoding name `%s'",
+ text);
+ /* Texinfo::Encoding calls Encode::Alias, so knows
+ about more encodings than what we know about here.
+ TODO: Check when perl_encoding could be defined when
+ texinfo_encoding isn't.
+ Maybe we should check if an iconv conversion is possible
+ from this encoding to UTF-8. */
+
+ }
+
+ /* Set input_encoding from perl_encoding */
+ input_encoding = 0;
+ if (perl_encoding)
+ {
+ struct encoding_map {
+ char *from; char *to;
+ };
+ static struct encoding_map map[] = {
+ "utf8", "utf-8",
+ "utf-8-strict","utf-8",
+ "ascii", "us-ascii",
+ "shiftjis", "shift_jis",
+ "latin-1", "iso-8859-1",
+ "iso-8859-1", "iso-8859-1",
+ "iso-8859-2", "iso-8859-2",
+ "iso-8859-15", "iso-8859-15",
+ "koi8-r", "koi8",
+ "koi8-u", "koi8",
+ };
+ input_encoding = perl_encoding;
+ for (i = 0; i < sizeof map / sizeof *map; i++)
+ {
+ /* Elements in first column map to elements in
+ second column. Elements in second column map
+ to themselves. */
+ if (!strcasecmp (input_encoding, map[i].from)
+ || !strcasecmp (input_encoding, map[i].to))
+ {
+ input_encoding = map[i].to;
+ break;
+ }
+ }
+ }
+ if (input_encoding)
+ {
+ add_extra_string_dup (current, "input_encoding_name",
+ input_encoding);
+
+ global_info.input_encoding_name = input_encoding;
+ set_input_encoding (input_encoding);
+ }
+ }
+ else if (current->cmd == CM_documentlanguage)
+ {
+ char *p, *q;
+
+ /* Texinfo::Common::warn_unknown_language checks with
+ tp/Texinfo/Documentlanguages.pm, which is an automatically
+ generated list of official IANA language codes. For now,
+ just check if the language code looks right. */
+
+ p = text;
+ while (isalpha (*p))
+ p++;
+ if (*p && *p != '_')
+ {
+ /* non-alphabetic char in language code */
+ command_warn (current, "%s is not a valid language code",
+ text);
+ }
+ else
+ {
+ if (p - text > 4)
+ {
+ /* looks too long */
+ char c = *p;
+ *p = 0;
+ command_warn (current, "%s is not a valid language code",
+ text);
+ *p = c;
+ }
+ if (*p == '_')
+ {
+ q = p + 1;
+ p = q;
+ /* Language code should be of the form LL_CC,
+ language code followed by country code. */
+ while (isalpha (*p))
+ p++;
+ if (*p || p - q > 4)
+ {
+ /* non-alphabetic char in country code or code
+ is too long. */
+ command_warn (current,
+ "%s is not a valid region code", q);
+ }
+ }
+ }
+
+ free (global_documentlanguage);
+ global_documentlanguage = strdup (text);
+ /* FIXME: check customization variable */
+ }
+ }
+ if (superfluous_arg)
+ {
+ char *texi_line, *p, *p1;
+ p = convert_to_texinfo (args_child_by_index(current, 0));
+
+ texi_line = p;
+ while (isspace (*texi_line))
+ texi_line++;
+
+ /* Trim leading and trailing whitespace. */
+ p1 = strchr (texi_line, '\0');
+ if (p1 > texi_line)
+ {
+ while (p1 > texi_line && isspace (p1[-1]))
+ p1--;
+ c = *p1;
+ *p1 = '\0';
+ }
+ command_error (current, "bad argument to @%s: %s",
+ command_name(current->cmd), texi_line);
+ free (p);
+ }
+ }
+ else if (current->cmd == CM_node)
+ {
+ int i;
+ ELEMENT *arg;
+
+ NODE_SPEC_EXTRA **nodes_manuals;
+
+ /* Construct 'nodes_manuals' array. Maximum of four elements
+ (node name, up, prev, next). */
+ nodes_manuals = malloc (sizeof (NODE_SPEC_EXTRA *) * 5);
+
+ for (i = 0; i < current->args.number && i < 4; i++)
+ {
+ arg = current->args.list[i];
+ nodes_manuals[i] = parse_node_manual (arg);
+ }
+ nodes_manuals[i] = 0;
+
+ add_extra_node_spec_array (current, "nodes_manuals", nodes_manuals);
+
+ check_internal_node (nodes_manuals[0]);
+
+ if (nodes_manuals[0])
+ {
+ ELEMENT *label = 0;
+ if (nodes_manuals[0]->node_content)
+ {
+ /* Copy the first 'node_content' array, to avoid the complication
+ of it being referenced in two different places.
+ This might be better with a separate function. */
+
+ label = new_element (0);
+ int i;
+
+ for (i = 0; i<nodes_manuals[0]->node_content->contents.number;
+ i++)
+ {
+ add_to_contents_as_array (label,
+ contents_child_by_index(nodes_manuals[0]->node_content, i));
+ }
+ }
+ register_label (current, label);
+ }
+
+ current_node = current;
+ }
+ else if (current->cmd == CM_listoffloats)
+ {
+ parse_float_type (current);
+ }
+ else
+ {
+ /* All the other "line" commands. Check they have an argument. Empty
+ @top is allowed. */
+ if (current->args.list[0]->contents.number == 0
+ && current->cmd != CM_top)
+ {
+ command_warn (current, "@%s missing argument",
+ command_name(current->cmd));
+ add_extra_integer (current, "missing_argument", 1);
+ }
+ else
+ {
+ if ((current->parent->cmd == CM_ftable
+ || current->parent->cmd == CM_vtable)
+ && (current->cmd == CM_item || current->cmd == CM_itemx))
+ {
+ enter_index_entry (current->parent->cmd,
+ current->cmd,
+ current,
+ current->args.list[0]);
+ }
+ else
+ {
+ // 3273 FIXME possibly check for @def... command
+ }
+
+
+ /* Index commands */
+ if (command_flags(current) & CF_index_entry_command)
+ {
+ enter_index_entry (current->cmd, current->cmd, current,
+ current->args.list[0]);
+ current->type = ET_index_entry_command;
+ }
+ }
+ }
+
+ current = current->parent;
+ if (end_command) /* Set above */
+ {
+ /* More processing of @end */
+ ELEMENT *end_elt;
+
+ debug ("END COMMAND %s", end_command);
+
+ /* Reparent the "@end" element to be a child of the block element. */
+ end_elt = pop_element_from_contents (current);
+
+ /* If not a conditional */
+ if (command_data(end_id).data != BLOCK_conditional)
+ {
+ ELEMENT *closed_command;
+ /* This closes tree elements (e.g. paragraphs) until we reach
+ end_command. It can print an error if another block command
+ is found first. */
+ current = close_commands (current, end_id, &closed_command, 0);
+ if (!closed_command)
+ destroy_element_and_children (end_elt);
+ else
+ {
+ add_extra_element (closed_command, "end_command", end_elt);
+ close_command_cleanup (closed_command);
+
+ add_to_element_contents (closed_command, end_elt);
+
+ if (command_flags(closed_command) & CF_menu
+ && current_context () == ct_menu)
+ {
+ ELEMENT *e;
+ debug ("CLOSE menu but still in menu context");
+ e = new_element (ET_menu_comment);
+ add_to_element_contents (current, e);
+ current = e;
+ push_context (ct_preformatted);
+ }
+ }
+ if (close_preformatted_command (end_id))
+ current = begin_preformatted (current);
+ }
+ else
+ {
+ /* The "@end" line does not appear in the final tree for a
+ conditional block. */
+ destroy_element_and_children (end_elt);
+ }
+ }
+ else
+ {
+ if (close_preformatted_command (cmd))
+ current = begin_preformatted (current);
+ }
+
+ /* If a file was included, remove the include command completely.
+ Also ignore @setfilename in included file, as said in the manual. */
+ if (included_file || (cmd == CM_setfilename && top_file_index () > 0))
+ destroy_element_and_children (pop_element_from_contents (current));
+ else if (cmd == CM_setfilename && (current_node || current_section))
+ command_warn (misc_cmd, "@setfilename after the first element");
+ else if (cmd == CM_columnfractions)
+ {
+ ELEMENT *before_item;
+ KEY_PAIR *misc_args;
+
+ /* Check if in multitable. */
+ if (!current->parent || current->parent->cmd != CM_multitable)
+ {
+ command_error (current,
+ "@columnfractions only meaningful on a @multitable line");
+ }
+ else
+ {
+ pop_context (); /* ct_line */;
+
+ current = current->parent;
+
+ if ((misc_args = lookup_extra (misc_cmd, "misc_args")))
+ {
+ add_extra_element (current, "columnfractions", misc_cmd);
+ add_extra_integer (current, "max_columns",
+ misc_args->value->contents.number);
+ }
+ else
+ add_extra_integer (current, "max_columns", 0);
+
+ before_item = new_element (ET_before_item);
+ add_to_element_contents (current, before_item);
+ current = before_item;
+ }
+ }
+ else if (command_data(cmd).flags & CF_root)
+ {
+ current = last_contents_child (current);
+ if (cmd == CM_node)
+ counter_pop (&count_remaining_args);
+
+ /* Destroy all contents (TODO: check why do we do this?) */
+ while (last_contents_child (current))
+ destroy_element (pop_element_from_contents (current));
+
+ /* Set 'associated_section' extra key for a node. */
+ if (cmd != CM_node && cmd != CM_part)
+ {
+ if (current_node)
+ {
+ if (!lookup_extra (current_node, "associated_section"))
+ {
+ add_extra_element
+ (current_node, "associated_section", current);
+ add_extra_element
+ (current, "associated_node", current_node);
+ }
+ }
+
+ if (current_part)
+ {
+ add_extra_element (current, "associated_part", current_part);
+ add_extra_element (current_part, "part_associated_section",
+ current);
+ if (current->cmd == CM_top)
+ {
+ line_error_ext (1, &current_part->line_nr,
+ "@part should not be associated with @top");
+ }
+ current_part = 0;
+ }
+
+ current_section = current;
+ }
+ else if (cmd == CM_part)
+ {
+ current_part = current;
+ if (current_node
+ && !lookup_extra (current_node, "associated_section"))
+ {
+ line_warn ("@node precedes @part, but parts may not be "
+ "associated with nodes");
+ }
+ }
+ }
+
+ return current;
+}
+
+/* Actions to be taken when a whole line of input has been processed */
+ELEMENT *
+end_line (ELEMENT *current)
+{
+ ELEMENT *current_old = current; /* Used at very end of function */
+
+ /* If empty line, start a new paragraph. */
+ if (last_contents_child (current)
+ && last_contents_child (current)->type == ET_empty_line)
+ {
+ debug ("END EMPTY LINE");
+ if (current->type == ET_paragraph)
+ {
+ ELEMENT *e;
+ /* Remove empty_line element. */
+ e = pop_element_from_contents (current);
+
+ current = end_paragraph (current, 0, 0);
+
+ /* Add empty_line to higher-level element. */
+ add_to_element_contents (current, e);
+ }
+ else if (current->type == ET_preformatted
+ && current->parent->type == ET_menu_entry_description)
+ {
+ ELEMENT *empty_line, *e;
+ empty_line = pop_element_from_contents (current);
+ if (current->contents.number == 0)
+ {
+ current = current->parent;
+ destroy_element (pop_element_from_contents (current));
+ }
+ else
+ current = current->parent;
+
+ pop_context (); //ct_preformatted
+
+ current = current->parent->parent;
+ e = new_element (ET_menu_comment);
+ add_to_element_contents (current, e);
+
+ current = e;
+ e = new_element (ET_preformatted);
+ add_to_element_contents (current, e);
+
+ current = e;
+ e = new_element (ET_after_description_line);
+ text_append (&e->text, empty_line->text.text);
+ destroy_element (empty_line);
+ add_to_element_contents (current, e);
+
+ push_context (ct_preformatted);
+ debug ("MENU: END DESCRIPTION, OPEN COMMENT");
+ }
+ else if (in_paragraph_context (current_context ()))
+ {
+ current = end_paragraph (current, 0, 0);
+ }
+ }
+ /* The end of the line of a menu. */
+ else if (current->type == ET_menu_entry_name
+ || current->type == ET_menu_entry_node)
+ {
+ ELEMENT *end_comment = 0;
+ int empty_menu_entry_node = 0;
+
+ if (current->type == ET_menu_entry_node)
+ {
+ ELEMENT *last = last_contents_child (current);
+
+ if (current->contents.number > 0
+ && (last->cmd == CM_c || last->cmd == CM_comment))
+ {
+ end_comment = pop_element_from_contents (current);
+ }
+
+ /* If contents empty or is all whitespace. */
+ if (current->contents.number == 0
+ || (current->contents.number == 1
+ && last->text.end > 0
+ && !last->text.text[strspn (last->text.text,
+ whitespace_chars)]))
+ {
+ empty_menu_entry_node = 1;
+ if (end_comment)
+ add_to_element_contents (current, end_comment);
+ }
+ }
+
+ /* Abort the menu entry if there is no destination node given. */
+ if (empty_menu_entry_node || current->type == ET_menu_entry_name)
+ {
+ ELEMENT *menu, *menu_entry, *description_or_menu_comment = 0;
+ debug ("FINALLY NOT MENU ENTRY");
+ menu = current->parent->parent;
+ menu_entry = pop_element_from_contents (menu);
+ if (menu->contents.number > 0
+ && last_contents_child(menu)->type == ET_menu_entry)
+ {
+ ELEMENT *entry, *description = 0;
+ int j;
+
+ entry = last_contents_child(menu);
+ for (j = entry->args.number - 1; j >= 0; j--)
+ {
+ ELEMENT *e = args_child_by_index (entry, j);
+ if (e->type == ET_menu_entry_description)
+ {
+ description = e;
+ break;
+ }
+ }
+ if (description)
+ description_or_menu_comment = description;
+ else
+ { // 2707
+ /* Normally this cannot happen. */
+ abort ();
+ }
+ }
+ else if (menu->contents.number > 0
+ && last_contents_child(menu)->type == ET_menu_comment)
+ {
+ description_or_menu_comment = last_contents_child(menu);
+ }
+ if (description_or_menu_comment)
+ {
+ current = description_or_menu_comment;
+ if (current->contents.number > 0
+ && last_contents_child(current)->type == ET_preformatted)
+ current = last_contents_child(current);
+ else // 2725
+ {
+ /* This should not happen */
+ //abort ();
+ ELEMENT *e;
+ e = new_element (ET_preformatted);
+ add_to_element_contents (current, e);
+ current = e;
+ }
+ push_context (ct_preformatted);
+ }
+ else
+ {
+ ELEMENT *e;
+ e = new_element (ET_menu_comment);
+ add_to_element_contents (menu, e);
+ current = e;
+ e = new_element (ET_preformatted);
+ add_to_element_contents (current, e);
+ current = e;
+ push_context (ct_preformatted);
+ debug ("THEN MENU_COMMENT OPEN");
+ }
+ {
+ int i, j;
+ for (i = 0; i < menu_entry->args.number; i++)
+ {
+ ELEMENT *arg = args_child_by_index(menu_entry, i);
+ if (arg->text.end > 0)
+ current = merge_text (current, arg->text.text);
+ else
+ {
+ ELEMENT *e;
+ for (j = 0; j < arg->contents.number; j++)
+ {
+ e = contents_child_by_index (arg, j);
+ if (e->text.end > 0)
+ {
+ current = merge_text (current, e->text.text);
+ destroy_element (e);
+ }
+ else
+ {
+ add_to_element_contents (current, e);
+ }
+ }
+ }
+ destroy_element (arg);
+ }
+ destroy_element (menu_entry);
+ }
+ }
+ else
+ {
+ debug ("MENU ENTRY END LINE");
+ current = current->parent;
+ current = enter_menu_entry_node (current);
+ if (end_comment)
+ add_to_element_contents (current, end_comment);
+ }
+ }
+
+ /* End of a definition line, like @deffn */
+ else if (current->parent && current->parent->type == ET_def_line)
+ {
+ enum command_id def_command, original_def_command;
+ DEF_INFO *def_info = 0;
+ static DEF_INFO zero_def_info; /* always stays zeroed */
+ KEY_PAIR *k;
+
+ if (pop_context () != ct_def)
+ abort ();
+
+ k = lookup_extra (current->parent, "original_def_cmdname");
+ if (k)
+ original_def_command = lookup_command ((char *) k->value);
+ else
+ original_def_command = current->parent->parent->cmd;
+ // TODO: What if k is not defined?
+
+ def_command = original_def_command;
+ /* Strip an trailing x from the command, e.g. @deffnx -> @deffn */
+ if (command_data(def_command).flags & CF_line)
+ {
+ char *stripped = strdup (command_name(def_command));
+ stripped[strlen (stripped) - 1] = '\0';
+ def_command = lookup_command (stripped);
+ free (stripped);
+ }
+
+ def_info = parse_def (def_command, current);
+
+ /* Record the index entry if def_info is not empty. */
+ if (!memcmp(def_info, &zero_def_info, sizeof (DEF_INFO)))
+ {
+ free (def_info);
+ command_warn (current->parent, "missing category for @%s",
+ command_name (original_def_command));
+ }
+ else
+ {
+ ELEMENT *index_entry = 0; /* Index entry text. */
+
+ add_extra_def_info (current->parent, "def_parsed_hash", def_info);
+
+ if (def_info->name)
+ {
+ char *t;
+ /* Set index_entry unless an empty ET_bracketed_def_content. */
+ if (def_info->name->type == ET_bracketed_def_content
+ && (def_info->name->contents.number == 0
+ || (def_info->name->contents.number == 1
+ && (t = def_info->name->contents.list[0]->text.text)
+ && t[strspn (t, whitespace_chars)] == '\0')))
+ {
+ }
+ else
+ index_entry = def_info->name;
+ }
+
+ if (index_entry)
+ {
+ ELEMENT *index_contents = 0;
+
+ if (def_info->class &&
+ (def_command == CM_defop
+ || def_command == CM_deftypeop
+ || def_command == CM_defmethod
+ || def_command == CM_deftypemethod
+ || def_command == CM_defivar
+ || def_command == CM_deftypeivar
+ || def_command == CM_deftypecv))
+ {
+ add_extra_string_dup (current->parent, "documentlanguage",
+ global_documentlanguage);
+ }
+ else
+ {
+ index_contents = new_element (ET_NONE);
+ if (index_contents->contents.number == 0)
+ add_to_contents_as_array (index_contents, index_entry);
+ }
+
+ enter_index_entry (def_command,
+ original_def_command,
+ current->parent,
+ index_contents);
+ }
+ else
+ {
+ command_warn (current->parent, "missing name for @%s",
+ command_name (original_def_command));
+ }
+ }
+
+ current = current->parent->parent;
+ current = begin_preformatted (current);
+ }
+ /* End of a line starting a block. */
+ else if (current->type == ET_block_line_arg)
+ {
+ current = end_line_starting_block (current);
+ }
+
+ /* after an "@end verbatim" */
+ else if (current->contents.number
+ && last_contents_child(current)->type == ET_empty_line_after_command
+ && contents_child_by_index(current, -2)
+ && contents_child_by_index(current, -2)->cmd == CM_verbatim)
+ {
+ /*
+ if we are after a @end verbatim, we must restart a preformatted if needed,
+ since there is no @end command explicitly associated to raw commands
+ it won't be done elsewhere.
+ */
+
+ current = begin_preformatted (current);
+ }
+ else if (current->type == ET_line_arg)
+ {
+ current = end_line_misc_line (current);
+ }
+ else if (current->contents.number == 1
+ && current->contents.list[0]->type == ET_empty_line_after_command
+ || current->contents.number == 2
+ && current->contents.list[0]->type == ET_empty_line_after_command
+ && (current->contents.list[1]->cmd == CM_c
+ || current->contents.list[1]->cmd == CM_comment))
+ {
+ if (current->type == ET_preformatted
+ || current->type == ET_rawpreformatted)
+ {
+ /* Empty line after a @menu, or before a preformatted. Reparent
+ to the menu or other format. */
+ ELEMENT *parent, *to_reparent;
+
+ parent = current->parent;
+ if (parent->type == ET_menu_comment
+ && parent->contents.number == 1)
+ {
+ parent = parent->parent;
+ }
+ to_reparent = pop_element_from_contents (parent);
+ debug ("LINE AFTER COMMAND IN PREFORMATTED");
+ while (current->contents.number > 0)
+ {
+ ELEMENT *e;
+ e = remove_from_contents (current, 0);
+ add_to_element_contents (parent, e);
+ }
+ add_to_element_contents (parent, to_reparent);
+ }
+ }
+
+ /* 'line' or 'def' at top of "context stack" - this happens when
+ line commands are nested (always incorrectly?) */
+ if (current_context () == ct_line || current_context () == ct_def)
+ {
+ debug ("Still opened line command");
+ if (current_context () == ct_def)
+ {
+ while (current->parent
+ && current->parent->type != ET_def_line)
+ {
+ current = close_current (current, 0, 0);
+ }
+ }
+ else
+ {
+ while (current->parent
+ && current->type != ET_line_arg
+ && current->type != ET_block_line_arg)
+ {
+ current = close_current (current, 0, 0);
+ }
+ }
+
+ /* 3470 Check for infinite loop bugs */
+ if (current == current_old)
+ abort ();
+
+ current = end_line (current);
+ }
+ return current;
+}
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.c
new file mode 100644
index 0000000000..92baf0ff98
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.c
@@ -0,0 +1,232 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "tree_types.h"
+#include "errors.h"
+#include "input.h"
+#include "text.h"
+
+typedef struct {
+ char *message;
+ enum error_type type;
+ LINE_NR line_nr;
+} ERROR_MESSAGE;
+
+static ERROR_MESSAGE *error_list = 0;
+static size_t error_number = 0;
+static size_t error_space = 0;
+
+static void
+line_error_internal (enum error_type type, LINE_NR *cmd_line_nr,
+ char *format, va_list v)
+{
+ char *message;
+ vasprintf (&message, format, v);
+ if (!message) abort ();
+ if (error_number == error_space)
+ {
+ error_list = realloc (error_list,
+ (error_space += 10) * sizeof (ERROR_MESSAGE));
+ }
+ error_list[error_number].message = message;
+ error_list[error_number].type = type;
+
+ if (cmd_line_nr)
+ {
+ if (cmd_line_nr->line_nr)
+ error_list[error_number++].line_nr = *cmd_line_nr;
+ else
+ error_list[error_number++].line_nr = line_nr;
+ }
+ else
+ error_list[error_number++].line_nr = line_nr;
+}
+
+void
+line_error_ext (enum error_type type, LINE_NR *cmd_line_nr,
+ char *format, ...)
+{
+ va_list v;
+
+ va_start (v, format);
+ line_error_internal (type, cmd_line_nr, format, v);
+}
+
+void
+line_error (char *format, ...)
+{
+ va_list v;
+
+ va_start (v, format);
+ line_error_internal (error, 0, format, v);
+}
+
+void
+line_warn (char *format, ...)
+{
+ va_list v;
+
+ va_start (v, format);
+ line_error_internal (warning, 0, format, v);
+}
+
+void
+command_warn (ELEMENT *e, char *format, ...)
+{
+ va_list v;
+
+ va_start (v, format);
+ line_error_internal (warning, &e->line_nr, format, v);
+}
+
+void
+command_error (ELEMENT *e, char *format, ...)
+{
+ va_list v;
+
+ va_start (v, format);
+ line_error_internal (error, &e->line_nr, format, v);
+}
+
+void
+wipe_errors (void)
+{
+ int i;
+ for (i = 0; i < error_number; i++)
+ free (error_list[i].message);
+ error_number = 0;
+}
+
+static void
+bug_message_internal (char *format, va_list v)
+{
+ fprintf (stderr, "You found a bug: ");
+ vfprintf (stderr, format, v);
+ fprintf (stderr, "\n");
+ if (line_nr.file_name)
+ {
+ fprintf (stderr,
+ "last location %s:%d", line_nr.file_name, line_nr.line_nr);
+ if (line_nr.macro)
+ fprintf (stderr, " (possibly involving @%s)", line_nr.macro);
+ fprintf (stderr, "\n");
+ }
+ exit (1);
+}
+
+void
+bug_message (char *format, ...)
+{
+ va_list v;
+
+ va_start (v, format);
+ bug_message_internal (format, v);
+}
+
+static int indent = 0;
+
+/* Output INDENT spaces. */
+static void
+dump_indent (TEXT *text)
+{
+ int i;
+
+ for (i = 0; i < indent; i++)
+ text_append_n (text, " ", 1);
+}
+
+/* Ouput S escaping single quotes and backslashes, so that
+ Perl can read it in when it is surrounded by single quotes. */
+void
+dump_string (char *s, TEXT *text)
+{
+ while (*s)
+ {
+ if (*s == '\''
+ || *s == '\\')
+ text_append_n (text, "\\", 1);
+ text_append_n (text, s++, 1);
+ }
+}
+
+static void
+dump_line_nr (LINE_NR *line_nr, TEXT *text)
+{
+ text_append_n (text, "{\n", 2);
+ indent += 2;
+
+ dump_indent (text);
+ text_printf (text, "'file_name' => '%s',\n",
+ line_nr->file_name ?
+ line_nr->file_name : "");
+
+ if (line_nr->line_nr)
+ {
+ dump_indent (text);
+ text_append (text, "'line_nr' => ");
+ text_printf (text, "%d", line_nr->line_nr);
+ text_append (text, ",\n");
+ }
+
+ /* TODO: macro. */
+ if (line_nr->macro)
+ {
+ dump_indent (text);
+ text_append (text, "'macro' => ");
+ text_printf (text, "'%s'", line_nr->macro);
+ text_append (text, ",\n");
+ }
+ else
+ {
+ dump_indent (text);
+ text_append (text, "'macro' => ''\n");
+ }
+
+
+ indent -= 2;
+ dump_indent (text);
+ text_append_n (text, "},\n", 3);
+}
+
+char *
+dump_errors (void)
+{
+ int i;
+ static TEXT t;
+
+ text_reset (&t);
+ text_append (&t, "$ERRORS = [\n");
+ for (i = 0; i < error_number; i++)
+ {
+ text_append (&t, "{ 'message' =>\n'");
+ dump_string (error_list[i].message, &t);
+ text_append (&t, "',\n");
+ text_printf (&t, "'type' => '%s',", error_list[i].type == error ? "error"
+ : "warning");
+ text_append (&t, "'line_nr' => ");
+ dump_line_nr (&error_list[i].line_nr, &t);
+ text_append (&t, "},\n");
+ }
+ text_append (&t, "];\n");
+
+ return t.text;
+}
+
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.h
new file mode 100644
index 0000000000..112669f46b
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/errors.h
@@ -0,0 +1,14 @@
+/* errors.h - declarations for error.c */
+
+enum error_type { error, warning };
+
+void line_error (char *format, ...);
+void line_warn (char *format, ...);
+void command_error (ELEMENT *e, char *format, ...);
+void command_warn (ELEMENT *e, char *format, ...);
+void wipe_errors (void);
+void line_error_ext (enum error_type type, LINE_NR *cmd_line_nr,
+ char *format, ...);
+void bug_message (char *format, ...);
+
+char *dump_errors (void);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/extra.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/extra.c
new file mode 100644
index 0000000000..55cae17d6c
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/extra.c
@@ -0,0 +1,164 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <string.h>
+
+#include "parser.h"
+
+static void
+add_extra_key (ELEMENT *e, char *key, ELEMENT *value,
+ enum extra_type type)
+{
+ int i;
+ for (i = 0; i < e->extra_number; i++)
+ {
+ if (!strcmp (e->extra[i].key, key))
+ break;
+ }
+ if (i == e->extra_number)
+ {
+ if (e->extra_number == e->extra_space)
+ {
+ e->extra = realloc (e->extra,
+ (e->extra_space += 5) * sizeof (KEY_PAIR));
+ if (!e->extra)
+ abort ();
+ }
+ e->extra_number++;
+ }
+
+ e->extra[i].key = key;
+ e->extra[i].value = value;
+ e->extra[i].type = type;
+}
+
+/* Add an extra key that is a reference to another element (for example,
+ 'associated_section' on a node command element. */
+void
+add_extra_element (ELEMENT *e, char *key, ELEMENT *value)
+{
+ add_extra_key (e, key, value, extra_element);
+}
+
+/* Add an extra key that is a reference to another element that is
+ out-of-tree, i.e., not referenced anywhere in the tree. */
+void
+add_extra_element_oot (ELEMENT *e, char *key, ELEMENT *value)
+{
+ add_extra_key (e, key, value, extra_element_oot);
+}
+
+/* Add an extra key that is a reference to the contents array of another
+ element (for example, 'node_content' on a node command element). */
+void
+add_extra_contents (ELEMENT *e, char *key, ELEMENT *value)
+{
+ add_extra_key (e, key, value, extra_contents);
+}
+
+/* Like add_extra_contents but all of the contents are out-of-tree. */
+void
+add_extra_contents_oot (ELEMENT *e, char *key, ELEMENT *value)
+{
+ add_extra_key (e, key, value, extra_contents_oot);
+}
+
+/* An array of content arrays. */
+void
+add_extra_contents_array (ELEMENT *e, char *key, ELEMENT *value)
+{
+ add_extra_key (e, key, value, extra_contents_array);
+}
+
+/* Add an extra key that is a reference to the text field of another
+ element. */
+void
+add_extra_text (ELEMENT *e, char *key, ELEMENT *value)
+{
+ add_extra_key (e, key, value, extra_text);
+}
+
+#if 0
+/* Function not used */
+void
+add_extra_index_entry (ELEMENT *e, char *key, INDEX_ENTRY_REF *value)
+{
+ add_extra_key (e, key, (ELEMENT *) value, extra_index_entry);
+}
+#endif
+
+void
+add_extra_misc_args (ELEMENT *e, char *key, ELEMENT *value)
+{
+ if (!value) return;
+ add_extra_key (e, key, value, extra_misc_args);
+}
+
+void
+add_extra_node_spec (ELEMENT *e, char *key, NODE_SPEC_EXTRA *value)
+{
+ add_extra_key (e, key, (ELEMENT *) value, extra_node_spec);
+}
+
+/* Used for 'node_manuals' array for the arguments given on a
+ @node line. Argument is a null-terminated array of pointers. */
+void
+add_extra_node_spec_array (ELEMENT *e, char *key, NODE_SPEC_EXTRA **value)
+{
+ add_extra_key (e, key, (ELEMENT *) value, extra_node_spec_array);
+}
+
+void
+add_extra_def_info (ELEMENT *e, char *key, DEF_INFO *value)
+{
+ add_extra_key (e, key, (ELEMENT *) value, extra_def_info);
+}
+
+void
+add_extra_float_type (ELEMENT *e, char *key, EXTRA_FLOAT_TYPE *value)
+{
+ add_extra_key (e, key, (ELEMENT *) value, extra_float_type);
+}
+
+void
+add_extra_string (ELEMENT *e, char *key, char *value)
+{
+ add_extra_key (e, key, (ELEMENT *) value, extra_string);
+}
+
+void
+add_extra_string_dup (ELEMENT *e, char *key, char *value)
+{
+ add_extra_key (e, key, (ELEMENT *) strdup (value), extra_string);
+}
+
+void
+add_extra_integer (ELEMENT *e, char *key, int value)
+{
+ add_extra_key (e, key, (ELEMENT *) value, extra_integer);
+}
+
+KEY_PAIR *
+lookup_extra (ELEMENT *e, char *key)
+{
+ int i;
+ for (i = 0; i < e->extra_number; i++)
+ {
+ if (!strcmp (e->extra[i].key, key))
+ return &e->extra[i];
+ }
+ return 0;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.c
new file mode 100644
index 0000000000..d5fa0bffe6
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.c
@@ -0,0 +1,1098 @@
+/* handle_commands.c -- what to do when a command name is first read */
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "parser.h"
+#include "input.h"
+#include "text.h"
+
+/* Return a containing @itemize or @enumerate if inside it. */
+ELEMENT *
+item_container_parent (ELEMENT *current)
+{
+ if ((current->cmd == CM_item
+ || current->type == ET_before_item)
+ && current->parent
+ && ((current->parent->cmd == CM_itemize
+ || current->parent->cmd == CM_enumerate)))
+ {
+ return current->parent;
+ }
+ return 0;
+}
+
+/* Check that there are no text holding environments (currently
+ checking only paragraphs and preformatted) in contents. */
+int
+check_no_text (ELEMENT *current)
+{
+ int after_paragraph = 0;
+ int i, j;
+ for (i = 0; i < current->contents.number; i++)
+ {
+ enum element_type t;
+ ELEMENT *f;
+ f = current->contents.list[i];
+ t = f->type;
+ if (t == ET_paragraph)
+ {
+ after_paragraph = 1;
+ break;
+ }
+ else if (t == ET_preformatted
+ || t == ET_rawpreformatted)
+ {
+ for (j = 0; j < f->contents.number; j++)
+ {
+ ELEMENT *g = f->contents.list[j];
+ if ((g->text.end > 0
+ && g->text.text[strspn (g->text.text, whitespace_chars)])
+ || (g->cmd && g->cmd != CM_c
+ && g->cmd != CM_comment
+ && g->type != ET_index_entry_command))
+ {
+ after_paragraph = 1;
+ break;
+ }
+ }
+ if (after_paragraph)
+ break;
+ }
+ }
+ return after_paragraph;
+}
+
+/* noarg skipspace */
+ELEMENT *
+handle_other_command (ELEMENT *current, char **line_inout,
+ enum command_id cmd, int *status)
+{
+ ELEMENT *misc = 0;
+ char *line = *line_inout;
+ int arg_spec;
+
+ *status = STILL_MORE_TO_PROCESS;
+
+ arg_spec = command_data(cmd).data;
+ if (arg_spec == OTHER_noarg)
+ {
+ if (command_data(cmd).flags & CF_in_heading)
+ {
+ line_error ("@%s should only appear in heading or footing",
+ command_name(cmd));
+ }
+
+ misc = new_element (ET_NONE);
+ misc->cmd = cmd;
+ add_to_element_contents (current, misc);
+ register_global_command (misc);
+ if (close_preformatted_command(cmd))
+ current = begin_preformatted (current);
+ }
+ else
+ {
+ /* @item can occur in several contents: in an @itemize, a @table, or
+ a @multitable. */
+ if (cmd == CM_item || cmd == CM_headitem || cmd == CM_tab)
+ {
+ ELEMENT *parent;
+
+ /* @itemize or @enumerate */
+ if ((parent = item_container_parent (current)))
+ {
+ if (cmd == CM_item)
+ {
+ debug ("ITEM CONTAINER");
+ counter_inc (&count_items);
+ misc = new_element (ET_NONE);
+ misc->cmd = CM_item;
+
+ add_extra_integer (misc, "item_number",
+ counter_value (&count_items, parent));
+
+ add_to_element_contents (parent, misc);
+ current = misc;
+ }
+ else
+ {
+ line_error ("@%s not meaningful inside `@%s' block",
+ command_name(cmd),
+ command_name(parent->cmd));
+ }
+ current = begin_preformatted (current);
+ }
+ /* @table, @vtable, @ftable */
+ else if ((parent = item_line_parent (current)))
+ {
+ line_error ("@%s not meaningful inside `@%s' block",
+ command_name(cmd),
+ command_name(parent->cmd));
+ current = begin_preformatted (current);
+ }
+ /* In a @multitable */
+ else if ((parent = item_multitable_parent (current)))
+ {
+ if (cmd != CM_item && cmd != CM_headitem
+ && cmd != CM_tab)
+ {
+ line_error ("@%s not meaningful inside @%s block",
+ command_name(cmd),
+ command_name(parent->cmd));
+ }
+ else
+ {
+ int max_columns = 0;
+ KEY_PAIR *prototypes;
+
+ prototypes = lookup_extra (parent, "prototypes");
+ if (prototypes)
+ max_columns = prototypes->value->contents.number;
+ else
+ {
+ prototypes = lookup_extra(parent, "columnfractions");
+ if (prototypes)
+ {
+ prototypes = lookup_extra((ELEMENT *) prototypes->value,
+ "misc_args");
+ if (prototypes)
+ max_columns = prototypes->value->contents.number;
+ }
+ }
+
+ if (max_columns == 0)
+ {
+ line_warn ("@%s in empty multitable",
+ command_name(cmd));
+ }
+ else if (cmd == CM_tab)
+ {
+ ELEMENT *row;
+ row = last_contents_child (parent);
+ if (row->type == ET_before_item)
+ line_error ("@tab before @item");
+ else if (counter_value (&count_cells, row)
+ >= max_columns)
+ {
+ line_error ("too many columns in multitable item"
+ " (max %d)", max_columns);
+ }
+ else
+ {
+ counter_inc (&count_cells);
+ misc = new_element (ET_NONE);
+ misc->cmd = cmd;
+ add_to_element_contents (row, misc);
+ current = misc;
+ debug ("TAB");
+
+ add_extra_integer (current, "cell_number",
+ counter_value (&count_cells, row));
+ }
+ }
+ else /* @item or @headitem */
+ {
+ ELEMENT *row;
+
+ debug ("ROW");
+ row = new_element (ET_row);
+ add_to_element_contents (parent, row);
+
+ /* Note that the "row_number" extra value,
+ isn't actually used anywhere at present. */
+ add_extra_integer (row, "row_number",
+ parent->contents.number - 1);
+
+ misc = new_element (ET_NONE);
+ misc->cmd = cmd;
+ add_to_element_contents (row, misc);
+ current = misc;
+
+ if (counter_value (&count_cells, parent) != -1)
+ counter_pop (&count_cells);
+ counter_push (&count_cells, row, 1);
+ add_extra_integer (current, "cell_number",
+ counter_value (&count_cells, row));
+ }
+ }
+ current = begin_preformatted (current);
+ } /* In @multitable */
+ else if (cmd == CM_tab)
+ {
+ line_error ("ignoring @tab outside of multitable");
+ current = begin_preformatted (current);
+ }
+ else
+ {
+ line_error ("@%s outside of table or list",
+ command_name(cmd));
+ current = begin_preformatted (current);
+ }
+ if (misc)
+ misc->line_nr = line_nr;
+ }
+ else
+ {
+ misc = new_element (ET_NONE);
+ misc->cmd = cmd;
+ misc->line_nr = line_nr;
+ add_to_element_contents (current, misc);
+ }
+ start_empty_line_after_command (current, &line, misc);
+ if (cmd == CM_indent || cmd == CM_noindent)
+ {
+ /* Start a new paragraph if not in one already. */
+ int spaces;
+ ELEMENT *paragraph;
+
+ /* Check if if we should change an ET_empty_line_after_command
+ element to ET_empty_spaces_after_command by looking ahead
+ to see what comes next. */
+ if (!strchr (line, '\n'))
+ {
+ char *line2;
+ input_push_text (strdup (line), 0);
+ line2 = new_line ();
+ if (line2)
+ line = line2;
+ }
+ spaces = strspn (line, whitespace_chars);
+ if (spaces > 0)
+ {
+ char saved = line[spaces];
+ line[spaces] = '\0';
+ current = merge_text (current, line);
+ line[spaces] = saved;
+ line += spaces;
+ }
+ if (*line
+ && last_contents_child(current)->type
+ == ET_empty_line_after_command)
+ {
+ last_contents_child(current)->type
+ = ET_empty_spaces_after_command;
+ }
+ paragraph = begin_paragraph (current);
+ if (paragraph)
+ current = paragraph;
+ if (!*line)
+ {
+ *status = GET_A_NEW_LINE;
+ goto funexit;
+ }
+ }
+ }
+
+funexit:
+ *line_inout = line;
+ return current;
+}
+
+/* STATUS is set to GET_A_NEW_LINE if we should get a new line after this,
+ to FINISHED_TOTALLY if we should stop processing completely. */
+ELEMENT *
+handle_line_command (ELEMENT *current, char **line_inout,
+ enum command_id cmd, int *status)
+{
+ ELEMENT *misc = 0;
+ char *line = *line_inout;
+ int arg_spec;
+
+ *status = STILL_MORE_TO_PROCESS;
+
+ /* Root commands (like @node) and @bye */
+ if (command_data(cmd).flags & CF_root || cmd == CM_bye)
+ {
+ ELEMENT *closed_elt; /* Not used */
+ current = close_commands (current, 0, &closed_elt, cmd);
+ if (current->type == ET_text_root)
+ {
+ if (cmd != CM_bye)
+ {
+ /* Something to do with document_root and text_root. */
+ ELEMENT *new_root = new_element (ET_document_root);
+ add_to_element_contents (new_root, current);
+ current = new_root;
+ }
+ }
+ else
+ {
+ current = current->parent;
+ if (!current)
+ abort ();
+ }
+ }
+
+ /* Look up information about this command ( skipline text
+ line lineraw (a number) ). */
+ arg_spec = command_data(cmd).data;
+
+ /* All the cases using the raw line.
+ TODO: I don't understand what the difference is between these.
+ LINE_skipline is used where the command takes no argument at all. */
+ if (arg_spec == LINE_skipline || arg_spec == LINE_lineraw
+ || arg_spec == LINE_special)
+ {
+ ELEMENT *args = 0;
+ enum command_id equivalent_cmd = 0;
+ int has_comment = 0;
+ int ignored = 0;
+
+ if (cmd == CM_insertcopying)
+ {
+ ELEMENT *p = current;
+ while (p)
+ {
+ if (p->cmd == CM_copying)
+ {
+ line_error ("@%s not allowed inside `@copying' block",
+ command_name(cmd));
+ ignored = 1;
+ break;
+ }
+ p = p->parent;
+ }
+ }
+
+ /* If the current input is the result of a macro expansion,
+ it may not be a complete line. Check for this and acquire the rest
+ of the line if necessary. */
+ if (!strchr (line, '\n'))
+ {
+ char *line2;
+ LINE_NR save_ln;
+
+ input_push_text (strdup (line), 0);
+
+ save_ln = line_nr;
+ line2 = new_line ();
+ if (line2)
+ {
+ line = line2;
+ line_nr = save_ln;
+ }
+ }
+
+ misc = new_element (ET_NONE);
+ misc->cmd = cmd;
+
+ if (arg_spec == LINE_skipline || arg_spec == LINE_lineraw)
+ {
+ ELEMENT *arg;
+ args = new_element (ET_NONE);
+ arg = new_element (ET_NONE);
+ add_to_element_contents (args, arg);
+ text_append (&arg->text, line);
+ }
+ else /* arg_spec == LINE_special */
+ {
+ args = parse_special_misc_command (line, cmd, &has_comment);
+ add_extra_string (misc, "arg_line", strdup (line));
+ }
+
+ /* Handle @set txicodequoteundirected as an
+ alternative to @codequoteundirected. */
+ if (cmd == CM_set || cmd == CM_clear)
+ {
+ if (args->contents.number > 0
+ && args->contents.list[0]->text.end > 0)
+ {
+ if (!strcmp (args->contents.list[0]->text.text,
+ "txicodequoteundirected"))
+ equivalent_cmd = CM_codequoteundirected;
+ else if (!strcmp (args->contents.list[0]->text.text,
+ "txicodequotebacktick"))
+ equivalent_cmd = CM_codequotebacktick;
+ }
+ }
+ if (equivalent_cmd)
+ {
+ char *arg = 0;
+ ELEMENT *line_args;
+ ELEMENT *e;
+
+ if (cmd == CM_set)
+ arg = "on";
+ else
+ arg = "off";
+
+ /* Now manufacture the parse tree for the equivalent
+ command and add it to the tree. */
+
+ destroy_element_and_children (args);
+ args = new_element (ET_NONE);
+ e = new_element (ET_NONE);
+ text_append (&e->text, arg);
+ add_to_element_contents (args, e);
+
+ destroy_element_and_children (misc);
+ misc = new_element (ET_NONE);
+ misc->cmd = equivalent_cmd;
+ misc->line_nr = line_nr;
+
+ line_args = new_element (ET_line_arg);
+ add_to_element_args (misc, line_args);
+ add_extra_misc_args (misc, "misc_args", args);
+
+ add_extra_string_dup (misc, "spaces_before_argument", " ");
+
+ e = new_element (ET_NONE);
+ text_append (&e->text, arg);
+ add_to_element_contents (line_args, e);
+
+ e = new_element (ET_spaces_at_end);
+ text_append_n (&e->text, "\n", 1);
+ add_to_element_contents (line_args, e);
+
+ add_to_element_contents (current, misc);
+ }
+ else
+ {
+ int i;
+ if (!ignored)
+ {
+ add_to_element_contents (current, misc);
+
+ for (i = 0; i < args->contents.number; i++)
+ {
+ ELEMENT *misc_arg = new_element (ET_misc_arg);
+ text_append_n (&misc_arg->text,
+ args->contents.list[i]->text.text,
+ args->contents.list[i]->text.end);
+ add_to_element_args (misc, misc_arg);
+ }
+ /* TODO: Could we have just set misc->args directly as args? */
+ if (args->contents.number > 0 && arg_spec != LINE_skipline)
+ add_extra_misc_args (misc, "misc_args", args);
+ else
+ destroy_element_and_children (args);
+ }
+ else
+ {
+ destroy_element_and_children (misc);
+ destroy_element_and_children (args);
+ misc = 0;
+ }
+ }
+
+ if (cmd == CM_raisesections)
+ {
+ global_info.sections_level++;
+ }
+ else if (cmd == CM_lowersections)
+ {
+ global_info.sections_level--;
+ }
+ else if (cmd == CM_novalidate)
+ {
+ /* do nothing - novalidate is set in build_global_info */
+ }
+
+ if (misc)
+ register_global_command (misc);
+
+ if (arg_spec != LINE_special || !has_comment)
+ current = end_line (current);
+
+ if (cmd == CM_bye)
+ {
+ *status = FINISHED_TOTALLY;
+ goto funexit;
+ }
+
+ if (close_preformatted_command(cmd))
+ current = begin_preformatted (current);
+
+ *status = GET_A_NEW_LINE;
+ goto funexit;
+ }
+ else
+ {
+ ELEMENT *arg;
+
+ /* text, line, or a number.
+ (This includes handling of "@end", which is LINE_text.) */
+ if (cmd == CM_item_LINE || cmd == CM_itemx)
+ {
+ ELEMENT *parent;
+ if (parent = item_line_parent (current))
+ {
+ debug ("ITEM_LINE");
+ current = parent;
+ gather_previous_item (current, cmd);
+ }
+ else
+ {
+ line_error ("@%s outside of table or list",
+ cmd == CM_item_LINE ? "item" : "itemx");
+ current = begin_preformatted (current);
+ }
+ misc = new_element (ET_NONE);
+ misc->cmd = (cmd == CM_item_LINE) ? CM_item : CM_itemx;
+ misc->line_nr = line_nr;
+ add_to_element_contents (current, misc);
+ }
+ else
+ {
+ /* Add to contents */
+ misc = new_element (ET_NONE);
+ misc->cmd = cmd;
+ misc->line_nr = line_nr;
+ add_to_element_contents (current, misc);
+
+ if (command_data(cmd).flags & CF_sectioning)
+ {
+ if (global_info.sections_level)
+ {
+ add_extra_integer (misc, "sections_level",
+ global_info.sections_level);
+ }
+ }
+
+ /* @def*x */
+ if (command_data(cmd).flags & CF_def)
+ {
+ enum command_id base_command;
+ char *base_name;
+ int base_len;
+ int after_paragraph;
+
+ /* Find the command with "x" stripped from the end, e.g.
+ deffnx -> deffn. */
+ base_name = command_name(cmd);
+ add_extra_string_dup (misc, "original_def_cmdname", base_name);
+
+ base_name = strdup (base_name);
+ base_len = strlen (base_name);
+ if (base_name[base_len - 1] != 'x')
+ abort ();
+ base_name[base_len - 1] = '\0';
+ base_command = lookup_command (base_name);
+ if (base_command == CM_NONE)
+ abort ();
+ add_extra_string (misc, "def_command", base_name);
+
+ after_paragraph = check_no_text (current);
+ push_context (ct_def);
+ misc->type = ET_def_line;
+ if (current->cmd == base_command)
+ {
+ ELEMENT *e = pop_element_from_contents (current);
+ /* e should be the same as misc */
+ /* Gather an "inter_def_item" element. */
+ gather_def_item (current, cmd);
+ add_to_element_contents (current, e);
+ }
+ if (current->cmd != base_command || after_paragraph)
+ {
+ // error - deffnx not after deffn
+ line_error ("must be after `@%s' to use `@%s'",
+ command_name(base_command),
+ command_name(cmd));
+ add_extra_integer (misc, "not_after_command", 1);
+ }
+ }
+ }
+
+ /* change 'current' to its last child. This is ELEMENT *misc above. */
+ current = last_contents_child (current);
+ arg = new_element (ET_line_arg);
+ add_to_element_args (current, arg);
+
+ if (cmd == CM_node)
+ {
+ /* At most three comma-separated arguments to @node. This
+ is the only (non-block) line command taking comma-separated
+ arguments. Its arguments will be gathered the same as
+ those of some block line commands and brace commands. */
+ counter_push (&count_remaining_args, current, 3);
+ }
+ else if (cmd == CM_author)
+ {
+ ELEMENT *parent = current;
+ int found = 0;
+ while (parent->parent)
+ {
+ parent = parent->parent;
+ if (parent->type == ET_brace_command_context)
+ break;
+ if (parent->cmd == CM_titlepage)
+ {
+ add_extra_element (current, "titlepage", parent);
+ found = 1; break;
+ }
+ else if (parent->cmd == CM_quotation
+ || parent->cmd == CM_smallquotation)
+ {
+ KEY_PAIR *k; ELEMENT *e;
+ k = lookup_extra (parent, "authors");
+ if (k)
+ e = k->value;
+ else
+ {
+ e = new_element (ET_NONE);
+ add_extra_contents (parent, "authors", e);
+ }
+ add_to_contents_as_array (e, current);
+ add_extra_element (current, "quotation", parent);
+ found = 1; break;
+ }
+ }
+ if (!found)
+ line_warn ("@author not meaningful outside "
+ "`@titlepage' and `@quotation' environments");
+ }
+ else if (cmd == CM_dircategory && current_node)
+ line_warn ("@dircategory after first node");
+
+ current = last_args_child (current);
+
+ /* add 'line' to context_stack. This will be the
+ case while we read the argument on this line. */
+ if (!(command_data(cmd).flags & CF_def))
+ push_context (ct_line);
+ start_empty_line_after_command (current, &line, misc);
+ }
+
+ if (misc)
+ register_global_command (misc);
+ if (cmd == CM_dircategory)
+ add_to_contents_as_array (&global_info.dircategory_direntry, misc);
+
+funexit:
+ *line_inout = line;
+ return current;
+}
+
+
+struct expanded_format {
+ char *format;
+ int expandedp;
+};
+static struct expanded_format expanded_formats[] = {
+ "html", 0,
+ "docbook", 0,
+ "plaintext", 1,
+ "tex", 0,
+ "xml", 0,
+ "info", 1,
+};
+
+void
+clear_expanded_formats (void)
+{
+ int i;
+ for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
+ i++)
+ {
+ expanded_formats[i].expandedp = 0;
+ }
+}
+
+void
+add_expanded_format (char *format)
+{
+ int i;
+ for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
+ i++)
+ {
+ if (!strcmp (format, expanded_formats[i].format))
+ {
+ expanded_formats[i].expandedp = 1;
+ break;
+ }
+ }
+ if (!strcmp (format, "plaintext"))
+ add_expanded_format ("info");
+}
+
+int
+format_expanded_p (char *format)
+{
+ int i;
+ for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
+ i++)
+ {
+ if (!strcmp (format, expanded_formats[i].format))
+ return expanded_formats[i].expandedp;
+ }
+ return 0;
+}
+
+/* A command name has been read that starts a multiline block, which should
+ end in @end <command name>. The block will be processed until
+ "end_line_misc_line" in end_line.c processes the @end command. */
+ELEMENT *
+handle_block_command (ELEMENT *current, char **line_inout,
+ enum command_id cmd, int *get_new_line)
+{
+ char *line = *line_inout;
+ unsigned long flags = command_data(cmd).flags;
+
+ /* New macro being defined. */
+ if (cmd == CM_macro || cmd == CM_rmacro)
+ {
+ ELEMENT *macro;
+ macro = parse_macro_command_line (cmd, &line, current);
+ add_to_element_contents (current, macro);
+ current = macro;
+
+ /* A new line should be read immediately after this. */
+ line = strchr (line, '\0');
+ *get_new_line = 1;
+ goto funexit;
+ }
+ else if (command_data(cmd).data == BLOCK_conditional)
+ {
+ int iftrue = 0; /* Whether the conditional is true. */
+ if (cmd == CM_ifclear || cmd == CM_ifset
+ || cmd == CM_ifcommanddefined || cmd == CM_ifcommandnotdefined)
+ {
+ char *p = line;
+ p = line + strspn (line, whitespace_chars);
+ if (!*p)
+ line_error ("@%s requires a name", command_name(cmd));
+ else
+ {
+ char *flag = read_command_name (&p);
+ if (!flag)
+ goto bad_value;
+ else
+ {
+ p += strspn (p, whitespace_chars);
+ /* Check for a comment at the end of the line. */
+ if (memcmp (p, "@c", 2) == 0)
+ {
+ p += 2;
+ if (memcmp (p, "omment", 6) == 0)
+ p += 7;
+ if (*p && *p != '@' && !strchr (whitespace_chars, *p))
+ goto bad_value; /* @c or @comment not terminated. */
+ }
+ else if (*p)
+ goto bad_value; /* Trailing characters on line. */
+ }
+ if (1)
+ {
+ if (cmd == CM_ifclear || cmd == CM_ifset)
+ {
+ char *val = fetch_value (flag, strlen (flag));
+ if (val)
+ iftrue = 1;
+ if (cmd == CM_ifclear)
+ iftrue = !iftrue;
+ }
+ else /* cmd == CM_ifcommanddefined
+ || cmd == CM_ifcommandnotdefined */
+ {
+ enum command_id c = lookup_command (flag);
+ if (c)
+ iftrue = 1;
+ if (cmd == CM_ifcommandnotdefined)
+ iftrue = !iftrue;
+ }
+ }
+ else if (0)
+ {
+ bad_value:
+ line_error ("bad name for @%s", command_name(cmd));
+ }
+ free (flag);
+ }
+ }
+ else if (!memcmp (command_name(cmd), "if", 2)) /* e.g. @ifhtml */
+ {
+ int i; char *p;
+ /* Handle @if* and @ifnot* */
+
+ p = command_name(cmd) + 2; /* After "if". */
+ if (!memcmp (p, "not", 3))
+ p += 3; /* After "not". */
+ for (i = 0; i < sizeof (expanded_formats)/sizeof (*expanded_formats);
+ i++)
+ {
+ if (!strcmp (p, expanded_formats[i].format))
+ {
+ iftrue = expanded_formats[i].expandedp;
+ break;
+ }
+ }
+ if (!memcmp (command_name(cmd), "ifnot", 5))
+ iftrue = !iftrue;
+ }
+ else
+ bug_message ("unknown conditional command @%s", command_name(cmd));
+
+
+ /* If conditional true, push onto conditional stack. Otherwise
+ open a new element (which we shall later remove, in
+ process_remaining_on_line ("CLOSED conditional")). */
+
+ debug ("CONDITIONAL %s %d", command_name(cmd), iftrue);
+ if (iftrue)
+ push_conditional_stack (cmd);
+ else
+ {
+ /* Ignored. */
+ ELEMENT *e;
+ e = new_element (ET_NONE);
+ e->cmd = cmd;
+ add_to_element_contents (current, e);
+ current = e;
+ }
+ line = strchr (line, '\0');
+ *get_new_line = 1;
+ goto funexit;
+ }
+ else
+ {
+ ELEMENT *block = 0;
+ if (flags & CF_menu
+ && (current->type == ET_menu_comment
+ || current->type == ET_menu_entry_description))
+ {
+ /* This is for @detailmenu within @menu */
+ ELEMENT *menu = current->parent;
+ if (current->contents.number == 0)
+ destroy_element (pop_element_from_contents (menu));
+
+ if (pop_context () != ct_preformatted)
+ abort ();
+ if (menu->type == ET_menu_entry)
+ menu = menu->parent;
+ current = menu;
+ }
+
+ if (flags & CF_def)
+ {
+ ELEMENT *def_line;
+ push_context (ct_def);
+ block = new_element (ET_NONE);
+ block->cmd = cmd;
+ block->line_nr = line_nr;
+ add_to_element_contents (current, block);
+ current = block;
+ def_line = new_element (ET_def_line);
+ def_line->line_nr = line_nr;
+ add_to_element_contents (current, def_line);
+ current = def_line;
+ add_extra_string_dup (current, "def_command", command_name(cmd));
+ add_extra_string_dup (current, "original_def_cmdname",
+ command_name(cmd));
+ }
+ else
+ {
+ block = new_element (ET_NONE);
+
+ block->cmd = cmd;
+ add_to_element_contents (current, block);
+ current = block;
+ }
+
+ /* Check if 'block args command' */
+ if (command_data(cmd).data != BLOCK_raw)
+ {
+ if (command_data(cmd).flags & CF_preformatted)
+ push_context (ct_preformatted);
+ else if (command_data(cmd).flags & CF_format_raw)
+ {
+ push_context (ct_rawpreformatted);
+ if (!format_expanded_p (command_name(cmd)))
+ {
+ ELEMENT *e;
+ enum command_id dummy;
+ char *line_dummy;
+
+ e = new_element (ET_elided_block);
+ add_to_element_contents (current, e);
+ line_dummy = line;
+ while (!is_end_current_command (current,
+ &line_dummy, &dummy))
+ {
+ line = new_line ();
+ if (!line)
+ {
+ line = "";
+ break;
+ }
+ line_dummy = line;
+ }
+ e = new_element (ET_empty_line_after_command);
+ text_append_n (&e->text, "\n", 1);
+ add_to_element_contents (current, e);
+
+ e = new_element (ET_empty_line);
+ text_append (&e->text, "");
+ add_to_element_contents (current, e);
+ goto funexit;
+ }
+ }
+ else if (command_data(cmd).data == BLOCK_region)
+ {
+ if (current_region_cmd ())
+ {
+ line_error ("region %s inside region %s is not allowed",
+ command_name(cmd),
+ command_name(current_region_cmd ()));
+ }
+ push_region (block);
+ }
+
+ if (command_data(cmd).flags & CF_menu)
+ {
+ if (current_context () == ct_preformatted)
+ push_context (ct_preformatted);
+ else
+ push_context (ct_menu);
+
+ if (cmd == CM_direntry)
+ add_to_contents_as_array (&global_info.dircategory_direntry,
+ block);
+
+ if (current_node)
+ {
+ if (cmd == CM_direntry && conf.show_menu)
+ {
+ line_warn ("@direntry after first node");
+ }
+ else if (cmd == CM_menu)
+ {
+ if (!(command_flags(current->parent) & CF_root))
+ line_warn ("@menu in invalid context");
+ /* Add to array of menus for current node. Currently
+ done in Perl code. */
+ }
+ }
+ else if (cmd != CM_direntry)
+ {
+ if (conf.show_menu)
+ {
+ line_error ("@%s seen before first @node",
+ command_name(cmd));
+ line_error ("perhaps your @top node should be "
+ "wrapped in @ifnottex rather than @ifinfo?");
+ }
+ // 4810 unassociated menus
+ }
+ }
+
+ if (cmd == CM_itemize || cmd == CM_enumerate)
+ counter_push (&count_items, current, 0);
+ /* Note that no equivalent thing is done in the Perl code, because
+ 'item_count' is assumed to start at 0. */
+
+ {
+ ELEMENT *bla = new_element (ET_block_line_arg);
+ add_to_element_args (current, bla);
+
+ if (command_data (current->cmd).data > 1)
+ {
+ counter_push (&count_remaining_args,
+ current,
+ command_data (current->cmd).data - 1);
+ }
+
+ current = bla;
+ if (!(command_data(cmd).flags & CF_def))
+ push_context (ct_line);
+
+ /* Note that an ET_empty_line_after_command gets reparented in the
+ contents in 'end_line'. */
+
+ }
+ }
+ block->line_nr = line_nr;
+ register_global_command (block);
+ start_empty_line_after_command (current, &line, block);
+ }
+
+funexit:
+ *line_inout = line;
+ return current;
+}
+
+ELEMENT *
+handle_brace_command (ELEMENT *current, char **line_inout, enum command_id cmd)
+{
+ char *line = *line_inout;
+ ELEMENT *e;
+
+ e = new_element (ET_NONE);
+ e->cmd = cmd;
+
+ /* The line number information is only ever used for brace commands
+ if the command is given with braces, but it's easier just to always
+ store the information. */
+ e->line_nr = line_nr;
+
+ add_to_element_contents (current, e);
+
+ if (cmd == CM_sortas)
+ {
+ if (!(command_flags(current->parent) & CF_index_entry_command))
+ {
+ line_warn ("@%s should only appear in an index entry",
+ command_name(cmd));
+ }
+ }
+
+ current = e;
+
+ if (cmd == CM_click)
+ {
+ add_extra_string_dup (e, "clickstyle", global_clickstyle);
+ }
+ else if (cmd == CM_kbd)
+ {
+ if (current_context () == ct_preformatted
+ && global_kbdinputstyle != kbd_distinct
+ || global_kbdinputstyle == kbd_code)
+ {
+ add_extra_integer (e, "code", 1);
+ }
+ else if (global_kbdinputstyle == kbd_example)
+ {
+ /* TODO: Understand what is going on here. */
+ ELEMENT *tmp = current->parent;
+ while (tmp->parent
+ && (command_flags(tmp->parent) & CF_brace)
+ && command_data(tmp->parent->cmd).data != BRACE_context)
+ {
+ if (command_flags(tmp->parent) & CF_code_style)
+ {
+ add_extra_integer (e, "code", 1);
+ break;
+ }
+ tmp = tmp->parent->parent;
+ }
+ }
+ }
+ else if (command_data(cmd).flags & CF_INFOENCLOSE)
+ {
+ INFO_ENCLOSE *ie = lookup_infoenclose (cmd);
+ if (ie)
+ {
+ add_extra_string_dup (e, "begin", ie->begin);
+ add_extra_string_dup (e, "end", ie->end);
+ }
+ e->type = ET_definfoenclose_command;
+ }
+
+ *line_inout = line;
+ return current;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.h
new file mode 100644
index 0000000000..01847d1887
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/handle_commands.h
@@ -0,0 +1,14 @@
+/* handle_commands.h - declarations for handle_commands.c */
+
+ELEMENT *handle_other_command (ELEMENT *current, char **line_inout,
+ enum command_id cmd_id, int *status);
+ELEMENT *handle_line_command (ELEMENT *current, char **line_inout,
+ enum command_id cmd_id, int *status);
+ELEMENT *handle_block_command (ELEMENT *current, char **line_inout,
+ enum command_id cmd_id, int *new_line);
+ELEMENT *handle_brace_command (ELEMENT *current, char **line_inout,
+ enum command_id cmd_id);
+int check_no_text (ELEMENT *current);
+
+void clear_expanded_formats (void);
+void add_expanded_format (char *format);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.c
new file mode 100644
index 0000000000..35051094e0
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.c
@@ -0,0 +1,334 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "parser.h"
+#include "indices.h"
+
+INDEX **index_names = 0;
+int number_of_indices = 0;
+int space_for_indices = 0;
+
+typedef struct {
+ enum command_id cmd;
+ INDEX *idx;
+} CMD_TO_IDX;
+
+/* Array mapping Texinfo commands to index data structures. */
+static CMD_TO_IDX *cmd_to_idx = 0;
+static size_t num_index_commands = 0;
+static size_t cmd_to_idx_space = 0;
+
+static void
+associate_command_to_index (enum command_id cmd, INDEX *idx)
+{
+ if (num_index_commands == cmd_to_idx_space)
+ {
+ cmd_to_idx = realloc (cmd_to_idx,
+ sizeof (CMD_TO_IDX) * (cmd_to_idx_space += 10));
+ if (!cmd_to_idx)
+ abort ();
+ }
+
+ cmd_to_idx[num_index_commands].cmd = cmd;
+ cmd_to_idx[num_index_commands++].idx = idx;
+}
+
+/* Get the index associated with CMD. */
+INDEX *
+index_of_command (enum command_id cmd)
+{
+ int i;
+
+ for (i = 0; i < num_index_commands; i++)
+ {
+ if (cmd_to_idx[i].cmd == cmd)
+ return cmd_to_idx[i].idx;
+ }
+ return 0;
+}
+
+
+/* Save a new Texinfo command with the name CMDNAME and record that it
+ creates index entries in IDX. */
+static void
+add_index_command (char *cmdname, INDEX *idx)
+{
+ enum command_id new = add_texinfo_command (cmdname);
+ user_defined_command_data[new & ~USER_COMMAND_BIT].flags
+ = CF_line | CF_index_entry_command;
+ user_defined_command_data[new & ~USER_COMMAND_BIT].data = LINE_line;
+ associate_command_to_index (new, idx);
+}
+
+static INDEX *
+add_index_internal (char *name, int in_code)
+{
+ INDEX *idx = malloc (sizeof (INDEX));
+
+ memset (idx, 0, sizeof *idx);
+ idx->name = name;
+ idx->prefix = name;
+ idx->in_code = in_code;
+ if (number_of_indices == space_for_indices)
+ {
+ space_for_indices += 5;
+ index_names = realloc (index_names, (space_for_indices + 1)
+ * sizeof (INDEX *));
+ }
+ index_names[number_of_indices++] = idx;
+ index_names[number_of_indices] = 0;
+ return idx;
+}
+
+/* NAME is the name of an index, e.g. "cp" */
+INDEX *
+index_by_name (char *name)
+{
+ int i;
+
+ for (i = 0; i < number_of_indices; i++)
+ {
+ if (!strcmp (index_names[i]->name, name))
+ return index_names[i];
+ }
+ return 0;
+}
+
+
+/* Add a user defined index with the name NAME */
+void
+add_index (char *name, int in_code)
+{
+ INDEX *idx;
+ char *cmdname;
+
+ idx = add_index_internal (name, in_code);
+
+ /* For example, "rq" -> "rqindex". */
+ asprintf (&cmdname, "%s%s", name, "index");
+ add_index_command (cmdname, idx);
+ free (cmdname);
+}
+
+static void
+wipe_index (INDEX *idx)
+{
+ int i;
+ INDEX_ENTRY *ie;
+ for (i = 0; i < idx->index_number; i++)
+ {
+ ie = &idx->index_entries[i];
+ /* Destroy element if it is not in the main tree */
+ if (ie->content && !ie->content->parent)
+ destroy_element (ie->content);
+ }
+ free (idx->name);
+ free (idx->index_entries);
+}
+
+void
+wipe_indices (void)
+{
+ int i;
+ for (i = 0; i < number_of_indices; i++)
+ {
+ wipe_index (index_names[i]);
+ free (index_names[i]);
+ }
+ number_of_indices = 0;
+ return;
+}
+
+void
+init_index_commands (void)
+{
+ INDEX *idx;
+
+ struct def { char *name; int in_code; }
+ *p, default_indices[] = {
+ "cp", 0, /* concepts */
+ "fn", 1, /* functions */
+ "vr", 1, /* variables */
+ "ky", 1, /* keystrokes */
+ "pg", 1, /* programs */
+ "tp", 1, /* types */
+ 0, 0
+ };
+ int i, j;
+
+ char name[] = "?index";
+ char name2[] = "??index";
+
+#define MAX (10 * 2)
+
+#define X(command) CM_##command, CM_##command##x
+ struct def_cmds { char *name; enum command_id id[MAX]; }
+ def_command_indices[] = {
+ "fn",
+
+ {X(deffn),
+ X(deftypefn),
+ X(deftypeop),
+ X(defop),
+ X(defun),
+ X(defmac),
+ X(defspec),
+ X(deftypefun),
+ X(defmethod),
+ X(deftypemethod),
+ },
+
+ "vr",
+
+ {X(defvr),
+ X(deftypevr),
+ X(defcv),
+ X(deftypecv),
+ X(defvar),
+ X(defivar),
+ X(defopt),
+ X(deftypevar),
+ X(deftypeivar),
+ },
+
+ "tp",
+
+ {X(deftp),}
+ };
+#undef X
+
+ number_of_indices = 0;
+ num_index_commands = 0;
+
+ for (p = default_indices; p->name; p++)
+ {
+ /* Both @cindex and @cpindex are added. */
+ idx = add_index_internal (strdup (p->name), p->in_code);
+
+ *name = p->name[0];
+ add_index_command (name, idx); /* @cindex */
+
+ name2[0] = p->name[0];
+ name2[1] = p->name[1];
+ add_index_command (name2, idx); /* @cpindex */
+ }
+
+ associate_command_to_index (CM_vtable, index_by_name ("vr"));
+ associate_command_to_index (CM_ftable, index_by_name ("fn"));
+
+ for (i = 0;
+ i < sizeof (def_command_indices) / sizeof (def_command_indices[0]);
+ i++)
+ {
+ enum command_id cmd;
+ idx = index_by_name (def_command_indices[i].name);
+ if (idx)
+ {
+ for (j = 0; j < MAX; j++)
+ {
+ cmd = def_command_indices[i].id[j];
+ if (cmd)
+ associate_command_to_index (cmd, idx);
+ }
+ }
+ }
+#undef MAX
+}
+
+
+/* A reference to an index entry, in the "index_entry" extra key of
+ an element. index->index_entries[entry] is the referred-to index
+ entry. Not actually used in api.c (element_to_perl_hash). */
+typedef struct {
+ INDEX *index;
+ int entry;
+} INDEX_ENTRY_REF;
+
+
+/* INDEX_TYPE_COMMAND is used to determine which index to enter the entry in.
+ INDEX_AT_COMMAND is the Texinfo @-command defining the index entry.
+ CONTENT is an element whose contents represent the text of the
+ index entry. CURRENT is the element in the main body of the manual that
+ the index entry refers to.
+
+ CONTENT_NORMALIZED would be "the index entry content, independent
+ of the current language." */
+void
+enter_index_entry (enum command_id index_type_command,
+ enum command_id index_at_command,
+ ELEMENT *current, ELEMENT *content)
+{
+ INDEX *idx;
+ INDEX_ENTRY *entry;
+ KEY_PAIR *k;
+
+ idx = index_of_command (index_type_command);
+ if (idx->index_number == idx->index_space)
+ {
+ idx->index_entries = realloc (idx->index_entries,
+ sizeof (INDEX_ENTRY) * (idx->index_space += 20));
+ if (!idx->index_entries)
+ abort ();
+ }
+ entry = &idx->index_entries[idx->index_number++];
+ memset (entry, 0, sizeof (INDEX_ENTRY));
+
+ entry->index_name = idx->name;
+ entry->index_at_command = index_at_command;
+ entry->index_type_command = index_type_command;
+ entry->index_prefix = idx->prefix;
+ entry->content = content;
+ entry->command = current;
+ entry->number = idx->index_number;
+
+ k = lookup_extra (current, "sortas");
+ if (k)
+ entry->sortas = (char *) k->value;
+
+ if (current_region ())
+ entry->region = current_region ();
+ else
+ entry->node = current_node;
+
+ entry->number = idx->index_number;
+
+#if 0
+ /* This reference is not used in api.c when the Perl tree is output. */
+ {
+ INDEX_ENTRY_REF *ier;
+ ier = malloc (sizeof (INDEX_ENTRY_REF));
+ ier->index = idx;
+ ier->entry = idx->index_number - 1;
+
+ add_extra_index_entry (current, "index_entry", ier);
+ }
+#endif
+
+ if (!current_region () && !current_node && !current_section)
+ line_warn ("entry for index `%s' outside of any node", idx->name);
+}
+
+
+INDEX *
+ultimate_index (INDEX *index)
+{
+ while (index->merged_in)
+ index = index->merged_in;
+ return index;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.h
new file mode 100644
index 0000000000..fa960b186f
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/indices.h
@@ -0,0 +1,14 @@
+/* indices.h - declarations for indices.h */
+
+extern INDEX **index_names;
+
+void init_index_commands (void);
+
+INDEX *index_by_name (char *name);
+void add_index (char *name, int in_code);
+INDEX *index_of_command (enum command_id cmd);
+void enter_index_entry (enum command_id index_type_command,
+ enum command_id index_at_command, ELEMENT *current,
+ ELEMENT *content);
+INDEX *ultimate_index (INDEX *index);
+void wipe_indices (void);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.c
new file mode 100644
index 0000000000..cad1edc475
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.c
@@ -0,0 +1,586 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <iconv.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "tree_types.h"
+#include "input.h"
+#include "text.h"
+#include "commands.h"
+
+enum input_type { IN_file, IN_text };
+
+enum character_encoding {
+ ce_latin1,
+ ce_latin2,
+ ce_utf8,
+ ce_shiftjis
+};
+
+typedef struct {
+ enum input_type type;
+
+ FILE *file;
+ char *input_encoding;
+ LINE_NR line_nr;
+
+ char *text; /* Input text to be parsed as Texinfo. */
+ char *ptext; /* How far we are through 'text'. Used to split 'text'
+ into lines. */
+} INPUT;
+
+static INPUT *input_stack = 0;
+int input_number = 0;
+int input_space = 0;
+
+/* Current filename and line number. Used for reporting. */
+LINE_NR line_nr;
+
+/* Change the line number of filename of the top input source. Used to
+ record a #line directive. If FILENAME is non-null, it should hbae
+ been returned from save_string. */
+void
+save_line_directive (int line_nr, char *filename)
+{
+ INPUT *top = &input_stack[input_number - 1];
+ if (line_nr)
+ top->line_nr.line_nr = line_nr;
+ if (filename)
+ top->line_nr.file_name = filename;
+}
+
+/* Collect text from the input sources until a newline is found. This is used
+ instead of next_text when we need to be sure we get an entire line of
+ Texinfo input (for example as a line argument to a command), which might not
+ be the case if the input is the result of a macro expansion.
+
+ Return value should not be freed by caller, and becomes invalid after
+ a subsequent call. */
+char *
+new_line (void)
+{
+ static TEXT t;
+ char *new = 0;
+
+ t.end = 0;
+
+ while (1)
+ {
+ new = next_text ();
+ if (!new)
+ break;
+ text_append (&t, new);
+ free (new);
+
+ if (t.text[t.end - 1] == '\n')
+ break;
+ }
+
+ if (t.end > 0)
+ return t.text;
+ else
+ return 0;
+}
+
+
+static iconv_t iconv_from_latin1 = (iconv_t) 0;
+static iconv_t iconv_from_latin2;
+static iconv_t iconv_from_shiftjis;
+
+/* Run iconv using text buffer as output buffer. */
+size_t
+text_buffer_iconv (TEXT *buf, iconv_t iconv_state,
+ ICONV_CONST char **inbuf, size_t *inbytesleft)
+{
+ size_t out_bytes_left;
+ char *outptr;
+ size_t iconv_ret;
+
+ outptr = buf->text + buf->end;
+ if (buf->end == buf->space - 1)
+ {
+ errno = E2BIG;
+ return (size_t) -1;
+ }
+ out_bytes_left = buf->space - buf->end - 1;
+ iconv_ret = iconv (iconv_state, inbuf, inbytesleft,
+ &outptr, &out_bytes_left);
+
+ buf->end = outptr - buf->text;
+
+ return iconv_ret;
+}
+
+
+
+/* Return conversion of S according to ENC. This function frees S. */
+static char *
+convert_to_utf8 (char *s, char *input_encoding)
+{
+ iconv_t our_iconv;
+ static TEXT t;
+ ICONV_CONST char *inptr; size_t bytes_left;
+ size_t iconv_ret;
+ enum character_encoding enc;
+
+ /* Convert from @documentencoding to UTF-8.
+ It might be possible not to convert to UTF-8 and use an 8-bit encoding
+ throughout, but then we'd have to not set the UTF-8 flag on the Perl
+ strings in api.c. If multiple character encodings were used in a single
+ file, then we'd have to keep track of which strings needed the UTF-8 flag
+ and which didn't. */
+
+ /* Could and check for malformed input: see
+ <http://savannah.gnu.org/bugs/?42896>. */
+
+ if (iconv_from_latin1 == (iconv_t) 0)
+ {
+ /* Initialize the conversion for the first time. */
+ iconv_from_latin1 = iconv_open ("UTF-8", "ISO-8859-1");
+ if (iconv_from_latin1 == (iconv_t) -1)
+ {
+ abort ();
+
+ /* big trouble. if we do return it unconverted, we will have to
+ remember not to set the UTF-8 flags on the Perl strings, otherwise
+ Perl will choke. */
+ return s;
+ }
+ }
+ if (iconv_from_latin2 == (iconv_t) 0)
+ {
+ /* Initialize the conversion for the first time. */
+ iconv_from_latin2 = iconv_open ("UTF-8", "ISO-8859-2");
+ if (iconv_from_latin2 == (iconv_t) -1)
+ iconv_from_latin2 = iconv_from_latin1;
+ }
+ if (iconv_from_shiftjis == (iconv_t) 0)
+ {
+ /* Initialize the conversion for the first time. */
+ iconv_from_shiftjis = iconv_open ("UTF-8", "SHIFT-JIS");
+ if (iconv_from_shiftjis == (iconv_t) -1)
+ iconv_from_shiftjis = iconv_from_latin1;
+ }
+
+ enc = ce_latin1;
+ if (!input_encoding)
+ ;
+ else if (!strcmp (input_encoding, "utf-8"))
+ enc = ce_utf8;
+ else if (!strcmp (input_encoding, "iso-8859-2"))
+ enc = ce_latin2;
+ else if (!strcmp (input_encoding, "shift_jis"))
+ enc = ce_shiftjis;
+
+ switch (enc)
+ {
+ case ce_utf8:
+ return s; /* no conversion required. */
+ break;
+ case ce_latin1:
+ our_iconv = iconv_from_latin1;
+ break;
+ case ce_latin2:
+ our_iconv = iconv_from_latin2;
+ break;
+ case ce_shiftjis:
+ our_iconv = iconv_from_shiftjis;
+ break;
+ }
+
+ t.end = 0;
+ inptr = s;
+ bytes_left = strlen (s);
+ text_alloc (&t, 10);
+
+ while (1)
+ {
+ iconv_ret = text_buffer_iconv (&t, our_iconv,
+ &inptr, &bytes_left);
+
+ /* Make sure libiconv flushes out the last converted character.
+ This is required when the conversion is stateful, in which
+ case libiconv might not output the last character, waiting to
+ see whether it should be combined with the next one. */
+ if (iconv_ret != (size_t) -1
+ && text_buffer_iconv (&t, our_iconv, 0, 0) != (size_t) -1)
+ /* Success: all of input converted. */
+ break;
+
+ switch (errno)
+ {
+ case E2BIG:
+ text_alloc (&t, t.space + 20);
+ break;
+ default:
+ abort ();
+ break;
+ }
+ }
+
+ free (s);
+ t.text[t.end] = '\0';
+ //fprintf (stderr, "CONVERTED STRING IS <<%s>>", t.text);
+ return strdup (t.text);
+}
+
+int
+expanding_macro (char *macro)
+{
+ int i;
+ for (i = 0; i < input_number; i++)
+ {
+ if (input_stack[i].line_nr.macro
+ && !strcmp (input_stack[i].line_nr.macro, macro))
+ {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+char *save_string (char *string);
+
+/* Return value to be freed by caller. Return null if we are out of input. */
+char *
+next_text (void)
+{
+ ssize_t status;
+ char *line = 0;
+ size_t n;
+ FILE *input_file;
+
+ while (input_number > 0)
+ {
+ /* Check for pending input. */
+ INPUT *i = &input_stack[input_number - 1];
+
+ switch (i->type)
+ {
+ char *p, *new;
+ case IN_text:
+ if (!*i->ptext)
+ {
+ /* End of text reached. */
+ free (i->text);
+ break;
+ }
+ /* Split off a line of input. */
+ p = strchrnul (i->ptext, '\n');
+ new = strndup (i->ptext, p - i->ptext + 1);
+ if (*p)
+ i->ptext = p + 1;
+ else
+ i->ptext = p; /* The next time, we will pop the input source. */
+
+ if (!i->line_nr.macro)
+ i->line_nr.line_nr++;
+
+ line_nr = i->line_nr;
+
+ return new;
+
+ break;
+ case IN_file: // 1911
+ input_file = input_stack[input_number - 1].file;
+ status = getline (&line, &n, input_file);
+ if (status != -1)
+ {
+ char *comment;
+ if (feof (input_file))
+ {
+ /* Add a newline at the end of the file if one is missing. */
+ char *line2;
+ asprintf (&line2, "%s\n", line);
+ free (line);
+ line = line2;
+ }
+
+ /* Strip off a comment. */
+ comment = strchr (line, '\x7F');
+ if (comment)
+ *comment = '\0';
+
+ i->line_nr.line_nr++;
+ line_nr = i->line_nr;
+
+ return convert_to_utf8 (line, i->input_encoding);
+ }
+ free (line); line = 0;
+ break;
+ default:
+ abort ();
+ }
+
+ /* Top input source failed. Pop it and try the next one. */
+
+ if (input_stack[input_number - 1].type == IN_file)
+ {
+ FILE *file = input_stack[input_number - 1].file;
+
+ if (file != stdin)
+ {
+ if (fclose (input_stack[input_number - 1].file) == EOF)
+ fprintf (stderr, "error on closing %s: %s",
+ input_stack[input_number - 1].line_nr.file_name,
+ strerror (errno));
+ }
+ }
+
+ input_number--;
+ }
+ return 0;
+}
+
+void
+input_push (char *text, char *macro, char *filename, int line_number)
+{
+ if (input_number == input_space)
+ {
+ input_space++; input_space *= 1.5;
+ input_stack = realloc (input_stack, input_space * sizeof (INPUT));
+ if (!input_stack)
+ abort ();
+ }
+
+ input_stack[input_number].type = IN_text;
+ input_stack[input_number].file = 0;
+ input_stack[input_number].text = text;
+ input_stack[input_number].ptext = text;
+ input_stack[input_number].input_encoding = 0;
+
+ if (!macro)
+ line_number--;
+ input_stack[input_number].line_nr.line_nr = line_number;
+ input_stack[input_number].line_nr.file_name = save_string (filename);
+ input_stack[input_number].line_nr.macro = save_string (macro);
+ input_number++;
+}
+
+/* For filenames and macro names, it is possible that they won't be referenced
+ in the line number of any element. It would be too much work to keep track,
+ so just keep them all here, and free them all together at the end. */
+static char **small_strings;
+static size_t small_strings_num;
+static size_t small_strings_space;
+
+char *
+save_string (char *string)
+{
+ char *ret = string ? strdup (string) : 0;
+ if (ret)
+ {
+ if (small_strings_num == small_strings_space)
+ {
+ small_strings_space++;
+ small_strings_space += (small_strings_space >> 2);
+ small_strings = realloc (small_strings, small_strings_space
+ * sizeof (char *));
+ if (!small_strings)
+ abort ();
+ }
+ small_strings[small_strings_num++] = ret;
+ }
+ return ret;
+}
+
+/* Called in reset_parser. */
+void
+free_small_strings (void)
+{
+ size_t i;
+ for (i = 0; i < small_strings_num; i++)
+ {
+ free (small_strings[i]);
+ }
+ small_strings_num = 0;
+}
+
+
+/* Store TEXT as a source for Texinfo content. TEXT should be a UTF-8
+ string. TEXT will be later free'd and must be allocated on the heap.
+ MACRO is the name of a macro that the text came from. */
+void
+input_push_text (char *text, char *macro)
+{
+ if (text)
+ {
+ char *filename = 0;
+ if (input_number > 0)
+ {
+ filename = input_stack[input_number - 1].line_nr.file_name;
+ }
+ input_push (text, macro, filename, line_nr.line_nr);
+ }
+}
+
+/* Used in tests - like input_push_text, but the lines from the text have
+ line numbers. */
+void
+input_push_text_with_line_nos (char *text, int starting)
+{
+ input_push (text, 0, 0, starting);
+ input_stack[input_number - 1].type = IN_text;
+}
+
+void
+input_reset_input_stack (void)
+{
+ int i;
+ for (i = 0; i < input_number; i++)
+ {
+ switch (input_stack[i].type)
+ {
+ case IN_file:
+ if (input_stack[i].file != stdin)
+ fclose (input_stack[i].file);
+ break;
+ case IN_text:
+ free (input_stack[i].text);
+ break;
+ }
+ }
+ input_number = 0;
+}
+
+int
+top_file_index (void)
+{
+ int i = input_number - 1;
+ while (i >= 0 && input_stack[i].type != IN_file)
+ i--;
+ return i;
+}
+
+void
+set_input_encoding (char *encoding)
+{
+ int i;
+
+ /* Set encoding of top file in stack. */
+ i = top_file_index ();
+ if (i >= 0)
+ input_stack[i].input_encoding = encoding;
+}
+
+
+static char **include_dirs;
+static size_t include_dirs_number;
+static size_t include_dirs_space;
+
+void
+add_include_directory (char *filename)
+{
+ int len;
+ if (include_dirs_number == include_dirs_space)
+ {
+ include_dirs = realloc (include_dirs,
+ sizeof (char *) * (include_dirs_space += 5));
+ }
+ filename = strdup (filename);
+ include_dirs[include_dirs_number++] = filename;
+ len = strlen (filename);
+ if (len > 0 && filename[len - 1] == '/')
+ filename[len - 1] = '\0';
+}
+
+char *
+locate_include_file (char *filename)
+{
+ char *fullpath;
+ struct stat dummy;
+ int i, status;
+
+ /* Checks if filename is absolute or relative to current directory.
+ TODO: Could use macros in top-level config.h for this. */
+ /* TODO: The Perl code (in Common.pm, 'locate_include_file') handles
+ a volume in a path (like "A:"), possibly more general treatment
+ with File::Spec module. */
+ if (!memcmp (filename, "/", 1)
+ || !memcmp (filename, "../", 3)
+ || !memcmp (filename, "./", 2))
+ {
+ status = stat (filename, &dummy);
+ if (status == 0)
+ return filename;
+ }
+ else
+ {
+ for (i = 0; i < include_dirs_number; i++)
+ {
+ asprintf (&fullpath, "%s/%s", include_dirs[i], filename);
+ status = stat (fullpath, &dummy);
+ if (status == 0)
+ return fullpath;
+ free (fullpath);
+ }
+ }
+ return 0;
+}
+
+/* Try to open a file called FILENAME, looking for it in the list of include
+ directories. */
+int
+input_push_file (char *filename)
+{
+ FILE *stream;
+
+ stream = fopen (filename, "r");
+ if (!stream)
+ return errno;
+
+ if (input_number == input_space)
+ {
+ input_stack = realloc (input_stack, (input_space += 5) * sizeof (INPUT));
+ if (!input_stack)
+ abort ();
+ }
+
+ /* Strip off a leading directory path. */
+ char *p, *q;
+ p = 0;
+ q = strchr (filename, '/');
+ while (q)
+ {
+ p = q;
+ q = strchr (q + 1, '/');
+ }
+ if (p)
+ filename = save_string (p+1);
+ else
+ filename = save_string (filename);
+
+ input_stack[input_number].type = IN_file;
+ input_stack[input_number].file = stream;
+ input_stack[input_number].line_nr.file_name = filename;
+ input_stack[input_number].line_nr.line_nr = 0;
+ input_stack[input_number].line_nr.macro = 0;
+ input_stack[input_number].text = 0;
+ input_stack[input_number].ptext = 0;
+ input_stack[input_number].input_encoding = 0;
+ input_number++;
+
+ return 0;
+}
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.h
new file mode 100644
index 0000000000..4ac660375b
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/input.h
@@ -0,0 +1,24 @@
+/* input.h - declarations for input.c */
+
+char *new_line (void);
+char *next_text (void);
+
+void save_line_directive (int line_nr, char *filename);
+
+void input_push (char *text, char *macro, char *filename, int line_number);
+void input_push_text (char *line, char *macro);
+void input_push_text_with_line_nos (char *text, int starting);
+int input_push_file (char *filename);
+void input_reset_input_stack (void);
+int expanding_macro (char *macro);
+int top_file_index (void);
+char *locate_include_file (char *filename);
+void set_input_encoding (char *encoding);
+void add_include_directory (char *filename);
+
+char *save_string (char *string);
+void free_small_strings (void);
+
+extern LINE_NR line_nr;
+
+extern int input_number;
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.c
new file mode 100644
index 0000000000..0b7f5f0375
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.c
@@ -0,0 +1,76 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include "parser.h"
+#include "convert.h"
+#include "labels.h"
+
+/* Array of recorded labels. */
+/* If looking through this array turns out to be slow, we might have to replace
+ it with some kind of hash table implementation. */
+LABEL *labels_list = 0;
+size_t labels_number = 0;
+size_t labels_space = 0;
+
+/* Register a label, that is something that may be the target of a reference
+ and must be unique in the document. Corresponds to @node, @anchor, and
+ second arg of @float. */
+void
+register_label (ELEMENT *current, ELEMENT *label)
+{
+ if (labels_number == labels_space)
+ {
+ labels_space += 1;
+ labels_space *= 1.5;
+ labels_list = realloc (labels_list, labels_space * sizeof (LABEL));
+ if (!labels_list)
+ abort ();
+ }
+ labels_list[labels_number++].target = current;
+
+ if (label)
+ add_extra_contents (current, "node_content", label);
+}
+
+void
+reset_labels (void)
+{
+ labels_number = 0;
+}
+
+
+
+ELEMENT **internal_xref_list = 0;
+size_t internal_xref_number = 0;
+size_t internal_xref_space = 0;
+
+void
+remember_internal_xref (ELEMENT *element)
+{
+ if (internal_xref_number == internal_xref_space)
+ {
+ internal_xref_list = realloc (internal_xref_list,
+ (internal_xref_space += 2)
+ * sizeof (*internal_xref_list));
+ }
+ internal_xref_list[internal_xref_number++] = element;
+}
+
+void
+reset_internal_xrefs (void)
+{
+ internal_xref_number = 0;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.h
new file mode 100644
index 0000000000..7877987466
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/labels.h
@@ -0,0 +1,37 @@
+/* labels.h - declarations for labels.c */
+/* 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 <http://www.gnu.org/licenses/>. */
+
+/* Information about a possible target of a cross-reference, often a node. */
+typedef struct {
+ /* Pointer to the element for the command defining this label, usually a
+ node element. FIXME: I'm not sure if we actualy need to get to the
+ target - much of the use of the labels_information is to check that
+ references are to real places. */
+ ELEMENT *target;
+} LABEL;
+
+extern LABEL *labels_list;
+extern size_t labels_number;
+void register_label (ELEMENT *current, ELEMENT *label);
+void reset_labels (void);
+
+
+extern ELEMENT **internal_xref_list;
+extern size_t internal_xref_number;
+extern size_t internal_xref_space;
+
+void remember_internal_xref (ELEMENT *element);
+void reset_internal_xrefs (void);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.c
new file mode 100644
index 0000000000..9c26ccf8d1
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.c
@@ -0,0 +1,747 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#define _GNU_SOURCE
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "parser.h"
+#include "tree.h"
+#include "text.h"
+#include "input.h"
+#include "convert.h"
+
+static MACRO *macro_list;
+static size_t macro_number;
+static size_t macro_space;
+
+
+/* Macro definition. */
+
+void
+new_macro (char *name, ELEMENT *macro)
+{
+ enum command_id new;
+ MACRO *m = 0;
+ ELEMENT tmp;
+
+ /* Check for an existing definition first for us to overwrite. */
+ new = lookup_command (name);
+ if (new)
+ m = lookup_macro (new);
+ if (!m)
+ {
+ if (macro_number == macro_space)
+ {
+ macro_list = realloc (macro_list,
+ (macro_space += 5) * sizeof (MACRO));
+ if (!macro_list)
+ abort ();
+ }
+ new = add_texinfo_command (name);
+ m = &macro_list[macro_number];
+ m->cmd = new;
+ macro_number++;
+ new &= ~USER_COMMAND_BIT;
+ user_defined_command_data[new].flags |= CF_MACRO;
+ }
+ else
+ {
+ free (m->macro_name);
+ }
+
+ m->macro_name = strdup (name);
+ m->element = macro;
+
+ memset (&tmp, 0, sizeof (ELEMENT));
+ tmp.contents = macro->contents;
+ m->macrobody = convert_to_texinfo (&tmp);
+}
+
+/* CMD will be either CM_macro or CM_rmacro. Read the line defining a macro's
+ name and the arguments it takes, and return this information in a new
+ ELEMENT. */
+ELEMENT *
+parse_macro_command_line (enum command_id cmd, char **line_inout,
+ ELEMENT *parent)
+{
+ char *line = *line_inout;
+ ELEMENT *macro, *macro_name;
+ char *name, *args_ptr;
+ int index;
+
+ macro = new_element (ET_NONE);
+ macro->cmd = cmd;
+ macro->line_nr = line_nr;
+
+ add_extra_string (macro, "arg_line", strdup (line));
+ /* TODO: This extra value isn't used much, so is a candidate for
+ simplification. */
+
+ line += strspn (line, whitespace_chars);
+ name = read_command_name (&line);
+
+ if (*line && *line != '{' && !strchr (whitespace_chars, *line))
+ {
+ line_error ("bad name for @%s", command_name (cmd));
+ add_extra_integer (macro, "invalid_syntax", 1);
+ return macro;
+ }
+ else if (!name)
+ {
+ line_error ("@%s requires a name", command_name (cmd));
+ add_extra_integer (macro, "invalid_syntax", 1);
+ return macro;
+ }
+
+ macro_name = new_element (ET_macro_name);
+ text_append (&macro_name->text, name);
+ free (name);
+ add_to_element_args (macro, macro_name);
+
+ args_ptr = line;
+ args_ptr += strspn (args_ptr, whitespace_chars);
+
+ if (*args_ptr != '{')
+ {
+ /* Either error or no args. */
+ goto check_trailing;
+ }
+ args_ptr++;
+
+ index = 0;
+ while (1)
+ {
+ /* args_ptr is after a '{' or ','. INDEX holds the number of
+ the macro argument */
+
+ char *q, *q2;
+ ELEMENT *arg;
+
+ args_ptr += strspn (args_ptr, whitespace_chars);
+
+ /* Find end of current argument. */
+ q = args_ptr;
+ while (*q != '\0' && *q != ',' && *q != '}')
+ q++;
+
+ if (!*q)
+ {
+ /* End of string reached before closing brace. */
+ abort ();
+ }
+
+ /* Disregard trailing whitespace. */
+ q2 = q;
+ while (q2 > args_ptr && strchr (whitespace_chars, q2[-1]))
+ q2--;
+
+ if (q2 == args_ptr)
+ {
+ /* argument is completely whitespace */
+ if (*q == ',')
+ {
+ line_error ("bad or empty @%s formal argument: ",
+ command_name(cmd));
+ arg = new_element (ET_macro_arg);
+ add_to_element_args (macro, arg);
+ text_append_n (&arg->text, "", 0);
+ add_extra_integer (macro, "invalid_syntax", 1);
+ }
+ }
+ else
+ {
+ arg = new_element (ET_macro_arg);
+ text_append_n (&arg->text, args_ptr, q2 - args_ptr);
+ add_to_element_args (macro, arg);
+
+ /* Check the argument name. */
+ {
+ char *p;
+ for (p = args_ptr; p < q2; p++)
+ {
+ if (!isalnum (*p) && *p != '_' && *p != '-')
+ {
+ char c = *q2; *q2 = 0;
+ line_error ("bad or empty @%s formal argument: %s",
+ command_name(cmd), args_ptr);
+ *q2 = c;
+ add_extra_integer (macro, "invalid_syntax", 1);
+ break;
+ }
+ }
+ }
+ }
+
+ args_ptr = q + 1;
+
+ if (*q == '}')
+ break;
+
+ index++;
+ }
+
+check_trailing:
+ line = args_ptr;
+ line += strspn (line, whitespace_chars);
+ if (*line && *line != '@')
+ {
+ line_error ("bad syntax for @%s argument: %s",
+ command_name(cmd), line);
+ add_extra_integer (macro, "invalid_syntax", 1);
+ }
+ //line += strlen (line); /* Discard rest of line. */
+
+funexit:
+ *line_inout = line;
+ return macro;
+}
+
+
+/* Macro use. */
+
+/* Return index into given arguments to look for the value of NAME.
+ Return -1 if not found. */
+
+int
+lookup_macro_parameter (char *name, ELEMENT *macro)
+{
+ int i, pos;
+ ELEMENT **args;
+
+ /* Find 'arg' in MACRO parameters. */
+ args = macro->args.list;
+ pos = 0;
+ for (i = 0; i < macro->args.number; i++)
+ {
+ if (args[i]->type == ET_macro_arg)
+ {
+ if (!strcmp (args[i]->text.text, name))
+ return pos;
+ pos++;
+ }
+ }
+ return -1;
+}
+
+/* LINE points the first non-whitespace character after the opening brace in a
+ macro invocation. CMD is the command identifier of the macro command.
+ Return array of the arguments. Return value to be freed by caller. */
+char **
+expand_macro_arguments (ELEMENT *macro, char **line_inout, enum command_id cmd)
+{
+ char *line = *line_inout;
+ char *pline = line;
+ TEXT arg;
+ int braces_level = 1;
+ int args_total;
+
+ char **arg_list = 0;
+ size_t arg_number = 0;
+ size_t arg_space = 0;
+
+ arg_list = malloc (sizeof (char *));
+ args_total = macro->args.number - 1;
+
+ text_init (&arg);
+
+ while (braces_level > 0)
+ {
+ /* At the beginning of this loop pline is at the start
+ of an argument. */
+ char *sep;
+
+ sep = pline + strcspn (pline, "\\,{}");
+ if (!*sep)
+ {
+ debug ("MACRO ARG end of line");
+ text_append (&arg, pline);
+ line = new_line ();
+ if (!line)
+ {
+ line_error ("@%s missing closing brace", command_name(cmd));
+ line = "\n";
+ free (arg.text);
+ goto funexit;
+ }
+ pline = line;
+ continue;
+ }
+
+ text_append_n (&arg, pline, sep - pline);
+
+ switch (*sep)
+ {
+ case '\\':
+ if (!strchr ("\\{},", sep[1]))
+ text_append_n (&arg, sep, 1);
+ if (sep[1])
+ {
+ text_append_n (&arg, &sep[1], 1);
+ pline = sep + 2;
+ }
+ else
+ pline = sep + 1;
+ break;
+ case '{':
+ braces_level++;
+ text_append_n (&arg, sep, 1);
+ pline = sep + 1;
+ break;
+ case '}':
+ braces_level--;
+ if (braces_level > 0)
+ {
+ text_append_n (&arg, sep, 1);
+ pline = sep + 1;
+ break;
+ }
+
+ /* Fall through to add argument. */
+ case ',':
+ if (braces_level > 1)
+ {
+ text_append_n (&arg, sep, 1);
+ pline = sep + 1;
+ break;
+ }
+
+ // check for too many args
+ if (*sep == '}' || arg_number < args_total - 1)
+ {
+ /* Add the last argument read to the list. */
+ if (arg_number == arg_space)
+ {
+ arg_list = realloc (arg_list,
+ (1+(arg_space += 5)) * sizeof (char *));
+ /* Include space for terminating null element. */
+ if (!arg_list)
+ abort ();
+ }
+ if (arg.space > 0)
+ arg_list[arg_number++] = arg.text;
+ else
+ arg_list[arg_number++] = strdup ("");
+ text_init (&arg);
+
+ debug ("MACRO NEW ARG");
+ pline = sep + 1;
+
+ if (*sep == ',')
+ pline += strspn (pline, whitespace_chars);
+ }
+ else
+ {
+ if (args_total != 1)
+ line_error ("macro `%s' called with too many args",
+ command_name(cmd));
+ text_append_n (&arg, ",", 1);
+ pline = sep + 1;
+ }
+ break;
+ }
+ }
+
+ debug ("END MACRO ARGS EXPANSION");
+ line = pline;
+
+ if (args_total == 0 && arg_number > 0
+ && arg_list[0] && *arg_list[0])
+ {
+ line_error
+ ("macro `%s' declared without argument called with an argument",
+ command_name(cmd));
+ }
+
+funexit:
+ *line_inout = line;
+ arg_list[arg_number] = 0;
+ return arg_list;
+}
+
+/* ARGUMENTS are the arguments used in the macro invocation. EXPANDED gets the
+ result of the expansion. */
+static void
+expand_macro_body (MACRO *macro_record, char *arguments[], TEXT *expanded)
+{
+ int pos; /* Index into arguments. */
+ ELEMENT *macro;
+ char *macrobody;
+ char *ptext;
+
+ macro = macro_record->element;
+
+ macrobody = macro_record->macrobody;
+
+ /* Initialize TEXT object. */
+ expanded->end = 0;
+
+ if (!macrobody)
+ return;
+
+ ptext = macrobody;
+ while (1)
+ {
+ /* At the start of this loop ptext is at the beginning or
+ just after the last backslash sequence. */
+
+ char *bs; /* Pointer to next backslash. */
+
+ bs = strchrnul (ptext, '\\');
+ text_append_n (expanded, ptext, bs - ptext);
+ if (!*bs)
+ break; /* End of line. */
+
+ ptext = bs + 1;
+ if (*ptext == '\\')
+ {
+ text_append_n (expanded, "\\", 1); /* Escaped backslash (\\). */
+ ptext++;
+ }
+ else
+ {
+ bs = strchr (ptext, '\\');
+ if (!bs)
+ {
+ // TODO: error - malformed
+ return;
+ abort ();
+ }
+
+ *bs = '\0';
+ pos = lookup_macro_parameter (ptext, macro);
+ if (pos == -1)
+ {
+ line_error ("\\ in @%s expansion followed `%s' instead of "
+ "parameter name or \\",
+ macro->args.list[0]->text.text,
+ ptext);
+ text_append (expanded, "\\");
+ text_append (expanded, ptext);
+ }
+ else
+ {
+ if (arguments && arguments[pos])
+ text_append (expanded, arguments[pos]);
+ }
+ *bs = '\\';
+ ptext = bs + 1;
+ }
+ }
+}
+
+MACRO *
+lookup_macro (enum command_id cmd)
+{
+ int i;
+
+ for (i = 0; i < macro_number; i++)
+ {
+ if (macro_list[i].cmd == cmd)
+ return &macro_list[i];
+ }
+ return 0;
+}
+
+void
+delete_macro (char *name)
+{
+ enum command_id cmd;
+ MACRO *m;
+ cmd = lookup_command (name);
+ if (!cmd)
+ return;
+ m = lookup_macro (cmd);
+ if (!m)
+ return;
+ m->cmd = 0;
+ free (m->macro_name);
+ m->macro_name = strdup ("");
+ free (m->macrobody);
+ m->macrobody = 0;
+ m->element = 0;
+ remove_texinfo_command (cmd);
+}
+
+void
+wipe_macros (void)
+{
+ int i;
+
+ for (i = 0; i < macro_number; i++)
+ {
+ free (macro_list[i].macro_name);
+ free (macro_list[i].macrobody);
+ }
+ macro_number = 0;
+}
+
+/* Handle macro expansion. CMD is the macro command. */
+ELEMENT *
+handle_macro (ELEMENT *current, char **line_inout, enum command_id cmd)
+{
+ char *line, *p;
+ MACRO *macro_record;
+ ELEMENT *macro;
+ TEXT expanded;
+ char **arguments = 0;
+ int args_number;
+
+ line = *line_inout;
+ text_init (&expanded);
+
+ macro_record = lookup_macro (cmd);
+ if (!macro_record)
+ abort ();
+ macro = macro_record->element;
+
+ /* Get number of args. - 1 for the macro name. */
+ args_number = macro->args.number - 1;
+
+ p = line + strspn (line, whitespace_chars);
+ if (*p == '{')
+ {
+ line = p;
+ line++;
+ line += strspn (line, whitespace_chars);
+ arguments = expand_macro_arguments (macro, &line, cmd);
+ }
+ /* Warning depending on the number of arguments this macro
+ is supposed to take. */
+ else if (args_number != 1)
+ {
+ if (args_number > 1)
+ line_warn ("@%s defined with zero or more than one argument should "
+ "be invoked with {}", command_name(cmd));
+ /* As agreed on the bug-texinfo mailing list, no warn when zero
+ arg and not called with {}. */
+ }
+ else
+ {
+ char *p;
+ /* If it takes a single line of input, and we don't have a full line of
+ input already, call new_line. */
+ if (!strchr (line, '\n'))
+ {
+ line = new_line ();
+ if (!line)
+ line = "";
+ }
+ line += strspn (line, whitespace_chars);
+
+ arguments = malloc (sizeof (char *) * 2);
+ arguments[0] = strdup (line);
+ arguments[1] = 0;
+
+ p = strchr (arguments[0], '\n');
+ if (p)
+ {
+ *p = '\0';
+ line = "\n";
+ }
+ }
+
+ expand_macro_body (macro_record, arguments, &expanded);
+ debug ("MACROBODY: %s||||||", expanded.text);
+
+ if (expanded.end > 0 && expanded.text[expanded.end - 1] == '\n')
+ expanded.text[--expanded.end] = '\0';
+
+ if (input_number >= 1000)
+ {
+ line_warn (
+ "macro call nested too deeply "
+ "(set MAX_NESTED_MACROS to override; current value %d)", 1000);
+ goto funexit;
+ /* TODO: actually check MAX_NESTED_MACROS? */
+ }
+
+ if (macro->cmd == CM_macro)
+ {
+ if (expanding_macro (command_name(cmd)))
+ {
+ line_error ("recursive call of macro %s is not allowed; "
+ "use @rmacro if needed", command_name(cmd));
+ expanded.text[0] = '\0';
+ expanded.end = 0;
+ }
+ }
+
+ /* Free arguments. */
+ if (arguments)
+ {
+ char **s = arguments;
+ while (*s)
+ {
+ free (*s);
+ s++;
+ }
+ free (arguments);
+ }
+
+ // 3958 Pop macro stack
+
+ /* Put expansion in front of the current line. */
+ input_push_text (strdup (line), 0);
+ line = strchr (line, '\0');
+ input_push_text (expanded.text, command_name(cmd));
+
+funexit:
+ *line_inout = line;
+ return current;
+}
+
+
+/* @set and @value */
+
+typedef struct {
+ char *name;
+ char *value;
+} VALUE;
+
+static VALUE *value_list;
+static size_t value_number;
+static size_t value_space;
+
+void
+wipe_values (void)
+{
+ size_t i;
+ for (i = 0; i < value_number; i++)
+ {
+ free (value_list[i].name);
+ free (value_list[i].value);
+ }
+ value_number = 0;
+}
+
+void
+store_value (char *name, char *value)
+{
+ int i;
+ VALUE *v = 0;
+ int len;
+
+ len = strlen (name);
+
+ /* Check if already defined. */
+ for (i = 0; i < value_number; i++)
+ {
+ if (!strncmp (value_list[i].name, name, len) && !value_list[i].name[len])
+ {
+ v = &value_list[i];
+ free (v->name); free (v->value);
+ break;
+ }
+ }
+
+ if (!v)
+ {
+ if (value_number == value_space)
+ {
+ value_list = realloc (value_list, (value_space += 5) * sizeof (VALUE));
+ }
+ v = &value_list[value_number++];
+ }
+
+ v->name = strdup (name);
+ v->value = strdup (value);
+}
+
+/* Clear the value the name of which is LEN bytes at NAME */
+void
+clear_value (char *name, int len)
+{
+ int i;
+ for (i = 0; i < value_number; i++)
+ {
+ if (!strncmp (value_list[i].name, name, len) && !value_list[i].name[len])
+ {
+ value_list[i].name[0] = '\0';
+ value_list[i].value[0] = '\0';
+ }
+ }
+}
+
+/* Look for a value the name of which is LEN bytes at NAME */
+char *
+fetch_value (char *name, int len)
+{
+ int i;
+ for (i = 0; i < value_number; i++)
+ {
+ if (!strncmp (value_list[i].name, name, len) && !value_list[i].name[len])
+ return value_list[i].value;
+ }
+
+ if (!strcmp (name, "txicommandconditionals"))
+ return "1";
+ return 0;
+}
+
+
+static INFO_ENCLOSE *infoencl_list;
+static size_t infoencl_number;
+static size_t infoencl_space;
+
+INFO_ENCLOSE *
+lookup_infoenclose (enum command_id cmd)
+{
+ int i;
+ for (i = 0; i < infoencl_number; i++)
+ {
+ if (infoencl_list[i].cmd == cmd)
+ return &infoencl_list[i];
+ }
+ return 0;
+}
+
+void
+add_infoenclose (enum command_id cmd, char *begin, char *end)
+{
+ int i;
+ INFO_ENCLOSE *ie = 0;
+
+ /* Check if already defined. */
+ for (i = 0; i < infoencl_number; i++)
+ {
+ if (infoencl_list[i].cmd == cmd)
+ {
+ ie = &infoencl_list[i];
+ free (ie->begin);
+ free (ie->end);
+ break;
+ }
+ }
+
+ if (!ie)
+ {
+ if (infoencl_number == infoencl_space)
+ {
+ infoencl_list = realloc (infoencl_list,
+ (infoencl_space += 5)
+ * sizeof (INFO_ENCLOSE));
+ }
+ ie = &infoencl_list[infoencl_number++];
+ }
+
+ ie->cmd = cmd;
+ ie->begin = strdup (begin);
+ ie->end = strdup (end);
+}
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.h
new file mode 100644
index 0000000000..9fb4d09a6d
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/macro.h
@@ -0,0 +1,44 @@
+/* macro.h - declarations for macro.c */
+/* 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 <http://www.gnu.org/licenses/>. */
+
+typedef struct {
+ enum command_id cmd;
+ char *begin;
+ char *end;
+} INFO_ENCLOSE;
+
+typedef struct {
+ char *macro_name;
+ ELEMENT *element;
+ enum command_id cmd;
+ char *macrobody;
+} MACRO;
+
+void new_macro (char *name, ELEMENT *macro);
+ELEMENT *parse_macro_command_line (enum command_id, char **line_inout,
+ ELEMENT *parent);
+ELEMENT *handle_macro (ELEMENT *current, char **line_inout,
+ enum command_id cmd_id);
+void delete_macro (char *name);
+MACRO *lookup_macro (enum command_id cmd);
+void wipe_macros (void);
+
+void store_value (char *name, char *value);
+char *fetch_value (char *name, int len);
+void clear_value (char *name, int len);
+INFO_ENCLOSE *lookup_infoenclose (enum command_id cmd);
+void add_infoenclose (enum command_id cmd, char *begin, char *end);
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/menus.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/menus.c
new file mode 100644
index 0000000000..d2e0ce1cc7
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/menus.c
@@ -0,0 +1,252 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "parser.h"
+#include "input.h"
+#include "text.h"
+#include "convert.h"
+#include "labels.h"
+
+/* Save 'menu_entry_name' 'menu_entry_node', and 'menu_entry_description'
+ extra keys on the top-level @menu element. */
+void
+register_extra_menu_entry_information (ELEMENT *current)
+{
+ int i;
+
+ for (i = 0; i < current->args.number; i++)
+ {
+ ELEMENT *arg = current->args.list[i];
+
+ if (arg->type == ET_menu_entry_name)
+ {
+ add_extra_element (current, "menu_entry_name", arg);
+ if (arg->contents.number == 0)
+ {
+ char *texi = convert_to_texinfo (current);
+ line_warn ("empty menu entry name in `%s'", texi);
+ free (texi);
+ }
+ }
+ else if (arg->type == ET_menu_entry_node)
+ {
+ NODE_SPEC_EXTRA *parsed_entry_node;
+
+ isolate_last_space (arg);
+
+ parsed_entry_node = parse_node_manual (arg);
+ if (!parsed_entry_node)
+ {
+ if (conf.show_menu)
+ line_error ("empty node name in menu entry");
+ }
+ else
+ add_extra_node_spec (current, "menu_entry_node",
+ parsed_entry_node);
+ }
+ else if (arg->type == ET_menu_entry_description)
+ {
+ add_extra_element (current, "menu_entry_description", arg);
+ }
+ }
+}
+
+/* Process the destination of the menu entry, and start a menu entry
+ description. */
+ELEMENT *
+enter_menu_entry_node (ELEMENT *current)
+{
+ ELEMENT *description, *preformatted;
+
+ description = new_element (ET_menu_entry_description);
+ add_to_element_args (current, description);
+ register_extra_menu_entry_information (current);
+ current->line_nr = line_nr;
+ remember_internal_xref (current);
+
+ current = description;
+ preformatted = new_element (ET_preformatted);
+ add_to_element_contents (current, preformatted);
+ current = preformatted;
+ push_context (ct_preformatted);
+ return current;
+}
+
+/* Called from 'process_remaining_on_line' in parser.c. Return 1 if we find
+ menu syntax to process, otherwise return 0. */
+int
+handle_menu (ELEMENT **current_inout, char **line_inout)
+{
+ ELEMENT *current = *current_inout;
+ char *line = *line_inout;
+ int retval = 1;
+
+ /* A "*" at the start of a line beginning a menu entry. */
+ if (*line == '*'
+ && current->type == ET_preformatted
+ && (current->parent->type == ET_menu_comment
+ || current->parent->type == ET_menu_entry_description)
+ && current->contents.number > 0
+ && last_contents_child(current)->type == ET_empty_line)
+ {
+ ELEMENT *star;
+
+ debug ("MENU STAR");
+ abort_empty_line (&current, 0);
+ line++; /* Past the '*'. */
+
+ star = new_element (ET_menu_star);
+ text_append (&star->text, "*");
+ add_to_element_contents (current, star);
+
+ /* The ET_menu_star element won't appear in the final tree. */
+ }
+ /* A space after a "*" at the beginning of a line. */
+ else if (strchr (whitespace_chars, *line)
+ && current->contents.number > 0
+ && last_contents_child(current)->type == ET_menu_star)
+ {
+ ELEMENT *menu_entry, *leading_text, *entry_name;
+ int leading_spaces;
+
+ debug ("MENU ENTRY (certainly)");
+ leading_spaces = strspn (line, whitespace_chars);
+
+ destroy_element (pop_element_from_contents (current));
+
+ if (current->type == ET_preformatted
+ && current->parent->type == ET_menu_comment)
+ {
+ ELEMENT *menu = current->parent->parent;
+
+ /* Remove an empty ET_preformatted, and an empty ET_menu_comment. */
+ if (current->contents.number == 0)
+ {
+ pop_element_from_contents (current->parent);
+ if (current->parent->contents.number == 0)
+ {
+ pop_element_from_contents (menu);
+ destroy_element (current->parent);
+ }
+ destroy_element (current);
+ }
+
+ current = menu;
+ }
+ else
+ {
+ /* current should be ET_preformatted,
+ 1st parent ET_menu_entry_description,
+ 2nd parent ET_menu_entry,
+ 3rd parent @menu. */
+ current = current->parent->parent->parent;
+ }
+
+ if (pop_context () != ct_preformatted)
+ abort (); /* bug */
+
+ menu_entry = new_element (ET_menu_entry);
+ leading_text = new_element (ET_menu_entry_leading_text);
+ entry_name = new_element (ET_menu_entry_name);
+ add_to_element_contents (current, menu_entry);
+ add_to_element_args (menu_entry, leading_text);
+ add_to_element_args (menu_entry, entry_name);
+ current = entry_name;
+
+ text_append (&leading_text->text, "*");
+ text_append_n (&leading_text->text, line, leading_spaces);
+ line += leading_spaces;
+ }
+ /* A "*" followed by anything other than a space. */
+ else if (current->contents.number > 0
+ && last_contents_child(current)->type == ET_menu_star)
+ {
+ debug ("ABORT MENU STAR");
+ last_contents_child(current)->type = ET_NONE;
+ }
+ /* After a separator in a menu (which would have been added in
+ handle_separator in separator.c). */
+ else if (current->args.number > 0
+ && last_args_child (current)->type == ET_menu_entry_separator)
+ {
+ ELEMENT *last_child;
+ char *separator;
+
+ last_child = last_args_child (current);
+ separator = last_child->text.text;
+
+ /* Separator is "::". */
+ if (!strcmp (separator, ":") && *line == ':')
+ {
+ text_append (&last_child->text, ":");
+ line++;
+ /* Whitespace following the "::" is subsequently appended to
+ the separator element. */
+ }
+ /* A "." not followed by a space. Not a separator. */
+ else if (!strcmp (separator, ".") && !strchr (whitespace_chars, *line))
+ {
+ pop_element_from_args (current);
+ current = last_args_child (current);
+ merge_text (current, last_child->text.text);
+ destroy_element (last_child);
+ }
+ else if (strchr (whitespace_chars_except_newline, *line))
+ {
+ int n;
+
+ n = strspn (line, whitespace_chars_except_newline);
+ text_append_n (&last_child->text, line, n);
+ line += n;
+ }
+ else if (!strncmp (separator, "::", 2))
+ {
+ ELEMENT *entry_name;
+
+ debug ("MENU NODE no entry %s", separator);
+ entry_name = args_child_by_index (current, -2);
+
+ /* Change it from ET_menu_entry_name (i.e. the label). */
+ entry_name->type = ET_menu_entry_node;
+ current = enter_menu_entry_node (current);
+ }
+ /* End of the label. Begin the element for the destination. */
+ else if (*separator == ':')
+ {
+ ELEMENT *entry_node;
+
+ debug ("MENU ENTRY %s", separator);
+ entry_node = new_element (ET_menu_entry_node);
+ add_to_element_args (current, entry_node);
+ current = entry_node;
+ }
+ else
+ {
+ debug ("MENU NODE");
+ current = enter_menu_entry_node (current);
+ }
+ }
+ else
+ retval = 0;
+
+ *current_inout = current;
+ *line_inout = line;
+
+ return retval;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/multitable.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/multitable.c
new file mode 100644
index 0000000000..7f98377f15
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/multitable.c
@@ -0,0 +1,136 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include "parser.h"
+
+/* Return the parent if in an item_line command, @*table */
+ELEMENT *
+item_line_parent (ELEMENT *current)
+{
+ if (current->type == ET_before_item && current->parent)
+ current = current->parent;
+
+ if (item_line_command (current->cmd))
+ return current;
+
+ return 0;
+}
+
+/* Return the parent if in a multitable. */
+ELEMENT *
+item_multitable_parent (ELEMENT *current)
+{
+ if (current->cmd == CM_headitem
+ || current->cmd == CM_item
+ || current->cmd == CM_tab)
+ {
+ if (current->parent && current->parent->parent)
+ current = current->parent->parent;
+ }
+ else if (current->type == ET_before_item)
+ {
+ current = current->parent;
+ }
+
+ if (current->cmd == CM_multitable)
+ return current;
+
+ return 0;
+}
+
+/* Put the contents of a @table row in a ET_table_entry container, containing
+ a ET_table_term element and a ET_table_item element. The elements of
+ this row currently occur the end of the contents of CURRENT as immediate
+ children.
+
+ NEXT_COMMAND is the command that ends this row, usually CM_item. It is null
+ at the end of a @table. If NEXT_COMMAND is given as CM_itemx, gather a
+ ET_inter_item container instead. */
+void
+gather_previous_item (ELEMENT *current, enum command_id next_command)
+{
+ ELEMENT *gathered;
+ enum element_type type;
+ int i, contents_count;
+
+ if (last_contents_child(current)
+ && last_contents_child(current)->type == ET_before_item)
+ {
+ if (next_command == CM_itemx)
+ line_warn ("@itemx should not begin @%s", command_name(current->cmd));
+ return;
+ }
+
+ type = next_command != CM_itemx ? ET_table_item : ET_inter_item;
+ gathered = new_element (type);
+
+ /* Starting from the end, collect everything that is not a ET_item
+ or ET_itemx and put it into the ET_table_item. */
+ contents_count = current->contents.number;
+ for (i = 0; i < contents_count; i++)
+ {
+ ELEMENT *e;
+ if (last_contents_child(current)->cmd == CM_item
+ || last_contents_child(current)->cmd == CM_itemx)
+ break;
+
+ e = pop_element_from_contents (current);
+ insert_into_contents (gathered, e, 0);
+ }
+ /* TODO: A similar algorithm is is in gather_def_item in def.c. If
+ speed is an issue then we could move all the elements at once instead
+ of calling insert_into_contents multiple times. */
+
+ if (type == ET_table_item)
+ {
+ ELEMENT *table_entry = new_element (ET_table_entry);
+ ELEMENT *table_term = new_element (ET_table_term);
+ add_to_element_contents (table_entry, table_term);
+
+ /* We previously collected elements into a ET_table_item. Now
+ do the same for ET_table_term. */
+ contents_count = current->contents.number;
+ for (i = 0; i < contents_count; i++)
+ {
+ ELEMENT *e;
+ if (last_contents_child(current)->type == ET_before_item
+ || last_contents_child(current)->type == ET_table_entry)
+ break;
+
+ e = pop_element_from_contents (current);
+ insert_into_contents (table_term, e, 0);
+ }
+
+ add_to_element_contents (current, table_entry);
+
+ if (gathered->contents.number > 0)
+ add_to_element_contents (table_entry, gathered);
+ else
+ destroy_element (gathered);
+ }
+ else /* Gathering ET_inter_item between @item and @itemx */
+ {
+ /* Text between @item and @itemx is only allowed in a few cases:
+ comments, empty lines, or index entries. */
+ if (check_no_text (gathered))
+ line_error ("@itemx must follow @item");
+
+ if (gathered->contents.number > 0)
+ add_to_element_contents (current, gathered);
+ else
+ destroy_element (gathered);
+ }
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.c
new file mode 100644
index 0000000000..d0d661969b
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.c
@@ -0,0 +1,1921 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#include "parser.h"
+#include "text.h"
+#include "input.h"
+
+
+/* Utility functions */
+
+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;
+}
+
+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)
+ abort ();
+ }
+ 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);
+}
+
+/* Record the information from a command of global effect. */
+int
+register_global_command (ELEMENT *current)
+{
+ enum command_id cmd = current->cmd;
+
+ 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 (cmd == CM_shortcontents)
+ cmd = CM_summarycontents;
+ 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(novalidate);
+ GLOBAL_UNIQUE_CASE(validatemenus);
+ GLOBAL_UNIQUE_CASE(pagesizes);
+ GLOBAL_UNIQUE_CASE(fonttextsize);
+ GLOBAL_UNIQUE_CASE(footnotestyle);
+ GLOBAL_UNIQUE_CASE(setchapternewpage);
+ GLOBAL_UNIQUE_CASE(everyheading);
+ GLOBAL_UNIQUE_CASE(everyfooting);
+ GLOBAL_UNIQUE_CASE(evenheading);
+ GLOBAL_UNIQUE_CASE(evenfooting);
+ GLOBAL_UNIQUE_CASE(oddheading);
+ GLOBAL_UNIQUE_CASE(oddfooting);
+ GLOBAL_UNIQUE_CASE(everyheadingmarks);
+ GLOBAL_UNIQUE_CASE(everyfootingmarks);
+ GLOBAL_UNIQUE_CASE(evenheadingmarks);
+ GLOBAL_UNIQUE_CASE(oddheadingmarks);
+ GLOBAL_UNIQUE_CASE(evenfootingmarks);
+ GLOBAL_UNIQUE_CASE(oddfootingmarks);
+ GLOBAL_UNIQUE_CASE(shorttitlepage);
+ GLOBAL_UNIQUE_CASE(title);
+#undef GLOBAL_UNIQUE_CASE
+ /* NOTE: Same list in api.c:build_global_info2 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.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));
+}
+
+
+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)
+ abort ();
+ memcpy (additional, text, leading_spaces);
+ additional[leading_spaces] = '\0';
+ }
+
+ if (abort_empty_line (&current, 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_last_space_menu_entry_node (ELEMENT *current)
+{
+ 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 = ET_space_at_end_menu_node;
+ }
+ 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 (ET_space_at_end_menu_node);
+ 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_last_space_menu_entry_node (current);
+ 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)
+ && 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_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, &current->line_nr,
+ "macro `%s' previously defined", name);
+ line_error_ext (1, &macro->element->line_nr,
+ "here is the previous definition of `%s'", name);
+ }
+ else if (!(existing & USER_COMMAND_BIT))
+ {
+ line_error_ext (1, &current->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)
+ abort(); //error
+
+ /* 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 (&current, 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 (&current, &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;
+ line += strspn (line, whitespace_chars);
+ if (*line != '{')
+ goto value_invalid;
+
+ line++;
+ if (!isalnum (*line) && *line != '-' && *line != '_')
+ {
+ line--;
+ goto value_invalid;
+ }
+ arg_start = line;
+
+ line++;
+ line = strpbrk (line,
+ " \t\f\r\n" /* whitespace */
+ "{\\}~^+\"<>|@"); /* other bytes that aren't allowed */
+ if (*line != '}')
+ {
+ line = arg_start - 1;
+ goto value_invalid;
+ }
+
+ if (1) /* @value syntax is valid */
+ {
+ char *value;
+value_valid:
+ value = fetch_value (arg_start, line - arg_start);
+ 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", line - arg_start,
+ arg_start);
+
+ abort_empty_line (&current, NULL);
+ value_elt = new_element (ET_NONE);
+ value_elt->cmd = CM_value;
+ text_append_n (&value_elt->text, arg_start,
+ line - arg_start);
+ /* 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;
+ }
+ 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)
+ {
+ case CM_setcontentsaftertitlepage:
+ msg = "move your @contents command if you want the "
+ "contents after the title page";
+ break;
+ case CM_setshortcontentsaftertitlepage:
+ msg = "move your @shortcontents and @contents command if "
+ "you want the contents after the title page";
+ break;
+ 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 (&current, 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)))
+ {
+ 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_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 (&current, 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 (&current, &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;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.h
new file mode 100644
index 0000000000..55fe467a83
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/parser.h
@@ -0,0 +1,211 @@
+/* parser.h - include many other header files. type declarations.
+ declarations for close.c, end_line.c, debug.c, separator.c, parser.c,
+ multitable.c, extra.c and menu.c. */
+
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include "tree_types.h"
+#include "tree.h"
+#include "context_stack.h"
+#include "commands.h"
+#include "handle_commands.h"
+#include "def.h"
+#include "errors.h"
+#include "counter.h"
+#include "macro.h"
+#include "conf.h"
+
+typedef struct GLOBAL_INFO {
+ char *input_file_name;
+ char *input_encoding_name;
+ char *input_perl_encoding;
+ int sections_level;
+ ELEMENT dircategory_direntry; /* an array of elements */
+
+ /* Elements that should be unique. */
+ ELEMENT *settitle; /* Title of document. */
+ ELEMENT *copying;
+ ELEMENT *title;
+ ELEMENT *titlepage;
+ ELEMENT *top;
+ ELEMENT *setfilename;
+ ELEMENT *documentdescription;
+ ELEMENT *setcontentsaftertitlepage;
+ ELEMENT *setshortcontentsaftertitlepage;
+ ELEMENT *novalidate;
+ ELEMENT *validatemenus;
+ ELEMENT *pagesizes;
+ ELEMENT *fonttextsize;
+ ELEMENT *footnotestyle;
+ ELEMENT *setchapternewpage;
+ ELEMENT *everyheading;
+ ELEMENT *everyfooting;
+ ELEMENT *evenheading;
+ ELEMENT *evenfooting;
+ ELEMENT *oddheading;
+ ELEMENT *oddfooting;
+ ELEMENT *everyheadingmarks;
+ ELEMENT *everyfootingmarks;
+ ELEMENT *evenheadingmarks;
+ ELEMENT *oddheadingmarks;
+ ELEMENT *evenfootingmarks;
+ ELEMENT *oddfootingmarks;
+ ELEMENT *shorttitlepage;
+
+ /* Arrays of elements */
+ ELEMENT footnotes;
+ ELEMENT hyphenation;
+ ELEMENT insertcopying;
+ ELEMENT printindex;
+ ELEMENT subtitle;
+ ELEMENT titlefont;
+ ELEMENT listoffloats;
+ ELEMENT detailmenu;
+ ELEMENT part;
+
+ ELEMENT allowcodebreaks;
+ ELEMENT clickstyle;
+ ELEMENT codequotebacktick;
+ ELEMENT codequoteundirected;
+ ELEMENT contents;
+ ELEMENT deftypefnnewline;
+ ELEMENT documentencoding;
+ ELEMENT documentlanguage;
+ ELEMENT exampleindent;
+ ELEMENT firstparagraphindent;
+ ELEMENT frenchspacing;
+ ELEMENT headings;
+ ELEMENT kbdinputstyle;
+ ELEMENT paragraphindent;
+ ELEMENT shortcontents;
+ ELEMENT urefbreakstyle;
+ ELEMENT xrefautomaticsectiontitle;
+} GLOBAL_INFO;
+
+
+/* In close.c */
+void close_command_cleanup (ELEMENT *current);
+ELEMENT *close_commands (ELEMENT *current, enum command_id closed_command,
+ ELEMENT **closed_element, enum command_id);
+ELEMENT *close_all_style_commands (ELEMENT *current,
+ enum command_id closed_command,
+ enum command_id interrupting_command);
+ELEMENT *close_current (ELEMENT *current,
+ enum command_id closed_command,
+ enum command_id interrupting_command);
+
+/* In end_line.c */
+NODE_SPEC_EXTRA *parse_node_manual (ELEMENT *node);
+ELEMENT *end_line (ELEMENT *current);
+ELEMENT *parse_special_misc_command (char *line, enum command_id cmd,
+ int *has_commment);
+int check_node_label (NODE_SPEC_EXTRA *nse, enum command_id cmd);
+
+typedef struct {
+ char *type;
+ ELEMENT *element;
+} FLOAT_RECORD;
+
+extern FLOAT_RECORD *floats_list;
+extern size_t floats_number;
+extern size_t floats_space;
+
+/* In debug.c */
+void debug (char *s, ...);
+void debug_nonl (char *s, ...);
+extern int debug_output;
+
+/* In separator.c */
+ELEMENT *handle_separator (ELEMENT *current, char separator,
+ char **line_inout);
+
+/* In parser.c */
+ELEMENT *parse_texi (ELEMENT *root_elt);
+void push_conditional_stack (enum command_id cond);
+enum command_id pop_conditional_stack (void);
+extern size_t conditional_number;
+ELEMENT *parse_texi_file (char *filename);
+int abort_empty_line (ELEMENT **current_inout, char *additional);
+ELEMENT *end_paragraph (ELEMENT *current,
+ enum command_id closed_command,
+ enum command_id interrupting_command);
+void isolate_last_space (ELEMENT *current);
+int command_with_command_as_argument (ELEMENT *current);
+ELEMENT *begin_preformatted (ELEMENT *current);
+ELEMENT *end_preformatted (ELEMENT *current,
+ enum command_id closed_command,
+ enum command_id interrupting_command);
+char *read_command_name (char **ptr);
+ELEMENT *merge_text (ELEMENT *current, char *text);
+void start_empty_line_after_command (ELEMENT *current, char **line_inout,
+ ELEMENT *command);
+ELEMENT *begin_paragraph (ELEMENT *current);
+int format_expanded_p (char *format);
+int is_end_current_command (ELEMENT *current, char **line,
+ enum command_id *end_cmd);
+void set_documentlanguage (char *);
+char *element_type_name (ELEMENT *e);
+
+/* Return values */
+#define GET_A_NEW_LINE 0
+#define STILL_MORE_TO_PROCESS 1
+#define FINISHED_TOTALLY 2
+
+
+extern const char *whitespace_chars, *whitespace_chars_except_newline;
+extern const char *digit_chars;
+
+extern ELEMENT *current_node;
+extern ELEMENT *current_section;
+extern ELEMENT *current_part;
+
+extern GLOBAL_INFO global_info;
+extern char *global_clickstyle;
+extern char *global_documentlanguage;
+
+enum kbd_enum {kbd_none, kbd_code, kbd_example, kbd_distinct };
+extern enum kbd_enum global_kbdinputstyle;
+
+int register_global_command (ELEMENT *current);
+void wipe_global_info (void);
+
+extern COUNTER count_remaining_args, count_items, count_cells;
+
+/* In multitable.c */
+ELEMENT *item_line_parent (ELEMENT *current);
+ELEMENT *item_multitable_parent (ELEMENT *current);
+void gather_previous_item (ELEMENT *current, enum command_id next_command);
+
+/* In extra.c */
+void add_extra_element (ELEMENT *e, char *key, ELEMENT *value);
+void add_extra_element_oot (ELEMENT *e, char *key, ELEMENT *value);
+void add_extra_contents (ELEMENT *e, char *key, ELEMENT *value);
+void add_extra_contents_oot (ELEMENT *e, char *key, ELEMENT *value);
+void add_extra_contents_array (ELEMENT *e, char *key, ELEMENT *value);
+void add_extra_text (ELEMENT *e, char *key, ELEMENT *value);
+void add_extra_misc_args (ELEMENT *e, char *key, ELEMENT *value);
+void add_extra_node_spec (ELEMENT *e, char *key, NODE_SPEC_EXTRA *value);
+void add_extra_node_spec_array (ELEMENT *, char *, NODE_SPEC_EXTRA **value);
+void add_extra_def_info (ELEMENT *e, char *key, DEF_INFO *value);
+void add_extra_float_type (ELEMENT *e, char *key, EXTRA_FLOAT_TYPE *value);
+void add_extra_string (ELEMENT *e, char *key, char *value);
+void add_extra_string_dup (ELEMENT *e, char *key, char *value);
+void add_extra_integer (ELEMENT *e, char *key, int value);
+KEY_PAIR *lookup_extra (ELEMENT *e, char *key);
+
+/* In menus.c */
+int handle_menu (ELEMENT **current_inout, char **line_inout);
+ELEMENT *enter_menu_entry_node (ELEMENT *current);
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/separator.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/separator.c
new file mode 100644
index 0000000000..57946d6b17
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/separator.c
@@ -0,0 +1,791 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "parser.h"
+#include "text.h"
+#include "convert.h"
+#include "input.h"
+#include "labels.h"
+
+ELEMENT *
+handle_open_brace (ELEMENT *current, char **line_inout)
+{
+ char *line = *line_inout;
+
+ abort_empty_line (&current, NULL);
+ if (command_flags(current) & CF_brace)
+ {
+ enum command_id command;
+ ELEMENT *arg;
+
+ command = current->cmd;
+ counter_push (&count_remaining_args, current,
+ command_data(current->cmd).data);
+ counter_dec (&count_remaining_args);
+
+ arg = new_element (ET_NONE);
+ add_to_element_args (current, arg);
+ current = arg;
+
+ if (command == CM_verb)
+ {
+ current->type = ET_brace_command_arg;
+ /* Save the deliminating character in 'type'. */
+ if (!*line || *line == '\n')
+ {
+ line_error ("@verb without associated character");
+ add_extra_string_dup (current->parent, "delimiter", "");
+ current->parent->type = 0;
+ }
+ else
+ {
+ static char c[2];
+ c[0] = *line++;
+ add_extra_string_dup (current->parent, "delimiter", c);
+ }
+ }
+ else if (command_data(command).data == BRACE_context)
+ {
+ if (command == CM_caption || command == CM_shortcaption)
+ {
+#define float floatxx
+ ELEMENT *float;
+ if (!current->parent->parent
+ || current->parent->parent->cmd != CM_float)
+ {
+ float = current->parent;
+ while (float->parent && float->cmd != CM_float)
+ float = float->parent;
+ if (float->cmd != CM_float)
+ {
+ line_error ("@%s is not meaningful outside "
+ "`@float' environment",
+ command_name(command));
+ float = 0;
+ }
+ else
+ line_warn ("@%s should be right below `@float'",
+ command_name(command));
+ }
+ else
+ float = current->parent->parent;
+ if (float)
+ {
+ if (lookup_extra (float, command_name(command)))
+ line_warn ("ignoring multiple @%s",
+ command_name(command));
+ else
+ {
+ add_extra_element (current->parent, "float", float);
+ add_extra_element (float, command_name(command),
+ current->parent);
+ }
+ }
+#undef float
+ }
+
+ /* Add to context stack. */
+ switch (command)
+ {
+ case CM_footnote:
+ push_context (ct_footnote);
+ break;
+ case CM_caption:
+ push_context (ct_caption);
+ break;
+ case CM_shortcaption:
+ push_context (ct_shortcaption);
+ break;
+ case CM_math:
+ push_context (ct_math);
+ break;
+ default:
+ abort ();
+ }
+
+ {
+ ELEMENT *e;
+ int n;
+ n = strspn (line, whitespace_chars_except_newline);
+ e = new_element (ET_empty_spaces_before_argument);
+ text_append_n (&e->text, line, n);
+ add_to_element_contents (current, e);
+ add_extra_element (e, "command", current->parent);
+ line += n;
+ }
+ current->type = ET_brace_command_context;
+ }
+ else /* not context brace */
+ {
+ current->type = ET_brace_command_arg;
+
+ /* Commands which are said to take a positive number of arguments
+ disregard leading and trailing whitespace. */
+ if (command_data(command).data > 0)
+ {
+ ELEMENT *e;
+ e = new_element (ET_empty_spaces_before_argument);
+ /* See comment in parser.c:merge_text */
+ text_append (&e->text, "");
+ add_to_element_contents (current, e);
+ add_extra_element (e, "command", current);
+
+ if (command == CM_inlineraw)
+ push_context (ct_inlineraw);
+ }
+ }
+ debug ("OPENED");
+ }
+ else if (current->parent && (current->parent->cmd == CM_multitable
+ || current->parent->type == ET_def_line))
+ {
+ ELEMENT *b, *e;
+ b = new_element (ET_bracketed);
+ add_to_element_contents (current, b);
+ current = b;
+
+ /* We need the line number here in case @ protects the
+ end of the line. */
+ if (current->parent->parent->type == ET_def_line)
+ current->line_nr = line_nr;
+
+ e = new_element (ET_empty_spaces_before_argument);
+ text_append (&e->text, ""); /* See comment in parser.c:merge_text */
+ add_to_element_contents (current, e);
+ debug ("BRACKETED in def/multitable");
+ add_extra_element (e, "command", current);
+ }
+ else if (current->type == ET_rawpreformatted)
+ {
+ ELEMENT *e = new_element (ET_NONE);
+ text_append (&e->text, "{");
+ add_to_element_contents (current, e);
+ }
+ else if (current_context() == ct_math
+ || current_context() == ct_rawpreformatted
+ || current_context() == ct_inlineraw)
+ {
+ ELEMENT *b = new_element (ET_bracketed);
+ b->line_nr = line_nr;
+ add_to_element_contents (current, b);
+ current = b;
+ debug ("BRACKETED in math");
+ }
+ else
+ {
+ line_error ("misplaced {");
+ if (current->contents.number > 0
+ && last_contents_child(current)->type
+ == ET_empty_spaces_before_argument)
+ {
+ /* FIXME: Is this right? */
+ remove_from_contents (current, 0);
+ }
+ }
+
+ *line_inout = line;
+ return current;
+}
+
+/* Return 1 if an element is all whitespace.
+ Note that this function isn't completely reliable because it
+ doesn't look deep into the element tree.
+ In the perl code it calls
+ Texinfo::Convert::NodeNameNormalization::normalize_node,
+ and checks that the result isn't all hyphens.
+ */
+int
+check_empty_expansion (ELEMENT *e)
+{
+ int i;
+ for (i = 0; i < e->contents.number; i++)
+ {
+ ELEMENT *f = e->contents.list[i];
+ if (!(
+ f->cmd == CM_SPACE
+ || f->cmd == CM_TAB
+ || f->cmd == CM_NEWLINE
+ || f->cmd == CM_c
+ || f->cmd == CM_comment
+ || f->cmd == CM_COLON
+ || f->type == ET_empty_spaces_before_argument
+ || f->type == ET_spaces_at_end
+ || (!f->cmd && !f->type && f->text.end == 0)
+ || (f->text.end > 0
+ && !*(f->text.text + strspn (f->text.text, whitespace_chars)))
+ ))
+ {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+ELEMENT *
+handle_close_brace (ELEMENT *current, char **line_inout)
+{
+ char *line = *line_inout;
+
+ abort_empty_line (&current, NULL);
+
+ if (current->type == ET_bracketed)
+ {
+ /* Used in @math */
+ current = current->parent;
+ goto funexit;
+ }
+ else if (command_flags(current->parent) & CF_brace)
+ {
+ enum command_id closed_command;
+ if (command_data(current->parent->cmd).data == BRACE_context)
+ {
+ (void) pop_context ();
+ /* The Perl code here checks that the popped context and the
+ parent command match as strings. */
+ }
+ else if (command_data(current->parent->cmd).data > 0)
+ {
+ /* @inline* always have end spaces considered as normal text */
+ if (!(command_flags(current->parent) & CF_inline))
+ isolate_last_space (current);
+ }
+
+ closed_command = current->parent->cmd;
+ debug ("CLOSING(brace) %s", command_data(closed_command).cmdname);
+ counter_pop (&count_remaining_args);
+
+ if (current->contents.number > 0
+ && command_data(closed_command).data == 0)
+ line_warn ("command @%s does not accept arguments",
+ command_name(closed_command));
+
+ if (closed_command == CM_anchor)
+ {
+ NODE_SPEC_EXTRA *parsed_anchor;
+ current->parent->line_nr = line_nr;
+ parsed_anchor = parse_node_manual (current);
+ if (check_node_label (parsed_anchor, CM_anchor))
+ {
+ register_label (current->parent, parsed_anchor->node_content);
+ if (current_region ())
+ add_extra_element (current, "region", current_region ());
+ if (parsed_anchor->manual_content)
+ destroy_element (parsed_anchor->manual_content);
+ }
+ free (parsed_anchor);
+ }
+ else if (command_data(closed_command).flags & CF_ref)
+ {
+ ELEMENT *ref = current->parent;
+ if (ref->args.number > 0)
+ {
+ if ((closed_command == CM_inforef
+ && (ref->args.number <= 0
+ || ref->args.list[0]->contents.number == 0)
+ && (ref->args.number <= 2
+ || ref->args.list[2]->contents.number == 0))
+ || (closed_command != CM_inforef
+ && (ref->args.number <= 0
+ || ref->args.list[0]->contents.number == 0)
+ && (ref->args.number <= 3
+ || ref->args.list[3]->contents.number == 0)
+ && (ref->args.number <= 4
+ || ref->args.list[4]->contents.number == 0)))
+ {
+ line_warn ("command @%s missing a node or external manual "
+ "argument", command_name(closed_command));
+ }
+ else
+ {
+ NODE_SPEC_EXTRA *nse;
+ nse = parse_node_manual (args_child_by_index (ref, 0));
+ if (nse && (nse->manual_content || nse->node_content))
+ add_extra_node_spec (ref, "node_argument", nse);
+ else
+ {
+ if (nse->manual_content)
+ destroy_element (nse->manual_content);
+ if (nse->node_content)
+ destroy_element (nse->node_content);
+ free (nse);
+ }
+ if (closed_command != CM_inforef
+ && (ref->args.number <= 3
+ || ref->args.number <= 4
+ && ref->args.list[3]->contents.number == 0
+ || (ref->args.list[3]->contents.number == 0
+ && ref->args.list[4]->contents.number == 0))
+ && !nse->manual_content)
+ {
+ remember_internal_xref (ref);
+ }
+ }
+
+ if (ref->args.number > 1
+ && ref->args.list[1]->contents.number > 0)
+ {
+ if (check_empty_expansion (ref->args.list[1]))
+ {
+ char *texi = 0;
+ if (ref->args.list[1])
+ texi = convert_to_texinfo (ref->args.list[1]);
+
+ line_warn ("in @%s empty cross reference name "
+ "after expansion `%s'",
+ command_name(closed_command),
+ ref->args.list[1] ? texi : "");
+ free (texi);
+ }
+ }
+
+ if (closed_command != CM_inforef
+ && ref->args.number > 2
+ && ref->args.list[2]->contents.number > 0)
+ {
+ if (check_empty_expansion (ref->args.list[2]))
+ {
+ char *texi = 0;
+ if (ref->args.list[2])
+ texi = convert_to_texinfo (ref->args.list[2]);
+
+ line_warn ("in @%s empty cross reference title "
+ "after expansion `%s'",
+ command_name(closed_command),
+ ref->args.list[2] ? texi : "");
+ free (texi);
+ }
+ }
+ }
+ }
+ else if (closed_command == CM_image)
+ {
+ ELEMENT *image = current->parent;
+ if (image->args.number == 0
+ || image->args.list[0]->contents.number == 0)
+ {
+ line_error ("@image missing filename argument");
+ }
+ if (global_info.input_perl_encoding)
+ add_extra_string_dup (image, "input_perl_encoding",
+ global_info.input_perl_encoding);
+ }
+ else if (closed_command == CM_dotless)
+ {
+ if (current->contents.number > 0)
+ {
+ char *text = current->contents.list[0]->text.text;
+ if (!text || (strcmp (text, "i") && strcmp (text, "j")))
+ {
+ line_error ("@dotless expects `i' or `j' as argument, "
+ "not `%s'", text);
+ }
+ }
+ }
+ else if ((command_data(closed_command).flags & CF_inline)
+ || closed_command == CM_abbr
+ || closed_command == CM_acronym)
+ {
+ if (current->parent->cmd == CM_inlineraw)
+ {
+ if (ct_inlineraw != pop_context ())
+ abort ();
+ }
+ if (current->parent->args.number == 0
+ || current->parent->args.list[0]->contents.number == 0)
+ {
+ line_warn ("@%s missing first argument",
+ command_name(current->parent->cmd));
+ }
+ }
+ else if (closed_command == CM_errormsg)
+ {
+ char *arg = current->contents.list[0]->text.text;
+ if (arg)
+ line_error (arg);
+ }
+ else if (closed_command == CM_U)
+ {
+ if (current->contents.number == 0)
+ {
+ line_warn ("no argument specified for @U");
+ }
+ else
+ {
+ char *arg = current->contents.list[0]->text.text;
+ int n = strspn (arg, "0123456789ABCDEFabcdef");
+ if (arg[n])
+ {
+ line_error ("non-hex digits in argument for @U: %s", arg);
+ }
+ else if (n < 4)
+ {
+ line_warn
+ ("fewer than four hex digits in argument for @U: %s", arg);
+ }
+ else
+ {
+ int val;
+ int ret = sscanf (arg, "%d", &val);
+ if (ret != 1)
+ {
+ debug ("hex sscanf failed %s", arg);
+ /* unknown error. possibly argument is too large
+ for an int. */
+ }
+ if (ret != 1 || val > 0x10FFF)
+ {
+ line_error
+ ("argument for @U exceeds Unicode maximum 0x10FFFF: %s",
+ arg);
+ }
+ }
+
+ }
+ }
+ else if (command_with_command_as_argument (current->parent->parent)
+ && current->contents.number == 0)
+ {
+ debug ("FOR PARENT ... command_as_argument_braces ...");
+ if (!current->parent->type)
+ current->parent->type = ET_command_as_argument;
+ add_extra_element (current->parent->parent->parent,
+ "command_as_argument", current->parent);
+ }
+ else if (current->parent->cmd == CM_sortas)
+ {
+ ELEMENT *e = current->contents.list[0];
+
+ if (e->text.end > 0)
+ {
+ ELEMENT *index_elt;
+ if (current->parent->parent
+ && current->parent->parent->parent
+ && (command_flags(current->parent->parent->parent)
+ & CF_index_entry_command))
+ {
+ index_elt = current->parent->parent->parent;
+ add_extra_string_dup (index_elt, "sortas",
+ e->text.text);
+ }
+ }
+ }
+ register_global_command (current->parent);
+
+ if (current->parent->cmd == CM_anchor
+ || current->parent->cmd == CM_hyphenation
+ || current->parent->cmd == CM_caption
+ || current->parent->cmd == CM_shortcaption
+ || current->parent->cmd == CM_sortas)
+ {
+ ELEMENT *e;
+ e = new_element (ET_empty_spaces_after_close_brace);
+ text_append (&e->text, "");
+ add_to_element_contents (current->parent->parent, e);
+ }
+
+ current = current->parent->parent;
+ if (close_preformatted_command(closed_command))
+ current = begin_preformatted (current);
+ } /* CF_brace */
+ else if (current->type == ET_rawpreformatted)
+ {
+ /* lone right braces are accepted in a rawpreformatted */
+ ELEMENT *e = new_element (ET_NONE);
+ text_append_n (&e->text, "}", 1);
+ add_to_element_contents (current, e);
+ goto funexit;
+ }
+ /* context brace command (e.g. @footnote) */
+ else if (current_context() == ct_footnote
+ || current_context() == ct_caption
+ || current_context() == ct_shortcaption
+ || current_context() == ct_math)
+ {
+ current = end_paragraph (current, 0, 0);
+ if (current->parent
+ && (command_flags(current->parent) & CF_brace)
+ && (command_data(current->parent->cmd).data == BRACE_context))
+ {
+ enum command_id closed_command;
+ (void) pop_context ();
+ debug ("CLOSING(context command)");
+ closed_command = current->parent->cmd;
+
+ register_global_command (current->parent);
+ current = current->parent->parent;
+ if (close_preformatted_command(closed_command))
+ current = begin_preformatted (current);
+ }
+ }
+ else
+ {
+ line_error ("misplaced }");
+ goto funexit;
+ }
+
+funexit:
+ *line_inout = line;
+ return current;
+}
+
+
+/* Handle a comma separating arguments to a Texinfo command. */
+ELEMENT *
+handle_comma (ELEMENT *current, char **line_inout)
+{
+ char *line = *line_inout;
+ enum element_type type;
+ ELEMENT *new_arg, *e;
+
+ abort_empty_line (&current, NULL);
+ isolate_last_space (current);
+
+ type = current->type;
+ current = current->parent;
+
+ if (command_flags(current) & CF_inline)
+ {
+ KEY_PAIR *k;
+ int expandp = 0;
+ debug ("THE INLINE PART");
+ k = lookup_extra (current, "format");
+ if (!k)
+ {
+ ELEMENT *arg = 0;
+ char *inline_type = 0;
+ if (current->args.number > 0
+ && current->args.list[0]->contents.number > 0
+ && (arg = current->args.list[0]->contents.list[0]))
+ {
+ if (arg->text.end > 0)
+ inline_type = arg->text.text;
+ }
+
+ debug ("INLINE <%s>", inline_type);
+ if (!inline_type)
+ {
+ /* Condition is missing */
+ debug ("INLINE COND MISSING");
+ }
+ else if (current->cmd == CM_inlineraw
+ || current->cmd == CM_inlinefmt
+ || current->cmd == CM_inlinefmtifelse)
+ {
+ if (format_expanded_p (inline_type))
+ {
+ expandp = 1;
+ add_extra_integer (current, "expand_index", 1);
+ }
+ else
+ expandp = 0;
+ }
+ else if (current->cmd == CM_inlineifset
+ || current->cmd == CM_inlineifclear)
+ {
+ expandp = 0;
+ if (fetch_value (inline_type, strlen (inline_type)))
+ expandp = 1;
+ if (current->cmd == CM_inlineifclear)
+ expandp = !expandp;
+ if (expandp)
+ add_extra_integer (current, "expand_index", 1);
+ }
+ else
+ expandp = 0;
+
+ if (inline_type)
+ add_extra_string_dup (current, "format", inline_type);
+ else
+ add_extra_string (current, "format", 0);
+
+ /* Skip first argument for a false @inlinefmtifelse */
+ if (!expandp && current->cmd == CM_inlinefmtifelse)
+ {
+ ELEMENT *e;
+ int brace_count = 1;
+
+ add_extra_integer (current, "expand_index", 2);
+
+ /* Add a dummy argument for the first argument. */
+ e = new_element (ET_elided);
+ add_to_element_args (current, e);
+
+ /* Scan forward to get the next argument. */
+ while (brace_count > 0)
+ {
+ line += strcspn (line, "{},");
+ switch (*line)
+ {
+ case ',':
+ if (brace_count == 1)
+ {
+ line++;
+ goto inlinefmtifelse_done;
+ }
+ break;
+ case '{':
+ brace_count++;
+ break;
+ case '}':
+ brace_count--;
+ break;
+ default:
+ line = next_text ();
+ if (!line)
+ goto funexit;
+ continue;
+ }
+ line++;
+ }
+inlinefmtifelse_done:
+ /* Check if the second argument is missing. */
+ if (brace_count == 0)
+ {
+ line--; /* on '}' */
+ }
+
+ counter_dec (&count_remaining_args);
+ expandp = 1;
+ }
+ }
+ else if (current->cmd == CM_inlinefmtifelse)
+ {
+ /* Second art of @inlinefmtifelse when condition is true. Discard
+ second argument. */
+ expandp = 0;
+ }
+
+ /* If this command is not being expanded, add a dummy argument, and
+ scan forward to the closing brace. */
+ if (!expandp)
+ {
+ static char *alloc_line;
+ ELEMENT *e;
+ int brace_count = 1;
+ e = new_element (ET_elided);
+ add_to_element_args (current, e);
+ while (brace_count > 0)
+ {
+ line += strcspn (line, "{}");
+ switch (*line)
+ {
+ case '{':
+ brace_count++;
+ break;
+ case '}':
+ brace_count--;
+ break;
+ default:
+ free (alloc_line);
+ alloc_line = next_text ();
+ if (!alloc_line)
+ goto funexit;
+ line = alloc_line;
+ continue;
+ }
+ line++;
+ }
+ current = last_args_child (current);
+ line--; /* on '}' */
+ goto funexit;
+ }
+ }
+
+ counter_dec (&count_remaining_args);
+ new_arg = new_element (type);
+ add_to_element_args (current, new_arg);
+ current = new_arg;
+ e = new_element (ET_empty_spaces_before_argument);
+ text_append (&e->text, ""); /* See comment in parser.c:merge_text */
+ add_to_element_contents (current, e);
+ add_extra_element (e, "command", current);
+
+funexit:
+ *line_inout = line;
+ return current;
+}
+
+/* Actions to be taken when a special character appears in the input. */
+ELEMENT *
+handle_separator (ELEMENT *current, char separator, char **line_inout)
+{
+ char *line = *line_inout;
+
+ if (separator == '{')
+ {
+ current = handle_open_brace (current, &line);
+ }
+ else if (separator == '}')
+ {
+ current = handle_close_brace (current, &line);
+ }
+ /* If a comma is seen after all the arguments for the command have been
+ read, it is included in the last argument. */
+ else if (separator == ','
+ && counter_value (&count_remaining_args, current->parent) > 0)
+ {
+ current = handle_comma (current, &line);
+ }
+ else if (separator == ',' && current->type == ET_line_arg
+ && current->parent->cmd == CM_node)
+ {
+ line_warn ("superfluous arguments for node");
+ }
+ /* After a separator in a menu. */
+ else if ((separator == ','
+ || separator == '\t'
+ || separator == '.')
+ && current->type == ET_menu_entry_node
+ || separator == ':' && current->type == ET_menu_entry_name)
+ {
+ ELEMENT *e;
+
+ current = current->parent;
+ e = new_element (ET_menu_entry_separator);
+ text_append_n (&e->text, &separator, 1);
+ add_to_element_args (current, e);
+
+ /* Note in 'handle_menu' in menus.c, if a '.' is not followed by
+ whitespace, we revert was was done here. */
+ }
+ else if (separator == '\f' && current->type == ET_paragraph)
+ {
+ ELEMENT *e;
+
+ /* A form feed stops and restarts a paragraph. */
+ current = end_paragraph (current, 0, 0);
+ e = new_element (ET_empty_line);
+ text_append_n (&e->text, "\f", 1);
+ add_to_element_contents (current, e);
+ e = new_element (ET_empty_line);
+ add_to_element_contents (current, e);
+ }
+ else
+ {
+ /* Default - merge the character as usual. */
+ char t[2];
+ t[0] = separator;
+ t[1] = '\0';
+ current = merge_text (current, t);
+ }
+
+ *line_inout = line;
+ return current;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.c
new file mode 100644
index 0000000000..3a5b70597f
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.c
@@ -0,0 +1,87 @@
+/* Copyright 2014-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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "tree_types.h"
+#include "text.h"
+
+/* Make sure there are LEN free bytes. */
+void
+text_alloc (TEXT *t, size_t len)
+{
+ if (t->end + len > t->space)
+ {
+ t->space = t->end + len;
+ if (t->space < 10)
+ t->space = 10;
+ /* This makes a huge difference under Valgrind, is not noticable
+ otherwise. */
+ t->space *= 2;
+ t->text = realloc (t->text, t->space);
+ if (!t->text)
+ abort ();
+ }
+}
+
+void
+text_printf (TEXT *t, char *format, ...)
+{
+ va_list v;
+ char *s;
+
+ va_start (v, format);
+ vasprintf (&s, format, v);
+ text_append (t, s);
+ free (s);
+ va_end (v);
+}
+
+void
+text_append_n (TEXT *t, char *s, size_t len)
+{
+ text_alloc (t, len + 1);
+ memcpy (t->text + t->end, s, len);
+ t->end += len;
+ t->text[t->end] = '\0';
+}
+
+void
+text_append (TEXT *t, char *s)
+{
+ size_t len = strlen (s);
+ text_append_n (t, s, len);
+}
+
+void
+text_reset (TEXT *t)
+{
+ if (t->end > 0)
+ {
+ t->end = 0;
+ t->text[0] = 0;
+ }
+}
+
+void
+text_init (TEXT *t)
+{
+ t->end = t->space = 0;
+ t->text = 0;
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.h
new file mode 100644
index 0000000000..423d5559fd
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/text.h
@@ -0,0 +1,24 @@
+/* text.h - declarations for text.c */
+/* Copyright 2014, 2015 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+void text_init (TEXT *t);
+void text_append (TEXT *t, char *s);
+void text_append_n (TEXT *t, char *s, size_t len);
+void text_printf (TEXT *t, char *format, ...);
+void text_alloc (TEXT *t, size_t len);
+void text_reset (TEXT *t);
+
+#define text_base(t) ((t)->space ? (t)->text : (char *) 0)
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.c b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.c
new file mode 100644
index 0000000000..39859e2f79
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.c
@@ -0,0 +1,361 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tree_types.h"
+#include "tree.h"
+
+//int element_counter;
+
+ELEMENT *
+new_element (enum element_type type)
+{
+ ELEMENT *e = malloc (sizeof (ELEMENT));
+
+ //element_counter++;
+
+ /* Zero all elements */
+ memset (e, 0, sizeof (*e));
+
+ e->type = type;
+ e->cmd = CM_NONE;
+ e->args.list = 0;
+ e->args.space = 0;
+ e->args.number = 0;
+ e->contents.list = 0;
+ e->contents.space = 0;
+ e->contents.number = 0;
+ e->parent = 0;
+ e->extra = 0;
+
+ return e;
+}
+
+void
+destroy_element (ELEMENT *e)
+{
+ int i;
+ free (e->text.text);
+
+ /* Note the pointers in these lists are not themselves freed. */
+ free (e->contents.list);
+ free (e->args.list);
+
+ for (i = 0; i < e->extra_number; i++)
+ {
+ switch (e->extra[i].type)
+ {
+ case extra_string:
+ case extra_index_entry:
+ free (e->extra[i].value);
+ break;
+ case extra_element_oot:
+ destroy_element_and_children (e->extra[i].value);
+ break;
+ case extra_contents:
+ if (e->extra[i].value)
+ destroy_element ((ELEMENT *) e->extra[i].value);
+ break;
+ case extra_contents_oot:
+ {
+ /* Only used for 'prototypes' */
+ /* Free each element in the array, but not any children
+ of each element. */
+ int j;
+ ELEMENT *array = e->extra[i].value;
+ for (j = 0 ; j < array->contents.number; j++)
+ {
+ if (array->contents.list[j])
+ {
+ free (array->contents.list[j]->text.text);
+ free (array->contents.list[j]);
+ }
+ }
+ destroy_element (array);
+ break;
+ }
+ case extra_contents_array:
+ {
+ int j;
+ ELEMENT *array = e->extra[i].value;
+ for (j = 0 ; j < array->contents.number; j++)
+ {
+ if (array->contents.list[j])
+ destroy_element (array->contents.list[j]);
+ }
+ destroy_element (array);
+ break;
+ }
+ case extra_node_spec:
+ {
+ NODE_SPEC_EXTRA *nse = (NODE_SPEC_EXTRA *) e->extra[i].value;
+
+ if (nse->manual_content)
+ destroy_element (nse->manual_content);
+ if (nse->node_content)
+ destroy_element (nse->node_content);
+ free (nse);
+ break;
+ }
+ case extra_node_spec_array:
+ {
+ NODE_SPEC_EXTRA **array = (NODE_SPEC_EXTRA **) e->extra[i].value;
+ NODE_SPEC_EXTRA **nse;
+
+ for (nse = array; (*nse); nse++)
+ {
+ if ((*nse)->manual_content)
+ destroy_element ((*nse)->manual_content);
+ if ((*nse)->node_content)
+ destroy_element ((*nse)->node_content);
+ free (*nse);
+ }
+ free (array);
+ break;
+ }
+ case extra_float_type:
+ {
+ EXTRA_FLOAT_TYPE *eft = (EXTRA_FLOAT_TYPE *) e->extra[i].value;
+ free (eft->normalized);
+
+ free (eft);
+ break;
+ }
+ case extra_misc_args:
+ destroy_element_and_children (e->extra[i].value);
+ break;
+ case extra_def_info:
+ free (e->extra[i].value);
+ break;
+
+ default:
+ break;
+ }
+ }
+ free (e->extra);
+
+ free (e);
+}
+
+/* Recursively destroy this element and all data in its descendants. */
+void
+destroy_element_and_children (ELEMENT *e)
+{
+ int i;
+
+ for (i = 0; i < e->contents.number; i++)
+ destroy_element_and_children (e->contents.list[i]);
+ for (i = 0; i < e->args.number; i++)
+ destroy_element_and_children (e->args.list[i]);
+
+ destroy_element (e);
+}
+
+/* Make sure there is space for at least one more element. */
+static void
+reallocate_list (ELEMENT_LIST *list)
+{
+ if (list->number + 1 >= list->space)
+ {
+ list->space += 10;
+ list->list = realloc (list->list, list->space * sizeof (ELEMENT *));
+ if (!list->list)
+ abort (); /* Out of memory. */
+ }
+}
+
+/* Make sure there is space for at least N more elements. */
+static void
+reallocate_list_for (int n, ELEMENT_LIST *list)
+{
+ if (list->number + n >= list->space)
+ {
+ list->space += n + 1;
+ list->list = realloc (list->list, list->space * sizeof (ELEMENT *));
+ if (!list->list)
+ abort (); /* Out of memory. */
+ }
+}
+
+void
+add_to_element_contents (ELEMENT *parent, ELEMENT *e)
+{
+ ELEMENT_LIST *list = &parent->contents;
+ reallocate_list (list);
+
+ list->list[list->number++] = e;
+ e->parent = parent;
+}
+
+/* Special purpose function for when we are only using PARENT as an
+ array, and we don't want to overwrite E->parent. */
+void
+add_to_contents_as_array (ELEMENT *parent, ELEMENT *e)
+{
+ ELEMENT_LIST *list = &parent->contents;
+ reallocate_list (list);
+
+ list->list[list->number++] = e;
+}
+
+void
+add_to_element_args (ELEMENT *parent, ELEMENT *e)
+{
+ ELEMENT_LIST *list = &parent->args;
+ reallocate_list (list);
+
+ list->list[list->number++] = e;
+ e->parent = parent;
+}
+
+/* Add the element E into the contents of PARENT at index WHERE. */
+void
+insert_into_contents (ELEMENT *parent, ELEMENT *e, int where)
+{
+ ELEMENT_LIST *list = &parent->contents;
+ reallocate_list (list);
+
+ if (where < 0)
+ where = list->number + where;
+
+ if (where < 0 || where > list->number)
+ abort ();
+
+ memmove (&list->list[where + 1], &list->list[where],
+ (list->number - where) * sizeof (ELEMENT *));
+ list->list[where] = e;
+ e->parent = parent;
+ list->number++;
+}
+
+/* Add the element E into the arguments of PARENT at index WHERE. */
+void
+insert_into_args (ELEMENT *parent, ELEMENT *e, int where)
+{
+ ELEMENT_LIST *list = &parent->args;
+ reallocate_list (list);
+
+ if (where < 0)
+ where = list->number + where;
+
+ if (where < 0 || where > list->number)
+ abort ();
+
+ memmove (&list->list[where + 1], &list->list[where],
+ (list->number - where) * sizeof (ELEMENT *));
+ list->list[where] = e;
+ e->parent = parent;
+ list->number++;
+}
+
+/* Insert elements to the contents of TO at position WHERE from FROM
+ from START inclusive to END exclusive. Do not set the parent fields. */
+void
+insert_slice_into_contents (ELEMENT *to, int where, ELEMENT *from,
+ int start, int end)
+{
+ int num = end - start;
+ reallocate_list_for (num, &to->contents);
+
+ memmove (&to->contents.list[where + num],
+ &to->contents.list[where],
+ (to->contents.number - where) * sizeof (ELEMENT *));
+ memmove (&to->contents.list[where],
+ &from->contents.list[start],
+ num * sizeof (ELEMENT *));
+
+ to->contents.number += num;
+}
+
+ELEMENT *
+remove_from_contents (ELEMENT *parent, int where)
+{
+ ELEMENT_LIST *list = &parent->contents;
+ ELEMENT *removed;
+
+ if (where < 0)
+ where = list->number + where;
+
+ if (where < 0 || where > list->number)
+ abort ();
+
+ removed = list->list[where];
+ memmove (&list->list[where], &list->list[where + 1],
+ (list->number - (where+1)) * sizeof (ELEMENT *));
+ list->number--;
+ return removed;
+}
+
+
+ELEMENT *
+pop_element_from_args (ELEMENT *parent)
+{
+ ELEMENT_LIST *list = &parent->args;
+
+ return list->list[--list->number];
+}
+
+ELEMENT *
+pop_element_from_contents (ELEMENT *parent)
+{
+ ELEMENT_LIST *list = &parent->contents;
+
+ return list->list[--list->number];
+}
+
+ELEMENT *
+last_args_child (ELEMENT *current)
+{
+ if (current->args.number == 0)
+ return 0;
+
+ return current->args.list[current->args.number - 1];
+}
+
+ELEMENT *
+last_contents_child (ELEMENT *current)
+{
+ if (current->contents.number == 0)
+ return 0;
+
+ return current->contents.list[current->contents.number - 1];
+}
+
+ELEMENT *
+contents_child_by_index (ELEMENT *e, int index)
+{
+ if (index < 0)
+ index = e->contents.number + index;
+
+ if (index < 0 || index >= e->contents.number)
+ return 0;
+
+ return e->contents.list[index];
+}
+
+ELEMENT *
+args_child_by_index (ELEMENT *e, int index)
+{
+ if (index < 0)
+ index = e->args.number + index;
+
+ if (index < 0 || index >= e->args.number)
+ return 0;
+
+ return e->args.list[index];
+}
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.h
new file mode 100644
index 0000000000..48e580a340
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree.h
@@ -0,0 +1,24 @@
+/* tree.h - declarations for tree.c */
+
+ELEMENT *new_element (enum element_type type);
+void add_to_element_contents (ELEMENT *parent, ELEMENT *e);
+void add_to_contents_as_array (ELEMENT *parent, ELEMENT *e);
+void add_to_element_args (ELEMENT *parent, ELEMENT *e);
+void insert_into_contents (ELEMENT *parent, ELEMENT *e, int where);
+void insert_into_args (ELEMENT *parent, ELEMENT *e, int where);
+void insert_slice_into_contents (ELEMENT *to, int idx, ELEMENT *from,
+ int start, int end);
+ELEMENT *remove_from_contents (ELEMENT *parent, int where);
+ELEMENT *last_args_child (ELEMENT *current);
+ELEMENT *last_contents_child (ELEMENT *current);
+ELEMENT *pop_element_from_args (ELEMENT *parent);
+ELEMENT *pop_element_from_contents (ELEMENT *parent);
+ELEMENT *contents_child_by_index (ELEMENT *e, int index);
+ELEMENT *args_child_by_index (ELEMENT *e, int index);
+void destroy_element (ELEMENT *e);
+void destroy_element_and_children (ELEMENT *e);
+
+#define element_contents_number(e) ((e)->contents.number)
+#define element_args_number(e) ((e)->args.number)
+#define element_text(e) (text_base (&(e)->text))
+
diff --git a/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree_types.h b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree_types.h
new file mode 100644
index 0000000000..344c8aa2e2
--- /dev/null
+++ b/macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi/tree_types.h
@@ -0,0 +1,133 @@
+/* tree_types.h - types for the parse tree that are used in many places */
+/* Copyright 2010-2018 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+
+#include "command_ids.h"
+#include "element_types.h"
+
+typedef struct TEXT {
+ char *text;
+ size_t space; /* Allocated bytes in 'text', including terminating null. */
+ size_t end;
+} TEXT;
+
+enum extra_type {
+ extra_element,
+ extra_element_oot,
+ extra_contents,
+ extra_contents_oot,
+ extra_contents_array,
+ extra_text,
+ extra_index_entry,
+ extra_misc_args,
+ extra_node_spec,
+ extra_node_spec_array,
+ extra_string,
+ extra_integer,
+ extra_def_info,
+ extra_float_type,
+ extra_deleted
+};
+
+typedef struct KEY_PAIR {
+ char *key;
+ enum extra_type type;
+ struct ELEMENT *value;
+} KEY_PAIR;
+
+typedef struct ELEMENT_LIST {
+ struct ELEMENT **list;
+ size_t number;
+ size_t space;
+} ELEMENT_LIST;
+
+typedef struct LINE_NR {
+ int line_nr;
+ char *file_name;
+ char *macro;
+} LINE_NR;
+
+typedef struct ELEMENT {
+ enum command_id cmd;
+ TEXT text;
+ enum element_type type;
+ ELEMENT_LIST args;
+ ELEMENT_LIST contents;
+ struct ELEMENT *parent;
+ LINE_NR line_nr;
+
+ KEY_PAIR *extra;
+ size_t extra_number;
+ size_t extra_space;
+
+ /********* Used when building Perl tree only ********************/
+ void *hv;
+ /* This should be HV *hv, but we don't want to include the Perl headers
+ everywhere; */
+} ELEMENT;
+
+typedef struct {
+ char *index_name;
+ char *index_prefix;
+ enum command_id index_at_command;
+ enum command_id index_type_command;
+
+ /* content->contents is the index entry text */
+ ELEMENT *content;
+ ELEMENT *command;
+ ELEMENT *node;
+ int number; /* Index of entry in containing index, 1-based. */
+ ELEMENT *region;
+ char *sortas; /* the sort key for the index */
+} INDEX_ENTRY;
+
+typedef struct INDEX {
+ char *name;
+ char *prefix;
+ int in_code;
+
+ struct INDEX *merged_in; /* Index this index is merged into, if any. */
+
+ INDEX_ENTRY *index_entries;
+ size_t index_number;
+ size_t index_space;
+
+ /********* Used when building Perl hash value ********************/
+ void *hv;
+ void *contained_hv;
+} INDEX;
+
+/* See parse_node_manual function. */
+typedef struct {
+ ELEMENT *manual_content;
+ ELEMENT *node_content;
+} NODE_SPEC_EXTRA;
+
+/* For 'def_parsed_hash'. */
+typedef struct {
+ ELEMENT *category;
+ ELEMENT *class;
+ ELEMENT *type;
+ ELEMENT *name;
+} DEF_INFO;
+
+typedef struct {
+ ELEMENT *content;
+ char *normalized;
+} EXTRA_FLOAT_TYPE;
+
+