%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2000-2012 The University of Melbourne.
% Copyright (C) 2013-2018 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% Utility services required by the other mlds_to_java*.m modules.
%
%---------------------------------------------------------------------------%

:- module ml_backend.mlds_to_java_util.
:- interface.

:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module libs.
:- import_module libs.indent.
:- import_module ml_backend.mlds.
:- import_module ml_backend.mlds_to_target_util.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.

:- import_module bool.
:- import_module io.
:- import_module list.
:- import_module map.
:- import_module maybe.

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

:- type code_addr_wrapper
    --->    code_addr_wrapper(
                caw_class           :: string,
                caw_ptr_num         :: maybe(int)
            ).

    % Keep the enum fields together, so they can be packed into one word.
:- type java_out_info
    --->    java_out_info(
                % These are static.
                joi_module_info     :: module_info,
                joi_auto_comments   :: bool,
                joi_line_numbers    :: bool,
                joi_foreign_line_numbers :: bool,
                joi_module_name     :: mlds_module_name,
                joi_source_filename :: string,
                joi_addrof_map      :: map(mlds_code_addr, code_addr_wrapper),

                % These are dynamic.
                joi_output_generics :: output_generics,
                joi_break_context   :: break_context,
                joi_univ_tvars      :: list(tvar)
            ).

:- func init_java_out_info(module_info, string,
    map(mlds_code_addr, code_addr_wrapper)) = java_out_info.

:- func get_debug_class_init(java_out_info) = bool.

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

:- type context_marker
    --->    marker_begin_block
            % The beginning of some Java foreign code whose errors
            % should be reported with Mercury line numbers.

    ;       marker_end_block
            % The end of such a block.

    ;       marker_comment.
            % This marks Mercury generated code for which Java's line numbers
            % should be used, it is just a comment for the Mercury developers.

:- pred output_context_for_java(io.text_output_stream::in, bool::in,
    context_marker::in, prog_context::in, io::di, io::uo) is det.

:- pred indent_line_after_context(io.text_output_stream::in, bool::in,
    context_marker::in, prog_context::in, indent::in, io::di, io::uo) is det.

:- pred write_string_with_context_block(java_out_info::in,
    io.text_output_stream::in, indent::in, string::in, prog_context::in,
    io::di, io::uo) is det.

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

:- pred maybe_output_inline_comment_for_java(java_out_info::in,
    io.text_output_stream::in, string::in, io::di, io::uo) is det.

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

:- pred is_specialised_method_ptr_arity(int::in) is semidet.

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

:- implementation.

:- import_module libs.globals.
:- import_module libs.options.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.

:- import_module char.
:- import_module int.
:- import_module string.
:- import_module term_context.

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

init_java_out_info(ModuleInfo, SourceFileName, AddrOfMap) = Info :-
    module_info_get_globals(ModuleInfo, Globals),
    globals.lookup_bool_option(Globals, auto_comments, AutoComments),
    globals.lookup_bool_option(Globals, line_numbers, LineNumbers),
    globals.lookup_bool_option(Globals, line_numbers_around_foreign_code,
        ForeignLineNumbers),
    module_info_get_name(ModuleInfo, ModuleName),
    MLDS_ModuleName = mercury_module_name_to_mlds(ModuleName),
    Info = java_out_info(ModuleInfo, AutoComments,
        LineNumbers, ForeignLineNumbers, MLDS_ModuleName, SourceFileName,
        AddrOfMap, do_not_output_generics, bc_none, []).

get_debug_class_init(Info) = DebugClassInit :-
    % It is not worth having an extra field in Info for this option,
    % since we only look it up twice per module.
    ModuleInfo = Info ^ joi_module_info,
    module_info_get_globals(ModuleInfo, Globals),
    lookup_bool_option(Globals, debug_class_init, DebugClassInit).

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

:- mutable(last_context, prog_context, dummy_context, ground,
    [untrailed, attach_to_io_state]).

