package Number::WithError::LaTeX;

use 5.008;
use strict;
use warnings;

our $VERSION = '0.04';

use base 'Number::WithError';

use base 'Exporter';
our %EXPORT_TAGS = %Number::WithError::EXPORT_TAGS;
our @EXPORT_OK = @Number::WithError::EXPORT_OK;

use TeX::Encode;
use Encode ();
use Carp qw/croak/;

sub witherror {	Number::WithError::LaTeX->new(@_) }
sub witherror_big {	Number::WithError::LaTeX->new_big(@_) }

use Params::Util qw/_ARRAY0/;


=head1 NAME

Number::WithError::LaTeX - LaTeX output for Number::WithError

=head1 SYNOPSIS

  use Number::WithError::LaTeX;
  
  my $num = Number::WithError::LaTeX->new(5.647, 0.31);
  print $num . "\n";
  # prints '5.65e+00 +/- 3.1e-01'
  # (I.e. it automatically does scientific rounding)
  
  print $num->latex() . "\n";
  # prints '5.65 \cdot 10^{0} \pm 3.1 \cdot 10^{-1}'
  
  print $num->latex(radix => ',', enclose => '$') . "\n";
  # prints '$5,\!65 \cdot 10^{0} \pm 3,\!1 \cdot 10^{-1}$'
  
  print $num->encode("This will encode an e-acute (".chr(0xe9).") as \\'e") . "\n";
  # Delegated to TeX::Encode::encode().
  # prints 'This is a German umlaut: \"a'

=head1 DESCRIPTION

This class is a subclass of L<Number::WithError>. It provides the same
interface and the same exports.

It adds several methods to every object. The main functionality is provided by
C<latex()>, which dumps the object
as valid LaTeX code. Also, C<encode()> is a convenient way to encode
any UTF-8 string into TeX. It is just a convenience thing since it is delegated
to L<TeX::Encode>.

Unlike C<Number::WithError>, this module requires perl version 5.8 or later.
(That is the rationale for creating a separate distribution, too.)

=head1 EXPORT

This module exports the following subroutines on demand. It supports
the C<:all> Exporter tag to export all of them. The subroutines are
documented in L<Math::WithError>.

=head2 witherror

=head2 witherror_big

=cut

=head1 METHODS

This is a list of public methods.

=head2 latex

This method stringifies the object as valid LaTeX code. The returned
string is valid in a LaTeX math mode. That means, you will have to
enclose it in dollars or in an C<equation> environment by default.

The method takes named parameters. All parameters are optional.

The C<enclose> parameter can set a string to enclose the produced
latex code in. This can be either a simple string like C<$> or an
array reference containing two strings. Those two strings will be
used for the start and end respectively. (For environments.)

Example: (let C<$obj> be '5.6e-01 +/- 2.3e-02')

  $obj->latex(enclose => '$');
  # returns '$5.6 \cdot 10^{-1} \pm 2.3 \cdot 10^{-2}$'

The asymmetric environment-like C<enclose> can be used as follows:

  $obj->latex(enclose => ['\begin{equation}', '\end{equation}']);
  # returns'\begin{equation}5.6 \cdot 10^{-1} \pm 2.3 \cdot 10^{-2}\end{equation}'

There are two convenience methods C<latex_math> and C<latex_equation> which do
exactly what the above examples demonstrated.

The C<radix> parameter can set the radix (I<decimal point>) used. The default is
a dot (C<.>). If you use a comma, LaTeX will generally typeset it in a way that
results in a space after the comma. Since that is not desireable, using a C<,>
as the radix results in the radix being set as C<,\!>. An example can be found
in the synopsis.

=cut

our $CFloatCapture = qr/([+-]?)(?=\d|\.\d)(\d*(?:\.\d*)?)((?:[Ee][+-]?\d+)?)/;

