package PYX::Stack;

use strict;
use warnings;

use Class::Utils qw(set_params);
use Error::Pure qw(err);
use PYX::Parser;

our $VERSION = 0.06;

# Constructor.
sub new {
	my ($class, @params) = @_;
	my $self = bless {}, $class;

	# Check bad end of element.
	$self->{'bad_end'} = 0;

	# Output handler.
	$self->{'output_handler'} = \*STDOUT;

	# Verbose.
	$self->{'verbose'} = 0;

	# Process params.
	set_params($self, @params);

	# PYX::Parser object.
	$self->{'_pyx_parser'} = PYX::Parser->new(
		'callbacks' => {
			'end_element' => \&_end_element,
			'start_element' => \&_start_element,
			'final' => \&_final,
		},
		'non_parser_options' => {
			'bad_end' => $self->{'bad_end'},
			'stack' => [],
			'verbose' => $self->{'verbose'},
		},
		'output_handler' => $self->{'output_handler'},
	);

	# Object.
	return $self;
}

# Parse pyx text or array of pyx text.
sub parse {
	my ($self, $pyx, $out) = @_;
	$self->{'_pyx_parser'}->parse($pyx, $out);
	return;
}

# Parse file with pyx text.
sub parse_file {
	my ($self, $file, $out) = @_;
	$self->{'_pyx_parser'}->parse_file($file, $out);
	return;
}

# Parse from handler.
sub parse_handler {
	my ($self, $input_file_handler, $out) = @_;
	$self->{'_pyx_parser'}->parse_handler($input_file_handler, $out);
	return;
}

# End of element.
sub _end_element {
	my ($pyx_parser_obj, $elem) = @_;
	my $stack_ar = $pyx_parser_obj->{'non_parser_options'}->{'stack'};
	if ($stack_ar->[-1] eq $elem) {
		pop @{$stack_ar};
	} elsif ($pyx_parser_obj->{'non_parser_options'}->{'bad_end'}) {
		err 'Bad end of element.',
			'Element', $elem;
	}
	if ($pyx_parser_obj->{'non_parser_options'}->{'verbose'}
		&& @{$stack_ar} > 0) {

		my $out = $pyx_parser_obj->{'output_handler'};
		print {$out} join('/', @{$stack_ar}), "\n";
	}
	return;
}

# Finalize.
sub _final {
	my $pyx_parser_obj = shift;
	my $stack_ar = $pyx_parser_obj->{'non_parser_options'}->{'stack'};
	if (@{$stack_ar} > 0) {
		err 'Stack has some elements.';
	}
	return;
}

# Start of element.
sub _start_element {
	my ($pyx_parser_obj, $elem) = @_;
	my $stack_ar = $pyx_parser_obj->{'non_parser_options'}->{'stack'};
	my $out = $pyx_parser_obj->{'output_handler'};
	push @{$stack_ar}, $elem;
	if ($pyx_parser_obj->{'non_parser_options'}->{'verbose'}) {
		print {$out} join('/', @{$stack_ar}), "\n";
	}
	return;
}

1;

__END__

=pod

=encoding utf8

=head1 NAME

PYX::Stack - Processing PYX data or file and process element stack.

=head1 SYNOPSIS

 use PYX::Stack;

 my $obj = PYX::Stack->new(%parameters);
 $obj->parse($pyx, $out);
 $obj->parse_file($input_file, $out);
 $obj->parse_handler($input_file_handler, $out);

=head1 METHODS

=head2 C<new>

 my $obj = PYX::Stack->new(%parameters);

Constructor.

=over 8

=item * C<bad_end>

Check bad end of element.
If set, print error on unopened end of element.

Default value is 0.

=item * C<output_handler>

Output handler.

Default value is \*STDOUT.

=item * C<verbose>

Verbose flag.
If set, each start element prints information to 'output_handler'.

Default value is 0.

=back

Returns instance of object.

=head2 C<parse>

 $obj->parse($pyx, $out);

Parse PYX text or array of PYX text.
If C<$out> not present, use 'output_handler'.

Returns undef.

=head2 C<parse_file>

 $obj->parse_file($input_file, $out);

Parse file with PYX data.
If C<$out> not present, use 'output_handler'.

Returns undef.

=head2 C<parse_handler>

 $obj->parse_handler($input_file_handler, $out);

Parse PYX defined by handler.
If C<$out> not present, use 'output_handler'.

Returns undef.

=head1 ERRORS

 new():
         From Class::Utils::set_params():
                 Unknown parameter '%s'.

 parse():
         Bad end of element.
                 Element: %s
         Stack has some elements.

 parse_file():
         Bad end of element.
                 Element: %s
         Stack has some elements.

 parse_handler():
         Bad end of element.
                 Element: %s
         Stack has some elements.

=head1 EXAMPLE1

=for comment filename=print_stack.pl

 use strict;
 use warnings;

 use PYX::Stack;

 # Example data.
 my $pyx = <<'END';
 (begin
 (middle
 (end
 -data
 )end
 )middle
 )begin
 END

 # PYX::Stack object.
 my $obj = PYX::Stack->new(
         'verbose' => 1,
 );

 # Parse.
 $obj->parse($pyx);

 # Output:
 # begin
 # begin/middle
 # begin/middle/end
 # begin/middle
 # begin

=head1 EXAMPLE2

=for comment filename=error1.pl

 use strict;
 use warnings;

 use Error::Pure;
 use PYX::Stack;

 # Error output.
 $Error::Pure::TYPE = 'PrintVar';

 # Example data.
 my $pyx = <<'END';
 (begin
 (middle
 (end
 -data
 )middle
 )begin
 END

 # PYX::Stack object.
 my $obj = PYX::Stack->new;

 # Parse.
 $obj->parse($pyx);

 # Output:
 # PYX::Stack: Stack has some elements.

=head1 EXAMPLE3

=for comment filename=error2.pl

 use strict;
 use warnings;

 use Error::Pure;
 use PYX::Stack;

 # Error output.
 $Error::Pure::TYPE = 'PrintVar';

 # Example data.
 my $pyx = <<'END';
 (begin
 (middle
 -data
 )end
 )middle
 )begin
 END

 # PYX::Stack object.
 my $obj = PYX::Stack->new(
         'bad_end' => 1,
 );

 # Parse.
 $obj->parse($pyx);

 # Output:
 # PYX::Stack: Bad end of element.
 # Element: end

=head1 DEPENDENCIES

L<Class::Utils>,
L<Error::Pure>,
L<PYX::Parser>.

=head1 SEE ALSO

=over

=item L<Task::PYX>

Install the PYX modules.

=back

=head1 REPOSITORY

L<https://github.com/michal-josef-spacek/PYX-Stack>

=head1 AUTHOR

Michal Josef Špaček L<mailto:skim@cpan.org>

L<http://skim.cz>

=head1 LICENSE AND COPYRIGHT

© 2011-2023 Michal Josef Špaček

BSD 2-Clause License

=head1 VERSION

0.06

=cut