%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
%
% File: e.m.
% Main author: bromage.
%
% This source file is hereby placed in the public domain.  -bromage.
%
% Calculate the base of natural logarithms using lazy evaluation.
%
% The algorithm is O(N^2) in the number of digits requested.
%
%-----------------------------------------------------------------------------%

:- module e.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

:- implementation.

:- import_module char.
:- import_module int.
:- import_module list.
:- import_module require.
:- import_module string.

%-----------------------------------------------------------------------------%

    % Default number of digits displayed (if this is not specified
    % on the command line).
    %
:- func default_digits = int.
default_digits = 1000.

    % Change this for a base other than 10.  Any integer between
    % 2 and 36 makes sense.
    %
:- func base = int.
base = 10.

    % Number of columns on the terminal.
    %
:- func columns = int.
columns = 78.

%-----------------------------------------------------------------------------%

    % This is a simple implementation of an infinite lazy stream.
    %
:- type int_stream
    --->    [int | int_stream]
    ;       closure((func) = int_stream).

:- inst int_stream for int_stream/0
    --->    [ground | int_stream]
    ;       closure((func) = is_out is det).

:- mode is_in  == in(int_stream).
:- mode is_out == out(int_stream).

%-----------------------------------------------------------------------------%

    % An infinite stream of ones.
    %
:- func ones = (int_stream :: is_out) is det.

ones = [1 | closure((func) = ones)].

    % All the digits of e in one stream.
    %
:- func digits_of_e = (int_stream :: is_out) is det.

digits_of_e = next_digit(ones).

:- func next_digit(int_stream::is_in) = (int_stream::is_out) is det.

next_digit(Stream0) = [Digit | closure((func) = next_digit(Stream))] :-
    scale(2, Digit, Stream0, Stream).

:- pred scale(int::in, int::out, int_stream::is_in, int_stream::is_out) is det.

scale(C, Digit, closure(Func), Stream) :-
    scale(C, Digit, apply(Func), Stream).
scale(C, Digit, [D | Ds], Stream) :-
    K = base * D,
    KdC = K // C,
    ( if KdC = (K + base - 1) // C then
        % We have the next digit.  Construct a closure to generate the rest.
        Digit = KdC,
        Stream =
            closure(
                (func) = [K rem C + NextDigit | Stream0] :-
                    scale(C + 1, NextDigit, Ds, Stream0)
            )
    else
        % We have a carry to factor in, so calculate the next digit eagerly
        % then add it on.
        scale(C + 1, A1, Ds, B1),
        Digit = (K + A1) // C,
        Stream = [(K + A1) rem C | B1]
    ).

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

main(!IO) :-
    io.command_line_arguments(Args, !IO),
    ( if
        Args = [Arg | _],
        string.to_int(Arg, Digits0)
    then
        Digits = Digits0
    else
        Digits = default_digits
    ),
    string.int_to_base_string(2, base, BaseString),
    io.write_strings([BaseString, "."], !IO),
    string.length(BaseString, BaseStringLength),
    main_2(Digits, columns - BaseStringLength - 1, digits_of_e, !IO),
    io.nl(!IO).

    % Print out digits until we don't have any more.
    %
:- pred main_2(int::in, int::in, int_stream::is_in, io::di, io::uo) is det.

main_2(Digits, Columns, closure(Func), !IO) :-
    main_2(Digits, Columns, apply(Func), !IO).
main_2(Digits, Columns, [I | Is], !IO) :-
    ( if Digits = 0 then
        true
    else if Columns = 0 then
        io.nl(!IO),
        main_2(Digits, columns, [I | Is], !IO)
    else
        Digit = char.det_int_to_decimal_digit(I),
        io.write_char(Digit, !IO),
        main_2(Digits - 1, Columns - 1, Is, !IO)
    ).

%-----------------------------------------------------------------------------%
:- end_module e.
%-----------------------------------------------------------------------------%