sub latex {
	my $self = shift;
	croak("Uneven number of arguments to ".__PACKAGE__."->latex().") if @_ % 2;
	my %opt = @_;
	my $radix = $opt{radix};
	if (not defined $radix) {
		$radix = '.';
	}
	elsif ($radix eq '.') {
		#fine
	}
	else {
		$radix .= '\!';
	}
	my $enclose = $opt{enclose};
	$enclose = '' if not defined $enclose;
	$enclose = '' if _ARRAY0($enclose) and @$enclose != 2;
	
	my $str = $self->round();
	
	my $result;
	pos($str) = 0;
	my $p = -1;
	my $number = 1;
	while (defined pos($str) and pos($str) < length($str)) {
		die "Failed to advance string parser at position $p in '$str'." if pos($str) == $p;
		$p = pos($str);
		
		# number
		if ($number) {
			$str =~ /\G\s*$CFloatCapture\s*/cgo or die "Expected number starting at position $p in '$str'.";
			my $sgn = $1;
			my $num = $2;
			my $exp = $3;

			unless ($exp =~ s/^[eE]([+-]?)(\d+)$/" \\cdot 10^{".($1eq'-'?'-':'').(0+$2)."}"/e) {
				$exp = ' \cdot 10^{0}';
			}
			
			$num =~ s/\./$radix/;
			
			$result .= "$sgn$num$exp";
			$number = 0;
		}
		# +/-, +, -
		else {
			$str =~ /\G\s*(\+\/\-|\+|\-)\s*/cgo or die "Expected operator (+/-, +, -) starting at position $p in '$str'.";
			my $op = $1;
			if ($op eq '+/-') {
				$op = '\pm';
			}

			$result .= " $op ";
			$number = 1;
		}
		
	}
	
	if (_ARRAY0($enclose)) {
		return $enclose->[0].$result.$enclose->[1];
	}
	else {
		return $enclose.$result.$enclose;
	}
}

=head2 latex_math

Works exactly like C<latex()> except that the C<enclose> string defaults to C<$>.

=cut

sub latex_math {
	shift()->latex(enclose => '$', @_)
}

=head2 latex_equation

Works exactly like C<latex()> except that the C<enclose> string defaults to the
environment C<\begin{equation}\n> and C<\n\end{equation}>.

=cut

sub latex_equation {
	shift()->latex(enclose => ["\\begin{equation}\n", "\n\\end{equation}"], @_)
}

=head2 encode

This method encodes an arbitrary UTF-8 string as TeX. Syntax:

  my $encoded = $obj->encode($string);

For detailed documentation, please refer to L<TeX::Encode>.

=cut

sub encode {
	my $self = shift;
	return Encode::encode('latex', shift);
}


1;

__END__

=pod

=head1 SUPPORT

Bugs should be reported via the CPAN bug tracker at

L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Number-WithError-LaTeX>

For other issues, contact the author.

=head1 SEE ALSO

It is important that you have a look at the L<TeX::Encode> module if you use
the C<encode()> method. The C<decode()> operation from that module, however,
is not supported by C<Number::WithError::LaTeX>.

You may use L<Math::BigFloat> with this module. Also, it should be possible to
use L<Math::Symbolic> to calculate larger formulas. Just assign a
C<Number::WithError::LaTeX> object to the C<Math::Symbolic> variables and it should
work.

You also possibly want to have a look at the L<prefork> pragma.

The test suite is implemented using the L<Test::LectroTest> module. In order to
keep the total test time in reasonable bounds, the default number of test attempts
to falsify the test properties is kept at a low number of 100. You can
enable more rigorous testing by setting the environment variable
C<PERL_TEST_ATTEMPTS> to a higher value. A value in the range of C<1500> to
C<3000> is probably a good idea, but takes a long time to test.

=head1 AUTHOR

Steffen Mueller E<lt>modules at steffen-mueller dot netE<gt>, L<http://steffen-mueller.net/>

=head1 COPYRIGHT

Copyright 2006 Steffen Mueller. All rights reserved.

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

=cut