output_context_for_java(Stream, OutputLineNumbers, Marker, ProgContext, !IO) :-
    (
        OutputLineNumbers = yes,
        get_last_context(LastContext, !IO),
        File = term_context.context_file(ProgContext),
        Line = term_context.context_line(ProgContext),
        ( if
            % It is safe to ignore marker comments when the comment isn't
            % useful. All other marker types must be emitted in all cases.
            (
                Marker = marker_comment
            =>
                (
                    ProgContext \= LastContext,
                    Line > 0,
                    File \= ""
                )
            )
        then
            % Java doesn't have an equivalent of #line directives.
            % We use the token MER_LINE to allow us to filter these lines out
            % of the file when mangling javac's output.
            % \u is treated as a Unicode escape even with comments.
            string.replace_all(File, "\\u", "\\\\u", SafePath),
            % Do not modify this format string without modifying
            % mfilterjavac/mfilterjavac.m.
            io.format(Stream, "// %s %s:%d\n",
                [s(marker_string(Marker)), s(SafePath), i(Line)], !IO),
            set_last_context(ProgContext, !IO)
        else
            true
        )
    ;
        OutputLineNumbers = no
    ).

    % Do not modify these strings without modifying util/mfilterjavac.m.
    %
:- func marker_string(context_marker) = string.

marker_string(marker_begin_block) = "MER_FOREIGN_BEGIN".
marker_string(marker_end_block) = "MER_FOREIGN_END".
marker_string(marker_comment) = "".

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

indent_line_after_context(Stream, OutputLineNumbers, Marker, Context,
        N, !IO) :-
    output_context_for_java(Stream, OutputLineNumbers, Marker, Context, !IO),
    write_indent2(Stream, N, !IO).

write_string_with_context_block(Info, Stream, Indent, Code, Context, !IO) :-
    indent_line_after_context(Stream, Info ^ joi_foreign_line_numbers,
        marker_begin_block, Context, Indent, !IO),
    io.write_string(Stream, Code, !IO),
    io.nl(Stream, !IO),
    % The num_lines(Code) call is supposed to count the number of lines
    % occupied by Code in the source file. The result will be incorrect if
    % there were any escape sequences representing CR or LF characters --
    % they are expanded out in Code.
    Context = context(File, Lines0),
    ContextEnd = context(File, Lines0 + num_lines(Code)),
    indent_line_after_context(Stream, Info ^ joi_foreign_line_numbers,
        marker_end_block, ContextEnd, Indent, !IO).

:- func num_lines(string) = int.

num_lines(String) = Num :-
    % The initial "previous" character may be anything other than \r.
    string.foldl2(count_new_lines, String, 1, Num, 'x', _).

    % Increment the line count !N whenever we see CR or LF or CRLF,
    % ensuring that the latter counts as only ONE newline.
    %
:- pred count_new_lines(char::in, int::in, int::out, char::in, char::out)
    is det.

count_new_lines(C, !N, Prev, C) :-
    ( if
        (
            C = '\r'
        ;
            C = '\n',
            Prev \= '\r'
        )
    then
        !:N = !.N + 1
    else
        true
    ).

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

maybe_output_inline_comment_for_java(Info, Stream, Comment, !IO) :-
    AutoComments = Info ^ joi_auto_comments,
    (
        AutoComments = yes,
        io.format(Stream, "/* %s */ ", [s(Comment)], !IO)
    ;
        AutoComments = no
    ).

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

is_specialised_method_ptr_arity(Arity) :-
    Arity > 0,  % No specialised method ptr for arity zero predicates.
    Arity =< max_specialised_method_ptr_arity.

    % The highest arity for which there is a specialised MethodPtr<n>
    % interface.
    %
:- func max_specialised_method_ptr_arity = int.

max_specialised_method_ptr_arity = 15.

%---------------------------------------------------------------------------%
:- end_module ml_backend.mlds_to_java_util.
%---------------------------------------------------------------------------%
