diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi |
Initial commit
Diffstat (limited to 'macros/texinfo/texinfo/tp/Texinfo/XS/parsetexi')
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 (¤t->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 (¤t, 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, ¤t_part->line_nr, + "@part should not be associated with @top"); + } + current_part = 0; + } + + current_section = current; + } + else if (cmd == CM_part) + { + current_part = current; + if (current_node + && !lookup_extra (current_node, "associated_section")) + { + line_warn ("@node precedes @part, but parts may not be " + "associated with nodes"); + } + } + } + + return current; +} + +/* Actions to be taken when a whole line of input has been processed */ +ELEMENT * +end_line (ELEMENT *current) +{ + ELEMENT *current_old = current; /* Used at very end of function */ + + /* If empty line, start a new paragraph. */ + if (last_contents_child (current) + && last_contents_child (current)->type == ET_empty_line) + { + debug ("END EMPTY LINE"); + if (current->type == ET_paragraph) + { + ELEMENT *e; + /* Remove empty_line element. */ + e = pop_element_from_contents (current); + + current = end_paragraph (current, 0, 0); + + /* Add empty_line to higher-level element. */ + add_to_element_contents (current, e); + } + else if (current->type == ET_preformatted + && current->parent->type == ET_menu_entry_description) + { + ELEMENT *empty_line, *e; + empty_line = pop_element_from_contents (current); + if (current->contents.number == 0) + { + current = current->parent; + destroy_element (pop_element_from_contents (current)); + } + else + current = current->parent; + + pop_context (); //ct_preformatted + + current = current->parent->parent; + e = new_element (ET_menu_comment); + add_to_element_contents (current, e); + + current = e; + e = new_element (ET_preformatted); + add_to_element_contents (current, e); + + current = e; + e = new_element (ET_after_description_line); + text_append (&e->text, empty_line->text.text); + destroy_element (empty_line); + add_to_element_contents (current, e); + + push_context (ct_preformatted); + debug ("MENU: END DESCRIPTION, OPEN COMMENT"); + } + else if (in_paragraph_context (current_context ())) + { + current = end_paragraph (current, 0, 0); + } + } + /* The end of the line of a menu. */ + else if (current->type == ET_menu_entry_name + || current->type == ET_menu_entry_node) + { + ELEMENT *end_comment = 0; + int empty_menu_entry_node = 0; + + if (current->type == ET_menu_entry_node) + { + ELEMENT *last = last_contents_child (current); + + if (current->contents.number > 0 + && (last->cmd == CM_c || last->cmd == CM_comment)) + { + end_comment = pop_element_from_contents (current); + } + + /* If contents empty or is all whitespace. */ + if (current->contents.number == 0 + || (current->contents.number == 1 + && last->text.end > 0 + && !last->text.text[strspn (last->text.text, + whitespace_chars)])) + { + empty_menu_entry_node = 1; + if (end_comment) + add_to_element_contents (current, end_comment); + } + } + + /* Abort the menu entry if there is no destination node given. */ + if (empty_menu_entry_node || current->type == ET_menu_entry_name) + { + ELEMENT *menu, *menu_entry, *description_or_menu_comment = 0; + debug ("FINALLY NOT MENU ENTRY"); + menu = current->parent->parent; + menu_entry = pop_element_from_contents (menu); + if (menu->contents.number > 0 + && last_contents_child(menu)->type == ET_menu_entry) + { + ELEMENT *entry, *description = 0; + int j; + + entry = last_contents_child(menu); + for (j = entry->args.number - 1; j >= 0; j--) + { + ELEMENT *e = args_child_by_index (entry, j); + if (e->type == ET_menu_entry_description) + { + description = e; + break; + } + } + if (description) + description_or_menu_comment = description; + else + { // 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 = ¯o_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 (¯o_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 ¯o_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 (¤t, 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 (¤t, additional)) + text += leading_spaces; + + free (additional); + + current = begin_paragraph (current); + } + + last_child = last_contents_child (current); + if (last_child + /* There is a difference between the text being defined and empty, + and not defined at all. The latter is true for 'brace_command_arg' + elements. We need either to make sure that we initialize all elements + with text_append (&e->text, "") where we want merging with following + text, or treat as a special case here. + Unfortunately we can't make a special case for + ET_empty_spaces_before_argument, because abort_empty_line above + produces such an element that shouldn't be merged with. */ + && (last_child->text.space > 0 + && !strchr (last_child->text.text, '\n') + ) /* || last_child->type == ET_empty_spaces_before_argument) */ + && last_child->cmd != CM_value + && !no_merge_with_following_text) + { + /* Append text to contents */ + text_append (&last_child->text, text); + debug ("MERGED TEXT: %s|||", text); + } + else + { + ELEMENT *e = new_element (ET_NONE); + text_append (&e->text, text); + add_to_element_contents (current, e); + debug ("NEW TEXT: %s|||", text); + } + + return current; +} + +/* If last contents child of CURRENT is an empty line element, remove + or merge text, and return true. */ +int +abort_empty_line (ELEMENT **current_inout, char *additional_spaces) +{ + ELEMENT *current = *current_inout; + int retval; + + ELEMENT *last_child = last_contents_child (current); + + if (!additional_spaces) + additional_spaces = ""; + + if (last_child + && (last_child->type == ET_empty_line + || last_child->type == ET_empty_line_after_command + || last_child->type == ET_empty_spaces_before_argument + || last_child->type == ET_empty_spaces_after_close_brace)) + { + ELEMENT *owning_element = 0, *e; + KEY_PAIR *k; + + retval = 1; + + k = lookup_extra (last_child, "command"); + if (k) + owning_element = (ELEMENT *) k->value; + + debug ("ABORT EMPTY %s additional text |%s| " + "current |%s|", + element_type_name(last_child), + additional_spaces, + last_child->text.text); + text_append (&last_child->text, additional_spaces); + + /* Remove element altogether if it's empty. */ + if (last_child->text.end == 0) + { + e = pop_element_from_contents (current); + destroy_element (e); + } + else if (last_child->type == ET_empty_line) + { + last_child->type = begin_paragraph_p (current) + ? ET_empty_spaces_before_paragraph : ET_NONE; + } + else if (last_child->type == ET_empty_line_after_command + || last_child->type == ET_empty_spaces_before_argument) + { + if (owning_element) + { + /* Remove element from main tree. */ + ELEMENT *e = pop_element_from_contents (current); + add_extra_string_dup (owning_element, "spaces_before_argument", + e->text.text); + destroy_element (e); + } + else + { + last_child->type = ET_empty_spaces_after_command; + } + } + } + else + retval = 0; + + *current_inout = current; + return retval; +} + +static void +isolate_last_space_internal (ELEMENT *current) +{ + ELEMENT *last_elt; + + last_elt = last_contents_child (current); + char *text = element_text (last_elt); + + int text_len = last_elt->text.end; + + /* If text all whitespace */ + if (text[strspn (text, whitespace_chars)] == '\0') + { + add_extra_string_dup (current, "spaces_after_argument", + last_elt->text.text); + destroy_element (pop_element_from_contents (current)); + } + else + { + int i, trailing_spaces; + static TEXT t; + + text_reset (&t); + + trailing_spaces = 0; + for (i = strlen (text) - 1; + i > 0 && strchr (whitespace_chars, text[i]); + i--) + trailing_spaces++; + + text_append_n (&t, + text + text_len - trailing_spaces, + trailing_spaces); + + text[text_len - trailing_spaces] = '\0'; + last_elt->text.end -= trailing_spaces; + + add_extra_string_dup (current, "spaces_after_argument", + t.text); + } +} + +static void +isolate_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, ¤t->line_nr, + "macro `%s' previously defined", name); + line_error_ext (1, ¯o->element->line_nr, + "here is the previous definition of `%s'", name); + } + else if (!(existing & USER_COMMAND_BIT)) + { + line_error_ext (1, ¤t->line_nr, + "redefining Texinfo language command: @%s", + name); + } + } + if (!lookup_extra (current, "invalid_syntax")) + { + new_macro (name, current); + } + } + } + + current = current->parent; + + /* Check for conditionals. */ + if (command_data(end_cmd).flags & CF_block + && command_data(end_cmd).data == BLOCK_conditional) + { + /* Remove an ignored block. */ + ELEMENT *popped; + popped = pop_element_from_contents (current); + if (popped->cmd != end_cmd) + 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 (¤t, 0); + paragraph = begin_paragraph (current); + if (paragraph) + current = paragraph; + + line = line_after_command; + retval = STILL_MORE_TO_PROCESS; + goto funexit; + } + free (command); + } + else + { + /* @ was followed by gibberish. "unexpected @" is printed + below. */ + } + } + if (cmd && (command_data(cmd).flags & CF_ALIAS)) + cmd = command_data(cmd).data; + } + + /* Handle user-defined macros before anything else because their expansion + may lead to changes in the line. */ + if (cmd && (command_data(cmd).flags & CF_MACRO)) + { + static char *allocated_line; + line = line_after_command; + current = handle_macro (current, &line, cmd); + free (allocated_line); + allocated_line = next_text (); + line = allocated_line; + } + + /* Cases that may "lead to command closing": brace commands that don't + need a brace: accent commands. + @definfoenclose. */ + /* This condition is only checked immediately after the command opening, + otherwise the current element is in the 'args' and not right in the + command container. */ + else if (command_flags(current) & CF_brace && *line != '{') + { + if (command_with_command_as_argument (current->parent)) + { + debug ("FOR PARENT @%s command_as_argument @%s", + command_name(current->parent->parent->cmd), + command_name(current->cmd)); + if (!current->type) + current->type = ET_command_as_argument; + add_extra_element (current->parent->parent, + "command_as_argument", current); + current = current->parent; + } + else if (command_flags(current) & CF_accent) + { + if (strchr (whitespace_chars_except_newline, *line)) + { + if (isalpha (command_name(current->cmd)[0])) + /* e.g. @dotaccent */ + { + char *p; char *s; + KEY_PAIR *k; + p = line + strspn (line, whitespace_chars_except_newline); + k = lookup_extra (current, "spaces"); + if (!k) + { + asprintf (&s, "%.*s", (int) (p - line), line); + add_extra_string (current, "spaces", s); + } + else + { + asprintf (&s, "%s%.*s", + (char *) k->value, + (int) (p - line), p); + free (k->value); + k->value = (ELEMENT *) s; + } + line = p; + } + else + { + line_warn ("accent command `@%s' must not be followed " + "by whitespace", command_name(current->cmd)); + current = current->parent; + } + } + else if (*line == '@') + { + line_error ("use braces to give a command as an argument " + "to @%s", command_name(current->cmd)); + current = current->parent; + } + else if (*line != '\0' && *line != '\n' && *line != '\r') + { + ELEMENT *e, *e2; + debug ("ACCENT"); + e = new_element (ET_following_arg); + add_to_element_args (current, e); + e2 = new_element (ET_NONE); + text_append_n (&e2->text, line, 1); + add_to_element_contents (e, e2); + + if (current->cmd == CM_dotless + && *line != 'i' && *line != 'j') + { + line_error ("@dotless expects `i' or `j' as argument, " + "not `%c'", *line); + } + if (isalpha (command_name(current->cmd)[0])) + e->type = ET_space_command_arg; + while (current->contents.number > 0) + destroy_element (pop_element_from_contents (current)); + line++; + current = current->parent; + } + else + { + debug ("STRANGE ACC"); + line_warn ("accent command `@%s' must not be followed by " + "new line", command_name(current->cmd)); + current = current->parent; + } + goto funexit; + } + else + { + if (conf.ignore_space_after_braced_command_name) + { + char *p; + p = line + strspn (line, whitespace_chars); + if (p != line) + { + line = p; + goto funexit; + } + } + line_error ("@%s expected braces", + command_name(current->cmd)); + current = current->parent; + } + } + else if (handle_menu (¤t, &line)) + { + ; /* Nothing - everything was done in handle_menu. */ + } + /* Any other @-command. */ + else if (cmd) + { + int def_line_continuation; + + line = line_after_command; + debug ("COMMAND %s", command_name(cmd)); + + /* @value */ + if (cmd == CM_value) + { + char *arg_start; + 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 (¤t, 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 (¤t, NULL) + && ((cmd == CM_node || cmd == CM_bye) + || (command_data(cmd).flags & CF_block) + || ((command_data(cmd).flags & CF_line) + && cmd != CM_comment + && cmd != CM_c + && cmd != CM_sp + && cmd != CM_columnfractions + && cmd != CM_item + && cmd != CM_verbatiminclude + && cmd != CM_set + && cmd != CM_clear + && cmd != CM_vskip))) + { + 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 (¤t, 0); + } + + e = new_element (ET_empty_line); + add_to_element_contents (current, e); + + n = strspn (line, whitespace_chars_except_newline); + text_append_n (&e->text, line, n); + line += n; + } + + /* Process from start of remaining line, advancing it until we run out + of line. */ + while (1) + { + int status = process_remaining_on_line (¤t, &line); + if (status == GET_A_NEW_LINE) + break; + if (status == FINISHED_TOTALLY) + goto finished_totally; + if (!line) + break; + } + } +finished_totally: + + /* Check for unclosed conditionals */ + while (conditional_number > 0) + { + line_error ("expected @end %s", + command_name(conditional_stack[conditional_number - 1])); + conditional_number--; + } + + { + ELEMENT *dummy; + current = close_commands (current, CM_NONE, &dummy, CM_NONE); + + /* Make sure we are at the very top - we could have stopped at the "top" + element, with "document_root" still to go. (This happens if the file + didn't end with "@bye".) */ + while (current->parent) + current = current->parent; + } + + input_reset_input_stack (); /* to avoid a memory leak if @bye is given */ + + /* TODO: Check for "unclosed stacks". */ + + return current; +} 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 (¤t, 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 (¤t, 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 (¤t, 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; + + |